Web Serial API

Living document

Draft Community Group Report

Latest published version:
https://www.w3.org/TR/serial/
Latest editor's draft:
https://wicg.github.io/serial/
Editor:
See contributors on GH
Participate:
GitHub wicg/serial
File an issue
Commit history
Pull requests

Abstract

The Serial API provides a way for websites to read and write from a serial device through script. Such an API would bridge the web and the physical world, by allowing documents to communicate with devices such as microcontrollers, 3D printers, and other serial devices. There is also a companion explainer document.

Status of This Document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

This is a work in progress. All contributions welcome.

GitHub Issues are preferred for discussion of this specification.

1. Extensions to the Navigator interface

WebIDL[Exposed=Window, SecureContext]
partial interface Navigator {
  [SameObject] readonly attribute Serial serial;
};

serial attribute

When getting, the serial attribute always returns the same instance of the Serial object.

2. Extensions to the WorkerNavigator interface

WebIDL[Exposed=DedicatedWorker, SecureContext]
partial interface WorkerNavigator {
  [SameObject] readonly attribute Serial serial;
};

serial attribute

When getting, the serial attribute always returns the same instance of the Serial object.

3. Serial interface

WebIDL[Exposed=(DedicatedWorker, Window), SecureContext]
interface Serial : EventTarget {
  attribute EventHandler onconnect;
  attribute EventHandler ondisconnect;
  Promise<sequence<SerialPort>> getPorts();
  [Exposed=Window] Promise<SerialPort> requestPort(optional SerialPortRequestOptions options = {});
};

3.1 requestPort() method

The requestPort() method steps are:
  1. Let promise be a new promise.
  2. If this's relevant global object's associated Document is not allowed to use the policy-controlled feature named "serial", reject promise with a "SecurityError" DOMException and return promise.
  3. If the relevant global object of this does not have transient activation, reject promise with a "SecurityError" DOMException and return promise.
  4. If options["filters"] is present, then for each filter in options["filters"] run the following steps:
    1. If filter["usbVendorId"] is not present, reject promise with a TypeError and return promise.
      Note
      This check implements the combined rule that a SerialPortFilter cannot be empty and if usbProductId is specified then usbVendorId must also be specified.
  5. Run the following steps in parallel:
    1. Prompt the user to grant the site access to a serial port by presenting them with a list of available ports that match any filter in options["filters"] if present and all available ports otherwise.
    2. If the user does not choose a port, queue a global task on the relevant global object of this using the serial port task source to reject promise with a "NotFoundError" DOMException and abort these steps.
    3. Let port be a SerialPort representing the port chosen by the user.
    4. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with port.
  6. Return promise.

3.1.1 SerialPortRequestOptions dictionary

WebIDLdictionary SerialPortRequestOptions {
  sequence<SerialPortFilter> filters;
};
filters member
Filters for serial ports

3.1.2 SerialPortFilter dictionary

WebIDLdictionary SerialPortFilter {
  unsigned short usbVendorId;
  unsigned short usbProductId;
};
usbVendorId member
USB Vendor ID
usbProductId member
USB Product ID

A serial port matches the filter filter if these steps return true:

  1. If filter["usbVendorId"] is not present, return true.
  2. If the serial port is not part of a USB device, return false.
  3. If the USB device's vendor ID is not equal to filter["usbVendorId"], return false.
  4. If filter["usbProductId"] is not present, return true.
  5. If the USB device's product ID is not equal to filter["usbProductId"], return false.
  6. Otherwise, return true.

A serial port matches any filter in a sequence of SerialPortFilter if these steps return true:

  1. For each filter in the sequence, run these sub-steps:
    1. If the serial port does not match the filter filter, return false.
  2. Return true.

3.2 getPorts() method

The getPorts() method steps are:
  1. Let promise be a new promise.
  2. If this's relevant global object's associated Document is not allowed to use the policy-controlled feature named "serial", reject promise with a "SecurityError" DOMException and return promise.
  3. Run the following steps in parallel:
    1. Let availablePorts be the sequence of available serial ports on the system which the user has allowed the site to access as the result of a previous call to requestPort().
    2. Let ports be the sequence of the SerialPorts representing the ports in availablePorts.
    3. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with ports.
  4. Return promise.

