WebUSB API

社区组草案报告

本版本:
https://wicg.github.io/webusb
问题追踪:
GitHub
规范内标注
编辑者:
(谷歌公司)
(谷歌公司)
(谷歌公司)
参与方式:
加入 W3C 社区组
IRC: #webusb 频道(W3C IRC)(可能需要等待回复,请耐心等待)
在 StackOverflow 上提问

摘要

本文档描述了一种用于安全地从网页访问通用串行总线(USB)设备的API。

本文档状态

本规范由 Web平台孵化社区组 发布。 它不是W3C标准,也不在W3C标准制定轨道上。 请注意,在 W3C社区贡献者许可协议(CLA) 下,有有限的选择退出权利,同时还适用其他条件。 了解更多有关 W3C社区和业务组的信息。

1. 引言

本节为非规范性内容。

通用串行总线(USB)是有线外设的事实标准。大多数 USB 设备实现了大约十几种标准“设备类”之一,这些设备类规定了设备用于声明其支持的功能、使用这些功能的命令和数据格式的方式。标准设备类包括键盘、鼠标、音频、视频和存储设备。操作系统通过操作系统厂商提供的“类驱动”支持这些设备。然而,仍有大量设备不属于这些标准化设备类。这些设备需要硬件厂商编写本地驱动和 SDK,开发者才能利用这些设备,而这些本地代码阻止了这些设备被 Web 所用。

WebUSB API 提供了一种安全地向 Web 暴露 USB 设备服务的方法。它为使用过现有本地 USB 库的开发者提供了熟悉的 API,并暴露了现有规范中定义的设备接口。有了该 API,硬件制造商可以为其设备构建跨平台的 JavaScript SDK。这对于 Web 是有益的,因为不必等到某种新设备足够流行、浏览器才会提供专门的 API,创新硬件可以从一开始就在 Web 上得到支持。

关于 USB 的更多信息,请参见§ 10 附录:USB 简要介绍

2. 应用动机

本节为非规范性内容。

2.1. 教育设备

Web 的软件交付模式是教育应用的关键推动因素,因为它们可以在任何计算机上快速加载,无需考虑平台兼容性或管理员权限。科学课程正将计算化测量和数据记录引入教学。这些工具通常需要捆绑的软件,而这些软件在受管计算机上很难安装,因为每增加一个本地应用都会给本已繁忙的 IT 部门增加负担。基于 Web 的硬件 API 允许直接在现有在线课程材料中为这些设备提供支持,实现完全无缝体验。

学习编程的学生使用各种微控制器开发套件,可以借助在线开发工具编写和上传代码。这些工具已经存在,但它们需要一个本地组件让浏览器与硬件通信。这些本地扩展提升了入门门槛,并且可能给用户带来安全隐患,而在 Web 沙箱环境中运行的代码不存在这些问题。

2.2. Web 驱动

Web 的可组合性使得完全由 Web 技术构建新的硬件支持生态成为可能。以 3D 打印机为例,想象一个托管 3D 对象模型的网站希望把打印功能直接集成到页面中。Web 支持二维打印,但并没有三维打印的 API。如果制造商托管可嵌入页面,利用 WebUSB API 向其打印机发送数据,网站就可以像集成地图一样把硬件支持添加进来,就像许多网站集成了嵌入式地图那样。

2.3. 设备升级与诊断

虽然蓝牙等无线协议通常是消费类设备更便捷的选择,但 USB 端口仍在不断普及,因为它们能提供便捷的供电方式,也可作为设备出现故障时的“最后一根救命稻草”。通过在支持网站中集成升级和诊断工具,硬件厂商可以为任何平台的用户提供工具,并在用户来求助时收集更好的诊断数据。落地页让设备厂商可以将用户引导至其网站上与设备相关的帮助页面。

3. 安全与隐私注意事项

本节为非规范性内容。

WebUSB API 是一项强大的特性,可能会给用户带来新的隐私和安全风险。这些风险大致可分为三类,后续小节会分别描述。

3.1. 滥用设备访问

外设的用途多种多样。例如,U 盘可以存储数据,相机或麦克风可以采集外部信息,打印机可以控制物理世界的对象。上述每种设备在 Web 平台上都有专门的高级 API,并具备安全机制防止恶意网页滥用:向外部存储读写数据需用户手动选择文件,启用麦克风或摄像头需用户授权并可能有指示灯提示,打印文档也需用户明确操作。本 API 提供了连接这类高级 API 未覆盖设备的一般机制,因此同样需要一套通用机制来防止恶意页面滥用设备。

首项保护措施是 requestDevice() 函数。当调用该函数时,UA(用户代理)可以显示权限弹窗。即使对于非恶意页面,此举也通过在连接设备前提示用户,有助于保护用户隐私。UA 还可在设备连接开启时显示事件指示。

第二项,本规范要求只有 [powerful-features] 所述的安全上下文才能访问 USB 设备。这既可保证被执行代码的来源可信,也可防止设备数据在传输过程中被窃取。

最后,因为 USB 设备无法区分来自多个来源的请求,操作系统只允许一个 USB 接口被单一用户态或内核态驱动占用。UA 作为用户态驱动,因此每次只允许有一个执行上下文独占 USB 接口。如果多个执行上下文试图 claim 接口,claimInterface() 函数会失败。

3.2. 攻击设备

历史上,除高安全性应用外,USB 设备往往被设计为信任所连接主机,因此主机是访问设备功能的传统门槛。在制定本规范时,曾考虑两种可能性:第一,UA 可通知设备请求的来源(origin),类似于 HTTP 请求包含的 Referrer 头。问题在于,这种方式将访问控制压力转嫁给设备,而设备通常处理与存储能力有限,因此规范努力简化设备所需工作量。

起初本规范采用的办法是要求 UA 通过类似 [CORS] 的机制控制访问。设备可为 UA 提供一组静态数据结构,用于定义允许访问的源(origin)。为兼容传统设备,还可通过公共注册表(out of band)方式提供允许的 origin,方便过渡。

这种方案的缺点有二:一是厂商必须设计支持 WebUSB 的新设备,或依赖难以规范化的公共注册表。而产品开发周期很长,仅一个编辑草案影响有限。二是没有为第三方开发者留出利用该 API 的途径。这无助于创新,也让新能力的实际受益开发者有限。

权衡后,作者认为 requestDevice() 方法所促进的权限提示与§ 8.1 权限策略集成已经能对设备访问提供足够保护。

3.3. 攻击主机

如果设备被攻破,攻击者除了滥用设备本身能力,还可用其进一步攻击连接主机,甚至若攻击具有持久性,今后无论连接哪台主机都可复现前述攻击。上文措施旨在减少该类攻击路径,因为一旦设备被攻击者控制(例如刷入恶意固件),UA 已无法再阻止进一步破坏。

本规范建议设备厂商采用纵深防御,设计设备时只接受数字签名固件升级,和/或要求物理接入设备才能更改某些配置信息。

4. WebUSB 描述符与请求

本规范定义了 UA 可用于收集与实现本 API 相关的设备信息的描述符与命令。

4.1. WebUSB 平台能力描述符

设备通过在其二进制对象存储区(Binary Object Store)中包含如下平台描述符来声明对 WebUSB 指令集的支持:

偏移量 字段 大小 说明
0 bLength 1 数字 描述符大小。必须为 24。
1 bDescriptorType 1 常量 DEVICE CAPABILITY 描述符类型([USB31] 表 9-6)。
2 bDevCapabilityType 1 常量 PLATFORM 能力类型([USB31] 表 9-14)。
3 bReserved 1 数字 保留字段,应置为 0。
4 PlatformCapabilityUUID 16 UUID 必须为 {3408b638-09a9-47a0-8bfd-a0768815b665}。
20 bcdVersion 2 BCD 支持的协议版本。必须设置为 0x0100。
22 bVendorCode 1 数字 发起 WebUSB 请求使用的 bRequest 值。
23 iLandingPage 1 数字 设备落地页的 URL 描述符索引。

iLandingPage 字段非 0 时,表示厂商希望用户访问的落地页的URL,用户可以通过此页面操作设备。UA 可在设备接入时建议用户访问该 URL。

注: USB 是小端序总线,因此按 [RFC4122],上述 UUID 必须按字节序 {0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} 传输。

4.2. WebUSB 设备请求

本规范定义的所有控制传输都属于厂商自定义请求。WebUSB 平台能力描述符中的 bVendorCode 表明设备希望主机发起 控制传输请求时应使用的 bRequest,类型由 wIndex 字段指定。

WebUSB 请求码
常量
(保留) 1
GET_URL 2

4.2.1. 获取 URL(Get URL)

该请求用于获取指定索引的 URL 描述符。

设备必须返回对应索引的URL 描述符,索引无效则中止传输。

bmRequestType bRequest wValue wIndex wLength 数据
11000000B bVendorCode 描述符索引 GET_URL 描述符长度 描述符

4.3. WebUSB 描述符

这些描述符类型是由本规范定义的请求返回的。

WebUSB 描述符类型
常量
(保留) 0-2
WEBUSB_URL 3

4.3.1. URL 描述符

该描述符包含一个 URL,由获取 URL(Get URL)请求返回。

偏移量 字段 大小 说明
0 bLength 1 数字 描述符大小。
1 bDescriptorType 1 常量 WEBUSB_URL。
2 bScheme 1 数字 URL 协议前缀。
3 URL 可变 字符串 UTF-8 编码(不含协议前缀)的 URL。

bScheme 字段必须取值如下之一:

URL 前缀
前缀
0 "http://"
1 "https://"
255 ""

特殊值 255 表示 URL 字段中已包含整个 URL(含协议)。

5. 设备枚举

dictionary USBDeviceFilter {
  unsigned short vendorId;
  unsigned short productId;
  octet classCode;
  octet subclassCode;
  octet protocolCode;
  DOMString serialNumber;
};

dictionary USBDeviceRequestOptions {
  required sequence<USBDeviceFilter> filters;
  sequence<USBDeviceFilter> exclusionFilters = [];
};

[Exposed=(Worker,Window), SecureContext]
interface USB : EventTarget {
  attribute EventHandler onconnect;
  attribute EventHandler ondisconnect;
  Promise<sequence<USBDevice>> getDevices();
  [Exposed=Window] Promise<USBDevice> requestDevice(USBDeviceRequestOptions options);
};

[Exposed=Window, SecureContext]
partial interface Navigator {
  [SameObject] readonly attribute USB usb;
};

[Exposed=Worker, SecureContext]
partial interface WorkerNavigator {
  [SameObject] readonly attribute USB usb;
};
本例演示如何获取设备列表,用于 UI 展示。当页面首次加载时,应该调用 getDevices() 检查是否已经拥有对已连接设备的访问权限,
document.addEventListener('DOMContentLoaded', async () => {
  let devices = await navigator.usb.getDevices();
  devices.forEach(device => {
    // 将 |device| 添加到 UI。
  });
});

页面加载后,用户可能会连接断开设备,因此脚本还需要注册相关事件,以保证界面实时更新:

navigator.usb.addEventListener('connect', event => {
  // 将 |event.device| 添加到 UI。
});

navigator.usb.addEventListener('disconnect', event => {
  // 从 UI 中移除 |event.device|。
});

如果是用户首次访问本页面,则不会拥有任何设备访问权限,因此页面需要在相关全局对象具备临时激活时,调用 requestDevice()。下面是页面仅支持厂商编号为 0xABCD、含厂商自定义子类 0x01 的设备时的示例:

let button = document.getElementById('request-device');
button.addEventListener('click', async () => {
  let device;
  try {
    device = await navigator.usb.requestDevice({ filters: [{
        vendorId: 0xABCD,
        classCode: 0xFF, // 厂商自定义
        protocolCode: 0x01
    }]});
  } catch (err) {
    // 用户未选择设备。
  }

  if (device !== undefined) {
    // 将 |device| 添加到 UI。
  }
});

本规范中的方法通常为异步完成,其工作会排入 USB 任务源(task source)

若 USB 设备 device 满足以下步骤则视为匹配过滤器 filter,返回match

  1. deviceDescdevice设备描述符

  2. 如果 filter.vendorId 存在且 deviceDesc.idVendor 不等于 filter.vendorId 则返回mismatch

  3. 如果 filter.productId 存在且 deviceDesc.idProduct 不等于 filter.productId 则返回mismatch

  4. 如果 filter.serialNumber 存在,则令serialNumber字符串描述符,索引为 deviceDesc.iSerialNumber。如 device 返回错误或 serialNumber 不等于 filter.serialNumber,则返回mismatch

  5. 如果 filter.classCode 存在且对于 device 的任一接口 interface,若 interface 匹配接口过滤器 filter,则返回match

  6. 如果 filter.classCode 存在且 deviceDesc.bDeviceClass 不等于 filter.classCode, 则返回mismatch

  7. 如果 filter.subclassCode 存在且 deviceDesc.bDeviceSubClass 不等于 filter.subclassCode, 则返回mismatch

  8. 如果 filter.protocolCode 存在且 deviceDesc.bDeviceProtocol 不等于 filter.protocolCode, 则返回mismatch

  9. 返回 match

注: 上述步骤将设备描述符bDeviceClass, bDeviceSubClass, bDeviceProtocol 字段视作 接口描述符的一部分进行比较。

若 USB 接口 interface 满足下列步骤则视为匹配接口过滤器 filter,返回 match

  1. descinterface接口描述符

  2. 如果 filter.classCode 存在且 desc.bInterfaceClass 不等于 filter.classCode, 则返回mismatch

  3. 如果 filter.subclassCode 存在且 desc.bInterfaceSubClass 不等于 filter.subclassCode, 则返回mismatch

  4. 如果 filter.protocolCode 存在且 desc.bInterfaceProtocol 不等于 filter.protocolCode, 则返回mismatch

  5. 返回 match

USBDeviceFilter filter 满足下述步骤则为有效过滤器(返回valid):

  1. 如果 filter.productId 存在,但 filter.vendorId 不存在, 返回 invalid

  2. 如果 filter.subclassCode 存在,但 filter.classCode 不存在, 返回 invalid

  3. 如果 filter.protocolCode 存在,但 filter.subclassCode 不存在, 返回 invalid

  4. 返回 valid

UA 必须能够枚举系统所有已连接设备。不过,不要求每次算法请求枚举时都执行实际操作。UA 可以在首次枚举后缓存结果,并开始监听插拔事件,将新连接设备加入缓存,移除被拔出的设备。这种方式更优,因为 getDevices()requestDevice() 方法可减少操作系统调用和总线流量。

onconnect 属性是 connect 事件类型的事件处理程序 IDL 属性。

ondisconnect 属性是 disconnect 事件类型的事件处理程序 IDL 属性。

getDevices() 方法执行时,必须运行以下步骤:
  1. globalthis相关全局对象

  2. documentglobal关联文档,如无就为 null

  3. storage 为:

    1. globalServiceWorkerGlobalScope,则为其脚本执行环境中的 USBPermissionStorage 对象;

    2. 否则为当前脚本环境的 USBPermissionStorage 对象。

  4. promise新 promise

  5. 并行执行以下步骤:

    1. 枚举所有系统已连接设备。记为 enumerationResult

    2. devices 为一个新空 Array

    3. 遍历 enumerationResult,对每个 device

      1. devicedocument 被屏蔽,则 继续

      2. 如果本方法为首次被调用,检查 device 的权限,用 storage

      3. storage.allowedDevices 找到元素 allowedDevicedevice 位于 allowedDevice.[[devices]]。 如未找到,继续下一个 device

    4. 将代表 deviceUSBDevice 对象加入 devices

    5. グローバルタスクをキューに追加 する(globalUSB タスクソースを指定)、promiseresolve(解決)し、devices で完了させる。

  6. 返回 promise

requestDevice(options) 方法执行时,必须运行以下步骤:
  1. 请求使用如下描述符的权限,

    {
      name: "usb"
      filters: options.filters
      exclusionFilters: options.exclusionFilters
    }
    

    permissionResult 为生成的 Promise

  2. permissionResult 被满足时,带有 result ,执行以下步骤:

    1. 如果 result.devices 为空,抛出 "NotFoundError" DOMException 并中止后续步骤。

    2. 返回 result.devices[0]。

请求 "usb" 权限时,给定 Document documentUSBPermissionStorage storageUSBPermissionDescriptor optionsUSBPermissionResult status,UA 必须执行:
  1. globalstorage相关全局对象

  2. 遍历 options.filters, 如果其中某个 filter 不是有效过滤器,返回 一个被拒 promise,原因是 TypeError

  3. 遍历options.exclusionFilters, 如果其中某个 exclusionFilter 不是有效过滤器,返回被拒 promise,原因是 TypeError

  4. 检查算法是否在 global 具备临时激活时触发。否则返回 被拒的 promise,报 SecurityError DOMException

  5. promise新 promise

  6. 并行执行以下步骤。

    1. 枚举系统所有已连接设备。令该结果为enumerationResult

    2. 如果 enumerationResult 中的设备对 document 在阻止列表中,则移除该设备。

    3. 如果 enumerationResult 中的设备不 匹配过滤器options.filters)中的过滤条件,则移除该设备。

    4. 如果 enumerationResult 中的设备 匹配过滤器options.exclusionFilters)中的过滤条件,则移除该设备。

    5. 向用户显示设备选择弹窗,请用户从 enumerationResult 中选择设备。UA 应为每个设备显示易于理解的名称。

    6. 等待用户选择了某个 device 或取消了弹窗。

    7. USB 任务源 USB task source 上,为 global 排队全局任务,执行下列步骤:

      1. status.state 赋值为 "ask"

      2. 如果用户取消弹窗,则将 status.devices 赋值为空的 FrozenArrayresolve promise,返回 undefined,并终止后续步骤。

      3. device 添加到 storage

      4. deviceObj 为表示 deviceUSBDevice 对象。

      5. status.devices 赋值为包含 deviceObj 的新 FrozenArray

      6. resolve promise,返回 undefined

  7. 返回 promise

将允许的 USB 设备 device 添加到 USBPermissionStorage storage 中,UA 必须执行以下步骤:

  1. storage.allowedDevices 查找元素 allowedDevice,其 [[devices]] 内包含 device。 若找到,终止后续步骤。

  2. vendorIdproductId 分别为 device厂商ID产品ID

  3. serialNumberdevice序列号,如无则为 undefined

  4. storage.allowedDevices 追加 { vendorId: vendorId, productId: productId, serialNumber: serialNumber }, 其 [[devices]] 内部插槽包含唯一的 device

检查新的 USB 设备 权限 device,给定 USBPermissionStorage storage,UA 必须执行以下步骤:

  1. vendorIdproductId 分别为 device厂商ID产品ID

  2. serialNumberdevice 的序列号,如无则为 undefined

  3. storage.allowedDevices 查找元素 allowedDevice,满足:

  4. 如未找到该元素,则返回 null

  5. device 添加到 allowedDevice@[[devices]]

  6. 返回 allowedDevice

移除已允许的 USB 设备 device,给定 USBPermissionStorage storage,UA 必须执行以下步骤:

  1. storage.allowedDevices 查找元素 allowedDevice,其 [[devices]] 包含 device,如未找到则中止后续步骤。

  2. allowedDevicestorage.allowedDevices 中移除。

5.1. 事件

