<pre class="metadata">
Title: WebHID API
Status: CG-DRAFT
ED: https://github.com/nondebug/webhid
Shortname: webhid
Level: 1
Editor: Matt Reynolds 105511, Google LLC https://www.google.com, mattreynolds@google.com
Abstract: This document describes an API for providing access to devices that support the Human Interface Device (HID) protocol.
Group: wicg
Repository: https://github.com/nondebug/webhid
</pre>

<style>
table {
  border-collapse: collapse;
  border-left-style: hidden;
  border-right-style: hidden;
  text-align: left;
}
table caption {
  font-weight: bold;
  padding: 3px;
  text-align: left;
}
table td, table th {
  border: 1px solid black;
  padding: 3px;
}
</style>




# Introduction # {#introduction}

<em>This section is non-normative</em>.

A HID (Human Interface Device) is a type of device that takes input from or provides output to humans. It also refers to the HID protocol, a standard for bi-directional communication between a host and a device that is designed to simplify the installation procedure. The HID protocol was originally developed for USB devices but has since been implemented over many other protocols, including Bluetooth.

The web platform already supports input from many HID devices. Keyboards, pointing devices, and gamepads are all typically implemented using the HID protocol. However, this support relies on the operating system's HID drivers that transform the HID input into high-level input APIs. Devices that are not well supported by the host's HID driver are often inaccessible to web pages. Similarly, the outputs on most devices that are not supported by the host's HID driver are inaccessible.


# Motivating Applications # {#motivating-applications}

<em>This section is non-normative</em>.

## Niche devices ## {#app-niche-devices}

The most common classes of HID input devices are already well-supported on the web platform through existing high-level input APIs. For instance, mouse input is accessible through the PointerEvent API and keyboard input is accessible through the Keyboard API. Input from these devices is handled using the host's native HID driver and typically does not require device-specific drivers or configuration to work correctly. WebHID is not intended for devices like these that are already well-supported through high-level input APIs.

For some classes of HID devices, the web platform supports some features of the device but limits access to other features. For instance, the Gamepad API supports the input capabilities of most game controllers but does not support less common capabilities like LED indicators or audio. These features are often not well-supported by host APIs and adding support within the user agent can lead to significant complexity. WebHID would give applications an alternative when the functionality provided by the high-level API is incomplete.

Many HID devices are not supported through any web platform API. The HID specification describes a wide array of devices that could be supported through HID, including virtual reality controls, flight simulators, medical equipment, and more. WebHID would allow these devices to be used without requiring additional drivers or modification to the user agent.

## Prototyping, hobbyists, and educational devices ## {#app-prototype-educational}

HID is attractive for prototyping and hobbyist applications because it allows devices to use the host's generic HID driver instead of requiring a driver for each host where the device will be used. The simplified installation procedure also makes it easier for educators to deploy a device to a classroom of students without modifying the system configuration on each host. Providing access to HID devices through the web platform would further reduce installation requirements, particularly for devices that are currently only supported through host-specific applications.


# Security and Privacy Considerations # {#security-and-privacy}

<em>This section is non-normative</em>.

## Abusing Access to a Device ## {#abusing-a-device}

HID peripherals may expose powerful functionality that should not be made accessible to the page without explicit consent from the user. For instance, a HID peripheral may have sensors that allow it to collect information about its surroundings. A device may store private information that should not be revealed or overwritten. Many devices expose functionality that allow the device firmware to be upgraded. Operating systems typically do not restrict access to HID devices from applications, and this access can occasionally be abused to damage the device or corrupt the data stored on it.

In some cases, a device may expose functionality that should not be accessible at all from a web page. Specific devices may be blocked by recognizing the device by its vendor and product ID and hiding such devices during enumeration. The HID report descriptor allows a device to describe its own capabilities, and this information can be used to block classes of devices even when the vendor and product ID cannot be known in advance. This can be accomplished by inspecting the usage values assigned to collections within the report descriptor; for instance, access to keyboard-like devices can be denied by denying access to devices that include a top-level collection with the Keyboard usage ID.

To mitigate abuse, a device will not be made available to the page until the user has granted explicit access. Access must first be requested by the page and will be granted once the user has selected the device from a chooser displaying all available devices. Once a page has been granted access, an indicator will be displayed to indicate the device is in use.

## Attacking a Device ## {#attacking-a-device}

The HID protocol is extremely versatile, and a wide variety of devices have been designed that take advantage of this versatility. As a result, there are many possibilities for how access to a device could allow a malicious page to damage the device itself. It is relatively common for HID peripherals to allow firmware updates to be sent using custom HID reports. Device manufacturers must take care to design the firmware upgrade mechanism to verify that the firmware binary is authentic, and should also protect against downgrades that could reduce the security of a device. Untrusted upgrades could be used to add new capabilities to a device or cause it to masquerade as an entirely different type of device.