3.3 onconnect attribute

onconnect is an event handler IDL attribute for the connect event type.

3.4 ondisconnect attribute

ondisconnect is an event handler IDL attribute for the disconnect event type.

4. SerialPort interface

WebIDL[Exposed=(DedicatedWorker,Window), SecureContext]
interface SerialPort : EventTarget {
  attribute EventHandler onconnect;
  attribute EventHandler ondisconnect;
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  SerialPortInfo getInfo();

  Promise<undefined> open(SerialOptions options);
  Promise<undefined> setSignals(optional SerialOutputSignals signals = {});
  Promise<SerialInputSignals> getSignals();
  Promise<undefined> close();
};
Methods on this interface typically complete asynchronously, queuing work on the serial port task source.

The get the parent algorithm for SerialPort returns the same Serial instance that is returned by the SerialPort's relevant global object's Navigator object's serial getter.

Instances of SerialPort are created with the internal slots described in the following table:

Internal slot Initial value Description (non-normative)
[[state]] "closed" Tracks the active state of the SerialPort
[[bufferSize]] undefined The amount of data to buffer for transmit and receive
[[readable]] null A ReadableStream that receives data from the port
[[readFatal]] false A flag indicating that the port has encountered a fatal read error
[[writable]] null A WritableStream that transmits data to the port
[[writeFatal]] false A flag indicating that the port has encountered a fatal write error
[[pendingClosePromise]] null A Promise used to wait for readable and writable to close

4.1 onconnect attribute

onconnect is an event handler IDL attribute for the connect event type.

When a serial port becomes available on the system that the user has allowed the site to access as the result of a previous call to requestPort(), run the following steps:

  1. Let port be a SerialPort representing the port.
  2. Fire an event named connect at port with its bubbles attribute initialized to true.

4.2 ondisconnect attribute

ondisconnect is an event handler IDL attribute for the disconnect event type.

When a serial port becomes unavailable on the system that the user has allowed the site to access as the result of a previous call to requestPort(), run the following steps:

  1. Let port be a SerialPort representing the port.
  2. Fire an event named disconnect at port with its bubbles attribute initialized to true.

4.3 getInfo() method

The getInfo() method steps are:
  1. Let info be a new SerialPortInfo dictionary.
  2. If the port is part of a USB device, perform the following steps:
    1. Set info["usbVendorId"] to the vendor ID of the device.
    2. Set info["usbProductId"] to the product ID of the device.
  3. Return info.

4.3.1 SerialPortInfo dictionary

WebIDLdictionary SerialPortInfo {
  unsigned short usbVendorId;
  unsigned short usbProductId;
};
usbVendorId member
If the port is part of a USB device this member will be the 16-bit vendor ID of that device. Otherwise it will be undefined.
usbProductId member
If the port is part of a USB device this member will be the 16-bit product ID of that device. Otherwise it will be undefined.

4.4 open() method

The open() method steps are:
  1. Let promise be a new promise.
  2. If this.[[state]] is not "closed", reject promise with an "InvalidStateError" DOMException and return promise.
  3. If options["dataBits"] is not 7 or 8, reject promise with TypeError and return promise.
  4. If options["stopBits"] is not 1 or 2, reject promise with TypeError and return promise.
  5. If options["bufferSize"] is 0, reject promise with TypeError and return promise.
  6. Optionally, if options["bufferSize"] is larger than the implementation is able to support, reject promise with a TypeError and return promise.
  7. Set this.[[state]] to "opening".
  8. Perform the following steps in parallel.
    1. Invoke the operating system to open the serial port using the connection parameters (or their defaults) specified in options.
    2. If this fails for any reason, queue a global task on the relevant global object of this using the serial port task source to reject promise with a "NetworkError" DOMException and abort these steps.
    3. Set this.[[state]] to "opened".
    4. Set this.[[bufferSize]] to options["bufferSize"].
    5. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined.
  9. Return promise.

4.4.1 SerialOptions dictionary