dictionary USBConnectionEventInit : EventInit {
    required USBDevice device;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBConnectionEvent : Event {
  constructor(DOMString type, USBConnectionEventInit eventInitDict);
  [SameObject] readonly attribute USBDevice device;
};

注: Worker 可以为 connectdisconnect 事件注册事件监听器,但只有在该 worker 处于活动状态时,事件监听器才会被调用。

当 UA 检测到一个新的 USB 设备 device 连接到主机时,它必须对每个脚本执行环境执行以下步骤:

  1. storage 为当前脚本执行环境中的 USBPermissionStorage 对象。

  2. 使用 storage 检查 device 的权限,并令 allowedDevice 为结果。

  3. 如果 allowedDevicenull,则中止这些步骤。

  4. deviceObj 为表示 deviceUSBDevice 对象。

  5. device相关全局对象Navigator 对象的 usb 上 触发 名为 connect 的事件,使用 USBConnectionEvent, 并将 device 属性设为 deviceObj

当 UA 检测到某个 USB 设备 device 已从主机断开时,它必须对每个脚本执行环境执行以下步骤:

  1. storage 为当前脚本执行环境中的 USBPermissionStorage 对象。

  2. storage.allowedDevices 中搜索元素 allowedDevice,使得 device 位于 allowedDevice@[[devices]], 如果不存在这样的元素, 则中止这些步骤。

  3. allowedDevice@[[devices]] 中移除 device

  4. 如果 allowedDevice.serialNumberundefinedallowedDevice@[[devices]] 为空,则从 storage.allowedDevices 中移除 allowedDevice

  5. device 为表示 deviceUSBDevice 对象。

  6. device相关全局对象Navigator 对象的 usb 上 触发 名为 disconnect 的事件,使用 USBConnectionEvent, 并将 device 属性设为 device

6. 设备使用

在本示例中,我们假设一个数据记录设备支持最多 8 个 16 位数据通道。它只有一个配置(bConfigurationValue 1),该配置包含一个接口(bInterfaceNumber 1)和一个批量端点(bEndpointAddress 0x81,意味着端点 1,且为 IN 端点)。当采样数据时,数据可在此端点上获取。该端点的最大包长度为 16 字节,以支持 8 个通道同时激活。但为节省总线带宽,可任意组合地激活或停用通道。数据包仅为传输所需的长度。

开始时我们打开设备,选择第一个配置(设备只有一个,但操作系统在枚举时可能尚未选择),并声明数据记录接口,

await device.open();
if (device.configuration === null)
  await device.selectConfiguration(1);
await device.claimInterface(1);

对于该应用,我们关心读取通道 1、2 和 5,因此发送一个 control transfer 来激活这些通道,

await device.controlTransferOut({
    requestType: 'vendor',
    recipient: 'interface',
    request: 0x01,  // 厂商特定请求:启用通道
    value: 0x0013,  // 0b00010011(通道 1、2 和 5)
    index: 0x0001   // 接收者为接口 1
});

应用现在可以开始轮询设备以获取数据。因为我们只期望来自 3 个通道的数据,所以请求 6 字节缓冲区。只要收到完整缓冲区(以大端传输),就把捕获到的值打印到控制台。如果设备遇到错误并通过端点 STALL 来表示,则在继续前先清除该错误,

while (true) {
  let result = await device.transferIn(1, 6);

  if (result.data && result.data.byteLength === 6) {
    console.log('Channel 1: ' + result.data.getUint16(0));
    console.log('Channel 2: ' + result.data.getUint16(2));
    console.log('Channel 5: ' + result.data.getUint16(4));
  }

  if (result.status === 'stall') {
    console.warn('Endpoint stalled. Clearing.');
    await device.clearHalt(1);
  }
}

6.1. USBDevice 接口

enum USBTransferStatus {
  "ok",
  "stall",
  "babble"
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBInTransferResult {
  constructor(USBTransferStatus status, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBOutTransferResult {
  constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0);
  readonly attribute unsigned long bytesWritten;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousInTransferPacket {
  constructor(USBTransferStatus status, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousInTransferResult {
  constructor(sequence<USBIsochronousInTransferPacket> packets, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute FrozenArray<USBIsochronousInTransferPacket> packets;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousOutTransferPacket {
  constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0);
  readonly attribute unsigned long bytesWritten;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousOutTransferResult {
  constructor(sequence<USBIsochronousOutTransferPacket> packets);
  readonly attribute FrozenArray<USBIsochronousOutTransferPacket> packets;
};

[Exposed=(Worker,Window), SecureContext]
interface USBDevice {
  readonly attribute octet usbVersionMajor;
  readonly attribute octet usbVersionMinor;
  readonly attribute octet usbVersionSubminor;
  readonly attribute octet deviceClass;
  readonly attribute octet deviceSubclass;
  readonly attribute octet deviceProtocol;
  readonly attribute unsigned short vendorId;
  readonly attribute unsigned short productId;
  readonly attribute octet deviceVersionMajor;
  readonly attribute octet deviceVersionMinor;
  readonly attribute octet deviceVersionSubminor;
  readonly attribute DOMString? manufacturerName;
  readonly attribute DOMString? productName;
  readonly attribute DOMString? serialNumber;
  readonly attribute USBConfiguration? configuration;
  readonly attribute FrozenArray<USBConfiguration> configurations;
  readonly attribute boolean opened;
  Promise<undefined> open();
  Promise<undefined> close();
  Promise<undefined> forget();
  Promise<undefined> selectConfiguration(octet configurationValue);
  Promise<undefined> claimInterface(octet interfaceNumber);
  Promise<undefined> releaseInterface(octet interfaceNumber);
  Promise<undefined> selectAlternateInterface(octet interfaceNumber, octet alternateSetting);
  Promise<USBInTransferResult> controlTransferIn(USBControlTransferParameters setup, unsigned short length);
  Promise<USBOutTransferResult> controlTransferOut(USBControlTransferParameters setup, optional BufferSource data);
  Promise<undefined> clearHalt(USBDirection direction, octet endpointNumber);
  Promise<USBInTransferResult> transferIn(octet endpointNumber, unsigned long length);
  Promise<USBOutTransferResult> transferOut(octet endpointNumber, BufferSource data);
  Promise<USBIsochronousInTransferResult> isochronousTransferIn(octet endpointNumber, sequence<unsigned long> packetLengths);
  Promise<USBIsochronousOutTransferResult> isochronousTransferOut(octet endpointNumber, BufferSource data, sequence<unsigned long> packetLengths);
  Promise<undefined> reset();
};

通过下表描述的 USBDevice 实例在创建时包含如下内部插槽(internal slots):

Internal Slot 初始值 说明(非规范性)
[[configurations]] 一个空的 sequence,元素类型为 USBConfiguration 设备支持的所有配置。
[[configurationValue]] <在说明中总是设置> 设备当前的配置值。
[[selectedAlternateSetting]] 一个 列表,元素为 integer 当前配置下每个接口的当前 alternate setting。
[[claimedInterface]] 一个 列表,元素为 boolean 当前配置下每个接口的声明(claimed)状态。
获取已连接 USB 设备的设备描述符, 执行下列步骤:
  1. deviceDescriptor 为通过执行 Get Descriptor(将 DescriptorType 设置为 DEVICE)而得到的已连接设备的 device descriptor

  2. 返回 deviceDescriptor

为已连接的 USB 设备查找配置描述符列表, 执行下列步骤:
  1. deviceDescriptor 为执行 获取已连接 USB 设备的设备描述符 的结果。

  2. numConfigurationsdeviceDescriptorbNumConfigurations 值。

  3. configurationIndex 为 0。

  4. configurationDescriptors 为一个 列表

  5. configurationIndex 小于 numConfigurations 时:

    1. descriptors 为通过执行 Get Descriptor(将 DescriptorType 设置为 CONFIGURATION,并将 DescriptorIndex 设为 configurationIndex)得到的描述符列表。

    2. 如果 descriptors[0] 的 bDescriptorType 等于 CONFIGURATION,则将 descriptors[0] 追加configurationDescriptors

    3. configurationIndex 加 1。

  6. 返回 configurationDescriptors

要对给定的 interface 对象 中止该接口上当前排定的传输,执行以下步骤:
  1. globalinterface相关全局对象

  2. configurationinterface.[[configuration]]

  3. deviceconfiguration.[[device]]

  4. 如果 configuration 与 使用 device 执行 查找当前配置 得到的结果不同,则返回。

  5. 如果 使用 interface 执行 查找接口是否被声明(claimed) 的结果不为 true,则返回。

  6. currAlternateInterface 为 使用 interface 执行 查找当前 alternate setting 的 alternate interface 的结果。

  7. 对每个 endpoint 属于 currAlternateInterface.[[endpoints]] 执行:

    1. 中止在 endpoint 上当前排定的所有传输。

    2. 在给定 globalUSB task source排队一个全局任务,以便 拒绝 相关的 promise,并以 "AbortError" DOMException 作为原因。

要使用给定的 interfaceNumberconfiguration 对象 查找接口索引,执行以下步骤:
  1. interfaceIndex 为 0。

  2. interfaceIndex 小于 configuration.[[interfaces]]大小 时:

    1. 如果 configuration.[[interfaces]][interfaceIndex].[[interfaceNumber]] 等于 interfaceNumber,则返回 interfaceIndex

    2. interfaceIndex 加 1。

  3. 返回 -1

要使用给定的 alternateSettinginterface 对象 查找 alternate 索引,执行以下步骤:
  1. alternateIndex 为 0。

  2. alternateIndex 小于 interface.[[alternates]]大小 时:

    1. 如果 interface.[[alternates]][alternateIndex].[[alternateSetting]] 等于 alternateSetting,则返回 alternateIndex

    2. alternateIndex 加 1。

  3. 返回 -1

要使用给定的 device 对象 查找当前配置,执行以下步骤:
  1. 对每个 configuration 属于 device.[[configurations]] 执行:

    1. 如果 configuration.[[configurationValue]] 等于 device.[[configurationValue]], 则返回 configuration

  2. 返回 null

要使用给定的 endpointAddressdevice 对象 查找端点,执行以下步骤:
  1. configuration 为 使用 device 执行 查找当前配置 的结果。

  2. 如果 configurationnull,则返回 null

  3. 对每个 interface 属于 configuration.[[interfaces]] 执行:

    1. 如果 使用 interface 执行 查找接口是否被声明(claimed) 的结果不为 true,则继续下一个 interface。

    2. alternate 为 使用 interface 执行 查找当前 alternate setting 的 alternate interface 的结果。

    3. 对每个 endpoint 属于 alternate.[[endpoints]] 执行:

      1. 如果 endpoint.[[endpointAddress]] 等于 endpointAddress,则返回 endpoint

  4. 返回 null

要使用给定的 device 执行 检查设备是否已配置,执行以下步骤:
  1. 如果 device 已不再连接到系统,返回 一个被拒的 promise,原因是 "NotFoundError" DOMException

  2. 如果 device.opened 不为 true,返回 一个被拒的 promise,原因是 "InvalidStateError" DOMException

  3. 如果 device.[[configurationValue]] 等于 0,返回 一个被拒的 promise,原因是 "InvalidStateError" DOMException

  4. 返回 undefined

当 USB 设备连接并被检测到时,会构造一个新的表示该连接设备的 USBDevice 对象,执行以下步骤:
  1. this.[[configurationValue]] 设置为执行 Get Configuration 返回的值。

  2. configurationDescriptors 为 执行 查找已连接 USB 设备的配置描述符列表 的结果。

  3. 对每个 configurationDescriptor 属于 configurationDescriptors 执行:

    1. configuration 为 使用 new 创建的 USBConfiguration 对象,调用形式为 USBConfiguration(device, configurationValue),其中 device 设置为 this,且 configurationValueconfigurationDescriptorbConfigurationValue

    2. configuration 追加this.[[configurations]]

    3. 如果 bConfigurationValue 等于 this.[[configurationValue]]

      1. numInterfacesconfiguration.[[interfaces]]大小

      2. this.[[selectedAlternateSetting]] 调整大小为 numInterfaces

      3. this.[[selectedAlternateSetting]] 填充为 0。

      4. this.[[claimedInterface]] 调整大小为 numInterfaces

      5. this.[[claimedInterface]] 填充为 false

所有 USB 设备必须有一个 默认控制通道,其 endpointNumber 0

6.1.1. 属性

usbVersionMajor, 类型为 octet, 只读
usbVersionMinor, 类型为 octet, 只读
usbVersionSubminor, 类型为 octet, 只读

usbVersionMajorusbVersionMinorusbVersionSubminor 属性声明了设备所支持的 USB 协议版本。它们必须对应于 bcdUSB 字段的值(见 设备描述符),使得值 0xJJMN 表示主版本为 JJ,次版本为 M,子次版本为 N

deviceClass, 类型为 octet, 只读
deviceSubclass, 类型为 octet, 只读
deviceProtocol, 类型为 octet, 只读

deviceClassdeviceSubclassdeviceProtocol 属性声明了设备支持的通信接口。它们必须分别对应于 设备描述符 中的 bDeviceClassbDeviceSubClassbDeviceProtocol 字段的值。

vendorId, 类型为 unsigned short, 只读
productId, 类型为 unsigned short, 只读

vendorIdproductId 必须等于该设备的 厂商 ID产品 ID

deviceVersionMajor, 类型为 octet, 只读
deviceVersionMinor, 类型为 octet, 只读
deviceVersionSubminor, 类型为 octet, 只读

deviceVersionMajordeviceVersionMinordeviceVersionSubminor 属性声明了设备制造商定义的设备发布号。它们必须对应于 设备描述符 中的 bcdDevice 字段,使得值 0xJJMN 表示主版本为 JJ,次版本为 M,子次版本为 N

manufacturerName, 类型为 DOMString, 只读,可为空
productName, 类型为 DOMString, 只读,可为空
serialNumber, 类型为 DOMString, 只读,可为空

manufacturerNameproductNameserialNumber 属性应当包含在 字符串描述符(由设备描述符中的 iManufactureriProductiSerialNumber 索引)中定义的值(如果这些值存在)。

configuration, 类型为 USBConfiguration, 只读,可为空

configuration 属性包含了设备当前选择的配置,且必须是 USBConfiguration 集合 configurations 中的一个。

configuration getter 的步骤如下:

  1. 返回使用 查找当前配置 并以 this 作为参数的结果。

configurations, 类型为 FrozenArray<USBConfiguration>, 只读

configurations 属性包含一个 sequence,其元素为表示设备支持的 USBConfiguration 对象。

configurations getter 的步骤如下:

  1. 返回 this.[[configurations]]

opened, 类型为 boolean, 只读

opened 属性当且仅当当前执行上下文已打开设备时应为 true;否则应为 false

6.1.2. 方法

open() 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "NotFoundError" DOMException

  3. 如果 this.openedtrue,则返回 一个已解决的 promise,值为 undefined

  4. 并行 执行以下步骤。

    1. 执行必要的平台特定步骤以开始与设备的会话。

    2. 在给定 globalUSB 任务源 上排队一个全局任务以运行以下步骤:

      1. 如果上述平台特定步骤因任何原因失败,则 拒绝 promise,原因是 "NetworkError" DOMException 并中止这些步骤。

      2. this.opened 设置为 true

      3. 解析 promise,值为 undefined

  5. 返回 promise

close() 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "NotFoundError" DOMException

  3. 如果 this.openedfalse,则返回 一个已解决的 promise,值为 undefined

  4. 中止当前针对该设备正在运行的所有其他算法,并拒绝它们关联的 promise,并抛出一个 "AbortError" DOMException

  5. 并行 执行以下步骤。

    1. 执行必要的平台特定步骤以释放所有被声明的接口,就像对每个被声明的接口调用了 releaseInterface(interfaceNumber) 一样。

    2. 执行必要的平台特定步骤以结束与设备的会话。

    3. 在给定 globalUSB 任务源 上排队一个全局任务以运行以下步骤:

      1. this.opened 设为 false

      2. 解析 promise,值为 undefined

  6. 返回 promise

注: 当没有任何 [ECMAScript] 代码可以再观察到某个 USBDevice 实例 device 时,UA 应当调用 device.close()(建议)。

forget() 方法在被调用时,必须执行以下步骤:
  1. devicethis

  2. storage 为当前脚本执行环境中的 USBPermissionStorage 对象。

  3. 使用 storage 执行 从存储中移除 device

  4. 返回 一个已解决的 promise,值为 undefined

注: 用户代理可以决定跨 API 合并权限,例如将 WebHID 与 WebUSB 的设备访问跟踪为统一的低级设备访问权限。因此,未来该方法也可能撤销额外的(目前未指定的)权限。

selectConfiguration(configurationValue) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "NotFoundError" DOMException

  3. selectedConfigurationnull

  4. 遍历 this.[[configurations]] 的每个 configuration

    1. 如果 configuration.[[configurationValue]] 等于 configurationValue,则将 selectedConfiguration 设为该 configuration 并跳出循环。

  5. 如果 selectedConfiguration 仍为 null,则返回 一个被拒绝的 promise,原因是 "NotFoundError" DOMException

  6. 如果 this.opened 不等于 true,则返回 一个被拒绝的 promise,原因是 "InvalidStateError" DOMException

  7. activeConfiguration 为使用 查找当前配置 并以 this 作为参数的结果。

  8. 并行 执行以下步骤。

    1. 如果 activeConfiguration 不为 null

      1. 遍历 activeConfiguration.[[interfaces]] 的每个 interface,对其执行 中止该接口上当前排定的传输

    2. 发出一个 SET_CONFIGURATION控制传输,其 configurationValue 设置为 configurationValue

    3. 在给定 globalUSB 任务源 上排队一个全局任务以运行以下步骤:

      1. 如果上述 控制传输 失败,则 拒绝 promise,原因是 "NetworkError" DOMException 并中止这些步骤。

      2. numInterfacesselectedConfiguration.[[interfaces]]大小

      3. this.[[selectedAlternateSetting]] 调整大小为 numInterfaces

      4. this.[[selectedAlternateSetting]] 填充为 0。

      5. this.[[claimedInterface]] 调整大小为 numInterfaces

      6. this.[[claimedInterface]] 填充为 false

      7. this.[[configurationValue]] 设为 configurationValue

      8. 解析 promise,值为 undefined

  9. 返回 promise

claimInterface(interfaceNumber) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果 使用 检查设备是否已配置 并以 this 为参数 的结果是一个 Promise,则返回该值。

  3. activeConfiguration 为使用 查找当前配置 并以 this 为参数 的结果。

  4. interfacesactiveConfiguration.[[interfaces]]

  5. interfaceIndex 为 使用 查找接口索引(以 interfaceNumberactiveConfiguration 为参数)的结果。

  6. 如果 interfaceIndex 等于 -1,则返回 一个被拒绝的 promise,原因是 "NotFoundError" DOMException

  7. 如果 this.[[claimedInterface]][interfaceIndex] 为 true,则返回 一个已解决的 promise,值为 undefined

  8. unrestrictedfalse

  9. documentglobal关联 Document,如果不存在则为 null

  10. 如果 document 不为 nulldocument 被允许使用 名为 "usb-unrestricted"策略控制特性,则将 unrestricted 设为 true

  11. 如果 interfaces[interfaceIndex].[[isProtectedClass]]trueunrestrictedfalse,则返回 一个被拒绝的 promise,原因是 "SecurityError" DOMException

  12. promise一个新 promise

  13. 并行 执行以下步骤。

    1. 执行必要的平台特定步骤以请求将 interfaces[interfaceIndex] 的独占控制权赋予当前执行上下文。

    2. 在给定 globalUSB 任务源 上排队一个全局任务以运行以下步骤:

      1. 如果上述平台特定步骤失败,则 拒绝 promise,原因是 "NetworkError" DOMException 并中止这些步骤。

      2. this.[[claimedInterface]][interfaceIndex] 设为 true

      3. 解析 promise,值为 undefined

  14. 返回 promise

releaseInterface(interfaceNumber) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise,则返回该值。

  3. activeConfiguration 为使用 查找当前配置this 的结果。

  4. interfacesactiveConfiguration.[[interfaces]]

  5. interfaceIndex 为用 查找接口索引,输入为 interfaceNumberactiveConfiguration 的结果。

  6. 如果 interfaceIndex 等于 -1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  7. 如果 this.[[claimedInterface]][interfaceIndex] 为 false, 则返回 一个以解决状态完成的 promise,其值为 undefined

  8. promise一个新的 promise

  9. 并行运行以下步骤。

    1. 执行必要的平台特定步骤,以放弃对 interfaces[interfaceIndex] 的独占控制。

    2. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. this.[[selectedAlternateSetting]][interfaceIndex] 设为 0

      2. this.[[claimedInterface]][interfaceIndex] 设为 false

      3. promise 解析为 undefined

  10. 返回 promise

selectAlternateInterface(interfaceNumber, alternateSetting) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. activeConfiguration 为使用 查找当前配置this 的结果。

  4. interfacesactiveConfiguration.[[interfaces]]

  5. interfaceIndex 为用 查找接口索引,输入为 interfaceNumberactiveConfiguration 的结果。

  6. 如果 interfaceIndex 等于 -1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  7. 如果 this.[[claimedInterface]][interfaceIndex] 为 false, 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidStateError" DOMException

  8. interfaceinterfaces[interfaceIndex]。

  9. alternateIndex 为用 查找备选设置索引,输入为 alternateSettinginterface 的结果。

  10. 如果 alternateIndex 等于 -1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  11. promise一个新的 promise

  12. 并行运行以下步骤。

    1. interface 中止当前已调度在某接口上的传输

    2. 发出一个 SET_INTERFACE 控制传输,其中 interfaceNumber 设为 interfaceNumberalternateSetting 设为 alternateSetting

    3. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. 如果上述 控制传输失败,则拒绝 promise,拒因为 "NetworkError" DOMException, 并中止这些步骤。

      2. this.[[selectedAlternateSetting]][interfaceIndex] 设为 alternateSetting

      3. promise 解析为 undefined

  13. 返回 promise

controlTransferIn(setup, length) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. 如果用 检查控制传输参数的有效性,输入为 thissetup,返回一个 Promise, 则返回该值。

  4. promise一个新的 promise

  5. 并行运行以下步骤。

    1. 如果 length 大于 0,令 buffer 为一个具有 length 字节空间的主机缓冲区。

    2. this 发起一次 控制传输,其 设置包 参数来自 setup,并将 bmRequestType 中的数据传输方向设为 “device to host”,将 wLength 设为 length。 如已定义,还应提供 buffer 作为接收此传输响应数据的写入目标。

    3. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. bytesTransferred 为写入 buffer 的字节数。

      2. result新的 USBInTransferResult

      3. 如果从设备接收到数据,则创建一个 新的 ArrayBuffer, 其包含 buffer 的前 bytesTransferred 个字节,并将 result.data 设为在其之上构建的一个 新的 DataView

      4. result.status 设为:

      5. 如果传输因其他任何原因失败,则拒绝 promise,拒因为 "NetworkError", 并中止这些步骤。

      6. promise 解析为 result

  6. 返回 promise

controlTransferOut(setup, data) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. 如果用 检查控制传输参数的有效性,输入为 thissetup,返回一个 Promise, 则返回该值。

  4. 获取缓冲区源 data 的一份拷贝,并令结果为 bytes

  5. promise一个新的 promise

  6. 并行运行以下步骤。

    1. 控制传输 的目标 this 发起传输,其 设置包 参数来自 setup,将 bmRequestType 中的数据传输方向设为 “host to device”,并将 wLength 设为 bytes 的长度

    2. 在传输的 数据阶段 发送 bytes

    3. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. result新的 USBOutTransferResult

      2. result.status 设为:

      3. 如果传输因其他任何原因失败,则拒绝 promise, 拒因为 "NetworkError" DOMException, 并中止这些步骤。

      4. promise 解析为 result

  7. 返回 promise

clearHalt(direction, endpointNumber) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. endpointAddressendpointNumber | 0x80,如果 direction 等于 "in", 否则为 endpointNumber

  4. endpoint 为用 查找端点,输入为 endpointAddressthis 的结果。

  5. 如果 endpointnull,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  6. promise一个新的 promise

  7. 并行运行以下步骤。

    1. 向设备发出 ClearFeature(ENDPOINT_HALT) 控制传输 以清除 endpoint 上的停滞状态。

    2. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. 如果上述 控制传输 失败,则拒绝 promise, 拒因为 "NetworkError" DOMException, 并中止这些步骤。

      2. promise 解析为 undefined

  8. 返回 promise

transferIn(endpointNumber, length) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. endpointAddressendpointNumber | 0x80(即 "in" 方向)。

  4. endpoint 为用 查找端点,输入为 endpointAddressthis 的结果。

  5. 如果 endpointnull,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  6. 如果 endpoint.type 不等于 "bulk""interrupt", 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidAccessError" DOMException

  7. promise一个新的 promise

  8. 并行运行以下步骤。

    1. buffer 为一个具有 length 字节空间的主机缓冲区。

    2. endpoint 的类型,执行 批量中断 IN 传输endpoint 上,从设备接收 length 字节数据到 buffer

    3. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. bytesTransferred 为写入 buffer 的字节数。

      2. result新的 USBInTransferResult

      3. 如果从设备接收到数据,则创建一个 新的 ArrayBuffer, 其包含 buffer 的前 bytesTransferred 个字节,并将 result.data 设为在其之上构建的一个 新的 DataView

      4. result.status 设为:

        • "stall", 如果设备通过停止 endpoint 进行响应。

        • "babble", 如果设备响应的数据多于 length 字节。

        • "ok", 否则。

      5. 如果传输因其他任何原因失败,则拒绝 promise,拒因为 "NetworkError" DOMException, 并中止这些步骤。

      6. promise 解析为 result

  9. 返回 promise

transferOut(endpointNumber, data) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. endpointAddressendpointNumber(即 "out" 方向)。

  4. endpoint 为用 查找端点,输入为 endpointAddressthis 的结果。

  5. 如果 endpointnull,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  6. 如果 endpoint.type 不等于 "bulk""interrupt", 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidAccessError" DOMException

  7. 获取缓冲区源 data 的一份拷贝,并令结果为 bytes

  8. promise一个新的 promise

  9. 并行运行以下步骤。

    1. endpoint 的类型,执行 批量中断 OUT 传输endpoint 上,将 bytes 发送到设备。

    2. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. result新的 USBOutTransferResult

      2. result.bytesWritten 设为成功发送到设备的数据量。

      3. result.status 设为:

        • "stall", 如果设备通过停止 endpoint 进行响应。

        • "ok", 否则。

      4. 如果传输因其他任何原因失败,则拒绝 promise,拒因为 "NetworkError" DOMException, 并中止这些步骤。

      5. promise 解析为 result

  10. 返回 promise

isochronousTransferIn(endpointNumber, packetLengths) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. endpointAddressendpointNumber | 0x80(即 "in" 方向)。

  4. endpoint 为用 查找端点,输入为 endpointAddressthis 的结果。

  5. 如果 endpointnull,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  6. 如果 endpoint.type 不等于 "isochronous", 则返回 一个以拒绝状态完成的 promise, 其拒因为 "InvalidAccessError" DOMException

  7. promise一个新的 promise

  8. 并行运行以下步骤。

    1. totalLengthpacketLengths 各元素之和。

    2. buffer 为一个具有 totalLength 字节空间的主机缓冲区。

    3. endpoint 上执行一次 等时 IN 传输,按照 packetLengths 从设备读取分组并写入 buffer

    4. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. result新的 USBIsochronousInTransferResult

      2. data 为一个 新的 ArrayBuffer,以 buffer 初始化。

      3. result.data 设为在 buffer 之上构建的一个 新的 DataView

      4. 对于每个分组 i0packetLengths.length - 1

        1. packet新的 USBIsochronousInTransferPacket

        2. view 为一个 新的 DataView, 其覆盖 data 中包含该分组从设备接收数据的部分,并将 packet.data 设为 view

        3. packet.status 设为:

          • "stall", 如果传输因设备在该分组接收前停止了 endpoint 而结束。

          • "babble", 如果传输因设备为该分组发送的数据多于 packetLengths[i] 字节而结束。

          • "ok", 否则。

        4. 如果传输因其他任何原因失败,则拒绝 promise,拒因为 "NetworkError" DOMException, 并中止这些步骤。

        5. result.packets[i] 设为 packet

      5. promise 解析为 result

  9. 返回 promise

isochronousTransferOut(endpointNumber, data, packetLengths) 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. endpointAddressendpointNumber(即 "out" 方向)。

  4. endpoint 为用 查找端点,输入为 endpointAddressthis 的结果。

  5. 如果 endpointnull,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError" DOMException

  6. 如果 endpoint.type 不等于 "isochronous", 则返回 一个以拒绝状态完成的 promise, 其拒因为 "InvalidAccessError" DOMException

  7. 获取缓冲区源 data 的一份拷贝,并令结果为 bytes

  8. promise一个新的 promise

  9. 并行运行以下步骤。

    1. endpoint 上执行一次 等时 OUT 传输,将 bytes 写入设备,分成 packetLengths.length 个分组, 每个分组为 packetLengths[i] 字节(对于分组 i0packetLengths.length - 1)。

    2. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. result新的 USBIsochronousOutTransferResult

      2. 对于每个分组 i0packetLengths.length - 1

        1. packet新的 USBIsochronousOutTransferPacket

        2. packet.bytesWritten 设为作为该分组的一部分成功发送到设备的数据量。

        3. packet.status 设为:

          • "stall", 如果传输因设备在该分组被确认前停止了 endpoint 而结束。

          • "ok", 否则。

        4. 如果传输因其他任何原因失败,则拒绝 promise,拒因为 "NetworkError" DOMException, 并中止这些步骤。

        5. result.packets[i] 设为 packet

      3. promise 解析为 result

  10. 返回 promise

reset() 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果用 检查设备是否已配置this 返回一个 Promise, 则返回该值。

  3. promise一个新的 promise

  4. 并行运行以下步骤。

    1. 中止设备上的所有操作,并在 全局任务队列中入队一个基于 USB 任务源 的任务, 以拒绝其相关的 promise,拒因为 "AbortError" DOMException

    2. 执行必要的平台特定操作以对设备进行软重置。

    3. 在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:

      1. 如果软重置失败,则拒绝 promise,拒因为 "NetworkError", 并中止这些步骤。

      2. promise 解析为 undefined

  5. 返回 promise

设备重置后处于什么配置? [Issue #36]

6.2. USBControlTransferParameters 字典

enum USBRequestType {
  "standard",
  "class",
  "vendor"
};

enum USBRecipient {
  "device",
  "interface",
  "endpoint",
  "other"
};

dictionary USBControlTransferParameters {
  required USBRequestType requestType;
  required USBRecipient recipient;
  required octet request;
  required unsigned short value;
  required unsigned short index;
};
为了使用给定的 USBDevice deviceUSBControlTransferParameters setup检查控制传输参数的有效性, 执行以下步骤:
  1. configuration 为用 device 查找当前配置 的结果。

  2. 如果 configurationnull,返回 undefined

  3. 如果 setup.recipient"interface", 则执行以下步骤:

    1. interfaceNumbersetup.index 的低 8 位。

    2. interfaceIndex 为用 interfaceNumberconfiguration 查找接口索引 的结果。

    3. 如果 interfaceIndex 等于 -1,返回 一个被拒绝的 promise,其拒因为 "NotFoundError" DOMException

    4. interfaceconfiguration.[[interfaces]][interfaceIndex]。

    5. 如果用 interface 查找接口是否已被声明 的结果不为 true,返回 一个被拒绝的 promise,其拒因为 "InvalidStateError" DOMException

  4. 如果 setup.recipient"endpoint", 则运行以下步骤:

    1. endpointAddresssetup.index

    2. endpoint 为用 endpointAddressdevice 查找端点 的结果。

    3. 如果 endpointnull,返回 一个被拒绝的 promise,其拒因为 "NotFoundError" DOMException

    4. alternateendpoint.[[alternateInterface]]

    5. interfacealternate.[[interface]]

    6. 如果用 interface 查找接口是否已被声明 的结果为 false,返回 一个被拒绝的 promise,其拒因为 "InvalidStateError" DOMException

  5. 返回 undefined

6.2.1. 成员

requestType 类型为 USBRequestType

requestType 属性填充 设置包 中的 bmRequestType 字段的一部分,以指示 此请求是 USB 标准的一部分、某个特定 USB 设备类规范,还是供应商特定协议的一部分。

recipient 类型为 USBRecipient

recipient 属性填充 设置包 中的 bmRequestType 字段的一部分,以指示 控制传输 是面向整个设备,还是某个特定的接口或端点。

request 类型为 octet

request 属性填充 设置包bRequest 字段。有效请求由 USB 标准、USB 设备类规范或设备供应商定义。

value 类型为 unsigned short

valueindex 属性分别填充 设置包wValuewIndex 字段。 这些字段的含义取决于所发起的请求。

6.3. USBConfiguration 接口

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBConfiguration {
  constructor(USBDevice device, octet configurationValue);
  readonly attribute octet configurationValue;
  readonly attribute DOMString? configurationName;
  readonly attribute FrozenArray<USBInterface> interfaces;
};

USBConfiguration 的实例具有以下表格中描述的 内部槽位

内部槽位 初始值 描述(非规范性)
[[device]] <在正文中设置> USBDevice 对象 this 所属的对象。
[[interfaces]] 一个空的由 USBInterface 组成的 sequence 此配置支持的所有接口。
[[configurationValue]] <在正文中设置> 此配置对应的配置值。
为了为某个配置查找描述符列表, 给定 configurationValue, 执行以下步骤:
  1. deviceDescriptor查找已连接 USB 设备的设备描述符 的结果。

  2. numConfigurationsdeviceDescriptorbNumConfigurations

  3. configurationIndex 为 0。

  4. configurationIndex 小于 numConfigurations 时:

    1. descriptors 为通过执行 Get Descriptor、将 DescriptorType 设为 CONFIGURATIONDescriptorIndex 设为 configurationIndex 所得到的描述符列表。

    2. 如果 descriptors[0] 的 bDescriptorType 等于 CONFIGURATIONdescriptorsbConfigurationValue 等于 configurationValue,则返回 descriptors

    3. configurationIndex 加 1。

  5. 返回 结果。

6.3.1. 构造函数

USBConfiguration(device, configurationValue) 构造函数在被调用时必须执行以下步骤:
  1. this.[[device]] 设为 device

  2. this.[[configurationValue]] 设为 configurationValue

  3. descriptors 为以 configurationValue 设为 configurationValue查找配置的描述符列表 的结果。

  4. seen 为一个空的 有序集合

  5. descriptors 中的每个 descriptor

    1. 如果 descriptorbDescriptorType 不等于 INTERFACE,跳过。

    2. 如果 descriptorbInterfaceNumberseen 中,跳过。

    3. interface 为一个 新的 USBInterface 对象,使用 USBInterface(configuration,interfaceNumber), 其中 configuration 设为 thisinterfaceNumber 设为 descriptorbInterfaceNumber

    4. interface 追加到 this.[[interfaces]]

    5. descriptorbInterfaceNumber 追加到 seen

6.3.2. 属性

configurationValue 类型为 octet,只读

每个设备配置都应具有唯一的 configurationValue, 它与定义该配置的 配置描述符bConfigurationValue 字段匹配。

configurationName 类型为 DOMString,只读,可空

configurationName 属性应包含 配置描述符iConfiguration 字段所引用的 字符串描述符 的值(如果已定义)。

interfaces 类型为 FrozenArray<USBInterface>,只读

interfaces 属性应包含此设备配置公开的接口列表。

interfaces 的 getter 步骤为:

  1. 返回 this.[[interfaces]]

需要包含一些关于设备配置的非规范性信息。[Issue #46]

6.4. USBInterface 接口

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBInterface {
  constructor(USBConfiguration configuration, octet interfaceNumber);
  readonly attribute octet interfaceNumber;
  readonly attribute USBAlternateInterface alternate;
  readonly attribute FrozenArray<USBAlternateInterface> alternates;
  readonly attribute boolean claimed;
};

USBInterface 的实例具有以下表格中描述的 内部槽位

内部槽位 初始值 描述(非规范性)
[[configuration]] <在正文中设置> USBConfiguration 对象 this 所属的对象。
[[interfaceNumber]] <在正文中设置> 此接口的接口编号。
[[alternates]] 一个空的由 USBAlternateInterface 组成的 sequence 此接口支持的所有备选设置。
[[isProtectedClass]] false 此接口是否具有属于受保护类别的任何备选设置。

接口描述符 interface 当且仅当其 bInterfaceClass 等于以下值之一时,具有受保护的接口类别

受保护的接口类别
代码 描述
0x01 音频(Audio)
0x03 HID(人机接口设备)
0x08 大容量存储(Mass Storage)
0x0B 智能卡(Smart Card)
0x0E 视频(Video)
0x10 音频/视频设备(Audio/Video Devices)
0xE0 无线控制器(Wireless Controller)

注意: 本规范试图在保护用户免受恶意内容侵害(通过限制对敏感设备的访问)与尽可能支持更多设备之间取得平衡。 如介绍所述,此 API 的目标是支持其他更高级别 API 未覆盖的设备。上述列表包含了已有高级别 API 的接口类别, 这些高级别 API 相比通过本 API 的低级访问,能为用户隐私和安全提供更好的保护。

为了使用给定的 interface 查找接口是否已被声明, 执行以下步骤:
  1. configurationinterface.[[configuration]]

  2. deviceconfiguration.[[device]]

  3. 如果 configuration 与用 device 查找当前配置 的结果不同,返回 false

  4. interfaceIndex 为用 interface.[[interfaceNumber]]configuration 查找接口索引 的结果。

  5. 断言interfaceIndex 不等于 -1

  6. 返回 device.[[claimedInterface]][interfaceIndex]。

为了使用给定的 interface 查找当前备选设置对应的备选接口, 执行以下步骤:
  1. configurationinterface.[[configuration]]

  2. deviceconfiguration.[[device]]

  3. alternateIndex 为 0。

  4. 如果用 interface 查找接口是否已被声明 的结果为 true

    1. interfaceIndex 为用 interface.[[interfaceNumber]]configuration 查找接口索引 的结果。

    2. 断言interfaceIndex 不等于 -1

    3. alternateIndex 设为用 device.[[selectedAlternateSetting]][interfaceIndex] 和 interface 查找备选索引 的结果。

  5. 断言alternateIndex 不等于 -1

  6. 返回 interface.alternates[alternateIndex]。

注意: 根据 接口描述符 [USB31],每个接口至少有一个备选设置, 因为在 接口描述符 中必须至少存在一个备选设置。

6.4.1. 构造函数

USBInterface(configuration, interfaceNumber) 构造函数在被调用时必须执行以下步骤:
  1. this.[[configuration]] 设为 configuration

  2. this.[[interfaceNumber]] 设为 interfaceNumber

  3. descriptors 为以 configurationValue 设为 configuration.[[configurationValue]]查找配置的描述符列表 的结果。

  4. descriptors 中的每个 descriptor

    1. 如果 descriptorbDescriptorType 不等于 INTERFACE,则继续。

    2. 如果 descriptorbInterfaceNumber 不等于 interfaceNumber,则继续。

    3. 如果 具有受保护的接口类别,则将 this.[[isProtectedClass]] 设为 true

    4. alternate 为一个 新的 USBAlternateInterface 对象, 使用 USBAlternateInterface(deviceInterface,alternateSetting), 其中 deviceInterface 设为 thisalternateSetting 设为 descriptorbAlternateSetting

    5. alternate 追加到 this.[[alternates]]

6.4.2. 属性

interfaceNumber 类型为 octet,只读

每个接口提供一组由其 接口描述符 中的 bInterfaceNumber 字段标识的 alternatesinterfaceNumber 属性必须与该字段匹配。

interfaceNumber 的 getter 步骤为:

  1. 返回 this.[[interfaceNumber]]

alternate 类型为 USBAlternateInterface,只读

alternate 属性应被设为当前为该接口选择的 USBAlternateInterface, 默认应为其 bAlternateSetting 等于 0 的那一个。

alternate 的 getter 步骤为:

  1. 返回以 查找当前备选设置对应的备选接口、参数为 this 的结果。

alternates 类型为 FrozenArray<USBAlternateInterface>,只读

alternates 方法提供由其 接口描述符 中的单个 bInterfaceNumber 字段标识的 USBAlternateInterface 对象集合。

alternates 的 getter 步骤为:

  1. 返回 this.[[alternates]]

claimed 类型为 boolean,只读

当该接口被当前执行上下文声明时,claimed 属性应设为 true;否则应设为 false

claimed 的 getter 步骤为:

  1. 返回以 查找接口是否已被声明、参数为 this 的结果。

6.5. USBAlternateInterface 接口

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBAlternateInterface {
  constructor(USBInterface deviceInterface, octet alternateSetting);
  readonly attribute octet alternateSetting;
  readonly attribute octet interfaceClass;
  readonly attribute octet interfaceSubclass;
  readonly attribute octet interfaceProtocol;
  readonly attribute DOMString? interfaceName;
  readonly attribute FrozenArray<USBEndpoint> endpoints;
};

USBAlternateInterface 的实例具有以下表格中描述的 内部槽位

内部槽位 初始值 描述(非规范性)
[[interface]] <在正文中设置> USBInterface 对象 this 所属的对象。
[[endpoints]] 一个空的由 USBEndpoint 组成的 sequence 此接口公开的所有端点。
[[alternateSetting]] <在正文中设置> 此接口的备选设置值。
为了使用给定的 alternateSettinginterfaceNumberconfigurationValue 查找端点描述符列表, 执行以下步骤:
  1. descriptors 为以 configurationValue查找配置的描述符列表 的结果。

  2. endpointDescriptors 为一个空的 列表

  3. index 为 0。

  4. index 小于 descriptors大小 时:

    1. descriptordescriptors[index]。

    2. 如果 descriptorbDescriptorType 不等于 INTERFACE, 将 index 加 1 并继续。

    3. 如果 descriptorbInterfaceNumber 不等于 interfaceNumber, 将 index 加 1 并继续。

    4. 如果 descriptorbAlternateSetting 不等于 alternateSetting, 将 index 加 1 并继续。

    5. 如果 bNumEndpoints 等于 0,返回 endpointDescriptors

    6. numEndpointsdescriptorbNumEndpoints

    7. indexEndpoints 为 0。

    8. offsetindex + 1。

    9. indexEndpoints 小于 numEndpoints 时:

      1. descriptors[indexEndpoints + offset] 追加到 endpointDescriptors

      2. indexEndpoints 加 1。

    10. 返回 endpointDescriptors

6.5.1. 构造函数

USBAlternateInterface(deviceInterface, alternateSetting) 构造函数在被调用时必须执行以下步骤:
  1. this.[[interface]] 设为 deviceInterface

  2. this.[[alternateSetting]] 设为 alternateSetting

  3. descriptors查找端点描述符列表 的结果, 其中 interfaceNumber 设为 deviceInterface.interfaceNumberalternateSetting 设为 alternateSettingconfigurationValue 设为 deviceInterface.[[configuration]].[[configurationValue]]

  4. descriptors 中的每个 descriptor

    1. 如果 descriptorbmAttributes 表明它是控制传输类型(即根据 端点描述符,位 0..100),则继续。

    2. endpointAddressdescriptorbEndpointAddress

    3. dir"out" 如果 endpointAddress & 0x800, 否则为 "in"

    4. endpoint 为一个 新的 USBEndpoint 对象, 使用 USBEndpoint(alternate,endpointNumber,direction), 其中 alternate 设为 thisendpointNumber 设为 endpointAddress & 0xFdirection 设为 dir

    5. endpoint 追加到 this.[[endpoints]]

6.5.2. 属性

alternateSetting 类型为 octet,只读

在给定接口内,每个备选接口配置应具有唯一的 alternateSetting, 并与定义它的 接口描述符bAlternateSetting 字段匹配。

interfaceClass 类型为 octet,只读
interfaceSubclass 类型为 octet,只读
interfaceProtocol 类型为 octet,只读

interfaceClassinterfaceSubclassinterfaceProtocol 属性声明了该接口所支持的通信接口。它们必须分别对应于 接口描述符bInterfaceClassbInterfaceSubClassbInterfaceProtocol 字段的取值。

interfaceName 类型为 DOMString,只读,可空

interfaceName 属性应包含 接口描述符iInterface 字段所索引的 字符串描述符 的值(如果已定义)。

endpoints 类型为 FrozenArray<USBEndpoint>,只读

endpoints 属性应包含此接口公开的端点列表。这些端点应从该 接口描述符 所包含的 端点描述符 中填充, 且该序列的元素数量应与 接口描述符bNumEndpoints 字段相匹配。

endpoints 的 getter 步骤为:

  1. 返回 this.[[endpoints]]

6.6. USBEndpoint 接口

enum USBDirection {
  "in",
  "out"
};

enum USBEndpointType {
  "bulk",
  "interrupt",
  "isochronous"
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBEndpoint {
  constructor(USBAlternateInterface alternate, octet endpointNumber, USBDirection direction);
  readonly attribute octet endpointNumber;
  readonly attribute USBDirection direction;
  readonly attribute USBEndpointType type;
  readonly attribute unsigned long packetSize;
};

USBEndpoint 的实例具有下表所述的 内部槽位

内部槽位 初始值 描述(非规范性)
[[alternateInterface]] <在正文中设置> USBAlternateInterface 对象,即 this 所隶属的对象。
[[endpointAddress]] <在正文中设置> 此端点的端点地址。
要使用给定的 endpoint 对象查找端点描述符,执行以下步骤:
  1. alternateInterfaceendpoint.[[alternateInterface]]

  2. interfacealternateInterface.[[interface]]

  3. configurationinterface.[[configuration]]

  4. endpointDescriptors查找端点描述符列表 的结果,其中 alternateSetting 设为 alternateInterface.alternateSettinginterfaceNumber 设为 interface.interfaceNumberconfigurationValue 设为 configuration.configurationValue

  5. 对于 endpointDescriptors 中的每个 endpointDescriptor

    1. 如果 endpoint.[[endpointAddress]] 等于 endpointDescriptorbEndpointAddress,则返回 endpointDescriptor

6.6.1. 构造函数

endpointNumber(alternate, endpointNumber, direction) 构造函数在被调用时必须执行以下步骤:
  1. this.[[alternateInterface]] 设为 alternate

  2. direction"in",则将 endpointAddress 设为 endpointNumber | 0x80;否则设为 endpointNumber

  3. this.[[endpointAddress]] 设为 endpointAddress

6.6.2. 属性

endpointNumber 类型为 octet,只读
direction 类型为 USBDirection,只读

在特定设备配置内,每个端点应具有唯一的 endpointNumberdirection 的组合。endpointNumber 必须等于定义该端点的 端点描述符bEndpointAddress 字段的低 4 位。

direction 属性声明该端点支持的数据传输方向;若 bEndpointAddress 的最高有效位被置位,则其值为 "in", 否则为 "out"。一个端点要么承载从设备到主机的 IN 数据,要么承载从主机到设备的 OUT 数据。

type 类型为 USBEndpointType,只读

type 属性声明该端点支持的数据传输类型。

type 的 getter 步骤为:

  1. endpointDescriptor 为以 查找端点描述符、参数为 this 的结果。

  2. attrendpointDescriptorbmAttributes

  3. typeBitsattr & 0x3

  4. typeBits 等于 b01,返回 "isochronous"

  5. typeBits 等于 b10,返回 "bulk"

  6. typeBits 等于 b11,返回 "interrupt"

注意: 按照 USBAlternateInterface(deviceInterface,alternateSetting) 的步骤,不应存在属于控制传输类型(Control Transfer Type)的端点对象。

packetSize 类型为 unsigned long,只读

packetSize 属性声明此端点采用的数据包大小,并且必须等于定义该端点的 端点描述符wMaxPacketSize 的值。在高速(High-Speed)、高带宽(High-Bandwidth)的端点中,该值包含通过每微帧多次事务所提供的乘数因子;在 SuperSpeed 设备中,该值包含 SuperSpeed 端点伴随描述符(Endpoint Companion descriptor)中 bMaxBurst 字段所提供的乘数因子。

packetSize 的 getter 步骤为:

  1. endpointDescriptor 为以 查找端点描述符、参数为 this 的结果。

  2. 返回 endpointDescriptorwMaxPacketSize

7. USB 阻止列表

// USBBlocklistEntry is never exposed.
dictionary USBBlocklistEntry {
  required unsigned short idVendor;
  required unsigned short idProduct;
  required unsigned short bcdDevice;
};

本规范依赖于此代码库中的 blocklist.txt 文件来限制网站可访问的设备集合。

在某个 URL url解析阻止列表 的结果,是通过以下算法生成的 列表(其元素为 USBBlocklistEntry 对象):

  1. 获取 url,令 contents 为其正文,并以 UTF-8 解码。

  2. lines 为从 contents 开头开始,在码位 '\n' 上进行严格分割的结果。

  3. blocklist 为一个空的 列表

  4. 对于 lines 中的每一行 line

    1. line 设为自行开头起、从 line收集一段码位序列(直至遇到不等于 '#' 的码位)所得的结果。

    2. line 设为从 line去除前导与尾随 ASCII 空白 的结果。

    3. components 为自 line 开头、在码位 ':' 上对 line 进行严格分割 的结果。

    4. components大小 不为 2 或 3,则继续

    5. idVendor 为将 components[0] 按十六进制数解释的结果。

    6. idProduct 为将 components[1] 按十六进制数解释的结果。

    7. bcdDevice0xFFFF

    8. components大小 为 3,则将 bcdDevice 设为将 components[2] 按十六进制数解释的结果。

    9. 追加 一个新的 USBBlocklistEntry, 其包含 idVendoridProductbcdDevice,并将其加入 blocklist

  5. 返回 blocklist

USB 阻止列表是在 解析阻止列表 https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt 后得到的结果。 UA 应当定期重新获取该阻止列表,但获取频率未作规定。

若以下步骤返回 “blocked”,则 USBDevice device 对于某个 Document document 被视为在阻止列表中

  1. 如果 documentnulldocument允许使用 名为 "usb-unrestricted"策略控制特性,则返回 “not blocked”。

  2. 对于 USB 阻止列表 中的每个 entry

    1. 如果 device.vendorId 不等于 entry.idVendor继续

    2. 如果 device.productId 不等于 entry.idProduct继续

    3. bcdDevicedevice.deviceVersionMajor << 8 + device.deviceVersionMinor << 4 + device.deviceVersionSubminor

    4. 如果 bcdDevice 小于或等于 entry.bcdDevice, 返回 “blocked”。

  3. 返回 “not blocked”。

8. 集成

8.1. 权限策略

本规范定义了一个由令牌 "usb" 标识的策略控制特性,用于控制 usb 属性是否暴露在 Navigator 对象上。

此特性的 默认允许列表["self"]

本规范还定义了第二个由令牌 "usb-unrestricted" 标识的策略控制特性,用于控制是否可以访问在阻止列表中的 USB 设备以及具有受保护类别的设备接口。该特性必须仅为在 Web 应用清单中声明了该特性的 Isolated Web Apps 启用(参见 [APPMANIFEST])。

此特性的 默认允许列表["self"]。通常这意味着默认允许该特性用于处于顶层可遍历中的 Document。 然而,由于要求该特性仅对在清单中声明了该特性的 Isolated Web Apps 启用,其有效的默认允许列表为 ["none"]

8.2. 权限 API

[permissions] API 为网站提供了一种统一的方式来向用户请求权限并查询已获得的权限。

"usb" 这一强大特性(powerful feature)定义如下:

权限描述符类型
dictionary USBPermissionDescriptor : PermissionDescriptor {
  sequence<USBDeviceFilter> filters;
  sequence<USBDeviceFilter> exclusionFilters;
};
额外权限数据类型
USBPermissionStorage, 定义如下:
dictionary AllowedUSBDevice {
  required octet vendorId;
  required octet productId;
  DOMString serialNumber;
};

dictionary USBPermissionStorage {
  sequence<AllowedUSBDevice> allowedDevices = [];
};

AllowedUSBDevice 实例具有一个内部槽位 [[devices]],其中保存一组USB 设备

权限结果类型
[Exposed=(Worker,Window)]
interface USBPermissionResult : PermissionStatus {
  attribute FrozenArray<USBDevice> devices;
};
权限查询算法
要使用 USBPermissionDescriptor descUSBPermissionStorage storageUSBPermissionResult status查询 “usb” 权限,UA 必须:
  1. 如果 desc.filters 已设置,则对于 desc.filters 中的每个 filter,若 filter 不是有效的过滤器,则抛出 TypeError 并中止这些步骤。

  2. 如果 desc.exclusionFilters 已设置,则对于 desc.exclusionFilters 中的每个 exclusionFilter,若 exclusionFilter 不是有效的过滤器,则抛出 TypeError 并中止这些步骤。

  3. status.state 设为 "prompt"

  4. matchingDevices 为一个新的 Array

  5. 对于 storage.allowedDevices 中的每个 allowedDevice,以及 allowedDevice@[[devices]] 中的每个 device,执行以下子步骤:

    1. 如果 desc.filters 已设置,且 device 未能匹配 desc.filters 中的某个设备过滤器,则继续下一个 device

    2. 如果 desc.exclusionFilters 已设置,且 device 匹配 desc.exclusionFilters 中的某个设备过滤器,则继续下一个 device

    3. 获取表示 deviceUSBDevice 并将其加入 matchingDevices

  6. status.devices 设为一个新的 FrozenArray,其内容为 matchingDevices

权限请求算法
请求 “usb” 权限

9. 术语

本规范使用了一些来自 [USB31] 的术语。尽管引用的是 USB 3.1 版本,但其中许多概念在更早的版本中也已存在。与本规范相关的不同 USB 版本之间的显著差异将明确指出。

描述符(Descriptors) 是可以从设备读取的二进制数据结构,用于描述其属性与功能:

二进制对象存储(Binary Object Store,BOS) 是一组较为自由形式的额外描述符。值得注意的是其中的 平台描述符(Platform Descriptor) 类型,允许第三方(例如本规范)声明自定义类型的描述符。每种类型由一个 UUID 标识。二进制对象存储详见 [USB31] 第 9.6.2 节。

USB 设备 具有一个 设备描述符,并链接到一个或多个 配置描述符。其 厂商 ID(vendor ID) 由 USB-IF 分配给设备制造商,并存储于 设备描述符idVendor 字段;其 产品 ID(product ID) 由制造商分配,并存储于 设备描述符idProduct 字段;其 序列号(serial number) 是可选属性:当 设备描述符iSerialNumber 字段不等于 0 时定义,其值为该索引引用的 字符串描述符

Get Configuration 是获取当前设备配置值的请求,详见 [USB31] 第 9.4.2 节。

Set Descriptor 是根据指定的描述符类型与描述符索引获取描述符的请求,详见 [USB31] 第 9.4.8 节。

Get Descriptor 是根据指定的描述符类型与描述符索引获取描述符的请求,详见 [USB31] 第 9.4.3 节。

注意: 由于设备的描述符通常在大多数时间保持不变(除非偶尔发生 Set Descriptor 操作修改描述符),为减少对设备冗余的 Get Descriptor 流量,实现可以采用读通 & 写通缓存层来存储设备的描述符

控制传输(control transfer) 是 USB 传输中的一种特殊类别,最常用于配置设备。它由三个阶段组成:设置(setup)数据(data)状态(status)。在设置阶段(setup stage),会向设备发送一个包含请求参数(包括传输方向与后续数据大小)的 设置包(setup packet);在数据阶段(data stage),数据会被发送到设备或从设备接收;在状态阶段(status stage),将确认请求已成功处理,或指示失败。

10. 附录:USB 简介

本节为非规范性内容

USB 是一种网络,但与传统的 TCP/IP 网络非常不同。它更像是一个 RPC 系统。所有流量都由主机(即你的计算机)主导。尽管某些设备(如智能手机)既可充当 USB 主机,又可充当 USB 客户端,但在同一时间内它们只能承担其中一种角色。

10.1. 描述符

USB 设备通过提供一组称为描述符的二进制结构向主机标识自身。主机首先读取的是设备描述符,其中包含基本信息,如由 USB-IF 和制造商分配的厂商与产品 ID。随后主机可以读取设备的 配置描述符,它描述了设备的能力,包括其公开的接口与端点。类(class)既可在设备级别声明,也可在单个接口级别声明。具有多个提供不同功能的接口的设备称为复合设备(composite device)

10.2. 传输

无论数据是从主机到设备,还是相反,传输始终由主机发起。OUT 传输 将数据从主机传至设备,并可能等待设备确认已接收数据;IN 传输 将数据从设备传回主机,并可能需要等待设备准备好要发送的数据。传输针对设备的某个端点执行,并根据所发送的流量类型而分为不同种类。

一致性

文档约定

一致性要求通过描述性断言与 RFC 2119 术语相结合进行表达。 术语 “MUST”、 “MUST NOT”、 “REQUIRED”、 “SHALL”、 “SHALL NOT”、 “SHOULD”、 “SHOULD NOT”、 “RECOMMENDED”、 “MAY” 与 “OPTIONAL” 在本文档的规范性部分应按照 RFC 2119 的描述进行解释。 但为便于阅读,本规范并未在所有位置使用全大写形式来书写这些词。

除明确标注为非规范性、示例与注释的部分外,本文档的全部文本均为规范性内容。[RFC2119]

本文中的示例将以 “for example” 引出,或通过 class="example" 与规范性文本区分,例如:

这是一个信息性示例。

信息性注释以 “Note” 开头,并通过 class="note" 与规范性文本区分,例如:

Note,这是一个信息性注释。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS数值与单位模块 第4版。网址:https://drafts.csswg.org/css-values-4/
[DOM]
Anne van Kesteren. DOM 标准。规范草案。网址:https://dom.spec.whatwg.org/
[ECMAScript]
ECMAScript 语言规范。网址:https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren 等。HTML 标准。规范草案。网址:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。规范草案。网址:https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions(权限规范)。网址: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. 权限策略(Permissions Policy)。网址:https://w3c.github.io/webappsec-permissions-policy/
[PERMISSIONS-REQUEST]
请求权限(Requesting Permissions)。社区组草案报告。网址:https://wicg.github.io/permissions-request/
[RFC2119]
S. Bradner. RFC中用以指示需求级别的关键词。1997年3月。最佳当前实践。网址:https://datatracker.ietf.org/doc/html/rfc2119
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers。网址:https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL 标准。规范草案。网址:https://url.spec.whatwg.org/
[USB31]
通用串行总线3.1规范。2013年7月26日。网址:http://www.usb.org/developers/docs/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。规范草案。网址:https://webidl.spec.whatwg.org/

补充性参考

[APPMANIFEST]
Marcos Caceres 等。Web 应用清单(Web Application Manifest)。网址:https://w3c.github.io/manifest/
[CORS]
Anne van Kesteren. 跨域资源共享(Cross-Origin Resource Sharing)。2020年6月2日规范。网址:https://www.w3.org/TR/cors/
[POWERFUL-FEATURES]
Mike West. 安全上下文(Secure Contexts)。网址: https://w3c.github.io/webappsec-secure-contexts/
[RFC4122]
通用唯一标识符(UUID)URN 命名空间。2005年7月。网址:https://tools.ietf.org/html/rfc4122

IDL 索引

dictionary USBDeviceFilter {
  unsigned short vendorId;
  unsigned short productId;
  octet classCode;
  octet subclassCode;
  octet protocolCode;
  DOMString serialNumber;
};

dictionary USBDeviceRequestOptions {
  required sequence<USBDeviceFilter> filters;
  sequence<USBDeviceFilter> exclusionFilters = [];
};

[Exposed=(Worker,Window), SecureContext]
interface USB : EventTarget {
  attribute EventHandler onconnect;
  attribute EventHandler ondisconnect;
  Promise<sequence<USBDevice>> getDevices();
  [Exposed=Window] Promise<USBDevice> requestDevice(USBDeviceRequestOptions options);
};

[Exposed=Window, SecureContext]
partial interface Navigator {
  [SameObject] readonly attribute USB usb;
};

[Exposed=Worker, SecureContext]
partial interface WorkerNavigator {
  [SameObject] readonly attribute USB usb;
};

dictionary USBConnectionEventInit : EventInit {
    required USBDevice device;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBConnectionEvent : Event {
  constructor(DOMString type, USBConnectionEventInit eventInitDict);
  [SameObject] readonly attribute USBDevice device;
};

enum USBTransferStatus {
  "ok",
  "stall",
  "babble"
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBInTransferResult {
  constructor(USBTransferStatus status, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBOutTransferResult {
  constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0);
  readonly attribute unsigned long bytesWritten;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousInTransferPacket {
  constructor(USBTransferStatus status, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousInTransferResult {
  constructor(sequence<USBIsochronousInTransferPacket> packets, optional DataView? data);
  readonly attribute DataView? data;
  readonly attribute FrozenArray<USBIsochronousInTransferPacket> packets;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousOutTransferPacket {
  constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0);
  readonly attribute unsigned long bytesWritten;
  readonly attribute USBTransferStatus status;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBIsochronousOutTransferResult {
  constructor(sequence<USBIsochronousOutTransferPacket> packets);
  readonly attribute FrozenArray<USBIsochronousOutTransferPacket> packets;
};

[Exposed=(Worker,Window), SecureContext]
interface USBDevice {
  readonly attribute octet usbVersionMajor;
  readonly attribute octet usbVersionMinor;
  readonly attribute octet usbVersionSubminor;
  readonly attribute octet deviceClass;
  readonly attribute octet deviceSubclass;
  readonly attribute octet deviceProtocol;
  readonly attribute unsigned short vendorId;
  readonly attribute unsigned short productId;
  readonly attribute octet deviceVersionMajor;
  readonly attribute octet deviceVersionMinor;
  readonly attribute octet deviceVersionSubminor;
  readonly attribute DOMString? manufacturerName;
  readonly attribute DOMString? productName;
  readonly attribute DOMString? serialNumber;
  readonly attribute USBConfiguration? configuration;
  readonly attribute FrozenArray<USBConfiguration> configurations;
  readonly attribute boolean opened;
  Promise<undefined> open();
  Promise<undefined> close();
  Promise<undefined> forget();
  Promise<undefined> selectConfiguration(octet configurationValue);
  Promise<undefined> claimInterface(octet interfaceNumber);
  Promise<undefined> releaseInterface(octet interfaceNumber);
  Promise<undefined> selectAlternateInterface(octet interfaceNumber, octet alternateSetting);
  Promise<USBInTransferResult> controlTransferIn(USBControlTransferParameters setup, unsigned short length);
  Promise<USBOutTransferResult> controlTransferOut(USBControlTransferParameters setup, optional BufferSource data);
  Promise<undefined> clearHalt(USBDirection direction, octet endpointNumber);
  Promise<USBInTransferResult> transferIn(octet endpointNumber, unsigned long length);
  Promise<USBOutTransferResult> transferOut(octet endpointNumber, BufferSource data);
  Promise<USBIsochronousInTransferResult> isochronousTransferIn(octet endpointNumber, sequence<unsigned long> packetLengths);
  Promise<USBIsochronousOutTransferResult> isochronousTransferOut(octet endpointNumber, BufferSource data, sequence<unsigned long> packetLengths);
  Promise<undefined> reset();
};

enum USBRequestType {
  "standard",
  "class",
  "vendor"
};

enum USBRecipient {
  "device",
  "interface",
  "endpoint",
  "other"
};

dictionary USBControlTransferParameters {
  required USBRequestType requestType;
  required USBRecipient recipient;
  required octet request;
  required unsigned short value;
  required unsigned short index;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBConfiguration {
  constructor(USBDevice device, octet configurationValue);
  readonly attribute octet configurationValue;
  readonly attribute DOMString? configurationName;
  readonly attribute FrozenArray<USBInterface> interfaces;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBInterface {
  constructor(USBConfiguration configuration, octet interfaceNumber);
  readonly attribute octet interfaceNumber;
  readonly attribute USBAlternateInterface alternate;
  readonly attribute FrozenArray<USBAlternateInterface> alternates;
  readonly attribute boolean claimed;
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBAlternateInterface {
  constructor(USBInterface deviceInterface, octet alternateSetting);
  readonly attribute octet alternateSetting;
  readonly attribute octet interfaceClass;
  readonly attribute octet interfaceSubclass;
  readonly attribute octet interfaceProtocol;
  readonly attribute DOMString? interfaceName;
  readonly attribute FrozenArray<USBEndpoint> endpoints;
};

enum USBDirection {
  "in",
  "out"
};

enum USBEndpointType {
  "bulk",
  "interrupt",
  "isochronous"
};

[
  Exposed=(Worker,Window),
  SecureContext
]
interface USBEndpoint {
  constructor(USBAlternateInterface alternate, octet endpointNumber, USBDirection direction);
  readonly attribute octet endpointNumber;
  readonly attribute USBDirection direction;
  readonly attribute USBEndpointType type;
  readonly attribute unsigned long packetSize;
};

// USBBlocklistEntry is never exposed.
dictionary USBBlocklistEntry {
  required unsigned short idVendor;
  required unsigned short idProduct;
  required unsigned short bcdDevice;
};

dictionary USBPermissionDescriptor : PermissionDescriptor {
  sequence<USBDeviceFilter> filters;
  sequence<USBDeviceFilter> exclusionFilters;
};

dictionary AllowedUSBDevice {
  required octet vendorId;
  required octet productId;
  DOMString serialNumber;
};

dictionary USBPermissionStorage {
  sequence<AllowedUSBDevice> allowedDevices = [];
};

[Exposed=(Worker,Window)]
interface USBPermissionResult : PermissionStatus {
  attribute FrozenArray<USBDevice> devices;
};

问题索引

设备重置后处于什么配置? [问题 #36]
补充一些关于设备配置的非规范性信息。[问题 #46]
MDN

USB/connect_event

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB/connect_event

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB/disconnect_event

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB/disconnect_event

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB/getDevices

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB/requestDevice

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USB

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBAlternateInterface

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConfiguration/USBConfiguration

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConfiguration/configurationName

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConfiguration/configurationValue

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConfiguration/interfaces

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConfiguration

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConnectionEvent/USBConnectionEvent

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConnectionEvent/device

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBConnectionEvent

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/claimInterface

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/clearHalt

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/close

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/configuration

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/configurations

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/controlTransferIn

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/controlTransferOut

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceClass

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceProtocol

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceSubclass

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceVersionMajor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceVersionMinor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/deviceVersionSubminor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/forget

In only one current engine.

FirefoxNoneSafariNoneChrome101+
Opera?Edge101+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/isochronousTransferIn

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/isochronousTransferOut

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/manufacturerName

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/open

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/opened

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/productId

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/productName

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/releaseInterface

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/reset

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/selectAlternateInterface

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/selectConfiguration

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/serialNumber

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/transferIn

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/transferOut

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/usbVersionMajor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/usbVersionMinor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/usbVersionSubminor

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice/vendorId

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBDevice

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBEndpoint

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBInTransferResult

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBInterface

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBIsochronousInTransferPacket

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBIsochronousInTransferResult

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBIsochronousOutTransferPacket

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBIsochronousOutTransferResult

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

USBOutTransferResult

In only one current engine.

FirefoxNoneSafariNoneChrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

Headers/Feature-Policy/usb

In only one current engine.

FirefoxNoneSafariNoneChrome60+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?

Headers/Permissions-Policy/usb

In only one current engine.

FirefoxNoneSafariNoneChrome88+
Opera?Edge88+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?