Some devices may be damaged when inputs are provided outside of the expected range. For instance, the rumble feature on a gamepad could become damaged if a page sends an invalid rumble command.

In general, these types of attacks are device-specific and cannot be mitigated at the API level.

## Attacking the Host ## {#attacking-the-host}

If a malicious page gains access to a device, in some cases this access can be used to attack the host. A major concern is whether the device can be used to generate trusted input events. These events serve as a proxy for user intent and can be used to access more powerful web platform features.

Mice and keyboards are typically implemented as HID peripherals, and are also capable of generating trusted input. A HID keyboard or mouse may contain advanced features like programmable macros, which store a sequence of inputs and allow the sequence to be played back at a later time. Device manufacturers must take care to design such features in a way that would prevent a malicious app from reprogramming the device with an unexpected input sequence, and must also protect against triggering macro playback without the user's explicit consent.

# Device Enumeration # {#enumeration}

<xmp class="idl">

dictionary HIDDeviceFilter {
    unsigned long vendorId;
    unsigned short productId;
    unsigned short usagePage;
    unsigned short usage;
};

dictionary HIDDeviceRequestOptions {
    required sequence<HIDDeviceFilter> filters;
};

[SecureContext]
interface HID : EventTarget {
    attribute EventHandler onconnect;
    attribute EventHandler ondisconnect;
    Promise<sequence<HIDDevice>> getDevices();
    Promise<sequence<HIDDevice>> requestDevice(
        HIDDeviceRequestOptions options);
};

[SecureContext] partial interface Navigator {
    [SameObject] readonly attribute HID hid;
};

</xmp>

<div class="example">
Retrieve devices and log the device names to the console.

<pre highlight="js">
  document.addEventListener('DOMContentLoaded', async () => {
    let devices = await navigator.hid.getDevices();
    devices.forEach(device => {
      console.log('HID: ${device.productName}');
    });
  });
</pre>

Register event listeners for connection and disconnection of HID devices.

<pre highlight="js">
  navigator.hid.addEventListener('connect', async () => {
    console.log('HID connected: ${device.productName}');
  });

  navigator.hid.addEventListener('disconnect', async () => {
    console.log('HID disconnected: ${device.productName}');
  });
</pre>

Devices are not accessible through {{HID/getDevices()}} and will not generate connection events until permission has been granted to access the device. The page may request permission using {{HID/requestDevice()}}. In this example, the page requests access to a device with vendor ID <code>0xABCD</code>, product ID <code>0x1234</code>. The device must also have a collection with usage page Consumer (<code>0x0C</code>) and usage ID Consumer Control (<code>0x01</code>).

<pre highlight="js">
  let requestButton = document.getElementById('request-hid-device');
  requestButton.addEventListener('click', async () => {
    let device;
    try {
      device = await navigator.hid.requestDevice({ filters: [{
          vendorId: 0xABCD,
          productId: 0x1234,
          usagePage: 0x0C,
          usage: 0x01
      }]});
    } catch (error) {
      console.log('No device was selected.');
    }

    if (device !== undefined) {
      console.log('HID: ${device.productName}');
    }
  });
</pre>
</div>

A HID device |device| <dfn data-lt="match a device filter">matches a device filter</dfn> |filter| if the following steps return <code>match</code>:

1. If <code>|filter|.{{HIDDeviceFilter/vendorId}}</code> is present and <code>|device|.{{HIDDevice/vendorId}}</code> does not equal <code>|filter|.{{HIDDeviceFilter/vendorId}}</code>, return <code>mismatch</code>.
1. If <code>|filter|.{{HIDDeviceFilter/productId}}</code> is present and <code>|device|.{{HIDDevice/productId}}</code> does not equal <code>|filter|.{{HIDDeviceFilter/productId}}</code>, return <code>mismatch</code>.
1. If <code>|filter|.{{HIDDeviceFilter/usagePage}}</code> is present, iterate over the {{HIDCollectionInfo}} collections in |device|.{{HIDDevice/collections}}. If no collection <a>matches the device filter</a>, return <code>mismatch</code>.
1. Return <code>match</code>.

A {{HIDCollectionInfo}} |collection| <dfn data-lt="matches the device filter">matches a device filter</dfn> |filter| if the following steps return <code>match</code>:

1. If <code>|filter|.{{HIDDeviceFilter/usagePage}}</code> is present and <code>|device|.{{HIDCollectionInfo/usagePage}}</code> does not equal <code>|filter|.{{HIDDeviceFilter/usagePage}}</code>, return <code>mismatch</code>.
1. If <code>|filter|.{{HIDDeviceFilter/usage}}</code> is present and <code>|device|.{{HIDCollectionInfo/usage}}</code> does not equal <code>|filter|.{{HIDDeviceFilter/usage}}</code>, return <code>mismatch</code>.
1. Return <code>match</code>.