WebIDLdictionary SerialOptions {
  required [EnforceRange] unsigned long baudRate;
  [EnforceRange] octet dataBits = 8;
  [EnforceRange] octet stopBits = 1;
  ParityType parity = "none";
  [EnforceRange] unsigned long bufferSize = 255;
  FlowControlType flowControl = "none";
};
baudRate member
A positive, non-zero value indicating the baud rate at which serial communication should be established.
Note
baudRate is the only required member of this dictionary. While there are common default for other connection parameters it is important for developers to consider and consult with the documentation for devices they intend to connect to determine the correct values. While some values are common there is no standard baud rate. Requiring this parameter reduces the potential for confusion if an arbitrary default were chosen by this specification.
dataBits member
The number of data bits per frame. Either 7 or 8.
stopBits member
The number of stop bits at the end of a frame. Either 1 or 2.
parity member
The parity mode.
bufferSize member
A positive, non-zero value indicating the size of the read and write buffers that should be created.
flowControl member
The flow control mode.
4.4.1.1 ParityType enum
WebIDLenum ParityType {
  "none",
  "even",
  "odd"
};
none
No parity bit is sent for each data word.
even
Data word plus parity bit has even parity.
odd
Data word plus parity bit has odd parity.
4.4.1.2 FlowControlType enum
WebIDLenum FlowControlType {
  "none",
  "hardware"
};
none
No flow control is enabled.
hardware
Hardware flow control using the RTS and CTS signals is enabled.

4.5 readable attribute

The readable getter steps are:
  1. If this.[[readable]] is not null, return this.[[readable]].
  2. If this.[[state]] is not "opened", return null.
  3. If this.[[readFatal]] is true, return null.
  4. Let stream be a new ReadableStream.
  5. Let pullAlgorithm be the following steps:
    1. Let desiredSize be the desired size of this.[[readable]]'s internal queue.
    2. Run the following steps in parallel:
      1. Invoke the operating system to read up to desiredSize bytes from the port, putting the result in the byte sequence bytes.
      2. Queue a global task on the relevant global object of this using the serial port task source to run the following steps:
        1. If no errors were encountered run the following steps:
          1. Let buffer be a new ArrayBuffer created from bytes.
          2. Let chunk be a new Uint8Array view over buffer, who's length is the length of bytes.
          3. Invoke enqueue on this.[[readable]] with chunk.
        2. If a buffer overrun condition was encountered, invoke error on this.[[readable]] with a "BufferOverrunError" DOMException and invoke the steps to handle closing the readable stream.
        3. If a break condition was encountered, invoke error on this.[[readable]] with a "BreakError" DOMException and invoke the steps to handle closing the readable stream.
        4. If a framing error was encountered, invoke error on this.[[readable]] with a "FramingError" DOMException and invoke the steps to handle closing the readable stream.
        5. If a parity error was encountered, invoke error on this.[[readable]] with a "ParityError" DOMException and invoke the steps to handle closing the readable stream.
        6. If an operating system error was encountered, invoke error on this.[[readable]] with an "UnknownError" DOMException and invoke the steps to handle closing the readable stream.
        7. If the port was disconnected, run the following steps:
          1. Set this.[[readFatal]] to true,
          2. Invoke error on this.[[readable]] with a "NetworkError" DOMException.
          3. Invoke the steps to handle closing the readable stream.
    3. Return a promise resolved with undefined.
    Note

    The Promise returned by this algorithm is immediately resolved so that it does not block canceling the stream. [STREAMS] specifies that this algorithm will not be invoked again until a chunk is enqueued.

  6. Let cancelAlgorithm be the following steps:
    1. Let promise be a new promise.
    2. Run the following steps in parallel.
      1. Invoke the operating system to discard the contents of all software and hardware receive buffers for the port.
      2. Queue a global task on the relevant global object of this using the serial port task source to run the following steps:
        1. Invoke the steps to handle closing the readable stream.
        2. Resolve promise with undefined.
    3. Return promise.
  7. Set up stream with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm, highWaterMark set to this.[[bufferSize]], and sizeAlgorithm set to a byte-counting size algorithm.
  8. Set this.[[readable]] to stream.
  9. Return stream.
To handle closing the readable stream perform the following steps:
  1. Set this.[[readable]] to null.
  2. If this.[[writable]] is null and this.[[pendingClosePromise]] is not null, resolve this.[[pendingClosePromise]] with undefined.

4.6 writable attribute

