From 7fa4218311722618d9cbc5fbd8b76fd58c8779e6 Mon Sep 17 00:00:00 2001 From: Reilly Grant Date: Thu, 1 Oct 2026 17:42:27 -0700 Subject: [PATCH 1/9] Convert specification to Bikeshed This conversion was done automatically and the output manually checked against the original ReSpec version. --- .github/workflows/pr-push.yml | 5 +- .pr_preview.json | 4 +- index.bs | 1431 +++++++++++++++++++++++ index.html | 2054 --------------------------------- respecConfig.js | 27 - styles/spec.css | 5 - tidyconfig.txt | 5 - 7 files changed, 1436 insertions(+), 2095 deletions(-) create mode 100644 index.bs delete mode 100644 index.html delete mode 100644 respecConfig.js delete mode 100644 styles/spec.css delete mode 100644 tidyconfig.txt diff --git a/.github/workflows/pr-push.yml b/.github/workflows/pr-push.yml index 602ff65..54b6450 100644 --- a/.github/workflows/pr-push.yml +++ b/.github/workflows/pr-push.yml @@ -12,6 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: w3c/spec-prod@v2 with: - SOURCE: index.html - TOOLCHAIN: respec + SOURCE: index.bs + DESTINATION: index.html + TOOLCHAIN: bikeshed GH_PAGES_BRANCH: gh-pages diff --git a/.pr_preview.json b/.pr_preview.json index a239968..49b0677 100644 --- a/.pr_preview.json +++ b/.pr_preview.json @@ -1,4 +1,4 @@ { - "src_file": "index.html", - "type": "respec" + "src_file": "index.bs", + "type": "bikeshed" } diff --git a/index.bs b/index.bs new file mode 100644 index 0000000..cd0a03c --- /dev/null +++ b/index.bs @@ -0,0 +1,1431 @@ +
+Title: Web Serial API
+Status: CG-DRAFT
+Group: wicg
+ED: https://wicg.github.io/serial/
+Shortname: serial
+Level: none
+Editor: See contributors on GH, , https://github.com/wicg/serial/graphs/contributors
+Repository: wicg/serial
+Logo: images/logo_serial.svg
+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 Text: This is a work in progress. All contributions welcome.
+Markup Shorthands: css no, markdown yes
+
+ + + +# Extensions to the {{Navigator}} interface # {#extensions-to-the-navigator-interface} + + +[Exposed=Window, SecureContext] +partial interface Navigator { + [SameObject] readonly attribute Serial serial; +}; + + +## serial attribute ## {#serial-attribute} + +When getting, the {{Navigator/serial}} attribute always returns the same +instance of the {{Serial}} object. + +# Extensions to the {{WorkerNavigator}} interface # {#extensions-to-the-workernavigator-interface} + + +[Exposed=DedicatedWorker, SecureContext] +partial interface WorkerNavigator { + [SameObject] readonly attribute Serial serial; +}; + + +## serial attribute ## {#serial-attribute-0} + +When getting, the {{WorkerNavigator/serial}} attribute always returns the same +instance of the {{Serial}} object. + +# Serial interface # {#serial-interface} + + +[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 = {}); +}; + + +## requestPort() method ## {#requestport-method} + +
+When the user first visits a site it will not have permission to access any +serial devices. A site must first call {{Serial/requestPort()}}. This call +gives the browser the opportunity to prompt the user for which device the site +should be allowed to control. If the site is designed to work with a +particular device which is always connected via USB the site can provide a +filter restricting the devices the user can select to only those that would be +compatible. For example, a site which programs Arduino-powered robots could +specify a like the following to limit the set of selectable ports to only USB +devices with Arduino's USB vendor ID, + + +const filter = { usbVendorId: 0x2341 }; +const port = await navigator.serial.requestPort({ filters: [filter] }); + + +If on the other hand the site expects to be used with a wide variety of +devices or devices connected through a USB to serial converter it may specify +no filter at all and rely on the user to select the appropriate device, + + +const port = await navigator.serial.requestPort(); + + +Asking the user to choose a port requires showing a prompt to the user and so +the site must have [=transient activation=] from something like the user +clicking a button. + + +<button id="connect">Connect</button> + + + +const connectButton = document.getElementById("connect"); +connectButton.addEventListener('click', () => { + try { + const port = await navigator.serial.requestPort(); + // Continue connecting to the device attached to |port|. + } catch (e) { + // The prompt has been dismissed without selecting a device. + } +}); + + +The user may choose not to select a device, in which case the {{Promise}} will +be rejected with a "{{NotFoundError}}" {{DOMException}} that the site must +handle. +
+ +
+The {{Serial/requestPort()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not + [=allowed to use=] the [=policy-controlled feature=] named + "[=policy-controlled feature/serial=]", [=reject=] |promise| with a + "{{SecurityError}}" {{DOMException}} and return |promise|. +1. If the [=relevant global object=] of [=this=] does not have [=transient + activation=], [=reject=] |promise| with a "{{SecurityError}}" + {{DOMException}} and return |promise|. +1. If |options|["{{SerialPortRequestOptions/filters}}"] is present, then for + each |filter| in |options|["{{SerialPortRequestOptions/filters}}"] run the + following steps: + 1. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is present: + 1. If |filter|["{{SerialPortFilter/usbVendorId}}"] is present, + [=reject=] |promise| with a {{TypeError}} and return |promise|. + 1. If |filter|["{{SerialPortFilter/usbProductId}}"] is present, + [=reject=] |promise| with a {{TypeError}} and return |promise|. + 1. If |filter|["{{SerialPortFilter/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 + {{SerialPortFilter/usbProductId}} is specified then + {{SerialPortFilter/usbVendorId}} must also be specified. +1. Run the following steps [=in parallel=]: + 1. Let |allPorts| be an empty [=list=]. + 1. [=list/For each=] Bluetooth device registered with the system: + 1. [=list/For each=] {{BluetoothServiceUUID}} |uuid| supported by the + device: + 1. If |uuid| is not a [=blocked Bluetooth service class UUID=]: + * If |uuid| is equal to the [=Serial Port Profile service + class ID=], or + * |options|["{{SerialPortRequestOptions/allowedBluetoothServiceClassIds}}"] + is present and [=list/contains=] |uuid|: + 1. Let |port| be a {{SerialPort}} representing the service + on the Bluetooth device. + 1. [=list/Append=] |port| to |allPorts|. + 1. [=list/For each=] [=available=] non-Bluetooth serial port: + 1. Let |port| be a {{SerialPort}} representing the port. + 1. [=list/Append=] |port| to |allPorts|. + 1. Prompt the user to grant the site access to a serial port by presenting + them with a list of ports in |allPorts| that [=match any filter=] in + |options|["{{SerialPortRequestOptions/filters}}"] if present and + |allPorts| otherwise. + 1. 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. + 1. Let |port| be a {{SerialPort}} representing the port chosen by the user. + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + |port|. +1. Return |promise|. + +
+ +A serial port is available if it is a wired serial port and the port +is physically connected to the system, or if it is a wireless serial port and +the wireless device hosting the port is registered with the system. + +### SerialPortRequestOptions dictionary ### {#serialportrequestoptions-dictionary} + + +dictionary SerialPortRequestOptions { + sequence<SerialPortFilter> filters; + sequence<BluetoothServiceUUID> allowedBluetoothServiceClassIds; +}; + + +
+
filters member
+
Filters for serial ports
+
allowedBluetoothServiceClassIds member
+
+ A list of {{BluetoothServiceUUID}} values representing Bluetooth service + class IDs. Bluetooth ports with custom service class IDs are excluded from + the list of ports presented to the user unless the service class ID is + included in this list. +
+
+ +### SerialPortFilter dictionary ### {#serialportfilter-dictionary} + + +dictionary SerialPortFilter { + unsigned short usbVendorId; + unsigned short usbProductId; + BluetoothServiceUUID bluetoothServiceClassId; +}; + + +
+
usbVendorId member
+
USB Vendor ID
+
usbProductId member
+
USB Product ID
+
bluetoothServiceClassId member
+
Bluetooth service class ID
+
+ +
+A serial port |port| matches the filter |filter| if these steps +return `true`: + +1. Let |info| be the result of calling |port|.{{SerialPort/getInfo()}}. +1. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is present: + 1. If the serial port is not part of a Bluetooth device, return `false`. + 1. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is equal to + |info|["{{SerialPortInfo/bluetoothServiceClassId}}"], return `true`. + 1. Otherwise, return `false`. +1. If |filter|["{{SerialPortFilter/usbVendorId}}"] is not present, return + `true`. +1. If the serial port is not part of a USB device, return `false`. +1. If |info|["{{SerialPortInfo/usbVendorId}}"] is not equal to + |filter|["{{SerialPortFilter/usbVendorId}}"], return `false`. +1. If |filter|["{{SerialPortFilter/usbProductId}}"] is not present, return + `true`. +1. If |info|["{{SerialPortInfo/usbProductId}}"] is not equal to + |filter|["{{SerialPortFilter/usbProductId}}"], return `false`. +1. Otherwise, return `true`. + +
+ +
+A serial port |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 |port| [=matches the filter=] |filter|, return `true`. +1. Return `false`. + +
+ +## getPorts() method ## {#getports-method} + +
+If a serial port is provided by a USB device then that device may be connected +or disconnected from the system. Once a site has permission to access a port +it can receive these events and query for the set of connected devices it +currently has access to. + + +// Check to see what ports are available when the page loads. +document.addEventListener('DOMContentLoaded', async () => { + let ports = await navigator.serial.getPorts(); + // Populate the UI with options for the user to select or + // automatically connect to devices. +}); + +navigator.serial.addEventListener('connect', e => { + // Add |e.target| to the UI or automatically connect. +}); + +navigator.serial.addEventListener('disconnect', e => { + // Remove |e.target| from the UI. If the device was open the + // disconnection can also be observed as a stream error. +}); + +
+ +
+The {{Serial/getPorts()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. 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|. +1. Run the following steps [=in parallel=]: + 1. Let |availablePorts| be the sequence of [=available=] serial ports which + the user has allowed the site to access as the result of a previous call + to {{Serial/requestPort()}}. + 1. Let |ports| be the sequence of the {{SerialPort}}s representing the + ports in |availablePorts|. + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + |ports|. +1. Return |promise|. + +
+ +## onconnect attribute ## {#onconnect-attribute} + +{{Serial/onconnect}} is an [=event handler IDL attribute=] for the +connect event type. + +## ondisconnect attribute ## {#ondisconnect-attribute} + +{{Serial/ondisconnect}} is an [=event handler IDL attribute=] for the +disconnect event type. + +# SerialPort interface # {#serialport-interface} + + +[Exposed=(DedicatedWorker,Window), SecureContext] +interface SerialPort : EventTarget { + attribute EventHandler onconnect; + attribute EventHandler ondisconnect; + readonly attribute boolean connected; + 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(); + Promise<undefined> forget(); +}; + + +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 {{Navigator/serial}} getter. + +Instances of {{SerialPort}} are created with the internal slots described in the +following table: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Internal slotInitial valueDescription (non-normative)
\[[state]]`"closed"`Tracks the active state of the {{SerialPort}}
\[[bufferSize]]undefinedThe amount of data to buffer for transmit and receive
\[[connected]]`false`A flag indicating the logical connection state of serial port
\[[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 {{SerialPort/readable}} and + {{SerialPort/writable}} to close +
+ +## onconnect attribute ## {#onconnect-attribute-0} + +{{SerialPort/onconnect}} is an [=event handler IDL attribute=] for the +{{SerialPort/connect}} event type. + +
+When a serial port that the user has allowed the site to access as the result of +a previous call to {{Serial/requestPort()}} becomes [=logically connected=], run +the following steps: + +1. Let |port| be a {{SerialPort}} representing the port. +1. Set |port|.{{SerialPort/[[connected]]}} to `true`. +1. [=Fire an event=] named {{SerialPort/connect}} at |port| with its + {{Event/bubbles}} attribute initialized to `true`. + +
+ +A serial port is logically connected if it is a wired serial port and +the port is physically connected to the system, or if it is a wireless serial +port and the system has active connections to the wireless device (e.g. an open +Bluetooth L2CAP channel). + +## ondisconnect attribute ## {#ondisconnect-attribute-0} + +{{SerialPort/ondisconnect}} is an [=event handler IDL attribute=] for the +{{SerialPort/disconnect}} event type. + +
+When a serial port that the user has allowed the site to access as the result of +a previous call to {{Serial/requestPort()}} is no longer [=logically +connected=], run the following steps: + +1. Let |port| be a {{SerialPort}} representing the port. +1. Set |port|.{{SerialPort/[[connected]]}} to `false`. +1. [=Fire an event=] named {{SerialPort/disconnect}} at |port| with its + {{Event/bubbles}} attribute initialized to `true`. + +
+ +## getInfo() method ## {#getinfo-method} + +
+The {{SerialPort/getInfo()}} method steps are: + +1. Let |info| be an empty [=ordered map=]. +1. If the port is part of a USB device, perform the following steps: + 1. Set |info|["{{SerialPortInfo/usbVendorId}}"] to the vendor ID of the + device. + 1. Set |info|["{{SerialPortInfo/usbProductId}}"] to the product ID of the + device. +1. If the port is a service on a Bluetooth device, perform the following steps: + 1. Set |info|["{{SerialPortInfo/bluetoothServiceClassId}}"] to the service + class UUID of the Bluetooth service. +1. Return |info|. + +
+ +### SerialPortInfo dictionary ### {#serialportinfo-dictionary} + + +dictionary SerialPortInfo { + unsigned short usbVendorId; + unsigned short usbProductId; + BluetoothServiceUUID bluetoothServiceClassId; +}; + + +
+
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`. +
+
bluetoothServiceClassId member
+
+ If the port is a service on a Bluetooth device this member will be a + {{BluetoothServiceUUID}} containing the service class UUID. Otherwise it + will be `undefined`. +
+
+ +## open() method ## {#open-method} + +
+Before communicating on a serial port it must be opened. Opening the port +allows the site to specify the necessary parameters which control how data is +transmitted and received. Developers should check the documentation for the +device they are connecting to for the appropriate parameters. + + +await port.open({ baudRate: /* pick your baud rate */ }); + + +Once {{SerialPort/open()}} has resolved the {{SerialPort/readable}} and +{{SerialPort/writable}} attributes can be accessed to get the +{{ReadableStream}} and {{WritableStream}} instances for receiving data from +and sending data to the connected device. +
+ +
+The {{SerialPort/open()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. If [=this=].{{SerialPort/[[state]]}} is not `"closed"`, reject |promise| + with an "{{InvalidStateError}}" {{DOMException}} and return |promise|. +1. If |options|["{{SerialOptions/baudRate}}"] is 0, reject |promise| with a + {{TypeError}} and return |promise|. +1. If |options|["{{SerialOptions/dataBits}}"] is not 7 or 8, reject |promise| + with a {{TypeError}} and return |promise|. +1. If |options|["{{SerialOptions/stopBits}}"] is not 1 or 2, reject |promise| + with a {{TypeError}} and return |promise|. +1. If |options|["{{SerialOptions/bufferSize}}"] is 0, reject |promise| with a + {{TypeError}} and return |promise|. +1. Optionally, if |options|["{{SerialOptions/bufferSize}}"] is larger than the + implementation is able to support, reject |promise| with a {{TypeError}} and + return |promise|. +1. Set [=this=].{{SerialPort/[[state]]}} to `"opening"`. +1. 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|. + 1. 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. + 1. Set [=this=].{{SerialPort/[[state]]}} to `"opened"`. + 1. Set [=this=].{{SerialPort/[[bufferSize]]}} to + |options|["{{SerialOptions/bufferSize}}"]. + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + `undefined`. +1. Return |promise|. + +
+ +### SerialOptions dictionary ### {#serialoptions-dictionary} + + +dictionary SerialOptions { + [EnforceRange] required 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: {{SerialOptions/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.
+
+ +#### ParityType enum #### {#paritytype-enum} + + +enum 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.
+
+ +#### FlowControlType enum #### {#flowcontroltype-enum} + + +enum FlowControlType { + "none", + "hardware" +}; + + +
+
none
+
No flow control is enabled.
+
hardware
+
Hardware flow control using the RTS and CTS signals is enabled.
+
+ +## connected attribute ## {#connected-attribute} + +
+The {{SerialPort/connected}} getter steps are: + +1. Return [=this=].{{SerialPort/[[connected]]}}. + +
+ +## readable attribute ## {#readable-attribute} + +
+An application receiving data from a serial port will typically use a nested +pair of loops like this, + + +while (port.readable) { + const reader = port.readable.getReader(); + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + // |reader| has been canceled. + break; + } + // Do something with |value|... + } + } catch (error) { + // Handle |error|... + } finally { + reader.releaseLock(); + } +} + + +The inner loop will read chunks of data from the port until an error is +encountered, at which point the code in the "catch" block will be executed. +The outer loop handles recoverable errors such as parity check failures by +opening a new reader. Fatal errors will cause {{SerialPort/readable}} to +become `null` and the loop to end. + +As long as the serial port is open it can continue to produce data and the +amount of data in each of the chunks returned by +{{ReadableStreamDefaultReader/read()}} will be essentially arbitrary based on +the timing of when it is called. It is up to the device and the code +communicating with it to decide what constitutes a complete message. For +example, a device might communicate with the host using ASCII-formatted text +where each message ends with a newline (or the sequence `"\r\n"`). A pipeline +of {{TransformStream}}s can be used to automatically convert the +{{Uint8Array}} chunks provided by {{SerialPort/readable}} into {{DOMString}}s +containing an entire line of text each. + + +class LineBreakTransformer { + constructor() { + this.container = ''; + } + + transform(chunk, controller) { + this.container += chunk; + const lines = this.container.split('\r\n'); + this.container = lines.pop(); + lines.forEach(line => controller.enqueue(line)); + } + + flush(controller) { + controller.enqueue(this.container); + } +} + +const decoder = new TextDecoderStream(); +const streamClosed = port.readable.pipeTo(decoder.writable); +const lineReader = decoder.readable + .pipeThrough(new TransformStream(new LineBreakTransformer())) + .getReader(); + + +As in Example 7 the pipe chain cannot be +constructed using only `pipeThrough()`. It is necessary to use `pipeTo()` when +attaching the first {{TransformStream}} to the {{SerialPort}} so that you can +wait for the pipe chain to be closed when you want to close the port. + + +lineReader.cancel(); +await streamClosed; +await port.close(); + + +Some other ways of encoding message boundaries are to prefix each message with +its length or to wait a defined length of time before transmitting the next +message. Implementing a {{TransformStream}} for these types of message +boundaries is left as an exercise for the reader. + +While the {{ReadableStreamDefaultReader/read()}} method is asynchronous and +does not block execution, in code using async/await syntax it can seem as if +it does. In this situation it may be helpful to implement a timeout which will +allow the code to continue execution if no data is received for a period of +time. The example below uses the {{ReadableStreamDefaultReader/releaseLock()}} +method to interrupt a call to {{ReadableStreamDefaultReader/read()}} after a +timer expires. This will not close the stream and so any data received after +the timeout can still be read later after calling +{{ReadableStream/getReader()}} again. + + +async function readWithTimeout(port, timeout) { + const reader = port.readable.getReader(); + const timer = setTimeout(() => { + reader.releaseLock(); + }, timeout); + const result = await reader.read(); + clearTimeout(timer); + reader.releaseLock(); + return result; +} + + +This feature of {{ReadableStreamDefaultReader/releaseLock()}} was added in +whatwg/streams#1168 +and has only recently been implemented by browsers. +
+ +
+The {{SerialPort/readable}} getter steps are: + +1. If [=this=].{{SerialPort/[[readable]]}} is not `null`, return + [=this=].{{SerialPort/[[readable]]}}. +1. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, return `null`. +1. If [=this=].{{SerialPort/[[readFatal]]}} is `true`, return `null`. +1. Let |stream| be a [=new=] {{ReadableStream}}. +1. Let |pullAlgorithm| be the following steps: + 1. Let |desiredSize| be the [=ReadableStream/desired size to fill up to the + high water mark=] for [=this=].{{SerialPort/[[readable]]}}. + 1. If [=this=].{{SerialPort/[[readable]]}}'s [=ReadableStream/current BYOB + request view=] is non-null, then set |desiredSize| to + [=this=].{{SerialPort/[[readable]]}}'s [=ReadableStream/current BYOB + request view=]'s [=BufferSource/byte length=]. + 1. Let |promise| be [=a new promise=]. + 1. 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|. + + Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` must + be treated as if |the port was disconnected|. + 1. [=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, then: + 1. If [=this=].{{SerialPort/[[readable]]}}'s + [=ReadableStream/current BYOB request view=] is non-null, + then [=ArrayBufferView/write=] |bytes| into + [=this=].{{SerialPort/[[readable]]}}'s + [=ReadableStream/current BYOB request view=], and set |view| + to [=this=].{{SerialPort/[[readable]]}}'s + [=ReadableStream/current BYOB request view=]. + 1. Otherwise, set |view| to the result of + [=ArrayBufferView/create|creating=] a {{Uint8Array}} from + |bytes| in [=this=]'s [=relevant Realm=]. + 1. [=ReadableStream/Enqueue=] |view| into + [=this=].{{SerialPort/[[readable]]}}. + 1. [=Resolve=] |promise| with `undefined`. + 1. If a buffer overrun condition was encountered, invoke + [=ReadableStream/error=] on [=this=].{{SerialPort/[[readable]]}} + with a "BufferOverrunError" {{DOMException}} and + invoke the steps to [=handle closing the readable stream=]. + 1. If a break condition was encountered, invoke + [=ReadableStream/error=] on [=this=].{{SerialPort/[[readable]]}} + with a "BreakError" {{DOMException}} and invoke the + steps to [=handle closing the readable stream=]. + 1. If a framing error was encountered, invoke + [=ReadableStream/error=] on [=this=].{{SerialPort/[[readable]]}} + with a "FramingError" {{DOMException}} and invoke + the steps to [=handle closing the readable stream=]. + 1. If a parity error was encountered, invoke + [=ReadableStream/error=] on [=this=].{{SerialPort/[[readable]]}} + with a "ParityError" {{DOMException}} and invoke + the steps to [=handle closing the readable stream=]. + 1. If an operating system error was encountered, invoke + [=ReadableStream/error=] on [=this=].{{SerialPort/[[readable]]}} + with an "{{UnknownError}}" {{DOMException}} and invoke the steps + to [=handle closing the readable stream=]. + 1. If |the port was disconnected|, run the following steps: + 1. Set [=this=].{{SerialPort/[[readFatal]]}} to `true`, + 1. Invoke [=ReadableStream/error=] on + [=this=].{{SerialPort/[[readable]]}} with a + "{{NetworkError}}" {{DOMException}}. + 1. Invoke the steps to [=handle closing the readable stream=]. + 1. Return |promise|. +1. Let |cancelAlgorithm| be the following steps: + 1. Let |promise| be [=a new promise=]. + 1. 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. + 1. [=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=]. + 1. [=Resolve=] |promise| with `undefined`. + 1. Return |promise|. +1. [=ReadableStream/Set up with byte reading support=] |stream| with + [=ReadableStream/set up with byte reading support/pullAlgorithm=] set + to |pullAlgorithm|, [=ReadableStream/set up with byte reading + support/cancelAlgorithm=] set to |cancelAlgorithm|, and + [=ReadableStream/set up with byte reading support/highWaterMark=] set + to [=this=].{{SerialPort/[[bufferSize]]}}. +1. Set [=this=].{{SerialPort/[[readable]]}} to |stream|. +1. Return |stream|. + +
+ +
+To handle closing the readable stream perform the following steps: + +1. Set [=this=].{{SerialPort/[[readable]]}} to `null`. +1. If [=this=].{{SerialPort/[[writable]]}} is `null` and + [=this=].{{SerialPort/[[pendingClosePromise]]}} is not `null`, [=resolve=] + [=this=].{{SerialPort/[[pendingClosePromise]]}} with `undefined`. + +
+ +## writable attribute ## {#writable-attribute} + +
+To write individual chunks of data to the port a +{{WritableStreamDefaultWriter}} can be created and released as necessary. This +example uses a `TextEncoder` to encode a {{DOMString}} as the necessary +{{Uint8Array}} for transmission. + + +const encoder = new TextEncoder(); +const writer = port.writable.getWriter(); +await writer.write(encoder.encode("PING")); +writer.releaseLock(); + + +When writing larger chunks it can be important to allow the port to apply back +pressure so that the serial transmitter does not get too far behind sending +data generated by the application. The +{{WritableStreamDefaultWriter/write()}} method returns a {{Promise}} which +resolves when data has been written. While having some data available in the +transmit buffer is important to maintain good throughput awaiting this +{{Promise}} before generating too many chunks of data is a good practice to +avoid excessive buffering. +
+ +
+The {{SerialPort/writable}} getter steps are: + +1. If [=this=].{{SerialPort/[[writable]]}} is not `null`, return + [=this=].{{SerialPort/[[writable]]}}. +1. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, return `null`. +1. If [=this=].{{SerialPort/[[writeFatal]]}} is `true`, return `null`. +1. Let |stream| be a [=new=] {{WritableStream}}. +1. Let |signal| be |stream|'s [=WritableStream/signal=]. +1. Let |writeAlgorithm| be the following steps, given |chunk|: + 1. Let |promise| be [=a new promise=]. + 1. Assert: |signal| is not [=AbortSignal/aborted=]. + 1. 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|. + 1. [=Get a copy of the buffer source=] |source| and save the result in + |bytes|. + 1. [=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. + + Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` must + be treated as if |the port was disconnected|. + 1. [=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. + 1. If an operating system error was encountered, [=reject=] + |promise| with an "{{UnknownError}}" {{DOMException}}. + 1. If |the port was disconnected|, run the following steps: + 1. Set [=this=].{{SerialPort/[[writeFatal]]}} to `true`. + 1. [=Reject=] |promise| with a "{{NetworkError}}" + {{DOMException}}. + 1. Invoke the steps to [=handle closing the writable stream=]. + 1. If |signal| is [=AbortSignal/aborted=], [=reject=] |promise| + with |signal|'s [=AbortSignal/abort reason=]. + 1. Return |promise|. +1. Let |abortAlgorithm| be the following steps: + 1. Let |promise| be [=a new promise=]. + 1. 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. + 1. [=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=]. + 1. [=Resolve=] |promise| with `undefined`. + 1. Return |promise|. +1. Let |closeAlgorithm| be the following steps: + 1. Let |promise| be [=a new promise=]. + 1. 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. + 1. [=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=]. + 1. If |signal| is [=AbortSignal/aborted=], [=reject=] |promise| + with |signal|'s [=AbortSignal/abort reason=]. + 1. Otherwise, [=resolve=] |promise| with `undefined`. + 1. Return |promise|. +1. [=WritableStream/Set up=] |stream| with + [=WritableStream/set up/writeAlgorithm=] set to |writeAlgorithm|, + [=WritableStream/set up/abortAlgorithm=] set to |abortAlgorithm|, + [=WritableStream/set up/closeAlgorithm=] set to |closeAlgorithm|, + [=WritableStream/set up/highWaterMark=] set to + [=this=].{{SerialPort/[[bufferSize]]}}, and + [=WritableStream/set up/sizeAlgorithm=] set to a byte-counting size + algorithm. +1. [=AbortSignal/Add=] the following abort steps to |signal|: + 1. Cause any invocation of the operating system to write to the port to + return as soon as possible no matter how much data has been written. +1. Set [=this=].{{SerialPort/[[writable]]}} to |stream|. +1. Return |stream|. + +
+ +
+To handle closing the writable stream perform the following steps: + +1. Set [=this=].{{SerialPort/[[writable]]}} to `null`. +1. If [=this=].{{SerialPort/[[readable]]}} is `null` and + [=this=].{{SerialPort/[[pendingClosePromise]]}} is not `null`, [=resolve=] + [=this=].{{SerialPort/[[pendingClosePromise]]}} with `undefined`. + +
+ +## setSignals() method ## {#setsignals-method} + +
+Serial ports include a number of additional signals for device detection and +flow control which can be queried and set explicitly. As an example, +programming some micro-controllers first requires entering a "programming" +mode by toggling the "Data Terminal Ready" (or DTR) signal. + + +await port.setSignals({ dataTerminalReady: false }); +await new Promise(resolve => setTimeout(resolve, 200)); +await port.setSignals({ dataTerminalReady: true }); + +
+ +
+The {{SerialPort/setSignals()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject |promise| + with an "{{InvalidStateError}}" {{DOMException}} and return |promise|. +1. If all of the specified members of |signals| are not present reject + |promise| with a {{TypeError}} and return |promise|. +1. Perform the following steps [=in parallel=]: + + Note: Ideally the changes specified in |signals| would be applied atomically + however this is not supported by either the POSIX or Windows APIs user + agents will use to implement these steps. Therefore the ordering given below + is likely to be relied upon by applications. + + 1. If |signals|["{{SerialOutputSignals/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. + 1. If |signals|["{{SerialOutputSignals/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. + 1. If |signals|["{{SerialOutputSignals/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. + 1. 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}}. + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + `undefined`. +1. Return |promise|. + +
+ +### SerialOutputSignals dictionary ### {#serialoutputsignals-dictionary} + + +dictionary SerialOutputSignals { + boolean dataTerminalReady; + boolean requestToSend; + boolean break; +}; + + +
+
dataTerminalReady
+
Data Terminal Ready (DTR)
+
requestToSend
+
Request To Send (RTS)
+
break
+
Break
+
+ +## getSignals() method ## {#getsignals-method} + +
+The {{SerialPort/getSignals()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject |promise| + with an "{{InvalidStateError}}" {{DOMException}} and return |promise|. +1. 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. + 1. 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. + 1. Let |dataCarrierDetect| be `true` if the "data carrier detect" or "DCD" + signal has been asserted by the device, and `false` otherwise. + 1. Let |clearToSend| be `true` if the "clear to send" or "CTS" signal has + been asserted by the device, and `false` otherwise. + 1. Let |ringIndicator| be `true` if the "ring indicator" or "RI" signal has + been asserted by the device, and `false` otherwise. + 1. Let |dataSetReady| be `true` if the "data set ready" or "DSR" signal has + been asserted by the device, and `false` otherwise. + 1. Let |signals| be the [=ordered map=] «[ + "{{SerialInputSignals/dataCarrierDetect}}" → |dataCarrierDetect|, + "{{SerialInputSignals/clearToSend}}" → |clearToSend|, + "{{SerialInputSignals/ringIndicator}}" → |ringIndicator|, + "{{SerialInputSignals/dataSetReady}}" → |dataSetReady| ]». + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + |signals|. +1. Return |promise|. + +
+ +### SerialInputSignals dictionary ### {#serialinputsignals-dictionary} + + +dictionary 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)
+
+ +## close() method ## {#close-method} + +
+When communication with the port is no longer required it can be closed and +the associated resources released by the system. + +Calling `port.`{{SerialPort/close()}} implicitly invokes +`port.`{{SerialPort/readable}}`.`{{ReadableStream/cancel()}} and +`port.`{{SerialPort/writable}}`.`{{WritableStream/abort()}} in order to clear +any buffered data. If the application has called +`port.`{{SerialPort/readable}}`.`{{ReadableStream/getReader()}} or +`port.`{{SerialPort/writable}}`.`{{WritableStream/getWriter()}} the stream is +locked and the port cannot be closed. This forces the developer to decide how +to handle any read or write operations that are in progress. For example, to +ensure that all buffered data has been transmitted before the port is closed +the application must await the {{Promise}} returned by +`writer.`{{WritableStreamDefaultWriter/close()}}. + + +const encoder = new TextEncoder(); +const writer = port.writable.getWriter(); +writer.write(encoder.encode("A long message that will take...")); +await writer.close(); +await port.close(); + + +To discard any unsent data the application could instead call +`writer.`{{WritableStreamDefaultWriter/abort()}}. + +If a {{TransformStream}} is being piped to `port`.{{SerialPort/writable}} then +waiting for the {{Promise}} returned by +`writer.`{{WritableStreamDefaultWriter/close()}} to resolve is insufficient. +The application must wait for the pipe chain to close by waiting for the +{{Promise}} returned by {{ReadableStream/pipeTo()}} to resolve instead. + + +const encoder = new TextEncoderStream(); +const writableStreamClosed = encoder.readable.pipeTo(port.writable); +const writer = encoder.writable.getWriter(); +writer.write("A long message that will take..."); +writer.close(); +await writableStreamClosed; +await port.close(); + + +If a loop is being used to read chunks from the port, as is done in +Example 4, then it must be exited before +calling `port.`{{SerialPort/close()}}. + + +let keepReading = true; +let reader; + +async function readUntilClosed() { + while (port.readable && keepReading) { + reader = port.readable.getReader(); + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + // |reader| has been canceled. + break; + } + // Do something with |value|... + } + } catch (error) { + // Handle |error|... + } finally { + reader.releaseLock(); + } + } + + await port.close(); +} + +const closed = readUntilClosed(); + +// Sometime later... +keepReading = false; +reader.cancel(); +await closed; + + +Calling `reader.`{{ReadableStreamGenericReader/cancel()}} causes the call to +`reader.`{{ReadableStreamDefaultReader/read()}} to return immediately, exiting +the inner loop and calling +`reader.`{{ReadableStreamDefaultReader/releaseLock()}}. The outer loop then +exits because `keepReading` has been set to `false` and with the stream +unlocked `port.`{{SerialPort/close()}} can complete successfully. + +While it is also possible to call `port.`{{SerialPort/close()}} immediately +after awaiting the {{Promise}} returned by +`reader.`{{ReadableStreamGenericReader/cancel()}} it is better to place the +call to `port.`{{SerialPort/close()}} as the last step of `readUntilClosed()` +so that the port is also closed when a fatal error is encountered and +`port.`{{SerialPort/readable}} becomes `null`. +
+ +
+The {{SerialPort/close()}} method steps are: + +1. Let |promise| be [=a new promise=]. +1. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject |promise| + with an "{{InvalidStateError}}" {{DOMException}} and return |promise|. +1. Let |cancelPromise| be the result of invoking [=ReadableStream/cancel=] on + [=this=].{{SerialPort/[[readable]]}} or [=a promise resolved with=] + `undefined` if [=this=].{{SerialPort/[[readable]]}} is `null`. +1. Let |abortPromise| be the result of invoking [=WritableStream/abort=] on + [=this=].{{SerialPort/[[writable]]}} or [=a promise resolved with=] + `undefined` if [=this=].{{SerialPort/[[writable]]}} is `null`. +1. Let |pendingClosePromise| be [=a new promise=]. +1. If [=this=].{{SerialPort/[[readable]]}} and + [=this=].{{SerialPort/[[writable]]}} are `null`, [=resolve=] + |pendingClosePromise| with `undefined`. +1. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} to + |pendingClosePromise|. +1. Let |combinedPromise| be the result of [=getting a promise to wait for all=] + with «|cancelPromise|, |abortPromise|, |pendingClosePromise|». +1. Set [=this=].{{SerialPort/[[state]]}} to `"closing"`. +1. [=promise/React=] to |combinedPromise|. + * If |combinedPromise| was fulfilled, then: + 1. Run the following steps [=in parallel=]: + 1. Invoke the operating system to close the serial port and release + any associated resources. + 1. Set [=this=].{{SerialPort/[[state]]}} to `"closed"`. + 1. Set [=this=].{{SerialPort/[[readFatal]]}} and + [=this=].{{SerialPort/[[writeFatal]]}} to `false`. + 1. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} to `null`. + 1. [=Queue a global task=] on the [=relevant global object=] of + [=this=] using the [=serial port task source=] to [=resolve=] + |promise| with `undefined`. + * If |combinedPromise| was rejected with reason |r|, then: + 1. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} to `null`. + 1. [=Queue a global task=] on the [=relevant global object=] of + [=this=] using the [=serial port task source=] to [=reject=] + |promise| with |r|. +1. Return |promise|. + +
+ +## forget() method ## {#forget-method} + +
+It is posssible to voluntarily revoke a permission to a serial port that was +granted by a user. + + +// Request a serial port. +const port = await navigator.serial.requestPort(); + +// Then later... revoke permission to the serial port. +await port.forget(); + +
+ +
+The {{SerialPort/forget()}} method steps are: + +1. If the user agent can't perform this action (e.g. permission was granted by + administrator policy), return [=a promise resolved with=] `undefined`. +1. Run the following steps [=in parallel=]: + 1. Set [=this=].{{SerialPort/[[state]]}} to `"forgetting"`. + 1. Remove [=this=] from the sequence of serial ports on the system which + the user has allowed the site to access as the result of a previous call + to {{Serial/requestPort()}}. + 1. Set [=this=].{{SerialPort/[[state]]}} to `"forgotten"`. + 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] + using the [=serial port task source=] to [=resolve=] |promise| with + `undefined`. +1. Return |promise|. + +
+ +# Blocklist # {#blocklist} + +This specification relies on a blocklist file in the +https://github.com/WICG/serial +repository to restrict the set of ports a website can access. + +The result of parsing the Bluetooth service class ID blocklist at a +URL url is a [=list=] of {{UUID}} values representing custom +service IDs. + +The Serial Port Profile service class ID is a +{{BluetoothServiceUUID}} with value "`00001101-0000-1000-8000-00805f9b34fb`". + +
+A {{BluetoothServiceUUID}} |serviceUuid| is a blocked Bluetooth service +class UUID if the following steps return `true`: + +1. Let |uuid| be the result of calling + {{BluetoothUUID}}.{{BluetoothUUID/getService()}} with |serviceUuid|. +1. Let |blocklist| be the result of [=parsing the Bluetooth service class ID + blocklist=] at + https://github.com/WICG/serial/blob/main/bluetooth-service-blocklist.txt. +1. If |blocklist| [=list/contains=] |uuid|, return `true`. +1. If |uuid| [=string/is=] the [=Serial Port Profile service class ID=], return + `false`. +1. If |uuid| [=string/ends with=] "`-0000-1000-8000-00805f9b34fb`", return + `true`. +1. Otherwise, return `false`. + +
+ +# Integrations # {#integrations} + +## Permissions Policy ## {#permissions-policy} + +This specification defines a feature that controls whether the methods exposed +by the {{Navigator/serial}} attribute on the {{Navigator}} object may be used. + +The feature name for this feature is "serial". + +The [=policy-controlled feature/default allowlist=] for this feature is +`'self'`. + +# Security considerations # {#security} + +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: + +* A malicious site that has tricked the user into granting it access to a + device using the device's intended capabilities for malicious purposes. For + example, a robot causing physical damage. +* A malicious site that has tricked the user into granting it access to a + device installing its own firmware into the device in order to modify the + device's intended capabilities for malicious purposes or to attack the host + to which it is connected. For example, triggering a buffer overflow in other + host software which communicates with the device. +* Malicious code injected into a trusted site which has been granted access to + the device doing any of the above. For example, an online firmware update + utility being hacked to deliver malicious firmware. +* An attacker convincing the user to connect a malicious device to their + system which colludes with a malicious or exploited site to create a + web-based channel for communicating back to the attacker. + +The primary mitigation to all of these attacks is the {{Serial/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: + +* A setting which prevents sites from calling {{Serial/requestPort()}} unless + added to an explicit allow list. + + Systems administrators could apply such a setting across their managed fleet + using enterprise policy controls. Such controls may allow the administrator + to automatically grant selected sites access to particular devices and no + others. +* A list of device IDs for hardware which is known to be exploitable could be + deployed with the user agent. Connections to listed devices would be + blocked. An implementation could use its automatic update or experiment + management system to deploy updates to this list on the fly to block an + active attack. + +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. + +# Privacy considerations # {#privacy} + +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 [[#security]] 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 {{Serial/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. diff --git a/index.html b/index.html deleted file mode 100644 index 1f12d08..0000000 --- a/index.html +++ /dev/null @@ -1,2054 +0,0 @@ - - - - - - Web Serial API - - - - - - -
- 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. -
-
- This is a work in progress. All contributions welcome. -
-
-

- Extensions to the {{Navigator}} interface -

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

- serial attribute -

When getting, the {{Navigator/serial}} attribute always returns the - same instance of the {{Serial}} object. -
-
-

- Extensions to the {{WorkerNavigator}} interface -

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

- serial attribute -

When getting, the {{WorkerNavigator/serial}} attribute always - returns the same instance of the {{Serial}} object. -
-
-

- {{Serial}} interface -

-
-    [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 = {});
-    };
-      
-
-

- requestPort() method -

- -

- The {{Serial/requestPort()}} method steps are: -

-
    -
  1. Let |promise:Promise| be [=a new promise=]. -
  2. -
  3. If [=this=]'s [=relevant global object=]'s [=associated - Document=] is not [=allowed to use=] the [=policy-controlled - feature=] named "[=policy-controlled feature/serial=]", [=reject=] - |promise| with a "{{SecurityError}}" {{DOMException}} and return - |promise|. -
  4. -
  5. If the [=relevant global object=] of [=this=] does not have - [=transient activation=], [=reject=] |promise| with a - "{{SecurityError}}" {{DOMException}} and return |promise|. -
  6. -
  7. If |options|["{{SerialPortRequestOptions/filters}}"] is present, - then for each |filter:SerialPortFilter| in - |options|["{{SerialPortRequestOptions/filters}}"] run the following - steps: -
      -
    1. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is - present: -
        -
      1. - If |filter|["{{SerialPortFilter/usbVendorId}}"] is present, - [=reject=] |promise| with a {{TypeError}} and return |promise|. -
      2. -
      3. - If |filter|["{{SerialPortFilter/usbProductId}}"] is present, - [=reject=] |promise| with a {{TypeError}} and return |promise|. -
      4. -
      -
    2. -
    3. If |filter|["{{SerialPortFilter/usbVendorId}}"] is not - present, [=reject=] |promise| with a {{TypeError}} and return - |promise|. -
      - This check implements the combined rule that a - {{SerialPortFilter}} cannot be empty and if - {{SerialPortFilter/usbProductId}} is specified then - {{SerialPortFilter/usbVendorId}} must also be specified. -
      -
    4. -
    -
  8. -
  9. Run the following steps [=in parallel=]: -
      -
    1. Let |allPorts:list<SerialPort>| be an empty [=list=]. -
    2. -
    3. [=list/For each=] Bluetooth device registered with the system: -
        -
      1. [=list/For each=] {{BluetoothServiceUUID}} - |uuid:BluetoothServiceUUID| supported by the device: -
          -
        1. If |uuid| is not a [=blocked Bluetooth service class - UUID=]: -
            -
          • If |uuid| is equal to the [=Serial Port Profile - service class ID=], or -
          • -
          • - |options|["{{SerialPortRequestOptions/allowedBluetoothServiceClassIds}}"] - is present and [=list/contains=] |uuid|: -
              -
            1. Let |port:SerialPort| be a {{SerialPort}} - representing the service on the Bluetooth device. -
            2. -
            3. [=list/Append=] |port| to |allPorts|. -
            4. -
            -
          • -
          -
        2. -
        -
      2. -
      -
    4. -
    5. [=list/For each=] [=available=] non-Bluetooth serial port: -
        -
      1. Let |port:SerialPort| be a {{SerialPort}} representing - the port. -
      2. -
      3. [=list/Append=] |port| to |allPorts|. -
      4. -
      -
    6. -
    7. Prompt the user to grant the site access to a serial port by - presenting them with a list of ports in |allPorts| that [=match - any filter=] in |options|["{{SerialPortRequestOptions/filters}}"] - if present and |allPorts| otherwise. -
    8. -
    9. 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. -
    10. -
    11. Let |port:SerialPort| be a {{SerialPort}} representing the - port chosen by the user. -
    12. -
    13. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with |port|. -
    14. -
    -
  10. -
  11. Return |promise|. -
  12. -
-

- A serial port is available if it is a wired serial port and - the port is physically connected to the system, or if it is a wireless - serial port and the wireless device hosting the port is registered - with the system. -

-
-

- SerialPortRequestOptions dictionary -

-
-        dictionary SerialPortRequestOptions {
-          sequence<SerialPortFilter> filters;
-          sequence<BluetoothServiceUUID> allowedBluetoothServiceClassIds;
-        };
-          
-
-
- filters member -
-
- Filters for serial ports -
-
- allowedBluetoothServiceClassIds member -
-
- A list of {{BluetoothServiceUUID}} values representing Bluetooth - service class IDs. Bluetooth ports with custom service class IDs - are excluded from the list of ports presented to the user unless - the service class ID is included in this list. -
-
-
-
-

- SerialPortFilter dictionary -

-
-        dictionary SerialPortFilter {
-          unsigned short usbVendorId;
-          unsigned short usbProductId;
-          BluetoothServiceUUID bluetoothServiceClassId;
-        };
-          
-
-
- usbVendorId member -
-
- USB Vendor ID -
-
- usbProductId member -
-
- USB Product ID -
-
- bluetoothServiceClassId member -
-
- Bluetooth service class ID -
-
-

- A serial port |port:SerialPort| matches the filter - |filter:SerialPortFilter| if these steps return `true`: -

-
    -
  1. Let |info:SerialPortInfo| be the result of calling - |port|.{{SerialPort/getInfo()}}. -
  2. -
  3. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is - present: -
      -
    1. If the serial port is not part of a Bluetooth device, - return `false`. -
    2. -
    3. If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] - is equal to - |info|["{{SerialPortInfo/bluetoothServiceClassId}}"], return - `true`. -
    4. -
    5. Otherwise, return `false`. -
    6. -
    -
  4. -
  5. If |filter|["{{SerialPortFilter/usbVendorId}}"] is not present, - return `true`. -
  6. -
  7. If the serial port is not part of a USB device, return `false`. -
  8. -
  9. If |info|["{{SerialPortInfo/usbVendorId}}"] is not equal to - |filter|["{{SerialPortFilter/usbVendorId}}"], return `false`. -
  10. -
  11. If |filter|["{{SerialPortFilter/usbProductId}}"] is not - present, return `true`. -
  12. -
  13. If |info|["{{SerialPortInfo/usbProductId}}"] is not equal to - |filter|["{{SerialPortFilter/usbProductId}}"], return `false`. -
  14. -
  15. Otherwise, return `true`. -
  16. -
-

- A serial port |port:SerialPort| 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 |port| [=matches the filter=] |filter|, return `true`. -
    2. -
    -
  2. -
  3. Return `false`. -
  4. -
-
-
-
-

- getPorts() method -

- -

- The {{Serial/getPorts()}} method steps are: -

-
    -
  1. Let |promise:Promise| be [=a new promise=]. -
  2. -
  3. 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|. -
  4. -
  5. Run the following steps [=in parallel=]: -
      -
    1. Let |availablePorts| be the sequence of [=available=] serial - ports which the user has allowed the site to access - as the result of a previous call to {{Serial/requestPort()}}. -
    2. -
    3. Let |ports| be the sequence of the {{SerialPort}}s - representing the ports in |availablePorts|. -
    4. -
    5. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with |ports|. -
    6. -
    -
  6. -
  7. Return |promise|. -
  8. -
-
-
-

- onconnect attribute -

{{Serial/onconnect}} is an [=event handler IDL attribute=] for the - connect event type. -
-
-

- ondisconnect attribute -

{{Serial/ondisconnect}} is an [=event handler IDL attribute=] for - the disconnect event type. -
-
-
-

- SerialPort interface -

-
-    [Exposed=(DedicatedWorker,Window), SecureContext]
-    interface SerialPort : EventTarget {
-      attribute EventHandler onconnect;
-      attribute EventHandler ondisconnect;
-      readonly attribute boolean connected;
-      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();
-      Promise<undefined> forget();
-    };
-      
-

- 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 {{Navigator/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 -
- [[\connected]] - - `false` - - A flag indicating the logical connection state of serial port -
- [[\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 {{SerialPort/readable}} and - {{SerialPort/writable}} to close -
-
-

- onconnect attribute -

{{SerialPort/onconnect}} is an [=event handler IDL attribute=] for - the {{connect}} event type. -

- When a serial port that the user has allowed the site to access as the - result of a previous call to {{Serial/requestPort()}} becomes - [=logically connected=], run the following steps: -

-
    -
  1. Let |port:SerialPort| be a {{SerialPort}} representing the port. -
  2. -
  3. Set |port|.{{SerialPort/[[connected]]}} to `true`. -
  4. -
  5. [=Fire an event=] named {{connect}} at |port| with its - {{Event/bubbles}} attribute initialized to `true`. -
  6. -
-

- A serial port is logically connected if it is a wired - serial port and the port is physically connected to the system, or if - it is a wireless serial port and the system has active connections to - the wireless device (e.g. an open Bluetooth L2CAP channel). -

-
-
-

- ondisconnect attribute -

{{SerialPort/ondisconnect}} is an [=event handler IDL attribute=] - for the {{disconnect}} event type. -

- When a serial port that the user has allowed the site to access as the - result of a previous call to {{Serial/requestPort()}} is no longer - [=logically connected=], run the following steps: -

-
    -
  1. Let |port:SerialPort| be a {{SerialPort}} representing the port. -
  2. -
  3. Set |port|.{{SerialPort/[[connected]]}} to `false`. -
  4. -
  5. [=Fire an event=] named {{disconnect}} at |port| with its - {{Event/bubbles}} attribute initialized to `true`. -
  6. -
-
-
-

- getInfo() method -

The {{SerialPort/getInfo()}} method steps are: -
    -
  1. Let |info:SerialPortInfo| be an empty [=ordered map=]. -
  2. -
  3. If the port is part of a USB device, perform the following steps: -
      -
    1. Set |info|["{{SerialPortInfo/usbVendorId}}"] to the vendor ID - of the device. -
    2. -
    3. Set |info|["{{SerialPortInfo/usbProductId}}"] to the product - ID of the device. -
    4. -
    -
  4. -
  5. If the port is a service on a Bluetooth device, perform the - following steps: -
      -
    1. Set |info|["{{SerialPortInfo/bluetoothServiceClassId}}"] to - the service class UUID of the Bluetooth service. -
    2. -
    -
  6. -
  7. Return |info|. -
  8. -
-
-

- SerialPortInfo dictionary -

-
-      dictionary SerialPortInfo {
-        unsigned short usbVendorId;
-        unsigned short usbProductId;
-        BluetoothServiceUUID bluetoothServiceClassId;
-      };
-          
-
-
- 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`. -
-
- bluetoothServiceClassId member -
-
- If the port is a service on a Bluetooth device this member will - be a {{BluetoothServiceUUID}} containing the service class UUID. - Otherwise it will be `undefined`. -
-
-
-
-
-

- open() method -

- -

- The {{SerialPort/open()}} method steps are: -

-
    -
  1. Let |promise| be [=a new promise=]. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"closed"`, reject - |promise| with an "{{InvalidStateError}}" {{DOMException}} and return - |promise|. -
  4. -
  5. If |options|["{{SerialOptions/baudRate}}"] is 0, reject |promise| - with a {{TypeError}} and return |promise|. -
  6. If |options|["{{SerialOptions/dataBits}}"] is not 7 or 8, reject - |promise| with a {{TypeError}} and return |promise|. -
  7. -
  8. If |options|["{{SerialOptions/stopBits}}"] is not 1 or 2, reject - |promise| with a {{TypeError}} and return |promise|. -
  9. -
  10. If |options|["{{SerialOptions/bufferSize}}"] is 0, reject - |promise| with a {{TypeError}} and return |promise|. -
  11. -
  12. Optionally, if |options|["{{SerialOptions/bufferSize}}"] is - larger than the implementation is able to support, reject |promise| - with a {{TypeError}} and return |promise|. -
  13. -
  14. Set [=this=].{{SerialPort/[[state]]}} to `"opening"`. -
  15. -
  16. 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. -
    3. 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. -
    4. -
    5. Set [=this=].{{SerialPort/[[state]]}} to `"opened"`. -
    6. -
    7. Set [=this=].{{SerialPort/[[bufferSize]]}} to - |options|["{{SerialOptions/bufferSize}}"]. -
    8. -
    9. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with `undefined`. -
    10. -
    -
  17. -
  18. Return |promise|. -
  19. -
-
-

- SerialOptions dictionary -

-
-        dictionary SerialOptions {
-          [EnforceRange] required 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. -
- {{SerialOptions/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. -
-
-
-
- ParityType enum -
-
-          enum 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. -
-
-
-
-
- FlowControlType enum -
-
-          enum FlowControlType {
-            "none",
-            "hardware"
-          };
-            
-
-
- none -
-
- No flow control is enabled. -
-
- hardware -
-
- Hardware flow control using the RTS and CTS signals is enabled. -
-
-
-
-
-
-

- connected attribute -

-

- The {{SerialPort/connected}} getter steps are: -

-
    -
  1. Return [=this=].{{SerialPort/[[connected]]}}. -
  2. -
-
-
-

- readable attribute -

- -

- The {{SerialPort/readable}} getter steps are: -

-
    -
  1. If [=this=].{{SerialPort/[[readable]]}} is not `null`, return - [=this=].{{SerialPort/[[readable]]}}. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, return - `null`. -
  4. -
  5. If [=this=].{{SerialPort/[[readFatal]]}} is `true`, return - `null`. -
  6. -
  7. Let |stream| be a [=new=] {{ReadableStream}}. -
  8. -
  9. Let |pullAlgorithm| be the following steps: -
      -
    1. Let |desiredSize| be the [=ReadableStream/desired size to - fill up to the high water mark=] for - [=this=].{{SerialPort/[[readable]]}}. -
    2. -
    3. If [=this=].{{SerialPort/[[readable]]}}'s - [=ReadableStream/current BYOB request view=] is non-null, then - set |desiredSize| to [=this=].{{SerialPort/[[readable]]}}'s - [=ReadableStream/current BYOB request view=]'s - [=BufferSource/byte length=]. -
    4. -
    5. Let |promise| be [=a new promise=]. -
    6. -
    7. 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|. -
        - [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` - must be treated as if |the port was disconnected|. -
        -
      2. -
      3. [=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, then: -
            -
          1. If [=this=].{{SerialPort/[[readable]]}}'s - [=ReadableStream/current BYOB request view=] is - non-null, then [=ArrayBufferView/write=] |bytes| into - [=this=].{{SerialPort/[[readable]]}}'s - [=ReadableStream/current BYOB request view=], and set - |view| to [=this=].{{SerialPort/[[readable]]}}'s - [=ReadableStream/current BYOB request view=]. -
          2. -
          3. Otherwise, set |view| to the result of - [=ArrayBufferView/create|creating=] a {{Uint8Array}} - from |bytes| in [=this=]'s [=relevant Realm=]. -
          4. -
          5. [=ReadableStream/Enqueue=] |view| into - [=this=].{{SerialPort/[[readable]]}}. -
          6. -
          7. [=Resolve=] |promise| with `undefined`. -
          8. -
          -
        2. -
        3. If a buffer overrun condition was encountered, invoke - [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with a - "BufferOverrunError" {{DOMException}} and - invoke the steps to [=handle closing the readable - stream=]. -
        4. -
        5. If a break condition was encountered, invoke - [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with a - "BreakError" {{DOMException}} and invoke the - steps to [=handle closing the readable stream=]. -
        6. -
        7. If a framing error was encountered, invoke - [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with a - "FramingError" {{DOMException}} and invoke - the steps to [=handle closing the readable stream=]. -
        8. -
        9. If a parity error was encountered, invoke - [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with a - "ParityError" {{DOMException}} and invoke - the steps to [=handle closing the readable stream=]. -
        10. -
        11. If an operating system error was encountered, invoke - [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with an - "{{UnknownError}}" {{DOMException}} and invoke the steps - to [=handle closing the readable stream=]. -
        12. -
        13. If |the port was disconnected|, run the following - steps: -
            -
          1. Set [=this=].{{SerialPort/[[readFatal]]}} to - `true`, -
          2. -
          3. Invoke [=ReadableStream/error=] on - [=this=].{{SerialPort/[[readable]]}} with a - "{{NetworkError}}" {{DOMException}}. -
          4. -
          5. Invoke the steps to [=handle closing the readable - stream=]. -
          6. -
          -
        14. -
        -
      4. -
      -
    8. -
    9. Return |promise|. -
    10. -
    -
  10. -
  11. Let |cancelAlgorithm| be the following steps: -
      -
    1. Let |promise| be [=a new promise=]. -
    2. -
    3. 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. -
      3. [=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. -
        3. [=Resolve=] |promise| with `undefined`. -
        4. -
        -
      4. -
      -
    4. -
    5. Return |promise|. -
    6. -
    -
  12. -
  13. [=ReadableStream/Set up with byte reading support=] |stream| with - [=ReadableStream/set up with byte reading - support/pullAlgorithm=] set to |pullAlgorithm|, - [=ReadableStream/set up with byte reading - support/cancelAlgorithm=] set to |cancelAlgorithm|, and - [=ReadableStream/set up with byte reading - support/highWaterMark=] set to - [=this=].{{SerialPort/[[bufferSize]]}}. -
  14. -
  15. Set [=this=].{{SerialPort/[[readable]]}} to |stream|. -
  16. -
  17. Return |stream|. -
  18. -
To handle closing the readable stream perform the - following steps: -
    -
  1. Set [=this=].{{SerialPort/[[readable]]}} to `null`. -
  2. -
  3. If [=this=].{{SerialPort/[[writable]]}} is `null` and - [=this=].{{SerialPort/[[pendingClosePromise]]}} is not `null`, - [=resolve=] [=this=].{{SerialPort/[[pendingClosePromise]]}} with - `undefined`. -
  4. -
-
-
-

- writable attribute -

- -

- The {{SerialPort/writable}} getter steps are: -

-
    -
  1. If [=this=].{{SerialPort/[[writable]]}} is not `null`, return - [=this=].{{SerialPort/[[writable]]}}. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, return - `null`. -
  4. -
  5. If [=this=].{{SerialPort/[[writeFatal]]}} is `true`, return - `null`. -
  6. -
  7. Let |stream:WritableStream| be a [=new=] {{WritableStream}}. -
  8. -
  9. Let |signal:AbortSignal| be |stream|'s [=WritableStream/signal=]. -
  10. -
  11. Let |writeAlgorithm| be the following steps, given |chunk|: -
      -
    1. Let |promise:Promise| be [=a new promise=]. -
    2. -
    3. Assert: |signal| is not [=AbortSignal/aborted=]. -
    4. -
    5. 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:BufferSource|. -
    6. -
    7. [=Get a copy of the buffer source=] |source| and save the - result in |bytes|. -
    8. -
    9. [=In parallel=], run the following steps: -
        -
      1. Invoke the operating system to write |bytes| to the port. - Alternately, store the chunk for future coalescing. -
        - The operating system may return from this operation once - |bytes| has been queued for transmission rather than - after it has been transmitted. -
        -
        - [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` - must be treated as if |the port was disconnected|. -
        -
      2. -
      3. [=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`. -
          - [[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. -
        3. If an operating system error was encountered, - [=reject=] |promise| with an "{{UnknownError}}" - {{DOMException}}. -
        4. -
        5. If |the port was disconnected|, run the following - steps: -
            -
          1. Set [=this=].{{SerialPort/[[writeFatal]]}} to - `true`. -
          2. -
          3. [=Reject=] |promise| with a "{{NetworkError}}" - {{DOMException}}. -
          4. -
          5. Invoke the steps to [=handle closing the writable - stream=]. -
          6. -
          -
        6. -
        7. If |signal| is [=AbortSignal/aborted=], [=reject=] - |promise| with |signal|'s [=AbortSignal/abort reason=]. -
        8. -
        -
      4. -
      -
    10. -
    11. Return |promise|. -
    12. -
    -
  12. -
  13. Let |abortAlgorithm| be the following steps: -
      -
    1. Let |promise| be [=a new promise=]. -
    2. -
    3. 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. -
      3. [=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. -
        3. [=Resolve=] |promise| with `undefined`. -
        4. -
        -
      4. -
      -
    4. -
    5. Return |promise|. -
    6. -
    -
  14. -
  15. Let |closeAlgorithm| be the following steps: -
      -
    1. Let |promise| be [=a new promise=]. -
    2. -
    3. 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. -
      3. [=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. -
        3. If |signal| is [=AbortSignal/aborted=], [=reject=] - |promise| with |signal|'s [=AbortSignal/abort reason=]. -
        4. -
        5. Otherwise, [=resolve=] |promise| with `undefined`. -
        6. -
        -
      4. -
      -
    4. -
    5. Return |promise|. -
    6. -
    -
  16. -
  17. [=WritableStream/Set up=] |stream| with - writeAlgorithm set to |writeAlgorithm|, - abortAlgorithm set to |abortAlgorithm|, - closeAlgorithm set to |closeAlgorithm|, - highWaterMark set to [=this=].{{SerialPort/[[bufferSize]]}}, - and - sizeAlgorithm set to a byte-counting size algorithm. -
  18. -
  19. [=AbortSignal/Add=] the following abort steps to |signal|: -
      -
    1. Cause any invocation of the operating system to write to the - port to return as soon as possible no matter how much data has - been written. -
    2. -
    -
  20. -
  21. Set [=this=].{{SerialPort/[[writable]]}} to |stream|. -
  22. -
  23. Return |stream|. -
  24. -
To handle closing the writable stream perform the - following steps: -
    -
  1. Set [=this=].{{SerialPort/[[writable]]}} to `null`. -
  2. -
  3. If [=this=].{{SerialPort/[[readable]]}} is `null` and - [=this=].{{SerialPort/[[pendingClosePromise]]}} is not `null`, - [=resolve=] [=this=].{{SerialPort/[[pendingClosePromise]]}} with - `undefined`. -
  4. -
-
-
-

- setSignals() method -

- -

- The {{SerialPort/setSignals()}} method steps are: -

-
    -
  1. Let |promise| be [=a new promise=]. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject - |promise| with an "{{InvalidStateError}}" {{DOMException}} and return - |promise|. -
  4. -
  5. If all of the specified members of |signals| are not present - reject |promise| with a {{TypeError}} and return |promise|. -
  6. -
  7. Perform the following steps [=in parallel=]: -
    - Ideally the changes specified in |signals| would be applied - atomically however this is not supported by either the POSIX or - Windows APIs user agents will use to implement these steps. - Therefore the ordering given below is likely to be relied upon by - applications. -
    -
      -
    1. If |signals|["{{SerialOutputSignals/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. -
    3. If |signals|["{{SerialOutputSignals/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. -
    4. -
    5. If |signals|["{{SerialOutputSignals/break}}"] is present, - invoke the operating system to either assert (if `true`) or - deassert (if `false`) the "break" signal on the serial port. -
      - 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. -
      -
    6. -
    7. 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}}. -
    8. -
    9. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with `undefined`. -
    10. -
    -
  8. -
  9. Return |promise|. -
  10. -
-
-

- SerialOutputSignals dictionary -

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

- getSignals() method -

The {{SerialPort/getSignals()}} method steps are: -
    -
  1. Let |promise:Promise| be [=a new promise=]. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject - |promise| with an "{{InvalidStateError}}" {{DOMException}} and return - |promise|. -
  4. -
  5. 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. -
    3. 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. -
    4. -
    5. Let |dataCarrierDetect| be `true` if the "data carrier - detect" or "DCD" signal has been asserted by the device, and - `false` otherwise. -
    6. -
    7. Let |clearToSend| be `true` if the "clear to send" or "CTS" - signal has been asserted by the device, and `false` otherwise. -
    8. -
    9. Let |ringIndicator| be `true` if the "ring indicator" or "RI" - signal has been asserted by the device, and `false` otherwise. -
    10. -
    11. Let |dataSetReady| be `true` if the "data set ready" or "DSR" - signal has been asserted by the device, and `false` otherwise. -
    12. -
    13. Let |signals| be the [=ordered map=] «[ - "{{SerialInputSignals/dataCarrierDetect}}" → |dataCarrierDetect|, - "{{SerialInputSignals/clearToSend}}" → |clearToSend|, - "{{SerialInputSignals/ringIndicator}}" → |ringIndicator|, - "{{SerialInputSignals/dataSetReady}}" → |dataSetReady| ]».
    14. -
    15. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with |signals|. -
    16. -
    -
  6. -
  7. Return |promise|. -
  8. -
-
-

- SerialInputSignals dictionary -

-
-      dictionary 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) -
-
-
-
-
-

- close() method -

- -

- The {{SerialPort/close()}} method steps are: -

-
    -
  1. Let |promise| be [=a new promise=]. -
  2. -
  3. If [=this=].{{SerialPort/[[state]]}} is not `"opened"`, reject - |promise| with an "{{InvalidStateError}}" {{DOMException}} and return - |promise|. -
  4. -
  5. Let |cancelPromise:Promise| be the result of invoking - [=ReadableStream/cancel=] on [=this=].{{SerialPort/[[readable]]}} or - [=a promise resolved with=] `undefined` if - [=this=].{{SerialPort/[[readable]]}} is `null`. -
  6. -
  7. Let |abortPromise:Promise| be the result of invoking - [=WritableStream/abort=] on [=this=].{{SerialPort/[[writable]]}} or - [=a promise resolved with=] `undefined` if - [=this=].{{SerialPort/[[writable]]}} is `null`. -
  8. -
  9. Let |pendingClosePromise| be [=a new promise=]. -
  10. -
  11. If [=this=].{{SerialPort/[[readable]]}} and - [=this=].{{SerialPort/[[writable]]}} are `null`, [=resolve=] - |pendingClosePromise| with `undefined`. -
  12. -
  13. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} to - |pendingClosePromise|. -
  14. -
  15. Let |combinedPromise:Promise| be the result of [=getting a - promise to wait for all=] with «|cancelPromise|, |abortPromise|, - |pendingClosePromise|». -
  16. -
  17. Set [=this=].{{SerialPort/[[state]]}} to `"closing"`. -
  18. -
  19. [=promise/React=] to |combinedPromise|. -
      -
    • If |combinedPromise| was fulfilled, then: -
        -
      1. Run the following steps [=in parallel=]: -
          -
        1. Invoke the operating system to close the serial port - and release any associated resources. -
        2. -
        3. Set [=this=].{{SerialPort/[[state]]}} to `"closed"`. -
        4. -
        5. Set [=this=].{{SerialPort/[[readFatal]]}} and - [=this=].{{SerialPort/[[writeFatal]]}} to `false`. -
        6. -
        7. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} - to `null`. -
        8. -
        9. [=Queue a global task=] on the [=relevant global - object=] of [=this=] using the [=serial port task - source=] to [=resolve=] |promise| with `undefined`. -
        10. -
        -
      2. -
      -
    • -
    • If |combinedPromise| was rejected with reason |r|, then: -
        -
      1. Set [=this=].{{SerialPort/[[pendingClosePromise]]}} to - `null`. -
      2. -
      3. [=Queue a global task=] on the [=relevant global object=] - of [=this=] using the [=serial port task source=] to - [=reject=] |promise| with |r|. -
      4. -
      -
    • -
    -
  20. -
  21. Return |promise|. -
  22. -
-
-
-

- forget() method -

- -

- The {{SerialPort/forget()}} method steps are: -

-
    -
  1. If the user agent can't perform this action (e.g. permission was - granted by administrator policy), return [=a promise resolved with=] - `undefined`. -
  2. -
  3. Run the following steps [=in parallel=]: -
      -
    1. Set [=this=].{{SerialPort/[[state]]}} to `"forgetting"`. -
    2. -
    3. Remove [=this=] from the sequence of serial ports - on the system which the user has allowed the site to access as - the result of a previous call to {{Serial/requestPort()}}. -
    4. -
    5. Set [=this=].{{SerialPort/[[state]]}} to `"forgotten"`. -
    6. -
    7. [=Queue a global task=] on the [=relevant global object=] of - [=this=] using the [=serial port task source=] to [=resolve=] - |promise| with `undefined`. -
    8. -
    -
  4. -
  5. Return |promise|. -
  6. -
-
-
-
-

- Blocklist -

-

- This specification relies on a blocklist file in the https://github.com/WICG/serial - repository to restrict the set of ports a website can access. -

-

- The result of parsing the Bluetooth service class ID - blocklist at a URL |url| is a [=list=] of {{UUID}} values - representing custom service IDs. -

-

- The Serial Port Profile service class ID is a - {{BluetoothServiceUUID}} with value - "`00001101-0000-1000-8000-00805f9b34fb`". -

-

- A {{BluetoothServiceUUID}} |serviceUuid:BluetoothServiceUUID| is a - blocked Bluetooth service class UUID if the following steps - return `true`: -

-
    -
  1. Let |uuid:UUID| be the result of calling - {{BluetoothUUID}}.{{BluetoothUUID/getService()}} with |serviceUuid|. -
  2. -
  3. Let |blocklist:list<BluetoothServiceUUID>| be the result of - [=parsing the Bluetooth service class ID blocklist=] at https://github.com/WICG/serial/blob/main/bluetooth-service-blocklist.txt. -
  4. -
  5. If |blocklist| [=list/contains=] |uuid|, return `true`. -
  6. -
  7. If |uuid| [=string/is=] the [=Serial Port Profile service class - ID=], return `false`. -
  8. -
  9. If |uuid| [=string/ends with=] "`-0000-1000-8000-00805f9b34fb`", - return `true`. -
  10. -
  11. Otherwise, return `false`. -
  12. -
-
-
-

- Integrations -

-
-

- Permissions Policy -

-

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

-

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

-

- The [=policy-controlled feature/default allowlist=] for this feature - is `'self'`. -

-
-
-
-

- Security considerations -

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 - {{Serial/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. -

-
-
-

- Privacy considerations -

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 - [[[#security]]] 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 {{Serial/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. -

-
-
-
-

- Acknowledgements -

The following people contributed to the development of this - document. - -
- - diff --git a/respecConfig.js b/respecConfig.js deleted file mode 100644 index b613bcf..0000000 --- a/respecConfig.js +++ /dev/null @@ -1,27 +0,0 @@ -var respecConfig = { - specStatus: "CG-DRAFT", - latestVersion: null, - shortName: "serial", - subtitle: "Living document", - editors: [ - { - name: "See contributors on GH", - url: "https://github.com/wicg/serial/graphs/contributors" - }, - ], - logos: [{ - src: "images/logo_serial.svg", - alt: "Serial API logo", - width: 100, - height: 100, - id: 'spec-logo', - }], - group: "wicg", - github: "https://github.com/wicg/serial", - xref: [ - "DOM", "HTML", "Infra", "PERMISSIONS-POLICY", "STREAMS", "WebIDL", - "web-bluetooth" - ], - // Suppress "Normative reference to BluetoothServiceUUID" warnings - lint: { "informative-dfn": false } -}; diff --git a/styles/spec.css b/styles/spec.css deleted file mode 100644 index 5d97b4c..0000000 --- a/styles/spec.css +++ /dev/null @@ -1,5 +0,0 @@ -#speclogo{ - position: absolute; - right: 2em; - top: 4em; -} \ No newline at end of file diff --git a/tidyconfig.txt b/tidyconfig.txt deleted file mode 100644 index 40ab3ac..0000000 --- a/tidyconfig.txt +++ /dev/null @@ -1,5 +0,0 @@ -char-encoding: utf8 -indent: yes -indent-spaces: 2 -wrap: 80 -tidy-mark: no From 9696f1d1ed1880283bff8559d6e3302a2fc04d75 Mon Sep 17 00:00:00 2001 From: Reilly Grant Date: Fri, 2 Oct 2026 18:10:32 -0700 Subject: [PATCH 2/9] Update metadata to make it a WHATWG Living Standard Some editorial changes are necessary to comply with the different linting rules. --- index.bs | 73 +++++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/index.bs b/index.bs index cd0a03c..bf1876e 100644 --- a/index.bs +++ b/index.bs @@ -1,21 +1,18 @@ @@ -67,11 +64,11 @@ interface Serial : EventTarget { ## requestPort() method ## {#requestport-method} -
+
When the user first visits a site it will not have permission to access any -serial devices. A site must first call {{Serial/requestPort()}}. This call +serial devices. A site has to first call {{Serial/requestPort()}}. This call gives the browser the opportunity to prompt the user for which device the site -should be allowed to control. If the site is designed to work with a +can be allowed to control. If the site is designed to work with a particular device which is always connected via USB the site can provide a filter restricting the devices the user can select to only those that would be compatible. For example, a site which programs Arduino-powered robots could @@ -84,7 +81,7 @@ const port = await navigator.serial.requestPort({ filters: [filter] }); If on the other hand the site expects to be used with a wide variety of -devices or devices connected through a USB to serial converter it may specify +devices or devices connected through a USB to serial converter it can specify no filter at all and rely on the user to select the appropriate device, @@ -92,7 +89,7 @@ const port = await navigator.serial.requestPort(); Asking the user to choose a port requires showing a prompt to the user and so -the site must have [=transient activation=] from something like the user +the site has to have [=transient activation=] from something like the user clicking a button. @@ -111,8 +108,8 @@ connectButton.addEventListener('click', () => { }); -The user may choose not to select a device, in which case the {{Promise}} will -be rejected with a "{{NotFoundError}}" {{DOMException}} that the site must +The user can choose not to select a device, in which case the {{Promise}} will +be rejected with a "{{NotFoundError}}" {{DOMException}} that the site has to handle.
@@ -141,7 +138,7 @@ The {{Serial/requestPort()}} method steps are: Note: This check implements the combined rule that a {{SerialPortFilter}} cannot be empty and if {{SerialPortFilter/usbProductId}} is specified then - {{SerialPortFilter/usbVendorId}} must also be specified. + {{SerialPortFilter/usbVendorId}} must also be specified. 1. Run the following steps [=in parallel=]: 1. Let |allPorts| be an empty [=list=]. 1. [=list/For each=] Bluetooth device registered with the system: @@ -253,8 +250,8 @@ filter in a sequence of {{SerialPortFilter}} if these steps return `true`: ## getPorts() method ## {#getports-method} -
-If a serial port is provided by a USB device then that device may be connected +
+If a serial port is provided by a USB device then that device can be connected or disconnected from the system. Once a site has permission to access a port it can receive these events and query for the set of connected devices it currently has access to. @@ -479,10 +476,10 @@ dictionary SerialPortInfo { ## open() method ## {#open-method} -
-Before communicating on a serial port it must be opened. Opening the port +
+Before communicating on a serial port it has to be opened. Opening the port allows the site to specify the necessary parameters which control how data is -transmitted and received. Developers should check the documentation for the +transmitted and received. Developers need to check the documentation for the device they are connecting to for the appropriate parameters. @@ -549,7 +546,7 @@ dictionary SerialOptions { A positive, non-zero value indicating the baud rate at which serial communication should be established. - Note: {{SerialOptions/baudRate}} is the only required member of this + Note: {{SerialOptions/baudRate}} is the only <span class="allow-2119">required</span> 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 @@ -702,7 +699,7 @@ boundaries is left as an exercise for the reader. While the {{ReadableStreamDefaultReader/read()}} method is asynchronous and does not block execution, in code using async/await syntax it can seem as if -it does. In this situation it may be helpful to implement a timeout which will +it does. In this situation it might be helpful to implement a timeout which will allow the code to continue execution if no data is received for a period of time. The example below uses the {{ReadableStreamDefaultReader/releaseLock()}} method to interrupt a call to {{ReadableStreamDefaultReader/read()}} after a @@ -748,7 +745,7 @@ The {{SerialPort/readable}} getter steps are: 1. Invoke the operating system to read up to |desiredSize| bytes from the port, putting the result in the [=byte sequence=] |bytes|. - Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` must + Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` can be treated as if |the port was disconnected|. 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] using the [=serial port task source=] to run the following @@ -828,7 +825,7 @@ To <dfn>handle closing the readable stream</dfn> perform the following steps: ## <dfn attribute for="SerialPort">writable</dfn> attribute ## {#writable-attribute} -<div class="example"> +<div class="example" id="example-writable"> To write individual chunks of data to the port a {{WritableStreamDefaultWriter}} can be created and released as necessary. This example uses a `TextEncoder` to encode a {{DOMString}} as the necessary @@ -872,11 +869,11 @@ The {{SerialPort/writable}} getter steps are: 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 + Note: The operating system can return from this operation once |bytes| has been queued for transmission rather than after it has been transmitted. - Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` must + Note: [=this=].{{SerialPort/[[state]]}} becoming `"forgotten"` can be treated as if |the port was disconnected|. 1. [=Queue a global task=] on the [=relevant global object=] of [=this=] using the [=serial port task source=] to run the following @@ -952,7 +949,7 @@ To <dfn>handle closing the writable stream</dfn> perform the following steps: ## <dfn method for="SerialPort" lt="setSignals(signals)|setSignals()">setSignals()</dfn> method ## {#setsignals-method} -<div class="example"> +<div class="example" id="example-set-signals"> Serial ports include a number of additional signals for device detection and flow control which can be queried and set explicitly. As an example, programming some micro-controllers first requires entering a "programming" @@ -1084,7 +1081,7 @@ dictionary SerialInputSignals { ## <dfn method for="SerialPort">close()</dfn> method ## {#close-method} <div id="close-example" class="example"> -When communication with the port is no longer required it can be closed and +When communication with the port is no longer needed it can be closed and the associated resources released by the system. Calling `port.`{{SerialPort/close()}} implicitly invokes @@ -1096,7 +1093,7 @@ any buffered data. If the application has called locked and the port cannot be closed. This forces the developer to decide how to handle any read or write operations that are in progress. For example, to ensure that all buffered data has been transmitted before the port is closed -the application must await the {{Promise}} returned by +the application has to await the {{Promise}} returned by `writer.`{{WritableStreamDefaultWriter/close()}}. <xmp highlight="js"> @@ -1113,7 +1110,7 @@ To discard any unsent data the application could instead call If a {{TransformStream}} is being piped to `port`.{{SerialPort/writable}} then waiting for the {{Promise}} returned by `writer.`{{WritableStreamDefaultWriter/close()}} to resolve is insufficient. -The application must wait for the pipe chain to close by waiting for the +The application has to wait for the pipe chain to close by waiting for the {{Promise}} returned by {{ReadableStream/pipeTo()}} to resolve instead. <xmp highlight="js"> @@ -1127,7 +1124,7 @@ await port.close(); If a loop is being used to read chunks from the port, as is done in -Example 4, then it must be exited before +Example 4, then it has to be exited before calling `port.`{{SerialPort/close()}}. @@ -1223,7 +1220,7 @@ The {{SerialPort/close()}} method steps are: ## <dfn method for="SerialPort">forget()</dfn> method ## {#forget-method} -<div class="example"> +<div class="example" id="example-forget"> It is posssible to voluntarily revoke a permission to a serial port that was granted by a user. @@ -1257,7 +1254,7 @@ The {{SerialPort/forget()}} method steps are: # Blocklist # {#blocklist} This specification relies on a blocklist file in the -<a href="https://github.com/WICG/serial">https://github.com/WICG/serial</a> +<a href="https://github.com/whatwg/serial">https://github.com/whatwg/serial</a> repository to restrict the set of ports a website can access. The result of <dfn>parsing the Bluetooth service class ID blocklist</dfn> at a @@ -1275,7 +1272,7 @@ class UUID</dfn> if the following steps return `true`: {{BluetoothUUID}}.{{BluetoothUUID/getService()}} with |serviceUuid|. 1. Let |blocklist| be the result of [=parsing the Bluetooth service class ID blocklist=] at - <a href="https://github.com/WICG/serial/blob/main/bluetooth-service-blocklist.txt">https://github.com/WICG/serial/blob/main/bluetooth-service-blocklist.txt</a>. + <a href="https://github.com/whatwg/serial/blob/main/bluetooth-service-blocklist.txt">https://github.com/whatwg/serial/blob/main/bluetooth-service-blocklist.txt</a>. 1. If |blocklist| [=list/contains=] |uuid|, return `true`. 1. If |uuid| [=string/is=] the [=Serial Port Profile service class ID=], return `false`. @@ -1301,7 +1298,7 @@ The [=policy-controlled feature/default allowlist=] for this feature is <em>This section is non-normative.</em> -This API poses similar a security risk to [[WEB-BLUETOOTH]] and [[WEBUSB]] and +This API poses similar a security risk to [[WEBBLUETOOTH]] and [[WEBUSB]] and so lessons from those are applicable here. The primary threats are: * A malicious site that has tricked the user into granting it access to a @@ -1366,7 +1363,7 @@ User agents can implement additional mechanisms to control access to devices: management system to deploy updates to this list on the fly to block an active attack. -Implementations of [[WEB-BLUETOOTH]] and [[WEBUSB]] have experimented with +Implementations of [[WEBBLUETOOTH]] 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 From 0872672f8b6fbdd91bd693eab52c91ed741db3df Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Fri, 2 Oct 2026 18:22:00 -0700 Subject: [PATCH 3/9] Update .pr_preview.json to "Status" to "LS-PR" --- .pr_preview.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.pr_preview.json b/.pr_preview.json index 49b0677..03fdd6b 100644 --- a/.pr_preview.json +++ b/.pr_preview.json @@ -1,4 +1,9 @@ { "src_file": "index.bs", - "type": "bikeshed" + "type": "bikeshed", + "params": { + "force": 1, + "md-status": "LS-PR", + "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" + } } From b54db3af3a7adcba1928cea4653cd8379a6aab47 Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Fri, 2 Oct 2026 18:24:31 -0700 Subject: [PATCH 4/9] Copy GitHub build workflow from Fetch API --- .github/workflows/build.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/pr-push.yml | 18 ------------------ 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/pr-push.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f6bf113 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: Build + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + - run: pip install bikeshed && bikeshed update + # Note: `make deploy` will do a deploy dry run on PRs. + - run: make deploy + env: + SERVER: ${{ secrets.MARQUEE_SERVER }} + SERVER_PUBLIC_KEY: ${{ secrets.MARQUEE_PUBLIC_KEY }} + SERVER_DEPLOY_KEY: ${{ secrets.MARQUEE_DEPLOY_KEY }} diff --git a/.github/workflows/pr-push.yml b/.github/workflows/pr-push.yml deleted file mode 100644 index 54b6450..0000000 --- a/.github/workflows/pr-push.yml +++ /dev/null @@ -1,18 +0,0 @@ - -name: CI -on: - pull_request: {} - push: - branches: [main] -jobs: - main: - name: Build, Validate and Deploy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: w3c/spec-prod@v2 - with: - SOURCE: index.bs - DESTINATION: index.html - TOOLCHAIN: bikeshed - GH_PAGES_BRANCH: gh-pages From 9fc203852c5b53d2b6314b3da50fa76a40886cfc Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Fri, 2 Oct 2026 18:28:19 -0700 Subject: [PATCH 5/9] Add standard WHATWG Makefile --- Makefile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed5bb0a --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +SHELL=/bin/bash -o pipefail +.PHONY: local remote deploy + +remote: index.bs + @ (HTTP_STATUS=$$(curl https://www.w3.org/publications/spec-generator/ \ + --output index.html \ + --write-out "%{http_code}" \ + --header "Accept: text/plain, text/html" \ + -F die-on=warning \ + -F md-Text-Macro="COMMIT-SHA LOCAL COPY" \ + -F file=@index.bs \ + -F type=bikeshed-spec \ + -F output=html) && \ + [[ "$$HTTP_STATUS" -eq "200" ]]) || ( \ + echo ""; cat index.html; echo ""; \ + rm -f index.html; \ + exit 22 \ + ); + +local: index.bs + bikeshed spec index.bs index.html --md-Text-Macro="COMMIT-SHA LOCAL-COPY" + +deploy: index.bs + curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh + EXTRA_FILES="demos/* demos/**/*" \ + bash ./deploy.sh \ No newline at end of file From c2af3102016bd44b01813ef78b6eaad3aa2f32a0 Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Fri, 2 Oct 2026 18:31:02 -0700 Subject: [PATCH 6/9] Remove EXTRA_FILES from deploy step --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index ed5bb0a..4221370 100644 --- a/Makefile +++ b/Makefile @@ -22,5 +22,4 @@ local: index.bs deploy: index.bs curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh - EXTRA_FILES="demos/* demos/**/*" \ bash ./deploy.sh \ No newline at end of file From 12a4af079cddbd5f81d2bd67d30b714d5745e62a Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Mon, 5 Oct 2026 18:59:36 -0700 Subject: [PATCH 7/9] Ran spec-factory --- .editorconfig | 22 ++ .gitattributes | 2 + .github/CONTRIBUTING.md | 1 + .github/ISSUE_TEMPLATE/0-new-issue.yml | 17 ++ .github/ISSUE_TEMPLATE/1-new-feature.yml | 27 ++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/pull_request_template.md | 21 ++ .gitignore | 18 +- .pr-preview.json | 9 + .pr_preview.json | 9 - CONTRIBUTING.md | 15 - LICENSE | 356 +++++++++++++++++++++++ Makefile | 2 +- README.md | 49 +++- 14 files changed, 506 insertions(+), 50 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/0-new-issue.yml create mode 100644 .github/ISSUE_TEMPLATE/1-new-feature.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/pull_request_template.md create mode 100644 .pr-preview.json delete mode 100644 .pr_preview.json delete mode 100644 CONTRIBUTING.md create mode 100644 LICENSE diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..df39c7c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true +max_line_length = 100 + +[Makefile] +indent_style = tab + +[*.md] +max_line_length = off + +[*.bs] +indent_size = 1 + +[*.py] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2f2e77e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.bs diff=html linguist-language=HTML diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d1ad113 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +Please see the [WHATWG Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md). diff --git a/.github/ISSUE_TEMPLATE/0-new-issue.yml b/.github/ISSUE_TEMPLATE/0-new-issue.yml new file mode 100644 index 0000000..ec6edb5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/0-new-issue.yml @@ -0,0 +1,17 @@ +name: New issue +description: File a new issue against the Serial Standard. +body: + - type: markdown + attributes: + value: | + Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct). You might also find the [FAQ](https://whatwg.org/faq) and [Working Mode](https://whatwg.org/working-mode) useful. + + If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat). + - type: textarea + attributes: + label: "What is the issue with the Serial Standard?" + validations: + required: true + - type: markdown + attributes: + value: "Thank you for taking the time to improve the Serial Standard!" diff --git a/.github/ISSUE_TEMPLATE/1-new-feature.yml b/.github/ISSUE_TEMPLATE/1-new-feature.yml new file mode 100644 index 0000000..1aa3bc1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-new-feature.yml @@ -0,0 +1,27 @@ +name: New feature +description: Request a new feature in the Serial Standard. +labels: ["addition/proposal", "needs implementer interest"] +body: + - type: markdown + attributes: + value: | + Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct), [FAQ](https://whatwg.org/faq), and [Working Mode](https://whatwg.org/working-mode). They help with setting expectations and making sure you know what is required. The FAQ ["How should I go about proposing new features to WHATWG standards?"](https://whatwg.org/faq#adding-new-features) is especially relevant. + + If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat). + - type: textarea + attributes: + label: "What problem are you trying to solve?" + validations: + required: true + - type: textarea + attributes: + label: "What solutions exist today?" + - type: textarea + attributes: + label: "How would you solve it?" + - type: textarea + attributes: + label: "Anything else?" + - type: markdown + attributes: + value: "Thank you for taking the time to improve the Serial Standard!" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..70e8d0d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Chat + url: https://whatwg.org/chat + about: Please do reach out with questions and feedback! + - name: Stack Overflow + url: https://stackoverflow.com/ + about: If you're having trouble building a web page, this is not the right repository. Consider asking your question on Stack Overflow instead. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..643e022 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +<!-- +Thank you for contributing to the Serial Standard! Please describe the change you are making and complete the checklist below if your change is not editorial. + +When you submit this PR, and each time you edit this comment (including checking a checkbox through the UI!), PR Preview will run and update it. As such make any edits in one go and only after PR Preview has run. + +If you think your PR is ready to land, please double-check that the build is passing and the checklist is complete before pinging. +--> + +- [ ] At least two implementers are interested (and none opposed): + * … + * … +- [ ] [Tests](https://github.com/web-platform-tests/wpt) are written and can be reviewed and commented upon at: + * … <!-- If these tests are tentative, link a PR to make them non-tentative. --> +- [ ] [Implementation bugs](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) are filed: + * Chromium: … + * Gecko: … + * WebKit: … +- [ ] [MDN issue](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) is filed: … +- [ ] The top of this comment includes a [clear commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md) to use. <!-- If you created this PR from a single commit, Github copied its message. Otherwise, you need to add a commit message yourself. --> + +(See [WHATWG Working Mode: Changes](https://whatwg.org/working-mode#changes) for more details.) diff --git a/.gitignore b/.gitignore index a72b52e..a591e95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,3 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz - -pids -logs -results - -npm-debug.log -node_modules +/serial.spec.whatwg.org/ +/deploy.sh +/index.html diff --git a/.pr-preview.json b/.pr-preview.json new file mode 100644 index 0000000..3b9efb1 --- /dev/null +++ b/.pr-preview.json @@ -0,0 +1,9 @@ +{ + "src_file": "index.bs", + "type": "bikeshed", + "params": { + "force": 1, + "md-status": "LS-PR", + "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" + } +} diff --git a/.pr_preview.json b/.pr_preview.json deleted file mode 100644 index 03fdd6b..0000000 --- a/.pr_preview.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "src_file": "index.bs", - "type": "bikeshed", - "params": { - "force": 1, - "md-status": "LS-PR", - "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" - } -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 87f0fdc..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,15 +0,0 @@ -For significant contributions (i.e., non-editorial changes to the API), -we require you to become a member of the -[Web Platform Incubator CG](http://www.w3.org/community/wicg/). This helps -keep the spec royalty free and gives us some patent protection! - -This spec is written using [ReSpec](http://w3.org/respec/). For -configuration options, etc. please see the [ReSpec -refeference](www.w3.org/respec/ref.html). - -Please make sure you run [HTML5 Tidy](http://w3c.github.io/tidy-html5/) -before sending a Pull Request: - -```Bash -tidy -config tidyconfig.txt -o index.html index.html -``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f2dcda4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,356 @@ +Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). + +This work is licensed under a Creative Commons Attribution 4.0 International +License. To the extent portions of it are incorporated into source code, +such portions in the source code are licensed under the BSD 3-Clause License instead. + +- - - - + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +- - - - + +BSD 3-Clause License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +- - - - diff --git a/Makefile b/Makefile index 4221370..163f5e6 100644 --- a/Makefile +++ b/Makefile @@ -22,4 +22,4 @@ local: index.bs deploy: index.bs curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh - bash ./deploy.sh \ No newline at end of file + bash ./deploy.sh diff --git a/README.md b/README.md index 8658c9a..c60cc14 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,48 @@ -# Serial API +This repository hosts the [Serial Standard](https://serial.spec.whatwg.org/). It is one of [many web standards](https://spec.whatwg.org/) developed by the [WHATWG community](https://whatwg.org/). -[Serial ports API](http://wicg.github.io/serial/) for the platform. +## Explainer -### Explainer +Details about the API including example usage code snippets and its motivation, privacy, and security considerations are described in [EXPLAINER.md](./EXPLAINER.md). Extensions to this API to support connections to Bluetooth RFCOMM services are described in [EXPLAINER_BL +UETOOTH.md](./EXPLAINER_BLUETOOTH.md). -Details about the API including example usage code snippets and its motivation, privacy, and security considerations are described in [EXPLAINER.md](./EXPLAINER.md). Extensions to this API to support connections to Bluetooth RFCOMM services are described in [EXPLAINER_BLUETOOTH.md](./EXPLAINER_BLUETOOTH.md). +## Implementation Status -### Code of conduct +This API has three implementations: [Blink](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/), [Gecko](https://github.com/mozilla-firefox/firefox/tree/main/dom/webserial), and [a polyfill](https://github.com/google/web-serial-polyfill/). -We are committed to providing a friendly, safe and welcoming environment for all. Please read and respect the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). +The Blink implementation is available in browsers based on Chromium 89 and later, such as Google Chrome and Microsoft Edge. Individual Chromium-based browsers may choose to enable or disable this API. The initial release was limited to desktop OSes (Windows, macOS, Linux, and ChromeOS) however as of Chromium 148 this API is available on Android-based devices as well. -### Implementation status +The Gecko implementation is available in browsers based on Firefox 151 and later on desktop OSes (Windows, macOS, and Linux). -This API has two implementations: [Blink](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/) and [a polyfill](https://github.com/google/web-serial-polyfill/) +The polyfill implementation is based on the WebUSB API and currently only supports standard USB communications class devices but could be expanded to support other proprietary USB to serial adapters. It could also be expanded to support Bluetooth Low Energy UARTs via the Web Bluetooth API. -The Blink implementation is available in browsers based on Chromium 89 and later, such as Google Chrome and Microsoft Edge. Individual Chromium-based browsers may choose to enable or disable this API. Chromium-based Android browsers do not support this API because Android itself does not provide a direct API for accessing serial ports. For the same reason this API is not available in Android WebView ([Chromium issue 1164036](https://crbug.com/1164036)). +## Code of conduct -The polyfill implementation is based on the WebUSB API and currently only supports standard USB communications class devices but could be expanded to support other proprietary USB to serial adapters. It could also be expanded to support Bluetooth Low Energy UARTs via the Web Bluetooth API. Because both WebUSB and Web Bluetooth are available in Chromium-based browsers on Android the polyfill is an option for sites to support Android while using the native browser implementation on desktop platforms. +We are committed to providing a friendly, safe, and welcoming environment for all. Please read and respect the [Code of Conduct](https://whatwg.org/code-of-conduct). + +## Contribution opportunities + +Folks notice minor and larger issues with the Serial Standard all the time and we'd love your help fixing those. Pull requests for typographical and grammar errors are also most welcome. + +Issues labeled ["good first issue"](https://github.com/whatwg/serial/labels/good%20first%20issue) are a good place to get a taste for editing the Serial Standard. Note that we don't assign issues and there's no reason to ask for availability either, just provide a pull request. + +If you are thinking of suggesting a new feature, read through the [FAQ](https://whatwg.org/faq) and [Working Mode](https://whatwg.org/working-mode) documents to get yourself familiarized with the process. + +We'd be happy to help you with all of this [on Chat](https://whatwg.org/chat). + +## Pull requests + +In short, change `index.bs` and submit your patch, with a [good commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md). + +Please add your name to the Acknowledgments section in your first pull request, even for trivial fixes. The names are sorted lexicographically. + +To ensure your patch meets all the necessary requirements, please also see the [Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md). Editors of the Serial Standard are expected to follow the [Maintainer Guidelines](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md). + +## Tests + +Tests are an essential part of the standardization process and will need to be created or adjusted as changes to the standard are made. Tests for the Serial Standard can be found in the `serial/` directory of [`web-platform-tests/wpt`](https://github.com/web-platform-tests/wpt). + +A dashboard showing the tests running against browser engines can be seen at [wpt.fyi/results/serial](https://wpt.fyi/results/serial). + +## Building "locally" + +For quick local iteration, run `make`; this will use a web service to build the standard, so that you don't have to install anything. See more in the [Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md#building). From cabcbf39d692138a541f3d77ea0af574e5636400 Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Tue, 6 Oct 2026 15:57:00 -0700 Subject: [PATCH 8/9] Clean up remaining files and properties that are already handled by the template --- LICENSE.md | 10 ---------- images/logo_serial.svg | 24 ------------------------ index.bs | 3 --- w3c.json | 5 ----- 4 files changed, 42 deletions(-) delete mode 100644 LICENSE.md delete mode 100644 images/logo_serial.svg delete mode 100644 w3c.json diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index ca197c0..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,10 +0,0 @@ -All Reports in this Repository are licensed by Contributors -under the -[W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). - -Contributions to Specifications are made under the -[W3C CLA](https://www.w3.org/community/about/agreements/cla/). - -Contributions to Test Suites are made under the -[W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) - diff --git a/images/logo_serial.svg b/images/logo_serial.svg deleted file mode 100644 index 1593964..0000000 --- a/images/logo_serial.svg +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="100%" height="100%" viewBox="0 0 202 202" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> - <title>WHATWG Serial API logo</title> - <defs></defs> - <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> - <g id="Oval-+-Rectangle-1-+-Group" sketch:type="MSLayerGroup" transform="translate(1.000000, 1.000000)"> - <path d="M200,100 C200,44.7715226 155.228477,0 100,0 C44.7715226,0 0,44.7715226 0,100 C0,155.228477 44.7715226,200 100,200 C155.228477,200 200,155.228477 200,100 Z M20,100 C20,55.8172176 55.8172176,20 100,20 C144.182782,20 180,55.8172176 180,100 C180,144.182782 144.182782,180 100,180 C55.8172176,180 20,144.182782 20,100 Z M20,100" id="Oval" fill="#3C790A" sketch:type="MSShapeGroup"></path> - <g id="Rectangle-1-+-Group" transform="translate(29.878049, 70.121951)" fill="#30680A" sketch:type="MSShapeGroup"> - <path d="M12.0974498,2.6214235 C3.29837747,3.23799964 -1.84713483,10.596543 0.615947951,19.096259 L7.92372146,44.31425 C10.3817354,52.7964743 19.5476455,59.6726756 28.3492618,59.6726756 L111.072219,59.6726756 C119.89492,59.6726756 129.120044,52.813966 131.666559,44.3882342 L139.33293,19.0222748 C141.88417,10.5809109 136.820976,3.27387374 127.984162,2.71892345 C127.984162,2.71892345 88.0527845,0 69.9960602,0 C45.6272125,1.49317217e-06 12.0974498,2.6214235 12.0974498,2.6214235 Z M94.2636566,8.762085 C97.798657,8.93246259 101.4998,9.12580352 105.318311,9.33786022 C110.651077,9.6340093 115.945111,9.95142013 120.957805,10.2688134 C122.711849,10.3798758 124.305054,10.4829775 125.707175,10.5754606 C126.546743,10.6308381 127.13487,10.6702889 127.441333,10.691156 C131.170696,10.9254703 132.747002,13.193158 131.683948,16.7105175 L124.017576,42.0764769 C122.488493,47.1358014 116.366297,51.6819839 111.072219,51.6819839 L28.3492618,51.6819839 C23.0969823,51.6819839 17.0647544,47.1494471 15.5986577,42.0901753 L8.2908842,16.8721843 C7.24614019,13.266935 8.92971171,10.8536817 12.6560108,10.5925692 C12.9722446,10.5681061 13.465132,10.5304827 14.1780413,10.4774967 C15.3717092,10.388779 16.7437095,10.2898127 18.2731218,10.1831592 C22.6470738,9.87814199 27.400623,9.57302611 32.3659115,9.28829791 C39.7614137,8.86421216 46.9678768,8.52350678 53.7325426,8.29652601 C59.6861009,8.09676105 65.1447205,7.99069205 69.9960607,7.99069176 C75.6621487,7.99069176 84.0010179,8.26745319 94.2636566,8.762085 Z M94.2636566,8.762085" id="Rectangle-1"></path> - <g id="Group" transform="translate(33.536585, 18.292683)"> - <path d="M9.45121951,4.72560976 C9.45121951,2.11572748 7.33549204,0 4.72560976,0 C2.11572748,0 0,2.11572748 0,4.72560976 C0,7.33549204 2.11572748,9.45121951 4.72560976,9.45121951 C7.33549204,9.45121951 9.45121951,7.33549204 9.45121951,4.72560976 Z M3.7804878,4.72560976 C3.7804878,4.20363324 4.20363324,3.7804878 4.72560976,3.7804878 C5.24758627,3.7804878 5.67073171,4.20363324 5.67073171,4.72560976 C5.67073171,5.24758627 5.24758627,5.67073171 4.72560976,5.67073171 C4.20363324,5.67073171 3.7804878,5.24758627 3.7804878,4.72560976 Z M3.7804878,4.72560976" id="Oval-2"></path> - <path d="M25.304878,4.72560976 C25.304878,2.11572748 23.1891506,0 20.5792683,0 C17.969386,0 15.8536585,2.11572748 15.8536585,4.72560976 C15.8536585,7.33549204 17.969386,9.45121951 20.5792683,9.45121951 C23.1891506,9.45121951 25.304878,7.33549204 25.304878,4.72560976 Z M19.6341463,4.72560976 C19.6341463,4.20363324 20.0572918,3.7804878 20.5792683,3.7804878 C21.1012448,3.7804878 21.5243902,4.20363324 21.5243902,4.72560976 C21.5243902,5.24758627 21.1012448,5.67073171 20.5792683,5.67073171 C20.0572918,5.67073171 19.6341463,5.24758627 19.6341463,4.72560976 Z M19.6341463,4.72560976" id="Oval-2"></path> - <path d="M41.4634146,4.72560976 C41.4634146,2.11572748 39.3476872,0 36.7378049,0 C34.1279226,0 32.0121951,2.11572748 32.0121951,4.72560976 C32.0121951,7.33549204 34.1279226,9.45121951 36.7378049,9.45121951 C39.3476872,9.45121951 41.4634146,7.33549204 41.4634146,4.72560976 Z M35.7926829,4.72560976 C35.7926829,4.20363324 36.2158284,3.7804878 36.7378049,3.7804878 C37.2597814,3.7804878 37.6829268,4.20363324 37.6829268,4.72560976 C37.6829268,5.24758627 37.2597814,5.67073171 36.7378049,5.67073171 C36.2158284,5.67073171 35.7926829,5.24758627 35.7926829,4.72560976 Z M35.7926829,4.72560976" id="Oval-2"></path> - <path d="M57.6219512,4.72560976 C57.6219512,2.11572748 55.5062237,0 52.8963415,0 C50.2864592,0 48.1707317,2.11572748 48.1707317,4.72560976 C48.1707317,7.33549204 50.2864592,9.45121951 52.8963415,9.45121951 C55.5062237,9.45121951 57.6219512,7.33549204 57.6219512,4.72560976 Z M51.9512195,4.72560976 C51.9512195,4.20363324 52.3743649,3.7804878 52.8963415,3.7804878 C53.418318,3.7804878 53.8414634,4.20363324 53.8414634,4.72560976 C53.8414634,5.24758627 53.418318,5.67073171 52.8963415,5.67073171 C52.3743649,5.67073171 51.9512195,5.24758627 51.9512195,4.72560976 Z M51.9512195,4.72560976" id="Oval-2"></path> - <path d="M73.1707317,4.72560976 C73.1707317,2.11572748 71.0550042,0 68.445122,0 C65.8352397,0 63.7195122,2.11572748 63.7195122,4.72560976 C63.7195122,7.33549204 65.8352397,9.45121951 68.445122,9.45121951 C71.0550042,9.45121951 73.1707317,7.33549204 73.1707317,4.72560976 Z M67.5,4.72560976 C67.5,4.20363324 67.9231454,3.7804878 68.445122,3.7804878 C68.9670985,3.7804878 69.3902439,4.20363324 69.3902439,4.72560976 C69.3902439,5.24758627 68.9670985,5.67073171 68.445122,5.67073171 C67.9231454,5.67073171 67.5,5.24758627 67.5,4.72560976 Z M67.5,4.72560976" id="Oval-2"></path> - <path d="M17.3780488,18.445122 C17.3780488,15.8352397 15.2623213,13.7195122 12.652439,13.7195122 C10.0425567,13.7195122 7.92682927,15.8352397 7.92682927,18.445122 C7.92682927,21.0550042 10.0425567,23.1707317 12.652439,23.1707317 C15.2623213,23.1707317 17.3780488,21.0550042 17.3780488,18.445122 Z M11.7073171,18.445122 C11.7073171,17.9231454 12.1304625,17.5 12.652439,17.5 C13.1744155,17.5 13.597561,17.9231454 13.597561,18.445122 C13.597561,18.9670985 13.1744155,19.3902439 12.652439,19.3902439 C12.1304625,19.3902439 11.7073171,18.9670985 11.7073171,18.445122 Z M11.7073171,18.445122" id="Oval-2"></path> - <path d="M33.2317073,18.445122 C33.2317073,15.8352397 31.1159798,13.7195122 28.5060976,13.7195122 C25.8962153,13.7195122 23.7804878,15.8352397 23.7804878,18.445122 C23.7804878,21.0550042 25.8962153,23.1707317 28.5060976,23.1707317 C31.1159798,23.1707317 33.2317073,21.0550042 33.2317073,18.445122 Z M27.5609756,18.445122 C27.5609756,17.9231454 27.984121,17.5 28.5060976,17.5 C29.0280741,17.5 29.4512195,17.9231454 29.4512195,18.445122 C29.4512195,18.9670985 29.0280741,19.3902439 28.5060976,19.3902439 C27.984121,19.3902439 27.5609756,18.9670985 27.5609756,18.445122 Z M27.5609756,18.445122" id="Oval-2"></path> - <path d="M49.3902439,18.445122 C49.3902439,15.8352397 47.2745164,13.7195122 44.6646341,13.7195122 C42.0547519,13.7195122 39.9390244,15.8352397 39.9390244,18.445122 C39.9390244,21.0550042 42.0547519,23.1707317 44.6646341,23.1707317 C47.2745164,23.1707317 49.3902439,21.0550042 49.3902439,18.445122 Z M43.7195122,18.445122 C43.7195122,17.9231454 44.1426576,17.5 44.6646341,17.5 C45.1866107,17.5 45.6097561,17.9231454 45.6097561,18.445122 C45.6097561,18.9670985 45.1866107,19.3902439 44.6646341,19.3902439 C44.1426576,19.3902439 43.7195122,18.9670985 43.7195122,18.445122 Z M43.7195122,18.445122" id="Oval-2"></path> - <path d="M65.5487805,18.445122 C65.5487805,15.8352397 63.433053,13.7195122 60.8231707,13.7195122 C58.2132885,13.7195122 56.097561,15.8352397 56.097561,18.445122 C56.097561,21.0550042 58.2132885,23.1707317 60.8231707,23.1707317 C63.433053,23.1707317 65.5487805,21.0550042 65.5487805,18.445122 Z M59.8780488,18.445122 C59.8780488,17.9231454 60.3011942,17.5 60.8231707,17.5 C61.3451472,17.5 61.7682927,17.9231454 61.7682927,18.445122 C61.7682927,18.9670985 61.3451472,19.3902439 60.8231707,19.3902439 C60.3011942,19.3902439 59.8780488,18.9670985 59.8780488,18.445122 Z M59.8780488,18.445122" id="Oval-2"></path> - </g> - </g> - </g> - </g> -</svg> \ No newline at end of file diff --git a/index.bs b/index.bs index bf1876e..8f68236 100644 --- a/index.bs +++ b/index.bs @@ -2,9 +2,7 @@ Group: WHATWG H1: Serial Shortname: serial -Repository: whatwg/serial Editor: Reilly Grant, Google https://google.com, reillyg@google.com, https://github.com/reillyeon -Logo: images/logo_serial.svg Abstract: The <cite>Serial API</cite> provides a way for websites to read and write from a serial device through script. Such an API would bridge the web and @@ -12,7 +10,6 @@ Abstract: such as microcontrollers, 3D printers, and other serial devices. There is also a companion <a href="https://github.com/whatwg/serial/blob/main/EXPLAINER.md">explainer</a> document. -Status: LS Markup Shorthands: css no, markdown yes </pre> diff --git a/w3c.json b/w3c.json deleted file mode 100644 index e0ef6f4..0000000 --- a/w3c.json +++ /dev/null @@ -1,5 +0,0 @@ - { - "group": ["80485"] -, "contacts": ["marcoscaceres","reillyeon"], - "repo-type": "cg-report" -} From 3be0c42f21fabf874f8035775bed4c24f7031744 Mon Sep 17 00:00:00 2001 From: Reilly Grant <reillyg@google.com> Date: Tue, 6 Oct 2026 16:24:14 -0700 Subject: [PATCH 9/9] Add acknowledgements section to replace Editor metadata --- index.bs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 8f68236..a9f1ce2 100644 --- a/index.bs +++ b/index.bs @@ -2,7 +2,6 @@ Group: WHATWG H1: Serial Shortname: serial -Editor: Reilly Grant, Google https://google.com, reillyg@google.com, https://github.com/reillyeon Abstract: The <cite>Serial API</cite> provides a way for websites to read and write from a serial device through script. Such an API would bridge the web and @@ -1423,3 +1422,34 @@ 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. + +<h2 id=acknowledgments class=no-num>Acknowledgments</h2> + +<p>Thanks to +Anatol Ulrich<!-- spookyvision; GitHub -->, +Chris Mumford<!-- cmumford; GitHub -->, +Clément Menard<!-- Clemenard; GitHub -->, +Domenic Denicola<!-- domenic; GitHub -->, +Dominique Hazael-Massieux<!-- dontcallmedom; GitHub -->, +Florian Loitsch<!-- floitsch; GitHub -->, +Florian Scholz<!-- Elchi3; GitHub -->, +Francis Gulotta<!-- reconbot; GitHub -->, +François Beaufort<!-- beaufortfrancois; GitHub -->, +Jack Hsieh<!-- chengweih001; GitHub -->, +Keavon Chambers<!-- Keavon; GitHub -->, +Kenneth Rohde Christiansen<!-- kenchris; GitHub -->, +Marcos Cáceres<!-- marcoscaceres; GitHub -->, +Matt Reynolds<!-- nondebug; GitHub -->, +Jesse Melhuish<!-- melhuishj; GitHub -->, +Michael Kohler<!-- MichaelKohler; GitHub -->, +Ms2ger<!-- Ms2ger; GitHub -->, +Rick Waldron<!-- rwaldron; GitHub -->, +Sankha Narayan Guria<!-- ngsankha; GitHub -->, +Simon Pieters<!-- zcorpan; GitHub -->, +Suz Hinton<!-- noopkat; GitHub -->, +Travis Leithead<!-- travisleithead; GitHub -->, and +Vincent Scheib<!-- scheib; GitHub --> +for being awesome. + +<p>This standard is written by <a href=https://reilly.io lang=en>Reilly Grant</a> +(<a href=https://www.google.com/>Google</a>, <a href=mailto:reillyg@google.com>reillyg@google.com</a>).