A {{HIDDeviceFilter}} |filter| <dfn data-lt="is not a valid filter">is valid</dfn> if the following steps return <code>valid</code>:

1. If <code>|filter|.{{HIDDeviceFilter/productId}}</code> is present and <code>|filter|.{{HIDDeviceFilter/vendorId}}</code> is not present, return <code>invalid</code>.
1. If <code>|filter|.{{HIDDeviceFilter/usage}}</code> is present and <code>|filter|.{{HIDDeviceFilter/usagePage}}</code> is not present, return <code>invalid</code>.
1. Return <code>valid</code>.

The UA MUST be able to <dfn>enumerate all devices attached to the system</dfn>. It is not required to perform this work each time an algorithm requests an enumeration. The UA MAY cache the result of the first enumeration it performs and then begin monitoring for device connection and disconnection events, adding connected devices to its cached enumeration and removing disconnected devices. This mode of operation is preferred as it reduces the number of operating system calls.

## Events ## {#events}

<xmp class="idl">

dictionary HIDConnectionEventInit : EventInit {
    required HIDDevice device;
};

[
    Constructor(DOMString type, HIDConnectionEventInit eventInitDict),
    SecureContext
] interface HIDConnectionEvent : Event {
    [SameObject] readonly attribute HIDDevice device;
};

dictionary HIDInputReportEventInit : EventInit {
    required HIDDevice device;
    required octet reportId;
    required DataView data;
};

[
    Constructor(DOMString type, HIDInputReportEventInit eventInitDict),
    SecureContext
] interface HIDInputReportEvent : Event {
    [SameObject] readonly attribute HIDDevice device;
    readonly attribute octet reportId;
    readonly attribute DataView data;
};

</xmp>



## HID Collection and Report Information ## {#report-descriptor}

<xmp class="idl">

[SecureContext] interface HIDReportItem {
    readonly attribute boolean isAbsolute;
    readonly attribute boolean isArray;
    readonly attribute boolean isRange;
    readonly attribute boolean hasNull;
    readonly attribute FrozenArray<unsigned long> usages;
    readonly attribute unsigned long usageMinimum;
    readonly attribute unsigned long usageMaximum;
    readonly attribute unsigned long designatorMinimum;
    readonly attribute unsigned long designatorMaximum;
    readonly attribute unsigned long stringMinimum;
    readonly attribute unsigned long stringMaximum;
    readonly attribute unsigned short reportSize;
    readonly attribute unsigned short reportCount;
    readonly attribute unsigned long unitExponent;
    readonly attribute unsigned long unit;
    readonly attribute long logicalMinimum;
    readonly attribute long logicalMaximum;
    readonly attribute long physicalMinimum;
    readonly attribute long physicalMaximum;
};

[SecureContext] interface HIDReportInfo {
    readonly attribute octet reportId;
    readonly attribute FrozenArray<HIDReportItem> items;
};

dictionary HIDFieldOptions {
    required octet reportId;
    required unsigned long fieldIndex;
    boolean isFeatureReport;
};

[SecureContext] interface HIDCollectionInfo {
    readonly attribute unsigned short usagePage;
    readonly attribute unsigned short usage;
    readonly attribute FrozenArray<HIDCollectionInfo> children;
    readonly attribute FrozenArray<HIDReportInfo> inputReports;
    readonly attribute FrozenArray<HIDReportInfo> outputReports;
    readonly attribute FrozenArray<HIDReportInfo> featureReports;
    readonly attribute FrozenArray<octet> reportIds;

    double getField(BufferSource reportData, HIDFieldOptions options);
    void setField(BufferSource reportData, HIDFieldOptions options,
                  double value);
};

</xmp>





# Device Usage # {#device-usage}

<xmp class="idl">

[SecureContext] interface HIDDevice : EventTarget {
    attribute EventHandler oninputreport;
    readonly attribute boolean opened;
    readonly attribute unsigned short vendorId;
    readonly attribute unsigned short productId;
    readonly attribute DOMString productName;
    readonly attribute FrozenArray<HIDCollectionInfo> collections;
    Promise<void> open();
    Promise<void> close();
    Promise<void> sendReport(octet reportId, BufferSource data);
    Promise<void> sendFeatureReport(octet reportId, BufferSource data);
    Promise<DataView> receiveFeatureReport(octet reportId);
};

</xmp>



# Integrations # {#integrations}

## Feature Policy ## {#feature-policy}

This specification defines a feature that controls whether the {{Navigator/hid}} attribute is exposed on the {{Navigator}} object.

The feature name for this feature is <code>"hid"</code>.

The <a>default allowlist</a> for this feature is <code>["self"]</code>.