The writable getter steps are:
  1. If this.[[writable]] is not null, return this.[[writable]].
  2. If this.[[state]] is not "opened", return null.
  3. If this.[[writeFatal]] is true, return null.
  4. Let stream be a new WritableStream.
  5. Let writeAlgorithm be the following steps, given chunk:
    1. Let promise be a new promise.
    2. If chunk cannot be converted to an IDL value of type BufferSource, reject promise with a TypeError and return promise. Otherwise, save the result of the conversion in source.
    3. Get a copy of the buffer source source and save the result in bytes.
    4. In parallel, run the following steps:
      1. Invoke the operating system to write bytes to the port. Alternately, store the chunk for future coalescing.
        Note
        The operating system may return from this operation once bytes has been queued for transmission rather than after it has been transmitted.
      2. Queue a global task on the relevant global object of this using the serial port task source to run the following steps:
        1. If the chunk was successfully written, or was stored for future coalescing, resolve promise with undefined.
          Note
          [STREAMS] specifies that writeAlgorithm will only be invoked after the Promise returned by a previous invocation of this algorithm has resolved. For efficiency an implementation is allowed to resolve this Promise early in order to coalesce multiple chunks waiting in the WritableStream's internal queue into a single request to the operating system.
        2. If an operating system error was encountered, reject promise with an "UnknownError" DOMException.
        3. If the port was disconnected, run the following steps:
          1. Set this.[[writeFatal]] to true.
          2. Reject promise with a "NetworkError" DOMException.
          3. Invoke the steps to handle closing the writable stream.
    5. Return promise.
  6. Let abortAlgorithm be the following steps:
    1. Let promise be a new promise.
    2. Run the following steps in parallel.
      1. Invoke the operating system to discard the contents of all software and hardware transmit buffers for the port.
      2. Queue a global task on the relevant global object of this using the serial port task source to run the following steps:
        1. Invoke the steps to handle closing the writable stream.
        2. Resolve promise with undefined.
    3. Return promise.
    Note
    [STREAMS] specifies that abortAlgorithm will only be invoked after the Promise returned by a previous invocation of writeAlgorithm (if any) has resolved. This blocks abort on completion of the most recent write operation. This could be fixed by passing an AbortSignal to writeAlgorithm.

    This enhancement is tracked in whatwg/streams#1015.

  7. Let closeAlgorithm be the following steps:
    1. Let promise be a new promise.
    2. Run the following steps in parallel.
      1. Invoke the operating system to flush the contents of all software and hardware transmit buffers for the port.
      2. Queue a global task on the relevant global object of this using the serial port task source to run the following steps:
        1. Invoke the steps to handle closing the writable stream.
        2. Resolve promise with undefined.
    3. Return promise.
  8. Set up stream with writeAlgorithm set to writeAlgorithm, abortAlgorithm set to abortAlgorithm, closeAlgorithm set to closeAlgorithm, highWaterMark set to this.[[bufferSize]], and sizeAlgorithm set to a byte-counting size algorithm.
  9. Set this.[[writable]] to stream.
  10. Return stream.
To handle closing the writable stream perform the following steps:
  1. Set this.[[writable]] to null.
  2. If this.[[readable]] is null and this.[[pendingClosePromise]] is not null, resolve this.[[pendingClosePromise]] with undefined.

4.7 setSignals() method

The setSignals() method steps are:
  1. Let promise be a new promise.
  2. If this.[[state]] is not "opened", reject promise with an "InvalidStateError" DOMException and return promise.
  3. If all of the specified members of signals are not present reject promise with TypeError and return promise.
  4. Perform the following steps in parallel:
    1. If signals["dataTerminalReady"] is present, invoke the operating system to either assert (if true) or deassert (if false) the "data terminal ready" or "DTR" signal on the serial port.
    2. If signals["requestToSend"] is present, invoke the operating system to either assert (if true) or deassert (if false) the "request to send" or "RTS" signal on the serial port.
    3. If signals["break"] is present, invoke the operating system to either assert (if true) or deassert (if false) the "break" signal on the serial port.
      Note
      The "break" signal is typically implemented as an in-band signal by holding the transmit line at the "mark" voltage and thus prevents data transmission for as long as it remains asserted.
    4. If the operating system fails to change the state of any of these signals for any reason, queue a global task on the relevant global object of this using the serial port task source to reject promise with a "NetworkError" DOMException.
    5. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined.
  5. Return promise.

4.7.1 SerialOutputSignals dictionary

WebIDLdictionary SerialOutputSignals {
  boolean dataTerminalReady;
  boolean requestToSend;
  boolean break;
};
dataTerminalReady
Data Terminal Ready (DTR)
requestToSend
Request To Send (RTS)
break
Break

4.8 getSignals() method

The getSignals() method steps are:
  1. Let promise be a new promise.
  2. If this.[[state]] is not "opened", reject promise with an "InvalidStateError" DOMException and return promise.
  3. Perform the following steps in parallel:
    1. Query the operating system for the status of the control signals that may be asserted by the device connected to the serial port.
    2. If the operating system fails to determine the status of these signals for any reason, queue a global task on the relevant global object of this using the serial port task source to reject promise with a "NetworkError" DOMException and abort these steps.
    3. Let signals be a new SerialInputSignals.
    4. Set signals["dataCarrierDetect"] to true if the "data carrier detect" or "DCD" signal has been asserted by the device, and false otherwise.
    5. Set signals["clearToSend"] to true if the "clear to send" or "CTS" signal has been asserted by the device, and false otherwise.
    6. Set signals["ringIndicator"] to true if the "ring indicator" or "RI" signal has been asserted by the device, and false otherwise.
    7. Set signals["dataSetReady"] to true if the "data set ready" or "DSR" signal has been asserted by the device, and false otherwise.
    8. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with signals.
  4. Return promise.

4.8.1 SerialInputSignals dictionary

WebIDLdictionary SerialInputSignals {
  required boolean dataCarrierDetect;
  required boolean clearToSend;
  required boolean ringIndicator;
  required boolean dataSetReady;
};
dataCarrierDetect member
Data Carrier Detect (DCD)
clearToSend member
Clear To Send (CTS)
ringIndicator member
Ring Indicator (RI)
dataSetReady member
Data Set Ready (DSR)

4.9 close() method

The close() method steps are:
  1. Let promise be a new promise.
  2. Let cancelPromise be the result of invoking cancel on this.[[readable]] or a promise resolved with undefined if this.[[readable]] is null.
  3. Let abortPromise be the result of invoking abort on this.[[writable]] or a promise resolved with undefined if this.[[writable]] is null.
  4. Let pendingClosePromise be a new promise.
  5. If this.[[readable]] and this.[[writable]] are null, resolve pendingClosePromise with undefined.
  6. Set this.[[pendingClosePromise]] to pendingClosePromise.
  7. Let combinedPromise be the result of getting a promise to wait for all with «cancelPromise, abortPromise, pendingClosePromise».
  8. Set this.[[state]] to "closing".
  9. React to combinedPromise.
  10. Return promise.

5. Integrations

5.1 Permissions Policy

This specification defines a feature that controls whether the methods exposed by the serial attribute on the Navigator object may be used.

The feature name for this feature is "serial"`.

The default allowlist for this feature is 'self'.

6. Security considerations

This section is non-normative.

This API poses similar a security risk to [WEB-BLUETOOTH] and [WEBUSB] and so lessons from those are applicable here. The primary threats are: The primary mitigation to all of these attacks is the requestPort() pattern, which requires user interaction and only supports granting access to a single device at a time. This prevents drive-by attacks because a site cannot enumerate all connected devices to determine whether a vulnerable device exists and must instead proactively inform the user that it desires access. Implementations may also provide a visual indication that a site is currently communicating with a device and controls for revoking that permission at any time.

This specification requires the site to be served from a secure context in order to prevent malicious code from being injected by a network-based attacker. This ensures that the site identity shown to the user when making permission decisions is accurate. This specification also requires top-level documents to opt-in through [PERMISSIONS-POLICY] before allowing a cross-origin iframe to use the API. When combined with [CSP3] these mechanisms provide protection against malicious code injection attacks.

The remaining concern is the exploitation of a connected device through a phishing attack that convinces the user to grant a malicious site access to a device. These attacks can be used to either exploit the device’s capabilities as designed or to install malicious firmware on the device that will in turn attack the host computer. Host software may be vulnerable to attack because it improperly validates input from connected devices. Security research in this area has encouraged software vendors to treat connected devices as untrustworthy.

There is no mechanism that will completely prevent this type of attack because data sent from a page to the device is an opaque sequence of bytes. Efforts to block a particular type of data from being sent are likely be met by workarounds on the part of device manufacturers who nevertheless want to send this type of data to their devices.

User agents can implement additional mechanisms to control access to devices:

Implementations of [WEB-BLUETOOTH] and [WEBUSB] have experimented with these mitigations however there are limits to their effectiveness. First, it is difficult to define whether a device is exploitable. For example, this API will allow a site to upload firmware to a microcontroller development board. This is a key use case for this API as these devices are common in the educational and hobbyist markets. These boards do not implement firmware signature verification and so can easily be turned into a malicious device. These boards are clearly exploitable but should not be blocked.

In addition, maintaining a list of vulnerable devices works well for USB and Bluetooth because those protocols define out-of-band mechanisms to gather device metadata. The make and model of such devices can thus be easily identified even if they present themselves to the host as a virtual serial ports. However, there are generic USB- or Bluetooth-to-serial adapters as well as systems with "real" serial ports using a DB-25, DE-9 or RJ-45 connector. For these there is no metadata that can be read to determine the identity of the device connected to the port and so blocking access to these is not possible.

7. Privacy considerations

This section is non-normative.

Serial ports and serial devices contain two kinds of sensitive information. When a port is a USB or Bluetooth device there are identifiers such as the vendor and product IDs (which identify the make and model) as well as a serial number or MAC address. The serial device itself may also have its own identifier that is available through commands sent via the serial port. The device may also store other private information which may or may not be identifying.

In order to manage device permissions an implementation will likely store device identifiers such as the USB vendor ID, product ID and serial number in its user preferences file to be used as stable identifiers for devices the user has granted sites access to. These would not be shared directly with sites and would be cleared when permission is revoked or site data in general is cleared.

Commands a page can send to the device after it has been granted access a page may also be able to access any of the other sensitive information stored by the device. For the reasons mentioned in § 6. Security considerations it is impractical and undesirable to attempt to prevent a page from accessing this information.

Implementations should provide users with complete control over which devices a site can access and not grant device access without user interaction. This is the intention of the requestPort() method. This prevents a site from silently enumerating and collecting data from all connected devices. This is similar to the file picker UI. A site has no knowledge of the filesystem, only the files or directories that have been chosen by the user. An implementation could notify the user when a site is using these permissions with an indicator icon appearing in the tab or address bar.

Implementations that provide a "private" or "incognito" browsing mode should ensure that permissions from the user's normal profile do not carry over to such a session and permissions granted in this session are not persisted when the session ends. An implementation may warn the user when granting access to a device in such as session as, similar to entering identifying information by hand, device identifiers and other unique properties available from communicating with the device mentioned previously can be used to identify the user between sessions.

Users may be surprised by the capabilities granted by this API if they do not understand the ways in which granting access to a device breaks traditional isolation boundaries in the web security model. Security UI and documentation should explain that granting a site access to a device could give the site full control over the device and any data contained within.

8. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

A. Acknowledgements

The following people contributed to the development of this document.

B. References

B.1 Normative references

[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
HTML Standard. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY]
Permissions Policy. Ian Clelland. W3C. 16 July 2020. W3C Working Draft. URL: https://www.w3.org/TR/permissions-policy-1/
[STREAMS]
Streams Standard. Adam Rice; Domenic Denicola; 吉野剛史 (Takeshi Yoshino). WHATWG. Living Standard. URL: https://streams.spec.whatwg.org/
[WebIDL]
Web IDL. Boris Zbarsky. W3C. 15 December 2016. W3C Editor's Draft. URL: https://heycam.github.io/webidl/

B.2 Informative references

[CSP3]
Content Security Policy Level 3. Mike West. W3C. 29 June 2021. W3C Working Draft. URL: https://www.w3.org/TR/CSP3/
[WEB-BLUETOOTH]
Web Bluetooth. Jeffrey Yasskin. W3C Web Bluetooth Community Group. Draft Community Group Report. URL: https://webbluetoothcg.github.io/web-bluetooth/
[WEBUSB]
WebUSB API. WICG. cg-draft. URL: https://wicg.github.io/webusb/