From 579e4130d5438fcf5a83048529ff25e2eea7c3b2 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Thu, 11 Nov 2021 19:22:16 +0900 Subject: [PATCH 01/33] [WIP] The WebSocketStream Interface --- index.bs | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/index.bs b/index.bs index 6b32a1c..77aa235 100644 --- a/index.bs +++ b/index.bs @@ -795,6 +795,128 @@ the following list: + + +# The {{WebSocketStream}} interface # {#the-websocketstream-interface} + +The Web IDL definition for the {{WebSocketStream}} class is given as follows: + + +dictionary WebSocketOpenInfo { + ReadableStream readable; + WritableStream writable; + DOMString extensions; + DOMString protocol; +}; + +dictionary WebSocketCloseInfo { + [Clamp] unsigned short code; + USVString reason = ""; +}; + +dictionary WebSocketStreamOptions { + sequence<USVString> protocols; + AbortSignal signal; +}; + +[ + Exposed=(Window,Worker) +] interface WebSocketStream { + constructor(USVString url, optional WebSocketStreamOptions options); + readonly attribute USVString url; + readonly attribute Promise<WebSocketOpenInfo> opened; + readonly attribute Promise<WebSocketCloseInfo> closed; + undefined close(optional WebSocketCloseInfo closeInfo); +}; + + +A {{WebSocketStream}} object has an associated url (a [=URL record=]). + +A {{WebSocketStream}} object has an associated opened promise. + +A {{WebSocketStream}} object has an associated closed promise. + +A {{WebSocketStream}} object has an associated was ever connected flag, which is +initially unset. + +
+ : |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options| ] + :: Creates a new {{WebSocketStream}} object, immediately establishing the associating WebSocket + connection. + + |url| is a string giving the URL over which the connection is established. + Only "`ws`" or "`wss`" schemes are allowed; others will cause a "{{SyntaxError}}" + {{DOMException}}. URLs with [=fragments=] will also cause such an exception. + + The |options| argument is an object whose properties can be set as follows: + + : {{WebSocketStreamOptions/protocols}} + :: An array of strings. If it is omitted, it is equivalent to an empty array. Each string in the + array is a subprotocol name. The connection will only be established if the server reports that + it has selected one of these subprotocols. The subprotocol names have to match the requirements + for elements that comprise the value of \``Sec-WebSocket-Protocol`\` fields as + defined by The WebSocket protocol. [[!WSP]] + + : {{WebSocketStreamOptions/signal}} + :: An {{AbortSignal}} that can be used to abort the handshake. After the handshake is complete the + signal does nothing. + + : |socket| . {{WebSocketStream/url}} + :: Returns the URL that was used to establish the WebSocket connection. + + : |socket| . {{WebSocketStream/opened}} + :: Returns a {{promise}} which resolves when the handshake successfully completes, or rejects if + the handshake fails. On success, it resolves to an object with the following properties: + + : {{WebSocketOpenInfo/readable}} + :: A {{ReadableStream}} that can be used to read messages from the server. Each chunk read + corresponds to one message. Text messages will be read as strings; binary messages will be read + as ArrayBuffer objects. + + The stream can be closed by calling {{ReadableStream/cancel()}} on + {{WebSocketOpenInfo/readable}}. If the argument passed to {{ReadableStream/cancel()}} is an + object with a {{WebSocketCloseInfo/code}} property and an optional {{WebSocketCloseInfo/reason}} + property then {{WebSocketCloseInfo/code}} will be used as [=the WebSocket connection close + code=] and {{WebSocketCloseInfo/reason}} or the empty string will be used as [=the WebSocket + connection close reason=]. + + If no messages are read, or if messages are read slower than they are sent, then backpressure + will be applied and eventually the server will stop sending new messages. + + : {{WebSocketOpenInfo/writable}} + :: A {{WritableStream}} that can be used to send messages to the server. Each chunk written will + be converted to one messages. Strings will be sent as text messages; {{BufferSource}} chunks will + be sent as binary messages. + + The WebSocket can be closed by calling {{WritableStream/close()}} on + {{WebSocketOpenInfo/writable}}. + + The stream can also be closed by calling {{WritableStream/abort()}} + {{WebSocketOpenInfo/writable}}. If an argument is passed to {{WritableStream/abort()}} then it + can be used to specify [=the WebSocket connection close code=] and [=the WebSocket connection + close reason=] as with {{ReadableStream/cancel()}} above. + + : {{WebSocketOpenInfo/extensions}} + :: The [=extensions in use=] for the connection. + + : {{WebSocketOpenInfo/protocol}} + :: The [=subprotocol in use=] for the connection. + + : |socket| . {{WebSocketStream/closed}} + :: A {{promise}} which resolves when the connection is closed. If the connection did not close + [=cleanly=] then the promise is rejected. When the connection closes [=cleanly=] the promise is + resolved with an object with properties {{WebSocketCloseInfo/code}} and + {{WebSocketCloseInfo/reason}}, giving [=the WebSocket connection close code=] and [=the WebSocket + connection close reason=] the were supplied by the server. + + : |socket| . {{WebSocketStream/close()}} + :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/code}} and + {{WebSocketCloseInfo/reason}} properties to indicate [=the WebSocket connection close code=] and + [=the WebSocket connection close reason=] that will be sent to the remote server. If the handshake + is still in progress then it will be aborted and {{WebSocketCloseInfo/code}} and + {{WebSocketCloseInfo/reason}} will be ignored. +
+

Acknowledgments

Until the creation of this standard in 2021, the text here was maintained in the Date: Wed, 24 Jan 2024 03:55:54 +0900 Subject: [PATCH 02/33] Fix grammatical errors --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 77aa235..979e19c 100644 --- a/index.bs +++ b/index.bs @@ -885,7 +885,7 @@ initially unset. : {{WebSocketOpenInfo/writable}} :: A {{WritableStream}} that can be used to send messages to the server. Each chunk written will - be converted to one messages. Strings will be sent as text messages; {{BufferSource}} chunks will + be converted to one message. Strings will be sent as text messages; {{BufferSource}} chunks will be sent as binary messages. The WebSocket can be closed by calling {{WritableStream/close()}} on @@ -907,7 +907,7 @@ initially unset. [=cleanly=] then the promise is rejected. When the connection closes [=cleanly=] the promise is resolved with an object with properties {{WebSocketCloseInfo/code}} and {{WebSocketCloseInfo/reason}}, giving [=the WebSocket connection close code=] and [=the WebSocket - connection close reason=] the were supplied by the server. + connection close reason=] that were supplied by the server. : |socket| . {{WebSocketStream/close()}} :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/code}} and From 8e5f06b9d13d255e1da324b212c2f77208985bb8 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 24 Jan 2024 04:10:17 +0900 Subject: [PATCH 03/33] Note that backpressure is obeyed for writable Temporarily force-link "writable stream writer" until it can be exported from the streams standard. --- index.bs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 979e19c..3046957 100644 --- a/index.bs +++ b/index.bs @@ -34,6 +34,8 @@ spec:html; type:dfn; spec:url; type:dfn; text:origin for:/; text:url +spec:streams; type:dfn + text:writable stream writer
@@ -886,7 +888,10 @@ initially unset.
   : {{WebSocketOpenInfo/writable}}
   :: A {{WritableStream}} that can be used to send messages to the server. Each chunk written will
    be converted to one message. Strings will be sent as text messages; {{BufferSource}} chunks will
-   be sent as binary messages.
+   be sent as binary messages. Backpressure due to the network or server being behind will
+   automatically be observed by piping. When using a [=writable stream writer=], waiting for the
+   {{WritableStreamDefaultWriter/ready|writer.ready}} promise will ensure that backpressure is
+   obeyed.
 
    The WebSocket can be closed by calling {{WritableStream/close()}} on
    {{WebSocketOpenInfo/writable}}.

From 5cb355b08edc34e8b526ae59d9f5c61f7e9f147c Mon Sep 17 00:00:00 2001
From: Adam Rice 
Date: Wed, 24 Jan 2024 04:15:57 +0900
Subject: [PATCH 04/33] Remove mention of passing close code or reason to
 abort() or cancel()

This feature is not worth the complexity so it is being removed.
---
 index.bs | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/index.bs b/index.bs
index 3046957..a43c3a5 100644
--- a/index.bs
+++ b/index.bs
@@ -876,11 +876,7 @@ initially unset.
    as ArrayBuffer objects.
 
    The stream can be closed by calling {{ReadableStream/cancel()}} on
-   {{WebSocketOpenInfo/readable}}. If the argument passed to {{ReadableStream/cancel()}} is an
-   object with a {{WebSocketCloseInfo/code}} property and an optional {{WebSocketCloseInfo/reason}}
-   property then {{WebSocketCloseInfo/code}} will be used as [=the WebSocket connection close
-   code=] and {{WebSocketCloseInfo/reason}} or the empty string will be used as [=the WebSocket
-   connection close reason=].
+   {{WebSocketOpenInfo/readable}}.
 
    If no messages are read, or if messages are read slower than they are sent, then backpressure
    will be applied and eventually the server will stop sending new messages.
@@ -893,13 +889,8 @@ initially unset.
    {{WritableStreamDefaultWriter/ready|writer.ready}} promise will ensure that backpressure is
    obeyed.
 
-   The WebSocket can be closed by calling {{WritableStream/close()}} on
-   {{WebSocketOpenInfo/writable}}.
-
-   The stream can also be closed by calling {{WritableStream/abort()}}
-   {{WebSocketOpenInfo/writable}}. If an argument is passed to {{WritableStream/abort()}} then it
-   can be used to specify [=the WebSocket connection close code=] and [=the WebSocket connection
-   close reason=] as with {{ReadableStream/cancel()}} above.
+   The WebSocket can be closed by calling {{WritableStream/close()}} or {{WritableStream/abort()}}
+   on {{WebSocketOpenInfo/writable}}.
 
   : {{WebSocketOpenInfo/extensions}}
   :: The [=extensions in use=] for the connection.

From 1f9a2d659503b41d0b49f5d4d02c75a4995489c5 Mon Sep 17 00:00:00 2001
From: Adam Rice 
Date: Sun, 28 Jan 2024 11:34:37 +0900
Subject: [PATCH 05/33] Revert "Remove mention of passing close code or reason
 to abort() or cancel()"

Put back mention of passing close code or reason to abort() or cancel(),
in preparation for updating it to use WebSocketError.
---
 index.bs | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/index.bs b/index.bs
index a43c3a5..3046957 100644
--- a/index.bs
+++ b/index.bs
@@ -876,7 +876,11 @@ initially unset.
    as ArrayBuffer objects.
 
    The stream can be closed by calling {{ReadableStream/cancel()}} on
-   {{WebSocketOpenInfo/readable}}.
+   {{WebSocketOpenInfo/readable}}. If the argument passed to {{ReadableStream/cancel()}} is an
+   object with a {{WebSocketCloseInfo/code}} property and an optional {{WebSocketCloseInfo/reason}}
+   property then {{WebSocketCloseInfo/code}} will be used as [=the WebSocket connection close
+   code=] and {{WebSocketCloseInfo/reason}} or the empty string will be used as [=the WebSocket
+   connection close reason=].
 
    If no messages are read, or if messages are read slower than they are sent, then backpressure
    will be applied and eventually the server will stop sending new messages.
@@ -889,8 +893,13 @@ initially unset.
    {{WritableStreamDefaultWriter/ready|writer.ready}} promise will ensure that backpressure is
    obeyed.
 
-   The WebSocket can be closed by calling {{WritableStream/close()}} or {{WritableStream/abort()}}
-   on {{WebSocketOpenInfo/writable}}.
+   The WebSocket can be closed by calling {{WritableStream/close()}} on
+   {{WebSocketOpenInfo/writable}}.
+
+   The stream can also be closed by calling {{WritableStream/abort()}}
+   {{WebSocketOpenInfo/writable}}. If an argument is passed to {{WritableStream/abort()}} then it
+   can be used to specify [=the WebSocket connection close code=] and [=the WebSocket connection
+   close reason=] as with {{ReadableStream/cancel()}} above.
 
   : {{WebSocketOpenInfo/extensions}}
   :: The [=extensions in use=] for the connection.

From 96baeed27da066fe2c6d50d6af2f36f8f8443a71 Mon Sep 17 00:00:00 2001
From: Adam Rice 
Date: Sun, 28 Jan 2024 11:38:31 +0900
Subject: [PATCH 06/33] Mention that it has to be a WebSocketError

---
 index.bs | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/index.bs b/index.bs
index 3046957..ff1e9c0 100644
--- a/index.bs
+++ b/index.bs
@@ -876,11 +876,10 @@ initially unset.
    as ArrayBuffer objects.
 
    The stream can be closed by calling {{ReadableStream/cancel()}} on
-   {{WebSocketOpenInfo/readable}}. If the argument passed to {{ReadableStream/cancel()}} is an
-   object with a {{WebSocketCloseInfo/code}} property and an optional {{WebSocketCloseInfo/reason}}
-   property then {{WebSocketCloseInfo/code}} will be used as [=the WebSocket connection close
-   code=] and {{WebSocketCloseInfo/reason}} or the empty string will be used as [=the WebSocket
-   connection close reason=].
+   {{WebSocketOpenInfo/readable}}. If the reason argument passed to {{ReadableStream/cancel()}} is a
+   {{WebSocketError}} object then {{WebSocketError/closeCode}} will be used as [=the WebSocket
+   connection close code=] and {{WebSocketError/reason}} will be used as [=the WebSocket connection
+   close reason=].
 
    If no messages are read, or if messages are read slower than they are sent, then backpressure
    will be applied and eventually the server will stop sending new messages.
@@ -897,9 +896,9 @@ initially unset.
    {{WebSocketOpenInfo/writable}}.
 
    The stream can also be closed by calling {{WritableStream/abort()}}
-   {{WebSocketOpenInfo/writable}}. If an argument is passed to {{WritableStream/abort()}} then it
-   can be used to specify [=the WebSocket connection close code=] and [=the WebSocket connection
-   close reason=] as with {{ReadableStream/cancel()}} above.
+   {{WebSocketOpenInfo/writable}}. If the reason passed to {{WritableStream/abort()}} is a
+   {{WebSocketError]}}, then it will be used to set [=the WebSocket connection close code=] and
+   [=the WebSocket connection close reason=] as with {{ReadableStream/cancel()}} above.
 
   : {{WebSocketOpenInfo/extensions}}
   :: The [=extensions in use=] for the connection.

From 69bae4f98ee7e51ebee2f8c9f01243fcc4ad5fc5 Mon Sep 17 00:00:00 2001
From: Adam Rice 
Date: Sun, 28 Jan 2024 12:29:42 +0900
Subject: [PATCH 07/33] Add IDL for WebSocketError

---
 index.bs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 55 insertions(+), 8 deletions(-)

diff --git a/index.bs b/index.bs
index ff1e9c0..a8b234f 100644
--- a/index.bs
+++ b/index.bs
@@ -812,7 +812,7 @@ dictionary WebSocketOpenInfo {
 };
 
 dictionary WebSocketCloseInfo {
-  [Clamp] unsigned short code;
+  [Clamp] unsigned short closeCode;
   USVString reason = "";
 };
 
@@ -897,7 +897,7 @@ initially unset.
 
    The stream can also be closed by calling {{WritableStream/abort()}}
    {{WebSocketOpenInfo/writable}}. If the reason passed to {{WritableStream/abort()}} is a
-   {{WebSocketError]}}, then it will be used to set [=the WebSocket connection close code=] and
+   {{WebSocketError}}, then it will be used to set [=the WebSocket connection close code=] and
    [=the WebSocket connection close reason=] as with {{ReadableStream/cancel()}} above.
 
   : {{WebSocketOpenInfo/extensions}}
@@ -908,19 +908,66 @@ initially unset.
 
  : |socket| . {{WebSocketStream/closed}}
  :: A {{promise}} which resolves when the connection is closed. If the connection did not close
-  [=cleanly=] then the promise is rejected. When the connection closes [=cleanly=] the promise is
-  resolved with an object with properties {{WebSocketCloseInfo/code}} and
-  {{WebSocketCloseInfo/reason}}, giving [=the WebSocket connection close code=] and [=the WebSocket
-  connection close reason=] that were supplied by the server.
+  [=cleanly=] then the promise is rejected with a {{WebSocketError}}. When the connection closes
+  [=cleanly=] the promise is resolved with an object with properties {{WebSocketCloseInfo/closeCode}}
+  and {{WebSocketCloseInfo/reason}}, giving [=the WebSocket connection close code=] and [=the
+  WebSocket connection close reason=] that were supplied by the server.
 
  : |socket| . {{WebSocketStream/close()}}
- :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/code}} and
+ :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/closeCode}} and
   {{WebSocketCloseInfo/reason}} properties to indicate [=the WebSocket connection close code=] and
   [=the WebSocket connection close reason=] that will be sent to the remote server. If the handshake
-  is still in progress then it will be aborted and {{WebSocketCloseInfo/code}} and
+  is still in progress then it will be aborted and {{WebSocketCloseInfo/closeCode}} and
   {{WebSocketCloseInfo/reason}} will be ignored.
 
 
+
+
+# The {{WebSocketError}} interface # {#the-websocketevent-interface}
+
+WebSocketError is a subclass of {{DOMException}}
+that represents the information associated with closing a WebSocket.
+
+
+[
+    Exposed=(Window,Worker),
+] interface WebSocketError : DOMException {
+  constructor(optional DOMString message = "",
+              optional WebSocketCloseInfo init = {});
+
+  readonly attribute unsigned short? closeCode;
+  readonly attribute USVString reason;
+};
+
+
+A {{WebSocketError}} object has an associated closeCode.
+
+A {{WebSocketError}} object has an associated reason.
+
+
+ : |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message| [, + |init| ] ] + :: Creates a new {{WebSocketError}} object. + + |message| is a string which will be used to initialize the {{DOMException/message}} attribute of + the base class. + + The |init| argument is an object whose properties can be set as follows: + + : {{WebSocketCloseInfo/closeCode}} + :: A number, either 1000 or any integer in the range 3000 to 4999, inclusive. Any other number + will result in an "{{InvalidAccessError}}" {{DOMException}}. + : {{WebSocketCloseInfo/reason}} + :: A string. Must be 123 bytes or less when converted to [=UTF-8=]. A longer string will result in + a "{{SyntaxError}}" {{DOMException}} being thrown. + + : |error| . {{WebSocketError/closeCode}} + :: Returns the [=the WebSocket connection close code=]. + + : |error| . {{WebSocketError/reason}} + :: Returns the [=the WebSocket connection close reason=]. +
+

Acknowledgments

Until the creation of this standard in 2021, the text here was maintained in the
Date: Mon, 29 Jan 2024 00:15:35 +0900 Subject: [PATCH 08/33] Add algorithm steps for WebSocketStream and WebSocketError Some algorithms that are common to WebSocket and WebSocketStream have been moved to a new section to be shared. --- index.bs | 435 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 365 insertions(+), 70 deletions(-) diff --git a/index.bs b/index.bs index a8b234f..fac3e42 100644 --- a/index.bs +++ b/index.bs @@ -283,7 +283,7 @@ It can have the following values: close code=] and |reason| as [=the WebSocket connection close reason=]. : |socket|.url - :: Returns the URL that was used to establish the WebSocket connection. + :: Returns the URL that was used to establish the WebSocket connection. : |socket|.readyState :: Returns the state of the WebSocket connection. It can have the values described above. @@ -323,14 +323,8 @@ It can have the following values: constructor steps are: 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. - 1. Let |urlRecord| be the result of applying the [=URL parser=] to |url| with |baseURL|. - 1. If |urlRecord| is failure, then throw a "{{SyntaxError}}" {{DOMException}}. - 1. If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s [=url/scheme=] to "`ws`". - 1. Otherwise, if |urlRecord|'s [=url/scheme=] is "`https`", set |urlRecord|'s [=url/scheme=] to - "`wss`". - 1. If |urlRecord|'s [=scheme=] is not "[=ws=]" or "[=wss=]", then throw a - "{{SyntaxError}}" {{DOMException}}. - 1. If |urlRecord|'s [=fragment=] is non-null, then throw a "{{SyntaxError}}" {{DOMException}}. + 1. Let |urlRecord| be the result of [=get a url record|getting a url record=] given |url| and + |baseURL|. 1. If |protocols| is a string, set |protocols| to a sequence consisting of just that string. 1. If any of the values in |protocols| occur more than once or otherwise fail to match the requirements for elements that comprise the value of @@ -366,58 +360,8 @@ string. After [=the WebSocket connection is established=], its value might chang
The close(|code|, |reason|) method steps are: - 1. If |code| is present, but is neither an integer equal to 1000 nor an integer in the range 3000 - to 4999, inclusive, throw an "{{InvalidAccessError}}" {{DOMException}}. - 1. If |reason| is present, then run these substeps: - 1. Let |reasonBytes| be the result of encoding |reason|. - 1. If |reasonBytes| is longer than 123 bytes, then throw a "{{SyntaxError}}" {{DOMException}}. - 1. Run the first matching steps from the following list: -
- : If [=this=]'s [=WebSocket/ready state=] is {{WebSocket/CLOSING}} (2) or {{WebSocket/CLOSED}} (3) - :: Do nothing. - -

The connection is already closing or is already closed. If it has not already, a - {{WebSocket/close}} event will eventually fire as described below. - - : If the WebSocket connection is not yet [=established=] [[!WSP]] - :: [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocket/ready state=] to - {{WebSocket/CLOSING}} (2). [[!WSP]] + 1. [=Close the WebSocket=] with this, |code|, and |reason|. -

The [=fail the WebSocket connection=] algorithm invokes the [=close the - WebSocket connection=] algorithm, which then establishes that [=the WebSocket connection is - closed=], which fires the {{WebSocket/close}} event as described - below. - - : If the WebSocket closing handshake has not yet been started [[!WSP]] - :: [=Start the WebSocket closing handshake=] and set [=this=]'s [=WebSocket/ready state=] to - {{WebSocket/CLOSING}} (2). [[!WSP]] - - If neither |code| nor |reason| is present, the WebSocket Close message must not have a body. - -

The WebSocket Protocol erroneously states that the status code is required for the [=start the WebSocket closing handshake=] algorithm. - - - If |code| is present, then the status code to use in the WebSocket Close - message must be the integer given by |code|. [[!WSP]] - - If |reason| is also present, then |reasonBytes| must be provided in the Close message after the - status code. [[!WSP]] - -

The [=start the WebSocket closing handshake=] algorithm eventually invokes the - [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket - connection is closed=], which fires the {{WebSocket/close}} event as - described below. - - : Otherwise - :: Set [=this=]'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). - -

[=The WebSocket closing handshake is started=], and will eventually invoke the - [=close the WebSocket connection=] algorithm, which will establish that [=the WebSocket - connection is closed=], and thus the {{WebSocket/close}} event will fire, as described below. -

The {{WebSocket/close()}} method does not discard previously sent messages before @@ -597,8 +541,8 @@ When [=a WebSocket message has been received=] with type |type| and data |data|, 1. [=Fire an event=] named message at the {{WebSocket}} object, using {{MessageEvent}}, with the {{MessageEvent/origin}} attribute initialized to the serialization of the {{WebSocket}} object's [=url=]'s [=origin=], and the - {{MessageEvent/data}} attribute initialized to |dataForEvent|. + serializer">serialization of the {{WebSocket}} object's [=internal-url|url=]'s [=origin=], + and the {{MessageEvent/data}} attribute initialized to |dataForEvent|.

User agents are encouraged to check if they can perform the above steps efficiently before they run the task, picking tasks from other [=task queues=] while they prepare the buffers @@ -832,14 +776,27 @@ dictionary WebSocketStreamOptions { }; -A {{WebSocketStream}} object has an associated url (a [=URL record=]). +A {{WebSocketStream}} object has an associated url (a [=URL +record=]). + +A {{WebSocketStream}} object has an associated opened promise. -A {{WebSocketStream}} object has an associated opened promise. +A {{WebSocketStream}} object has an associated closed promise. -A {{WebSocketStream}} object has an associated closed promise. +A {{WebSocketStream}} object has an associated readable stream, +which is initially unset. + +A {{WebSocketStream}} object has an associated writable stream, +which is initially unset. + +A {{WebSocketStream}} object has an associated was ever connected +flag, which is initially unset. + +A {{WebSocketStream}} object has an associated ready state, which +is a number representing the state of the connection. Initially it must be {{WebSocket/CONNECTING}} +(0). It has the same semantics as {{WebSocket}}'s [=WebSocket/ready state=], but is not exposed to +JavaScript. -A {{WebSocketStream}} object has an associated was ever connected flag, which is -initially unset.

: |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options| ] @@ -864,7 +821,7 @@ initially unset. signal does nothing. : |socket| . {{WebSocketStream/url}} - :: Returns the URL that was used to establish the WebSocket connection. + :: Returns the [=WebSocketStream/url|URL=] that was used to establish the WebSocket connection. : |socket| . {{WebSocketStream/opened}} :: Returns a {{promise}} which resolves when the handshake successfully completes, or rejects if @@ -921,6 +878,235 @@ initially unset. {{WebSocketCloseInfo/reason}} will be ignored.
+
+ + The new + WebSocketStream(|url|, |options|) constructor steps are: + + 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. + 1. Let |urlRecord| be the result of [=get a url record|getting a url record=] given |url| and + |baseURL|. + 1. Let |protocols| be |options|["{{WebSocketStreamOptions/protocols}}"] if it exists, otherwise an + empty sequence. + 1. If any of the values in |protocols| occur more than once or otherwise fail to match the + requirements for elements that comprise the value of + \``Sec-WebSocket-Protocol`\` fields as defined by The WebSocket protocol, + then throw a "{{SyntaxError}}" {{DOMException}}. [[!WSP]] + 1. Set [=this=]'s [=WebSocketStream/url=] to |urlRecord|. + 1. Set [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] to new + promises. + 1. Let |client| be [=this=]'s [=relevant settings object=]. + 1. Run this step [=in parallel=]: + 1. [=Establish a WebSocket connection=] given |urlRecord|, |protocols|, and |client|. [[!FETCH]] + +

If the [=establish a WebSocket connection=] algorithm fails, it triggers the + [=fail the WebSocket connection=] algorithm, which then invokes the [=close the WebSocket + connection=] algorithm, which then establishes that [=the WebSocket connection is closed=], + which rejects the {{WebSocketStream/opened}} and {{WebSocketStream/closed}} promises. +

+ +
+ +The url getter steps are to return [=this=]'s +[=WebSocketStream/url=], [=URL serializer|serialized=]. + +The opened getter steps are to return [=this=]'s +[=WebSocketStream/opened promise=]. + +The closed getter steps are to return [=this=]'s +[=WebSocketStream/closed promise=]. + +
+ The close(|closeInfo|) method steps are: + + 1. Let |code| be |closeInfo|["{{WebSocketCloseInfo/closeCode}}"] if present, or unset otherwise. + 1. Let |reason| be |closeInfo|["{{WebSocketCloseInfo/reason}}"] if present, or unset otherwise. + 1. [=Close the WebSocket=] with this, |code|, and |reason|. +
+ + +# Feedback to WebSocketStream from the protocol # {#feedback-to-websocket-stream-from-the-protocol} + +When [=the WebSocket connection is established=], the user agent must [=queue a task=] to run these +steps: + +
+ + 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/OPEN}} (1). + 1. Set |this|'s [=WebSocketStream/was ever connected flag=]. + 1. Let |extensions| be the [=extensions in use=]. + 1. Let |protocol| be the [=subprotocol in use=]. + 1. Let |pullAlgorithm| be an action that [=pulls bytes=] from the WebSocket. + 1. Let |cancelAlgorithm| be an action that [=cancels=] the WebSocketStream with |this| and |reason|, given + |reason|. + 1. Let |readable| be a [=new=] {{ReadableStream}}. + 1. [=ReadableStream/Set up=] |readable| with |pullAlgorithm| and |cancelAlgorithm|. + 1. Let |writeAlgorithm| be an action that [=writes=] |chunk| to |this|, given |chunk|. + 1. Let |closeAlgorithm| be an action that [=closes=] |this|. + 1. Let |abortAlgorithm| be an action that [=aborts=] |this| with |reason|, given |reason|. + 1. Let |writable| be a [=new=] {{WritableStream}}. + 1. [=WritableStream/Set up=] |writable| with |writeAlgorithm|, |closeAlgorithm|, and + |abortAlgorithm|. + 1. Set |this|'s [=WebSocketStream/readable stream=] to |readable|. + 1. Set |this|'s [=WebSocketStream/writable stream=] to |writable|. + 1. Let |openInfo| be a [=new=] {{WebSocketOpenInfo}}. + 1. Set |openInfo|["{{WebSocketOpenInfo/extensions}}"] to |extensions|. + 1. Set |openInfo|["{{WebSocketOpenInfo/protocol}}"] to |protocol|. + 1. Set |openInfo|["{{WebSocketOpenInfo/readable}}"] to |readable|. + 1. Set |openInfo|["{{WebSocketOpenInfo/writable}}"] to |writable|. + 1. [=Resolve=] |this|'s [=WebSocketStream/opened promise=] with |openInfo|. + +
+ +
+ +
+ +When [=a WebSocket message has been received=] for |this| with type |type| and data |data|, the user +agent must [=queue a task=] to follow these steps: [[!WSP]] + + 1. If [=WebSocket/ready state=] is not {{WebSocket/OPEN}} (1), then return. + 1. Let |chunk| be determined by switching on |type|: + +
+ : |type| indicates that the data is Text + :: a new {{DOMString}} containing |data| + + : |type| indicates that the data is Binary + :: a new {{Uint8Array}} object, created in the [=relevant Realm=] of the {{WebSocketStream}} + object, whose contents are |data| +
+ + 1. [=ReadableStream/Enqueue=] |chunk| into |this|'s [=WebSocketStream/readable stream=]. + 1. Apply backpressure to the WebSocket. + + Note: Applying backpressure should result in no new WebSocket + messages being handled until the backpressure is released. After an implementation-defined amount + of data is buffered, the client will stop reading from the TCP/IP socket resulting in network-layer + backpressure being applied. +
+ +
+ +When [=the WebSocket closing handshake is started=], the user agent must [=queue a task=] to change +the [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}} if it is not already. [[!WSP]] + +
+ +
+ +When [=the WebSocket connection is closed=] for |this|, possibly [=cleanly=], the user agent must + [=queue a task=] to run the following substeps: + + 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). + 1. If |this|'s [=WebSocketStream/was ever connected flag=] is not set, then [=reject=] |this|'s + [=opened promise=] with a [=new=] WebSocketError. + 1. Let |code| be [=the WebSocket connection close code=]. + 1. Let |reason| be the result of applying [=UTF-8 decode without BOM=] to [=the WebSocket + connection close reason=]. + 1. If the connection was closed [=cleanly=], + 1. [=ReadableStream/Close=] |this|'s [=WebSocketStream/readable stream=]. + 1. [=WritableStream/Error=] |this|'s [=WebSocketStream/writable stream=] with an + "{{InvalidStateError}}" {{DOMException}} indicating that a closed {{WebSocketStream}} cannot be + written to. + 1. [=Resolve=] |this|'s [=closed promise=] with a [=new=] {{WebSocketCloseInfo}} constructed with + |code| and |reason|. + 1. Otherwise, + 1. [=ReadableStream/Error=] |this|'s [=WebSocketStream/readable stream=] with a [=new=] + {{WebSocketError}} constructed with |code| and |reason|. + 1. [=WritableStream/Error=] |this|'s [=WebSocketStream/writable stream=] with a [=new=] + {{WebSocketError}} constructed with |code| and |reason|. + 1. [=Reject=] |this|'s [=closed promise=] with a [=new=] {{WebSocketError}} constructed with + |code| and |reason|. +
+ +
+ +The [=task source=] for all [=tasks=] queued in this section is the +[=WebSocket task source=] + +
+ + +# Stream operations # {#stream-operations} + +
+ +To pull bytes from a WebSocket, if the WebSocket is currently applying backpressure, +release backpressure. + + Note: If any messages are queued, one will be handled immediately as a result. +
+ +
+ +
+ +To cancel a WebSocketStream with |this| and |reason|, [=close using reason=] giving +|this| and |reason|. +
+ +
+ +
+ +To write |chunk| to this, the user agent must run these steps: + 1. Let |promise| be a new promise. + 1. If |chunk| is a {{BufferSource}}, + 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=]. + 1. Let |opcode| be a binary frame opcode. + 1. Otherwise, + 1. Let |string| be the result of [=Converted to an IDL value|converting=] |chunk| to an IDL + {{USVString}}. If this throws an exception, return [=a promise rejected with=] the exception. + 1. Let |data| be the result of [=UTF-8 encode|encoding=] |string|. + 1. Let |opcode| be a text frame opcode. + 1. In parallel, + 1. Wait until there is sufficient buffer space to send the message. + + Note: This means that backpressure will be applied when the user agent's buffers are full. + + 1. If [=the WebSocket closing handshake is started|the closing handshake has not yet started=], + [=Send a WebSocket Message=] comprised of |data| using |opcode|. + 1. [=Queue a task=] on the [=WebSocket task source=] to resolve |promise| with undefined. + 1. Return |promise|. +
+ +
+ +
+ +To close a WebSocketStream given |this|, [=close the WebSocket=] with |this| (code and reason are unset). +
+ +
+ +
+ +To abort a WebSocketStream with |this| and |reason|, [=close using reason=] giving |this| +and |reason|. +
+ +
+ +
+ +To close using reason a WebSocketStream given |this| and |reason|, + + 1. Let |code| and |reasonString| be unset. + 1. If |reason| is a {{WebSocketError}}, + 1. Set |code| to |reason|'s {{WebSocketError/closeCode}}. + 1. Set |reasonString| to |reason|'s {{WebSocketError/reason}}. + 1. [=Close the WebSocket=] with |this|, |code|, and |reasonString|. If this throws an exception, + discard |code| and |reasonString| and [=Close the WebSocket=] with |this|. + + Note: A {{WebSocketError}} object constructed from JavaScript will always have a + {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set, + however the {{WebSocketStream/closed}} promise might be rejected with {{WebSocketError/closeCode}} + values coming from the server that JavaScript is not permitted to set itself, such as 1001 "Going + Away". +
+ # The {{WebSocketError}} interface # {#the-websocketevent-interface} @@ -940,9 +1126,9 @@ that represents the information associated with closing a WebSocket. }; -A {{WebSocketError}} object has an associated closeCode. +A {{WebSocketError}} object has an associated closeCode. -A {{WebSocketError}} object has an associated reason. +A {{WebSocketError}} object has an associated reason.
: |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message| [, @@ -968,6 +1154,115 @@ A {{WebSocketError}} object has an associated reason. :: Returns the [=the WebSocket connection close reason=].
+
+ +
+ +The new +WebSocketError(|message|, |init|) constructor steps are: + + 1. Set |this|'s [=DOMException/name=] to "WebSocketError". + 1. Set |this|'s [=DOMException/message=] to |message|. + 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it exists, or unset otherwise. + 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it exists, or unset otherwise. + 1. [=Validate close code and reason=] with |code| and |reason|. + 1. If |reason| is non-empty, but |code| is not set, then set |code| to 1000 ("Normal Closure"). + 1. Set |this|'s [=WebSocketError/closeCode=] to |code|. + 1. Set |this|'s [=WebSocketError/reason=] to |reason|. +
+ +The closeCode getter steps are to return [=this=]'s +[=WebSocketError/closeCode=]. + +The reason getter steps are to return [=this=]'s +[=WebSocketError/reason=]. + +# Common algorithms # {#common-algorithms} + +These algorithms are shared between the {{WebSocket}} and {{WebSocketStream}} interfaces. + +
+ +To get a url record given a |url| and |baseURL|, + + 1. Let |urlRecord| be the result of applying the [=URL parser=] to |url| with |baseURL|. + 1. If |urlRecord| is failure, then throw a "{{SyntaxError}}" {{DOMException}}. + 1. If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s [=url/scheme=] to "`ws`". + 1. Otherwise, if |urlRecord|'s [=url/scheme=] is "`https`", set |urlRecord|'s [=url/scheme=] to + "`wss`". + 1. If |urlRecord|'s [=scheme=] is not "[=ws=]" or "[=wss=]", then throw a + "{{SyntaxError}}" {{DOMException}}. + 1. If |urlRecord|'s [=fragment=] is non-null, then throw a "{{SyntaxError}}" {{DOMException}}. + 1. Return |urlRecord|. +
+ +
+ +To validate close code and reason given a |code| and |reason|, + + 1. If |code| is set, but is neither an integer equal to 1000 nor an integer in the range 3000 + to 4999, inclusive, throw an "{{InvalidAccessError}}" {{DOMException}}. + 1. If |reason| is set, then run these substeps: + 1. Let |reasonBytes| be the result of encoding |reason|. + 1. If |reasonBytes| is longer than 123 bytes, then throw a "{{SyntaxError}}" {{DOMException}}. +
+ +
+ +To close the WebSocket given |this|, |code|, and |reason|, + + 1. [=Validate close code and reason=] with |code| and |reason|. + 1. Run the first matching steps from the following list: +
+ : If [=this=]'s [=WebSocket/ready state=] is {{WebSocket/CLOSING}} (2) or {{WebSocket/CLOSED}} (3) + :: Do nothing. + +

The connection is already closing or is already closed. If it has not already, a + {{WebSocket/close}} event will eventually fire if |this| is a {{WebSocket}}, or the + {{WebSocketStream/closed}} promise will become settled if |this| is a {{WebSocketStream}}. + + : If the WebSocket connection is not yet [=established=] [[!WSP]] + :: [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocket/ready state=] to + {{WebSocket/CLOSING}} (2). [[!WSP]] + +

The [=fail the WebSocket connection=] algorithm invokes the [=close the + WebSocket connection=] algorithm, which then establishes that [=the WebSocket connection is + closed=], which fires the {{WebSocket/close}} event if |this| is a {{WebSocket}}, or settles + the {{WebSocketStream/closed}} promise if |this| is a {{WebSocketStream}}. + + : If the WebSocket closing handshake has not yet been started [[!WSP]] + :: [=Start the WebSocket closing handshake=] and set [=this=]'s [=WebSocket/ready state=] to + {{WebSocket/CLOSING}} (2). [[!WSP]] + + If neither |code| nor |reason| is set, the WebSocket Close message must not have a body. + +

The WebSocket Protocol erroneously states that the status code is required for the [=start the WebSocket closing handshake=] algorithm. + + + if |reason| is non-empty but |code| is not set, then set |code| to 1000 ("Normal Closure"). + + If |code| is set, then the status code to use in the WebSocket Close + message must be the integer given by |code|. [[!WSP]] + + If |reason| is non-empty, then |reason|, encoded as UTF-8, must be + provided in the Close message after the status code. [[!WSP]] + +

The [=start the WebSocket closing handshake=] algorithm eventually invokes the + [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket + connection is closed=], which fires the {{WebSocket/close}} event if |this| is a {{WebSocket}}, + or settles the {{WebSocketStream/closed}} promise if |this| is a {{WebSocketStream}}. + + : Otherwise + :: Set [=this=]'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). + +

[=The WebSocket closing handshake is started=], and will eventually invoke the + [=close the WebSocket connection=] algorithm, which will establish that [=the WebSocket + connection is closed=], and thus the {{WebSocket/close}} event will fire or the + {{WebSocketStream/closed}} promise will resolve, depending on the type of |this|. +

+

Acknowledgments

Until the creation of this standard in 2021, the text here was maintained in the Date: Wed, 31 Jan 2024 04:09:23 +0900 Subject: [PATCH 09/33] Changes from reviews --- index.bs | 221 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 118 insertions(+), 103 deletions(-) diff --git a/index.bs b/index.bs index fac3e42..909757b 100644 --- a/index.bs +++ b/index.bs @@ -323,7 +323,7 @@ It can have the following values: constructor steps are: 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. - 1. Let |urlRecord| be the result of [=get a url record|getting a url record=] given |url| and + 1. Let |urlRecord| be the result of [=get a URL record|getting a URL record=] given |url| and |baseURL|. 1. If |protocols| is a string, set |protocols| to a sequence consisting of just that string. 1. If any of the values in |protocols| occur more than once or otherwise fail to match the @@ -360,7 +360,9 @@ string. After [=the WebSocket connection is established=], its value might chang
The close(|code|, |reason|) method steps are: - 1. [=Close the WebSocket=] with this, |code|, and |reason|. + 1. If |code| is the special value "missing", then set |code| to null. + 1. If |reason| is the special value "missing" then set |reason| to the empty string. + 1. [=Close the WebSocket=] with [=this=], |code|, and |reason|.
@@ -776,36 +778,38 @@ dictionary WebSocketStreamOptions { }; -A {{WebSocketStream}} object has an associated url (a [=URL -record=]). +Each {{WebSocketStream}} object has an associated url, which is a +[=URL record=]. -A {{WebSocketStream}} object has an associated opened promise. +Each {{WebSocketStream}} object has an associated opened promise, +which is a promise. -A {{WebSocketStream}} object has an associated closed promise. +Each {{WebSocketStream}} object has an associated closed promise, +which is a promise. -A {{WebSocketStream}} object has an associated readable stream, -which is initially unset. +Each {{WebSocketStream}} object has an associated readable stream, +which is a {{ReadableStream}}. -A {{WebSocketStream}} object has an associated writable stream, -which is initially unset. +Each {{WebSocketStream}} object has an associated writable stream, +which is a {{WritableStream}}. -A {{WebSocketStream}} object has an associated was ever connected +Each {{WebSocketStream}} object has an associated was ever connected flag, which is initially unset. -A {{WebSocketStream}} object has an associated ready state, which -is a number representing the state of the connection. Initially it must be {{WebSocket/CONNECTING}} -(0). It has the same semantics as {{WebSocket}}'s [=WebSocket/ready state=], but is not exposed to -JavaScript. +Each {{WebSocketStream}} object has an associated ready state, +which is a number representing the state of the connection. Initially it must be +{{WebSocket/CONNECTING}} (0). It has the same semantics as {{WebSocket}}'s [=WebSocket/ready +state=], but is not exposed to JavaScript.
- : |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options| ] + : |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options|]) :: Creates a new {{WebSocketStream}} object, immediately establishing the associating WebSocket connection. |url| is a string giving the URL over which the connection is established. - Only "`ws`" or "`wss`" schemes are allowed; others will cause a "{{SyntaxError}}" - {{DOMException}}. URLs with [=fragments=] will also cause such an exception. + Only "`ws`", "`wss`", "`http`", and "`https`" schemes are allowed; others will cause a + "{{SyntaxError}}" {{DOMException}}. URLs with [=fragments=] will also cause such an exception. The |options| argument is an object whose properties can be set as follows: @@ -817,20 +821,24 @@ JavaScript. defined by The WebSocket protocol. [[!WSP]] : {{WebSocketStreamOptions/signal}} - :: An {{AbortSignal}} that can be used to abort the handshake. After the handshake is complete the - signal does nothing. + :: An {{AbortSignal}} that can be used to abort the handshake. After the handshake is complete, + the signal does nothing. - : |socket| . {{WebSocketStream/url}} + : |socket|.{{WebSocketStream/url}} :: Returns the [=WebSocketStream/url|URL=] that was used to establish the WebSocket connection. - : |socket| . {{WebSocketStream/opened}} + : |socket|.{{WebSocketStream/opened}} :: Returns a {{promise}} which resolves when the handshake successfully completes, or rejects if the handshake fails. On success, it resolves to an object with the following properties: : {{WebSocketOpenInfo/readable}} :: A {{ReadableStream}} that can be used to read messages from the server. Each chunk read corresponds to one message. Text messages will be read as strings; binary messages will be read - as ArrayBuffer objects. + as {{Uint8Array}} objects. + + Note: The original {{WebSocket}} API supplied {{ArrayBuffer}} objects, but modern practice is to + prefer the {{Uint8Array}} type for binary data, particularly when using streams. The underlying + {{ArrayBuffer}} object can be recovered by calling chunk.buffer. The stream can be closed by calling {{ReadableStream/cancel()}} on {{WebSocketOpenInfo/readable}}. If the reason argument passed to {{ReadableStream/cancel()}} is a @@ -844,15 +852,15 @@ JavaScript. : {{WebSocketOpenInfo/writable}} :: A {{WritableStream}} that can be used to send messages to the server. Each chunk written will be converted to one message. Strings will be sent as text messages; {{BufferSource}} chunks will - be sent as binary messages. Backpressure due to the network or server being behind will - automatically be observed by piping. When using a [=writable stream writer=], waiting for the - {{WritableStreamDefaultWriter/ready|writer.ready}} promise will ensure that backpressure is - obeyed. + be sent as binary messages. Backpressure due to the network or server being unable to process + data fast enough will automatically be observed by piping. When using a [=writable stream + writer=], waiting for the {{WritableStreamDefaultWriter/ready|writer.ready}} promise will ensure + that backpressure is obeyed. The WebSocket can be closed by calling {{WritableStream/close()}} on {{WebSocketOpenInfo/writable}}. - The stream can also be closed by calling {{WritableStream/abort()}} + The stream can also be closed by calling {{WritableStream/abort()}} on {{WebSocketOpenInfo/writable}}. If the reason passed to {{WritableStream/abort()}} is a {{WebSocketError}}, then it will be used to set [=the WebSocket connection close code=] and [=the WebSocket connection close reason=] as with {{ReadableStream/cancel()}} above. @@ -863,28 +871,29 @@ JavaScript. : {{WebSocketOpenInfo/protocol}} :: The [=subprotocol in use=] for the connection. - : |socket| . {{WebSocketStream/closed}} + : |socket|.{{WebSocketStream/closed}} :: A {{promise}} which resolves when the connection is closed. If the connection did not close [=cleanly=] then the promise is rejected with a {{WebSocketError}}. When the connection closes - [=cleanly=] the promise is resolved with an object with properties {{WebSocketCloseInfo/closeCode}} - and {{WebSocketCloseInfo/reason}}, giving [=the WebSocket connection close code=] and [=the - WebSocket connection close reason=] that were supplied by the server. + [=cleanly=] the promise is fulfilled with an object with properties + {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}}, giving [=the WebSocket + connection close code=] and [=the WebSocket connection close reason=] that were supplied by the + server. - : |socket| . {{WebSocketStream/close()}} + : |socket|.{{WebSocketStream/close()}} :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}} properties to indicate [=the WebSocket connection close code=] and [=the WebSocket connection close reason=] that will be sent to the remote server. If the handshake - is still in progress then it will be aborted and {{WebSocketCloseInfo/closeCode}} and + is still in progress, then it will be aborted and {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}} will be ignored.
- The new + The new WebSocketStream(|url|, |options|) constructor steps are: 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. - 1. Let |urlRecord| be the result of [=get a url record|getting a url record=] given |url| and + 1. Let |urlRecord| be the result of [=get a URL record|getting a URL record=] given |url| and |baseURL|. 1. Let |protocols| be |options|["{{WebSocketStreamOptions/protocols}}"] if it exists, otherwise an empty sequence. @@ -901,8 +910,8 @@ JavaScript.

If the [=establish a WebSocket connection=] algorithm fails, it triggers the [=fail the WebSocket connection=] algorithm, which then invokes the [=close the WebSocket - connection=] algorithm, which then establishes that [=the WebSocket connection is closed=], - which rejects the {{WebSocketStream/opened}} and {{WebSocketStream/closed}} promises. + connection=] algorithm. This establishes that [=the WebSocket connection is closed=], which + rejects the {{WebSocketStream/opened}} and {{WebSocketStream/closed}} promises.


@@ -919,42 +928,41 @@ The closed getter steps are to return [
The close(|closeInfo|) method steps are: - 1. Let |code| be |closeInfo|["{{WebSocketCloseInfo/closeCode}}"] if present, or unset otherwise. - 1. Let |reason| be |closeInfo|["{{WebSocketCloseInfo/reason}}"] if present, or unset otherwise. + 1. Let |code| be |closeInfo|["{{WebSocketCloseInfo/closeCode}}"] if present, or null otherwise. + 1. Let |reason| be |closeInfo|["{{WebSocketCloseInfo/reason}}"]. 1. [=Close the WebSocket=] with this, |code|, and |reason|.
-# Feedback to WebSocketStream from the protocol # {#feedback-to-websocket-stream-from-the-protocol} +## Feedback to WebSocketStream from the protocol ## {#feedback-to-websocket-stream-from-the-protocol} -When [=the WebSocket connection is established=], the user agent must [=queue a task=] to run these -steps: +When [=the WebSocket connection is established=] for a {{WebSocketStream}} |stream|, the user agent +must [=queue a task=] to run these steps:
1. Change the [=WebSocketStream/ready state=] to {{WebSocket/OPEN}} (1). - 1. Set |this|'s [=WebSocketStream/was ever connected flag=]. + 1. Set |stream|'s [=WebSocketStream/was ever connected flag=]. 1. Let |extensions| be the [=extensions in use=]. 1. Let |protocol| be the [=subprotocol in use=]. - 1. Let |pullAlgorithm| be an action that [=pulls bytes=] from the WebSocket. - 1. Let |cancelAlgorithm| be an action that [=cancels=] the WebSocketStream with |this| and |reason|, given - |reason|. + 1. Let |pullAlgorithm| be an action that [=pulls bytes=] from |stream|. + 1. Let |cancelAlgorithm| be an action that [=cancels=] the {{WebSocketStream}} with |stream| and + |reason|, given |reason|. 1. Let |readable| be a [=new=] {{ReadableStream}}. 1. [=ReadableStream/Set up=] |readable| with |pullAlgorithm| and |cancelAlgorithm|. - 1. Let |writeAlgorithm| be an action that [=writes=] |chunk| to |this|, given |chunk|. - 1. Let |closeAlgorithm| be an action that [=closes=] |this|. - 1. Let |abortAlgorithm| be an action that [=aborts=] |this| with |reason|, given |reason|. + 1. Let |writeAlgorithm| be an action that [=writes=] |chunk| to |stream|, given |chunk|. + 1. Let |closeAlgorithm| be an action that [=closes=] |stream|. + 1. Let |abortAlgorithm| be an action that [=aborts=] |stream| with |reason|, given |reason|. 1. Let |writable| be a [=new=] {{WritableStream}}. 1. [=WritableStream/Set up=] |writable| with |writeAlgorithm|, |closeAlgorithm|, and |abortAlgorithm|. - 1. Set |this|'s [=WebSocketStream/readable stream=] to |readable|. - 1. Set |this|'s [=WebSocketStream/writable stream=] to |writable|. + 1. Set |stream|'s [=WebSocketStream/readable stream=] to |readable|. + 1. Set |stream|'s [=WebSocketStream/writable stream=] to |writable|. 1. Let |openInfo| be a [=new=] {{WebSocketOpenInfo}}. - 1. Set |openInfo|["{{WebSocketOpenInfo/extensions}}"] to |extensions|. - 1. Set |openInfo|["{{WebSocketOpenInfo/protocol}}"] to |protocol|. - 1. Set |openInfo|["{{WebSocketOpenInfo/readable}}"] to |readable|. - 1. Set |openInfo|["{{WebSocketOpenInfo/writable}}"] to |writable|. - 1. [=Resolve=] |this|'s [=WebSocketStream/opened promise=] with |openInfo|. + 1. Set |openInfo| to «[ "{{WebSocketOpenInfo/extensions}}" → |extensions|, + "{{WebSocketOpenInfo/protocol}}" → |protocol|, "{{WebSocketOpenInfo/readable}}" → |readable|, + "{{WebSocketOpenInfo/writable}}" → |writable| ]». + 1. [=Resolve=] |stream|'s [=WebSocketStream/opened promise=] with |openInfo|.
@@ -962,8 +970,8 @@ steps:
-When [=a WebSocket message has been received=] for |this| with type |type| and data |data|, the user -agent must [=queue a task=] to follow these steps: [[!WSP]] +When [=a WebSocket message has been received=] for a {{WebSocketStream}} |stream| with type |type| +and data |data|, the user agent must [=queue a task=] to follow these steps: [[!WSP]] 1. If [=WebSocket/ready state=] is not {{WebSocket/OPEN}} (1), then return. 1. Let |chunk| be determined by switching on |type|: @@ -977,80 +985,81 @@ agent must [=queue a task=] to follow these steps: [[!WSP]] object, whose contents are |data| - 1. [=ReadableStream/Enqueue=] |chunk| into |this|'s [=WebSocketStream/readable stream=]. + 1. [=ReadableStream/Enqueue=] |chunk| into |stream|'s [=WebSocketStream/readable stream=]. 1. Apply backpressure to the WebSocket. Note: Applying backpressure should result in no new WebSocket messages being handled until the backpressure is released. After an implementation-defined amount - of data is buffered, the client will stop reading from the TCP/IP socket resulting in network-layer - backpressure being applied. -
+ of data is buffered, the client will stop reading from the underlying network connection resulting + in network-layer backpressure being applied. +

-When [=the WebSocket closing handshake is started=], the user agent must [=queue a task=] to change -the [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}} if it is not already. [[!WSP]] +When [=the WebSocket closing handshake is started=] for a {{WebSocketStream}} |stream|, the user +agent must [=queue a task=] to change the |stream|'s [=WebSocketStream/ready state=] to +{{WebSocket/CLOSING}} if it is not already. [[!WSP]]
-When [=the WebSocket connection is closed=] for |this|, possibly [=cleanly=], the user agent must - [=queue a task=] to run the following substeps: +When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, possibly + [=cleanly=], the user agent must [=queue a task=] to run the following substeps: 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). - 1. If |this|'s [=WebSocketStream/was ever connected flag=] is not set, then [=reject=] |this|'s + 1. If |stream|'s [=WebSocketStream/was ever connected flag=] is not set, then [=reject=] |stream|'s [=opened promise=] with a [=new=] WebSocketError. 1. Let |code| be [=the WebSocket connection close code=]. 1. Let |reason| be the result of applying [=UTF-8 decode without BOM=] to [=the WebSocket connection close reason=]. 1. If the connection was closed [=cleanly=], - 1. [=ReadableStream/Close=] |this|'s [=WebSocketStream/readable stream=]. - 1. [=WritableStream/Error=] |this|'s [=WebSocketStream/writable stream=] with an + 1. [=ReadableStream/Close=] |stream|'s [=WebSocketStream/readable stream=]. + 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with an "{{InvalidStateError}}" {{DOMException}} indicating that a closed {{WebSocketStream}} cannot be written to. - 1. [=Resolve=] |this|'s [=closed promise=] with a [=new=] {{WebSocketCloseInfo}} constructed with - |code| and |reason|. + 1. [=Resolve=] |stream|'s [=closed promise=] with a [=new=] {{WebSocketCloseInfo}} constructed + with |code| and |reason|. 1. Otherwise, - 1. [=ReadableStream/Error=] |this|'s [=WebSocketStream/readable stream=] with a [=new=] + 1. [=ReadableStream/Error=] |stream|'s [=WebSocketStream/readable stream=] with a [=new=] {{WebSocketError}} constructed with |code| and |reason|. - 1. [=WritableStream/Error=] |this|'s [=WebSocketStream/writable stream=] with a [=new=] + 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with a [=new=] {{WebSocketError}} constructed with |code| and |reason|. - 1. [=Reject=] |this|'s [=closed promise=] with a [=new=] {{WebSocketError}} constructed with + 1. [=Reject=] |stream|'s [=closed promise=] with a [=new=] {{WebSocketError}} constructed with |code| and |reason|.

The [=task source=] for all [=tasks=] queued in this section is the -[=WebSocket task source=] +[=WebSocket task source=].
-# Stream operations # {#stream-operations} +## Stream operations ## {#stream-operations}
-To pull bytes from a WebSocket, if the WebSocket is currently applying backpressure, -release backpressure. +To pull bytes from a {{WebSocketStream}} |stream|, if |stream| is currently applying +backpressure, release backpressure. Note: If any messages are queued, one will be handled immediately as a result. -
+
-To cancel a WebSocketStream with |this| and |reason|, [=close using reason=] giving -|this| and |reason|. +To cancel a {{WebSocketStream}} |stream| given |reason|, [=close using reason=] giving +|stream| and |reason|.

-To write |chunk| to this, the user agent must run these steps: +To write |chunk| to a {{WebSocketStream}} |stream|, the user agent must run these steps: 1. Let |promise| be a new promise. 1. If |chunk| is a {{BufferSource}}, 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=]. @@ -1061,12 +1070,12 @@ To write |chunk| to this, the user agent must run t 1. Let |data| be the result of [=UTF-8 encode|encoding=] |string|. 1. Let |opcode| be a text frame opcode. 1. In parallel, - 1. Wait until there is sufficient buffer space to send the message. + 1. Wait until there is sufficient buffer space in |stream| to send the message. Note: This means that backpressure will be applied when the user agent's buffers are full. 1. If [=the WebSocket closing handshake is started|the closing handshake has not yet started=], - [=Send a WebSocket Message=] comprised of |data| using |opcode|. + [=Send a WebSocket Message=] to |stream| comprised of |data| using |opcode|. 1. [=Queue a task=] on the [=WebSocket task source=] to resolve |promise| with undefined. 1. Return |promise|.
@@ -1075,30 +1084,30 @@ To write |chunk| to this, the user agent must run t
-To close a WebSocketStream given |this|, [=close the WebSocket=] with |this| (code and reason are unset). +To close a {{WebSocketStream}} |stream|, [=close the WebSocket=] with |stream| (code and reason are not set).

-To abort a WebSocketStream with |this| and |reason|, [=close using reason=] giving |this| -and |reason|. +To abort a {{WebSocketStream}} |stream| given |reason|, [=close using reason=] giving +|stream| and |reason|.

-To close using reason a WebSocketStream given |this| and |reason|, +To close using reason a {{WebSocketStream}} |stream| given |reason|, 1. Let |code| and |reasonString| be unset. 1. If |reason| is a {{WebSocketError}}, 1. Set |code| to |reason|'s {{WebSocketError/closeCode}}. 1. Set |reasonString| to |reason|'s {{WebSocketError/reason}}. - 1. [=Close the WebSocket=] with |this|, |code|, and |reasonString|. If this throws an exception, - discard |code| and |reasonString| and [=Close the WebSocket=] with |this|. + 1. [=Close the WebSocket=] with |stream|, |code|, and |reasonString|. If this throws an exception, + discard |code| and |reasonString| and [=Close the WebSocket=] with |stream|. Note: A {{WebSocketError}} object constructed from JavaScript will always have a {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set, @@ -1109,7 +1118,7 @@ To close using reason a WebSocketStream given |this| and |reason|, -# The {{WebSocketError}} interface # {#the-websocketevent-interface} +## The {{WebSocketError}} interface ## {#the-websocketevent-interface} WebSocketError is a subclass of {{DOMException}} that represents the information associated with closing a WebSocket. @@ -1126,9 +1135,11 @@ that represents the information associated with closing a WebSocket. }; -A {{WebSocketError}} object has an associated closeCode. +Each {{WebSocketError}} object has an associated closeCode, which is +a number. -A {{WebSocketError}} object has an associated reason. +Each {{WebSocketError}} object has an associated reason, which is a +string.
: |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message| [, @@ -1147,10 +1158,10 @@ A {{WebSocketError}} object has an associated reason|error| . {{WebSocketError/closeCode}} + : |error|.{{WebSocketError/closeCode}} :: Returns the [=the WebSocket connection close code=]. - : |error| . {{WebSocketError/reason}} + : |error|.{{WebSocketError/reason}} :: Returns the [=the WebSocket connection close reason=].
@@ -1183,7 +1194,7 @@ These algorithms are shared between the {{WebSocket}} and {{WebSocketStream}} in
-To get a url record given a |url| and |baseURL|, +To get a URL record given a |url| and |baseURL|, 1. Let |urlRecord| be the result of applying the [=URL parser=] to |url| with |baseURL|. 1. If |urlRecord| is failure, then throw a "{{SyntaxError}}" {{DOMException}}. @@ -1200,17 +1211,20 @@ To get a url record given a |url| and |baseURL|, To validate close code and reason given a |code| and |reason|, - 1. If |code| is set, but is neither an integer equal to 1000 nor an integer in the range 3000 + 1. If |code| is not null, but is neither an integer equal to 1000 nor an integer in the range 3000 to 4999, inclusive, throw an "{{InvalidAccessError}}" {{DOMException}}. - 1. If |reason| is set, then run these substeps: + 1. If |reason| is not null, then run these substeps: 1. Let |reasonBytes| be the result of encoding |reason|. 1. If |reasonBytes| is longer than 123 bytes, then throw a "{{SyntaxError}}" {{DOMException}}.
-To close the WebSocket given |this|, |code|, and |reason|, +To close the WebSocket for a {WebSocket} or {WebSocketStream} |this|, given optional +|code| and optional |reason|, + 1. If |code| was not supplied, let |code| be null. + 1. If |reason| was not supplied, let |reason| be the empty string. 1. [=Validate close code and reason=] with |code| and |reason|. 1. Run the first matching steps from the following list:
@@ -1235,13 +1249,14 @@ To close the WebSocket given |this|, |code|, and |reason|, :: [=Start the WebSocket closing handshake=] and set [=this=]'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). [[!WSP]] - If neither |code| nor |reason| is set, the WebSocket Close message must not have a body. + If |code| is null and |reason| is the empty string, the WebSocket Close message must not have a + body.

The WebSocket Protocol erroneously states that the status code is required for the [=start the WebSocket closing handshake=] algorithm. - if |reason| is non-empty but |code| is not set, then set |code| to 1000 ("Normal Closure"). + if |reason| is non-empty but |code| is null, then set |code| to 1000 ("Normal Closure"). If |code| is set, then the status code to use in the WebSocket Close message must be the integer given by |code|. [[!WSP]] From f488ee44e962f569b1e42855ea9c4edb3f1bcecd Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 04:58:10 +0900 Subject: [PATCH 10/33] Add missing AbortSignal algorithm steps. Also apply backpressure on connection, and linkify "exists". --- index.bs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 909757b..884ad65 100644 --- a/index.bs +++ b/index.bs @@ -796,6 +796,9 @@ which is a {{WritableStream}}. Each {{WebSocketStream}} object has an associated was ever connected flag, which is initially unset. +Each {{WebSocketStream}} object has an associated handshake aborted +flag, which is initially unset. + Each {{WebSocketStream}} object has an associated ready state, which is a number representing the state of the connection. Initially it must be {{WebSocket/CONNECTING}} (0). It has the same semantics as {{WebSocket}}'s [=WebSocket/ready @@ -895,8 +898,8 @@ state=], but is not exposed to JavaScript. 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. 1. Let |urlRecord| be the result of [=get a URL record|getting a URL record=] given |url| and |baseURL|. - 1. Let |protocols| be |options|["{{WebSocketStreamOptions/protocols}}"] if it exists, otherwise an - empty sequence. + 1. Let |protocols| be |options|["{{WebSocketStreamOptions/protocols}}"] if it [=map/exists=], + otherwise an empty sequence. 1. If any of the values in |protocols| occur more than once or otherwise fail to match the requirements for elements that comprise the value of \``Sec-WebSocket-Protocol`\` fields as defined by The WebSocket protocol, @@ -904,6 +907,21 @@ state=], but is not exposed to JavaScript. 1. Set [=this=]'s [=WebSocketStream/url=] to |urlRecord|. 1. Set [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] to new promises. + 1. Apply backpressure to the WebSocket. + + Note: This means that messages won't be read until the application is ready for them. + + 1. If |options|["{{WebSocketStreamOptions/signal}}"] [=map/exists=], + 1. Let |signal| be |options|["{{WebSocketStreamOptions/signal}}"]. + 1. If |signal| is [=AbortSignal/aborted=], then reject [=this=]'s [=WebSocketStream/opened + promise=] and [=WebSocketStream/closed promise=] with |signal|'s [=abort reason=] and return. + 1. [=AbortSignal/add|Add the following abort steps=] to |signal|: + 1. If the WebSocket connection is not yet [=established=] [[!WSP]] + 1. [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocketStream/ready state=] to + {{WebSocket/CLOSING}}. + 1. [=Reject=] [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed + promise=] with |signal|'s [=abort reason=]. + 1. Set [=this=]'s [=WebSocketStream/handshake aborted flag=]. 1. Let |client| be [=this=]'s [=relevant settings object=]. 1. Run this step [=in parallel=]: 1. [=Establish a WebSocket connection=] given |urlRecord|, |protocols|, and |client|. [[!FETCH]] @@ -1008,6 +1026,7 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, [=cleanly=], the user agent must [=queue a task=] to run the following substeps: 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). + 1. If |stream|'s [=WebSocketStream/handshake aborted flag=] is set, then return. 1. If |stream|'s [=WebSocketStream/was ever connected flag=] is not set, then [=reject=] |stream|'s [=opened promise=] with a [=new=] WebSocketError. 1. Let |code| be [=the WebSocket connection close code=]. @@ -1174,8 +1193,10 @@ WebSocketError(|message|, |init|) constructor steps are: 1. Set |this|'s [=DOMException/name=] to "WebSocketError". 1. Set |this|'s [=DOMException/message=] to |message|. - 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it exists, or unset otherwise. - 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it exists, or unset otherwise. + 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it [=map/exists=], or unset + otherwise. + 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it [=map/exists=], or unset + otherwise. 1. [=Validate close code and reason=] with |code| and |reason|. 1. If |reason| is non-empty, but |code| is not set, then set |code| to 1000 ("Normal Closure"). 1. Set |this|'s [=WebSocketError/closeCode=] to |code|. From b1a58f4c59c55536568cccc0d1c58c5da41e1e7a Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 15:14:23 +0900 Subject: [PATCH 11/33] Use Bikeshed magic to get a URL record Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 884ad65..9d08941 100644 --- a/index.bs +++ b/index.bs @@ -323,7 +323,7 @@ It can have the following values: constructor steps are: 1. Let |baseURL| be [=this=]'s [=relevant settings object=]'s [=API base URL=]. - 1. Let |urlRecord| be the result of [=get a URL record|getting a URL record=] given |url| and + 1. Let |urlRecord| be the result of [=getting a URL record=] given |url| and |baseURL|. 1. If |protocols| is a string, set |protocols| to a sequence consisting of just that string. 1. If any of the values in |protocols| occur more than once or otherwise fail to match the From 8cc184c582c964125d62ac24f5cabd153460efbf Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 15:14:59 +0900 Subject: [PATCH 12/33] Add missing colon Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 9d08941..c4475e0 100644 --- a/index.bs +++ b/index.bs @@ -916,7 +916,7 @@ state=], but is not exposed to JavaScript. 1. If |signal| is [=AbortSignal/aborted=], then reject [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] with |signal|'s [=abort reason=] and return. 1. [=AbortSignal/add|Add the following abort steps=] to |signal|: - 1. If the WebSocket connection is not yet [=established=] [[!WSP]] + 1. If the WebSocket connection is not yet [=established=]: [[!WSP]] 1. [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}}. 1. [=Reject=] [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed From 781216301788de37fdb468de1af23fc4c38ae038 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 15:16:33 +0900 Subject: [PATCH 13/33] Remove "the user agent must run..." Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index c4475e0..1002531 100644 --- a/index.bs +++ b/index.bs @@ -1078,7 +1078,7 @@ To cancel a {{WebSocketStream}} |stream| given |reason|, [=close usin

-To write |chunk| to a {{WebSocketStream}} |stream|, the user agent must run these steps: +To write |chunk| to a {{WebSocketStream}} |stream|: 1. Let |promise| be a new promise. 1. If |chunk| is a {{BufferSource}}, 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=]. From 47ab0dadf2de473d32f5d688c94092744036ccec Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 15:17:14 +0900 Subject: [PATCH 14/33] Fix capitalisation of "close" Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 1002531..a0e4093 100644 --- a/index.bs +++ b/index.bs @@ -1126,7 +1126,7 @@ To close using reason a {{WebSocketStream}} |stream| given |reason|, 1. Set |code| to |reason|'s {{WebSocketError/closeCode}}. 1. Set |reasonString| to |reason|'s {{WebSocketError/reason}}. 1. [=Close the WebSocket=] with |stream|, |code|, and |reasonString|. If this throws an exception, - discard |code| and |reasonString| and [=Close the WebSocket=] with |stream|. + discard |code| and |reasonString| and [=close the WebSocket=] with |stream|. Note: A {{WebSocketError}} object constructed from JavaScript will always have a {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set, From 2b9ee80e1e0afef6cc1bd20ddc4eff4219c0eb9c Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 15:18:46 +0900 Subject: [PATCH 15/33] Server can't send a bad reason, and wording improvement (by @domenic) Co-authored-by: Domenic Denicola --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index a0e4093..85c6467 100644 --- a/index.bs +++ b/index.bs @@ -1129,9 +1129,9 @@ To close using reason a {{WebSocketStream}} |stream| given |reason|, discard |code| and |reasonString| and [=close the WebSocket=] with |stream|. Note: A {{WebSocketError}} object constructed from JavaScript will always have a - {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set, - however the {{WebSocketStream/closed}} promise might be rejected with {{WebSocketError/closeCode}} - values coming from the server that JavaScript is not permitted to set itself, such as 1001 "Going + {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set. + However, the {{WebSocketStream/closed}} promise might be rejected with a {{WebSocketError}} whose {{WebSocketError/closeCode}} has a + value coming from the server that JavaScript is not permitted to set itself, such as 1001 "Going Away".
From 12ff404633b6443fd906fbca43c865432ee12a8a Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 31 Jan 2024 18:00:56 +0900 Subject: [PATCH 16/33] Editorial changes from @domenic. Also change `closeCode` to [EnforceRange] --- index.bs | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/index.bs b/index.bs index 85c6467..a7d913a 100644 --- a/index.bs +++ b/index.bs @@ -758,7 +758,7 @@ dictionary WebSocketOpenInfo { }; dictionary WebSocketCloseInfo { - [Clamp] unsigned short closeCode; + [EnforceRange] unsigned short closeCode; USVString reason = ""; }; @@ -793,11 +793,11 @@ which is a {{ReadableStream}}. Each {{WebSocketStream}} object has an associated writable stream, which is a {{WritableStream}}. -Each {{WebSocketStream}} object has an associated was ever connected -flag, which is initially unset. +Each {{WebSocketStream}} object has an associated boolean was ever +connected, which is initially false. -Each {{WebSocketStream}} object has an associated handshake aborted -flag, which is initially unset. +Each {{WebSocketStream}} object has an associated boolean handshake +aborted, which is initially false. Each {{WebSocketStream}} object has an associated ready state, which is a number representing the state of the connection. Initially it must be @@ -921,7 +921,7 @@ state=], but is not exposed to JavaScript. {{WebSocket/CLOSING}}. 1. [=Reject=] [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] with |signal|'s [=abort reason=]. - 1. Set [=this=]'s [=WebSocketStream/handshake aborted flag=]. + 1. Set [=this=]'s [=WebSocketStream/handshake aborted=] to true. 1. Let |client| be [=this=]'s [=relevant settings object=]. 1. Run this step [=in parallel=]: 1. [=Establish a WebSocket connection=] given |urlRecord|, |protocols|, and |client|. [[!FETCH]] @@ -960,7 +960,7 @@ must [=queue a task=] to run these steps:
1. Change the [=WebSocketStream/ready state=] to {{WebSocket/OPEN}} (1). - 1. Set |stream|'s [=WebSocketStream/was ever connected flag=]. + 1. Set |stream|'s [=WebSocketStream/was ever connected=] to true. 1. Let |extensions| be the [=extensions in use=]. 1. Let |protocol| be the [=subprotocol in use=]. 1. Let |pullAlgorithm| be an action that [=pulls bytes=] from |stream|. @@ -1026,8 +1026,8 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, [=cleanly=], the user agent must [=queue a task=] to run the following substeps: 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). - 1. If |stream|'s [=WebSocketStream/handshake aborted flag=] is set, then return. - 1. If |stream|'s [=WebSocketStream/was ever connected flag=] is not set, then [=reject=] |stream|'s + 1. If |stream|'s [=WebSocketStream/handshake aborted=] is true, then return. + 1. If |stream|'s [=WebSocketStream/was ever connected=] is false, then [=reject=] |stream|'s [=opened promise=] with a [=new=] WebSocketError. 1. Let |code| be [=the WebSocket connection close code=]. 1. Let |reason| be the result of applying [=UTF-8 decode without BOM=] to [=the WebSocket @@ -1119,21 +1119,22 @@ To abort a {{WebSocketStream}} |stream| given |reason|, [=close using
-To close using reason a {{WebSocketStream}} |stream| given |reason|, +To close using reason a {{WebSocketStream}} |stream| given |reason|: - 1. Let |code| and |reasonString| be unset. - 1. If |reason| is a {{WebSocketError}}, - 1. Set |code| to |reason|'s {{WebSocketError/closeCode}}. - 1. Set |reasonString| to |reason|'s {{WebSocketError/reason}}. + 1. Let |code| be null. + 1. Let |reasonString| be the empty string. + 1. If |reason| [=implements=] {{WebSocketError}}, + 1. Set |code| to |reason|'s [=WebSocketError/closeCode=]. + 1. Set |reasonString| to |reason|'s [=WebSocketError/reason=]. 1. [=Close the WebSocket=] with |stream|, |code|, and |reasonString|. If this throws an exception, discard |code| and |reasonString| and [=close the WebSocket=] with |stream|. Note: A {{WebSocketError}} object constructed from JavaScript will always have a - {{WebSocketError/closeCode}} and {{WebSocketError/reason}} that JavaScript is permitted to set. - However, the {{WebSocketStream/closed}} promise might be rejected with a {{WebSocketError}} whose {{WebSocketError/closeCode}} has a - value coming from the server that JavaScript is not permitted to set itself, such as 1001 "Going - Away". -
+ [=WebSocketError/closeCode=] and [=WebSocketError/reason=] that JavaScript is permitted to set. + However, the {{WebSocketStream/closed}} promise might be rejected with a {{WebSocketError}} whose + [=WebSocketError/closeCode=] has a value coming from the server that JavaScript is not permitted to + set itself, such as 1001 "Going Away". +
@@ -1143,9 +1144,8 @@ To close using reason a {{WebSocketStream}} |stream| given |reason|, that represents the information associated with closing a WebSocket. -[ - Exposed=(Window,Worker), -] interface WebSocketError : DOMException { +[Exposed=(Window,Worker)] +interface WebSocketError : DOMException { constructor(optional DOMString message = "", optional WebSocketCloseInfo init = {}); From a2fc7c2f79e0a0c1fa91bf4cb9f83580fc5d4e4d Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 31 Jan 2024 18:08:51 +0900 Subject: [PATCH 17/33] Change `|this|` to `[=this=]` everywhere --- index.bs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/index.bs b/index.bs index a7d913a..b89a945 100644 --- a/index.bs +++ b/index.bs @@ -1191,16 +1191,16 @@ string. The <dfn constructor for="WebSocketError" data-lt="WebSocketError(message, init)"><code>new WebSocketError(|message|, |init|)</code></dfn> constructor steps are: - 1. Set |this|'s [=DOMException/name=] to "<code>WebSocketError</code>". - 1. Set |this|'s [=DOMException/message=] to |message|. + 1. Set [=this=]'s [=DOMException/name=] to "<code>WebSocketError</code>". + 1. Set [=this=]'s [=DOMException/message=] to |message|. 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it [=map/exists=], or unset otherwise. 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it [=map/exists=], or unset otherwise. 1. [=Validate close code and reason=] with |code| and |reason|. 1. If |reason| is non-empty, but |code| is not set, then set |code| to 1000 ("Normal Closure"). - 1. Set |this|'s [=WebSocketError/closeCode=] to |code|. - 1. Set |this|'s [=WebSocketError/reason=] to |reason|. + 1. Set [=this=]'s [=WebSocketError/closeCode=] to |code|. + 1. Set [=this=]'s [=WebSocketError/reason=] to |reason|. </div> The <dfn attribute for=WebSocketError>closeCode</dfn> getter steps are to return [=this=]'s @@ -1241,7 +1241,7 @@ To <dfn>validate close code and reason</dfn> given a |code| and |reason|, <div algorithm> -To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} |this|, given optional +To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} [=this=], given optional |code| and optional |reason|, 1. If |code| was not supplied, let |code| be null. @@ -1253,8 +1253,8 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} |this|, :: Do nothing. <p class="note">The connection is already closing or is already closed. If it has not already, a - {{WebSocket/close}} event will eventually fire if |this| is a {{WebSocket}}, or the - {{WebSocketStream/closed}} promise will become settled if |this| is a {{WebSocketStream}}. + {{WebSocket/close}} event will eventually fire if [=this=] is a {{WebSocket}}, or the + {{WebSocketStream/closed}} promise will become settled if [=this=] is a {{WebSocketStream}}. : If the WebSocket connection is not yet [=established=] [[!WSP]] :: [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocket/ready state=] to @@ -1262,8 +1262,8 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} |this|, <p class="note">The [=fail the WebSocket connection=] algorithm invokes the [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket connection is - closed=], which fires the {{WebSocket/close}} event if |this| is a {{WebSocket}}, or settles - the {{WebSocketStream/closed}} promise if |this| is a {{WebSocketStream}}. + closed=], which fires the {{WebSocket/close}} event if [=this=] is a {{WebSocket}}, or settles + the {{WebSocketStream/closed}} promise if [=this=] is a {{WebSocketStream}}. : If the WebSocket closing handshake has not yet been <a lt="the WebSocket closing handshake is started">started</a> [[!WSP]] @@ -1287,8 +1287,9 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} |this|, <p class="note">The [=start the WebSocket closing handshake=] algorithm eventually invokes the [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket - connection is closed=], which fires the {{WebSocket/close}} event if |this| is a {{WebSocket}}, - or settles the {{WebSocketStream/closed}} promise if |this| is a {{WebSocketStream}}. + connection is closed=], which fires the {{WebSocket/close}} event if [=this=] is a + {{WebSocket}}, or settles the {{WebSocketStream/closed}} promise if [=this=] is a + {{WebSocketStream}}. : Otherwise :: Set [=this=]'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). @@ -1296,7 +1297,7 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} |this|, <p class="note">[=The WebSocket closing handshake is started=], and will eventually invoke the [=close the WebSocket connection=] algorithm, which will establish that [=the WebSocket connection is closed=], and thus the {{WebSocket/close}} event will fire or the - {{WebSocketStream/closed}} promise will resolve, depending on the type of |this|. + {{WebSocketStream/closed}} promise will resolve, depending on the type of [=this=]. </dl> <h2 id="acks" class="no-num">Acknowledgments</h2> From 3a285c736906282525a8fa9169b75c86835eb025 Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 31 Jan 2024 18:35:00 +0900 Subject: [PATCH 18/33] Fix initialization of dictionaries and WebSocketErrors Also use <p class=note> everywhere, and other cleanups. --- index.bs | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/index.bs b/index.bs index b89a945..e59a544 100644 --- a/index.bs +++ b/index.bs @@ -839,9 +839,10 @@ state=], but is not exposed to JavaScript. corresponds to one message. Text messages will be read as strings; binary messages will be read as {{Uint8Array}} objects. - Note: The original {{WebSocket}} API supplied {{ArrayBuffer}} objects, but modern practice is to - prefer the {{Uint8Array}} type for binary data, particularly when using streams. The underlying - {{ArrayBuffer}} object can be recovered by calling <code><var ignore>chunk</var>.buffer</code>. + <p class=note>The original {{WebSocket}} API supplied {{ArrayBuffer}} objects, but modern + practice is to prefer the {{Uint8Array}} type for binary data, particularly when using streams. + The underlying {{ArrayBuffer}} object can be recovered by calling <code><var + ignore>chunk</var>.buffer</code>. The stream can be closed by calling {{ReadableStream/cancel()}} on {{WebSocketOpenInfo/readable}}. If the reason argument passed to {{ReadableStream/cancel()}} is a @@ -909,7 +910,7 @@ state=], but is not exposed to JavaScript. promises. 1. Apply backpressure to the WebSocket. - Note: This means that messages won't be read until the application is ready for them. + <p class=note>This means that messages won't be read until the application is ready for them. 1. If |options|["{{WebSocketStreamOptions/signal}}"] [=map/exists=], 1. Let |signal| be |options|["{{WebSocketStreamOptions/signal}}"]. @@ -917,8 +918,8 @@ state=], but is not exposed to JavaScript. promise=] and [=WebSocketStream/closed promise=] with |signal|'s [=abort reason=] and return. 1. [=AbortSignal/add|Add the following abort steps=] to |signal|: 1. If the WebSocket connection is not yet [=established=]: [[!WSP]] - 1. [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocketStream/ready state=] to - {{WebSocket/CLOSING}}. + 1. [=Fail the WebSocket connection=]. + 1. Set [=this=]'s [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}}. 1. [=Reject=] [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] with |signal|'s [=abort reason=]. 1. Set [=this=]'s [=WebSocketStream/handshake aborted=] to true. @@ -976,11 +977,10 @@ must [=queue a task=] to run these steps: |abortAlgorithm|. 1. Set |stream|'s [=WebSocketStream/readable stream=] to |readable|. 1. Set |stream|'s [=WebSocketStream/writable stream=] to |writable|. - 1. Let |openInfo| be a [=new=] {{WebSocketOpenInfo}}. - 1. Set |openInfo| to «[ "{{WebSocketOpenInfo/extensions}}" → |extensions|, - "{{WebSocketOpenInfo/protocol}}" → |protocol|, "{{WebSocketOpenInfo/readable}}" → |readable|, - "{{WebSocketOpenInfo/writable}}" → |writable| ]». - 1. [=Resolve=] |stream|'s [=WebSocketStream/opened promise=] with |openInfo|. + 1. [=Resolve=] |stream|'s [=WebSocketStream/opened promise=] with {{WebSocketOpenInfo}} «[ + "{{WebSocketOpenInfo/extensions}}" → |extensions|, "{{WebSocketOpenInfo/protocol}}" → + |protocol|, "{{WebSocketOpenInfo/readable}}" → |readable|, "{{WebSocketOpenInfo/writable}}" → + |writable| ]». </div> @@ -1006,7 +1006,7 @@ and data |data|, the user agent must [=queue a task=] to follow these steps: [[! 1. [=ReadableStream/Enqueue=] |chunk| into |stream|'s [=WebSocketStream/readable stream=]. 1. Apply backpressure to the WebSocket. - Note: Applying backpressure <span class=allow-2119>should</span> result in no new WebSocket + <p class=note>Applying backpressure <span class=allow-2119>should</span> result in no new WebSocket messages being handled until the backpressure is released. After an implementation-defined amount of data is buffered, the client will stop reading from the underlying network connection resulting in network-layer backpressure being applied. @@ -1037,15 +1037,18 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with an "{{InvalidStateError}}" {{DOMException}} indicating that a closed {{WebSocketStream}} cannot be written to. - 1. [=Resolve=] |stream|'s [=closed promise=] with a [=new=] {{WebSocketCloseInfo}} constructed - with |code| and |reason|. + 1. [=Resolve=] |stream|'s [=closed promise=] with {{WebSocketCloseInfo}} «[ + "{{WebSocketCloseInfo/closeCode}}" → |code|, "{{WebSocketCloseInfo/reason}}" → |reason|]». 1. Otherwise, 1. [=ReadableStream/Error=] |stream|'s [=WebSocketStream/readable stream=] with a [=new=] - {{WebSocketError}} constructed with |code| and |reason|. + {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] + is |reason|. 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with a [=new=] - {{WebSocketError}} constructed with |code| and |reason|. - 1. [=Reject=] |stream|'s [=closed promise=] with a [=new=] {{WebSocketError}} constructed with - |code| and |reason|. + {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] + is |reason|. + 1. [=Reject=] |stream|'s [=closed promise=] with [=new=] + {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] + is |reason|. </div> <hr> @@ -1053,7 +1056,6 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, The [=task source=] for all [=tasks=] <a lt="queue a task">queued</a> in this section is the [=WebSocket task source=]. -<hr> ## Stream operations ## {#stream-operations} @@ -1063,7 +1065,7 @@ The [=task source=] for all [=tasks=] <a lt="queue a task">queued</a> in this se To <dfn>pull bytes</dfn> from a {{WebSocketStream}} |stream|, if |stream| is currently applying backpressure, release backpressure. - Note: If any messages are queued, one will be handled immediately as a result. + <p class=note>If any messages are queued, one will be handled immediately as a result. </div> <hr> @@ -1081,7 +1083,7 @@ To <dfn>cancel</dfn> a {{WebSocketStream}} |stream| given |reason|, [=close usin To <dfn>write</dfn> |chunk| to a {{WebSocketStream}} |stream|: 1. Let |promise| be a new promise. 1. If |chunk| is a {{BufferSource}}, - 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=]. + 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=] given |chunk|. 1. Let |opcode| be a binary frame opcode. 1. Otherwise, 1. Let |string| be the result of [=Converted to an IDL value|converting=] |chunk| to an IDL @@ -1091,7 +1093,8 @@ To <dfn>write</dfn> |chunk| to a {{WebSocketStream}} |stream|: 1. In parallel, 1. Wait until there is sufficient buffer space in |stream| to send the message. - Note: This means that backpressure will be applied when the user agent's buffers are full. + <p class=note>This means that backpressure will be applied when the user agent's buffers are + full. 1. If [=the WebSocket closing handshake is started|the closing handshake has not yet started=], [=Send a WebSocket Message=] to |stream| comprised of |data| using |opcode|. @@ -1129,7 +1132,7 @@ To <dfn>close using reason</dfn> a {{WebSocketStream}} |stream| given |reason|: 1. [=Close the WebSocket=] with |stream|, |code|, and |reasonString|. If this throws an exception, discard |code| and |reasonString| and [=close the WebSocket=] with |stream|. - Note: A {{WebSocketError}} object constructed from JavaScript will always have a + <p class=note>A {{WebSocketError}} object constructed from JavaScript will always have a [=WebSocketError/closeCode=] and [=WebSocketError/reason=] that JavaScript is permitted to set. However, the {{WebSocketStream/closed}} promise might be rejected with a {{WebSocketError}} whose [=WebSocketError/closeCode=] has a value coming from the server that JavaScript is not permitted to From 397dd0021d5dd1a74e0eb619bba38d0d3388f368 Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 31 Jan 2024 22:00:03 +0900 Subject: [PATCH 19/33] Demote "Feedback from the protocol" and "The CloseEvent interface" Make those headings be indented under "The WebSocket interface" for a more logical organisation. Also move "Ping and Pong frames" section to the bottom as it applies to both APIs. --- index.bs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/index.bs b/index.bs index e59a544..f0294f4 100644 --- a/index.bs +++ b/index.bs @@ -495,7 +495,7 @@ that must be supported, as [=event handler IDL attributes=], by all objects impl </table> -# Feedback from the protocol # {#feedback-from-the-protocol} +## Feedback from the protocol ## {#feedback-from-the-protocol} When [=the WebSocket connection is established=], the user agent must [=queue a task=] to run these steps: @@ -636,23 +636,8 @@ The [=task source=] for all [=tasks=] <a lt="queue a task">queued</a> in this se <dfn export>WebSocket task source</dfn>. -# Ping and Pong frames # {#ping-and-pong-frames} - -<cite>The WebSocket protocol</cite> defines Ping and Pong frames that can be used for keep-alive, -heart-beats, network status probing, latency instrumentation, and so forth. These are not currently -exposed in the API. -User agents may send ping and unsolicited pong frames as desired, for example in an attempt to -maintain local network NAT mappings, to detect failed connections, or to display latency metrics to -the user. User agents must not use pings or unsolicited pongs to aid the server; it is assumed that -servers will solicit pongs whenever appropriate for the server's needs. - -<!-- v2: we'll probably add a way to make the client send pings and automatically terminate the -connection if they don't get a pong within an author-provided timeout; see -https://www.w3.org/Bugs/Public/show_bug.cgi?id=17264 --> - - -# The {{CloseEvent}} interface # {#the-closeevent-interface} +## The {{CloseEvent}} interface ## {#the-closeevent-interface} {{WebSocket}} objects use the {{CloseEvent}} interface for their {{WebSocket/close}} events: @@ -1303,6 +1288,24 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} [=this= {{WebSocketStream/closed}} promise will resolve, depending on the type of [=this=]. </dl> + +# Ping and Pong frames # {#ping-and-pong-frames} + +<cite>The WebSocket protocol</cite> defines Ping and Pong frames that can be used for keep-alive, +heart-beats, network status probing, latency instrumentation, and so forth. These are not currently +exposed in the API. + +User agents may send ping and unsolicited pong frames as desired, for example in an attempt to +maintain local network NAT mappings, to detect failed connections, or to display latency metrics to +the user. User agents must not use pings or unsolicited pongs to aid the server; it is assumed that +servers will solicit pongs whenever appropriate for the server's needs. + +<!-- v2: we'll probably add a way to make the client send pings and automatically terminate the +connection if they don't get a pong within an author-provided timeout; see +https://www.w3.org/Bugs/Public/show_bug.cgi?id=17264 --> + + + <h2 id="acks" class="no-num">Acknowledgments</h2> Until the creation of this standard in 2021, the text here was maintained in the <a From 9e8d8f713c0dfd093b0f6fe2abff31aaacacb94b Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Thu, 1 Feb 2024 20:29:28 +0900 Subject: [PATCH 20/33] Indent "Garbage Collection" and fix the WebSocketStream interface enabled property --- index.bs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index f0294f4..9a3e730 100644 --- a/index.bs +++ b/index.bs @@ -680,7 +680,7 @@ to. It represents the WebSocket connection close reason provided by the server. -# Garbage collection # {#garbage-collection} +## Garbage collection ## {#garbage-collection} A {{WebSocket}} object whose [=WebSocket/ready state=] was set to {{WebSocket/CONNECTING}} (0) as of the last time the [=event loop=] reached <a for="event loop">step 1</a> must not be garbage @@ -752,9 +752,8 @@ dictionary WebSocketStreamOptions { AbortSignal signal; }; -[ - Exposed=(Window,Worker) -] interface WebSocketStream { +[Exposed=(Window,Worker)] +interface WebSocketStream { constructor(USVString url, optional WebSocketStreamOptions options); readonly attribute USVString url; readonly attribute Promise<WebSocketOpenInfo> opened; From 673ace918d13eaa2a84da192c695e3c9f4a069cc Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 7 Feb 2024 05:03:23 +0900 Subject: [PATCH 21/33] Use "queue a global task" --- index.bs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/index.bs b/index.bs index 9a3e730..c32a36e 100644 --- a/index.bs +++ b/index.bs @@ -34,8 +34,6 @@ spec:html; type:dfn; spec:url; type:dfn; text:origin for:/; text:url -spec:streams; type:dfn - text:writable stream writer </pre> <pre class=anchors> @@ -940,7 +938,8 @@ The <dfn attribute for=WebSocketStream>closed</dfn> getter steps are to return [ ## Feedback to WebSocketStream from the protocol ## {#feedback-to-websocket-stream-from-the-protocol} When [=the WebSocket connection is established=] for a {{WebSocketStream}} |stream|, the user agent -must [=queue a task=] to run these steps: +must [=queue a global task=] on the [=WebSocket task source=] given |stream|'s [=relevant global +object=] to run these steps: <div algorithm="handle WebSocketStream connection establishment"> @@ -973,7 +972,8 @@ must [=queue a task=] to run these steps: <div algorithm="handle a WebSocketStream message"> When [=a WebSocket message has been received=] for a {{WebSocketStream}} |stream| with type |type| -and data |data|, the user agent must [=queue a task=] to follow these steps: [[!WSP]] +and data |data|, the user agent must [=queue a global task=] on the [=WebSocket task source=] given +|stream|'s [=relevant global object=] to follow these steps: [[!WSP]] 1. If [=WebSocket/ready state=] is not {{WebSocket/OPEN}} (1), then return. 1. Let |chunk| be determined by switching on |type|: @@ -998,8 +998,9 @@ and data |data|, the user agent must [=queue a task=] to follow these steps: [[! <hr> -When [=the WebSocket closing handshake is started=] for a {{WebSocketStream}} |stream|, the user -agent must [=queue a task=] to change the |stream|'s [=WebSocketStream/ready state=] to +When [=the WebSocket closing handshake is started=] for a {{WebSocketStream}} +|stream|, the user agent must [=queue a global task=] on the [=WebSocket task source=] given +|stream|'s [=relevant global object=] to change the |stream|'s [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}} if it is not already. [[!WSP]] <hr> @@ -1007,7 +1008,8 @@ agent must [=queue a task=] to change the |stream|'s [=WebSocketStream/ready sta <div algorithm="handle WebSocketStream connection close"> When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, possibly - [=cleanly=], the user agent must [=queue a task=] to run the following substeps: +[=cleanly=], the user agent must [=queue a global task=] on the [=WebSocket task source=] given +|stream|'s [=relevant global object=] to run the following substeps: 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). 1. If |stream|'s [=WebSocketStream/handshake aborted=] is true, then return. @@ -1024,22 +1026,14 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, 1. [=Resolve=] |stream|'s [=closed promise=] with {{WebSocketCloseInfo}} «[ "{{WebSocketCloseInfo/closeCode}}" → |code|, "{{WebSocketCloseInfo/reason}}" → |reason|]». 1. Otherwise, - 1. [=ReadableStream/Error=] |stream|'s [=WebSocketStream/readable stream=] with a [=new=] - {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] - is |reason|. - 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with a [=new=] - {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] - is |reason|. - 1. [=Reject=] |stream|'s [=closed promise=] with [=new=] + 1. Let |error| be a [=new=] {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] is |reason|. + 1. [=ReadableStream/Error=] |stream|'s [=WebSocketStream/readable stream=] with |error|. + 1. [=WritableStream/Error=] |stream|'s [=WebSocketStream/writable stream=] with |error|. + 1. [=Reject=] |stream|'s [=closed promise=] with |error|. </div> -<hr> - -The [=task source=] for all [=tasks=] <a lt="queue a task">queued</a> in this section is the -[=WebSocket task source=]. - ## Stream operations ## {#stream-operations} @@ -1082,7 +1076,8 @@ To <dfn>write</dfn> |chunk| to a {{WebSocketStream}} |stream|: 1. If [=the WebSocket closing handshake is started|the closing handshake has not yet started=], [=Send a WebSocket Message=] to |stream| comprised of |data| using |opcode|. - 1. [=Queue a task=] on the [=WebSocket task source=] to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=WebSocket task source=] given |stream|'s [=relevant global + object=] to resolve |promise| with undefined. 1. Return |promise|. </div> From 30f5f8b2f650dc3d969fe042871df514d0139e14 Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 7 Feb 2024 05:12:22 +0900 Subject: [PATCH 22/33] Explicitly say what realm write algorithm's promise is created in --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index c32a36e..784ce94 100644 --- a/index.bs +++ b/index.bs @@ -1059,7 +1059,7 @@ To <dfn>cancel</dfn> a {{WebSocketStream}} |stream| given |reason|, [=close usin <div algorithm="write algorithm"> To <dfn>write</dfn> |chunk| to a {{WebSocketStream}} |stream|: - 1. Let |promise| be a new promise. + 1. Let |promise| be [=a new promise=] created in |stream|'s [=relevant realm=]. 1. If |chunk| is a {{BufferSource}}, 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=] given |chunk|. 1. Let |opcode| be a binary frame opcode. From 7d85772ac079b12b3c7cd776a0df1070175af462 Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 7 Feb 2024 05:15:05 +0900 Subject: [PATCH 23/33] Add more <hr>s --- index.bs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.bs b/index.bs index 784ce94..5e01f56 100644 --- a/index.bs +++ b/index.bs @@ -1210,6 +1210,8 @@ To <dfn>get a URL record</dfn> given a |url| and |baseURL|, 1. Return |urlRecord|. </div> +<hr> + <div algorithm> To <dfn>validate close code and reason</dfn> given a |code| and |reason|, @@ -1221,6 +1223,8 @@ To <dfn>validate close code and reason</dfn> given a |code| and |reason|, 1. If |reasonBytes| is longer than 123 bytes, then throw a "{{SyntaxError}}" {{DOMException}}. </div> +<hr> + <div algorithm> To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} [=this=], given optional From 1d7d86827c780e5e062130029d86742f3fdf251d Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 7 Feb 2024 05:21:45 +0900 Subject: [PATCH 24/33] Place The WebSocket Protocol in <cite> tags --- index.bs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 5e01f56..b1fe0b2 100644 --- a/index.bs +++ b/index.bs @@ -803,7 +803,7 @@ state=], but is not exposed to JavaScript. array is a subprotocol name. The connection will only be established if the server reports that it has selected one of these subprotocols. The subprotocol names have to match the requirements for elements that comprise the value of \`<a http-header>`Sec-WebSocket-Protocol`</a>\` fields as - defined by The WebSocket protocol. [[!WSP]] + defined by <cite>The WebSocket Protocol</cite>. [[!WSP]] : {{WebSocketStreamOptions/signal}} :: An {{AbortSignal}} that can be used to abort the handshake. After the handshake is complete, @@ -885,8 +885,8 @@ state=], but is not exposed to JavaScript. otherwise an empty sequence. 1. If any of the values in |protocols| occur more than once or otherwise fail to match the requirements for elements that comprise the value of - \`<a http-header>`Sec-WebSocket-Protocol`</a>\` fields as defined by The WebSocket protocol, - then throw a "{{SyntaxError}}" {{DOMException}}. [[!WSP]] + \`<a http-header>`Sec-WebSocket-Protocol`</a>\` fields as defined by <cite>The WebSocket + Protocol</cite>, then throw a "{{SyntaxError}}" {{DOMException}}. [[!WSP]] 1. Set [=this=]'s [=WebSocketStream/url=] to |urlRecord|. 1. Set [=this=]'s [=WebSocketStream/opened promise=] and [=WebSocketStream/closed promise=] to new promises. @@ -1259,9 +1259,9 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} [=this= If |code| is null and |reason| is the empty string, the WebSocket Close message must not have a body. - <p class="note">The WebSocket Protocol erroneously states that the status code is <span - class=allow-2119>required</a> for the [=start the WebSocket closing handshake=] algorithm. - <!-- TODO(ricea): File an errata against RFC6455. --> + <p class="note"><cite>The WebSocket Protocol</cite> erroneously states that the status code is + <span class=allow-2119>required</a> for the [=start the WebSocket closing handshake=] + algorithm. <!-- TODO(ricea): File an errata against RFC6455. --> if |reason| is non-empty but |code| is null, then set |code| to 1000 ("Normal Closure"). @@ -1289,7 +1289,7 @@ To <dfn>close the WebSocket</dfn> for a {WebSocket} or {WebSocketStream} [=this= # Ping and Pong frames # {#ping-and-pong-frames} -<cite>The WebSocket protocol</cite> defines Ping and Pong frames that can be used for keep-alive, +<cite>The WebSocket Protocol</cite> defines Ping and Pong frames that can be used for keep-alive, heart-beats, network status probing, latency instrumentation, and so forth. These are not currently exposed in the API. From 7b81f796c7b544e73add97401f8bafe4e76bb90b Mon Sep 17 00:00:00 2001 From: Adam Rice <ricea@chromium.org> Date: Wed, 7 Feb 2024 06:04:25 +0900 Subject: [PATCH 25/33] Fix many small nits from domenic@'s review --- index.bs | 69 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/index.bs b/index.bs index b1fe0b2..f0f0b6f 100644 --- a/index.bs +++ b/index.bs @@ -718,7 +718,7 @@ the following list: : If the WebSocket closing handshake has not yet been <a lt="the WebSocket closing handshake is started">started</a> [[!WSP]] :: [=Start the WebSocket closing handshake=], with the status code<!--CLOSE CODE--> to use in the - WebSocket Close message being 1001. [[!WSP]] + WebSocket Close frame being 1001. [[!WSP]] : Otherwise :: Do nothing. @@ -1024,7 +1024,7 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, "{{InvalidStateError}}" {{DOMException}} indicating that a closed {{WebSocketStream}} cannot be written to. 1. [=Resolve=] |stream|'s [=closed promise=] with {{WebSocketCloseInfo}} «[ - "{{WebSocketCloseInfo/closeCode}}" → |code|, "{{WebSocketCloseInfo/reason}}" → |reason|]». + "{{WebSocketCloseInfo/closeCode}}" → |code|, "{{WebSocketCloseInfo/reason}}" → |reason| ]». 1. Otherwise, 1. Let |error| be a [=new=] {{WebSocketError}} whose [=WebSocketError/closeCode=] is |code| and [=WebSocketError/reason=] @@ -1066,7 +1066,7 @@ To <dfn>write</dfn> |chunk| to a {{WebSocketStream}} |stream|: 1. Otherwise, 1. Let |string| be the result of [=Converted to an IDL value|converting=] |chunk| to an IDL {{USVString}}. If this throws an exception, return [=a promise rejected with=] the exception. - 1. Let |data| be the result of [=UTF-8 encode|encoding=] |string|. + 1. Let |data| be the result of [=UTF-8 encoding=] |string|. 1. Let |opcode| be a text frame opcode. 1. In parallel, 1. Wait until there is sufficient buffer space in |stream| to send the message. @@ -1137,14 +1137,14 @@ interface WebSocketError : DOMException { Each {{WebSocketError}} object has an associated closeCode, which is -a number. +a number or null. Each {{WebSocketError}} object has an associated reason, which is a string.
: |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message| [, - |init| ] ] + |init|]]) :: Creates a new {{WebSocketError}} object. |message| is a string which will be used to initialize the {{DOMException/message}} attribute of @@ -1153,11 +1153,13 @@ string. The |init| argument is an object whose properties can be set as follows: : {{WebSocketCloseInfo/closeCode}} - :: A number, either 1000 or any integer in the range 3000 to 4999, inclusive. Any other number - will result in an "{{InvalidAccessError}}" {{DOMException}}. + :: A number, either 1000 or any integer in the range 3000 to 4999, inclusive. Any other + number will result in an "{{InvalidAccessError}}" {{DOMException}}. If a non-empty + {{WebSocketCloseInfo/reason}} is set, defaults to 1000, since there is no way to send a non-empty + reason without a close code. : {{WebSocketCloseInfo/reason}} :: A string. Must be 123 bytes or less when converted to [=UTF-8=]. A longer string will result in - a "{{SyntaxError}}" {{DOMException}} being thrown. + a "{{SyntaxError}}" {{DOMException}} being thrown. Defaults to the empty string. : |error|.{{WebSocketError/closeCode}} :: Returns the [=the WebSocket connection close code=]. @@ -1175,10 +1177,10 @@ WebSocketError(|message|, |init|) constructor steps are: 1. Set [=this=]'s [=DOMException/name=] to "WebSocketError". 1. Set [=this=]'s [=DOMException/message=] to |message|. - 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it [=map/exists=], or unset - otherwise. - 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it [=map/exists=], or unset + 1. Let |code| be |init|["{{WebSocketCloseInfo/closeCode}}"] if it [=map/exists=], or null otherwise. + 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it [=map/exists=], or the empty + string otherwise. 1. [=Validate close code and reason=] with |code| and |reason|. 1. If |reason| is non-empty, but |code| is not set, then set |code| to 1000 ("Normal Closure"). 1. Set [=this=]'s [=WebSocketError/closeCode=] to |code|. @@ -1197,13 +1199,14 @@ These algorithms are shared between the {{WebSocket}} and {{WebSocketStream}} in
-To get a URL record given a |url| and |baseURL|, +To get a URL record given a |url| and |baseURL|: 1. Let |urlRecord| be the result of applying the [=URL parser=] to |url| with |baseURL|. 1. If |urlRecord| is failure, then throw a "{{SyntaxError}}" {{DOMException}}. - 1. If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s [=url/scheme=] to "`ws`". + 1. If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s [=url/scheme=] to + "[=ws=]". 1. Otherwise, if |urlRecord|'s [=url/scheme=] is "`https`", set |urlRecord|'s [=url/scheme=] to - "`wss`". + "[=wss=]". 1. If |urlRecord|'s [=scheme=] is not "[=ws=]" or "[=wss=]", then throw a "{{SyntaxError}}" {{DOMException}}. 1. If |urlRecord|'s [=fragment=] is non-null, then throw a "{{SyntaxError}}" {{DOMException}}. @@ -1214,12 +1217,12 @@ To get a URL record given a |url| and |baseURL|,
-To validate close code and reason given a |code| and |reason|, +To validate close code and reason given a |code| and |reason|: 1. If |code| is not null, but is neither an integer equal to 1000 nor an integer in the range 3000 to 4999, inclusive, throw an "{{InvalidAccessError}}" {{DOMException}}. - 1. If |reason| is not null, then run these substeps: - 1. Let |reasonBytes| be the result of encoding |reason|. + 1. If |reason| is not null, then: + 1. Let |reasonBytes| be the result of [=UTF-8 encoding=] |reason|. 1. If |reasonBytes| is longer than 123 bytes, then throw a "{{SyntaxError}}" {{DOMException}}.
@@ -1227,36 +1230,36 @@ To validate close code and reason given a |code| and |reason|,
-To close the WebSocket for a {WebSocket} or {WebSocketStream} [=this=], given optional -|code| and optional |reason|, +To close the WebSocket for a {{WebSocket}} or {{WebSocketStream}} |object|, given +optional |code| and optional |reason|: 1. If |code| was not supplied, let |code| be null. 1. If |reason| was not supplied, let |reason| be the empty string. 1. [=Validate close code and reason=] with |code| and |reason|. 1. Run the first matching steps from the following list:
- : If [=this=]'s [=WebSocket/ready state=] is {{WebSocket/CLOSING}} (2) or {{WebSocket/CLOSED}} (3) + : If |object|'s [=WebSocket/ready state=] is {{WebSocket/CLOSING}} (2) or {{WebSocket/CLOSED}} (3) :: Do nothing.

The connection is already closing or is already closed. If it has not already, a - {{WebSocket/close}} event will eventually fire if [=this=] is a {{WebSocket}}, or the - {{WebSocketStream/closed}} promise will become settled if [=this=] is a {{WebSocketStream}}. + {{WebSocket/close}} event will eventually fire if |object| is a {{WebSocket}}, or the + {{WebSocketStream/closed}} promise will become settled if |object| is a {{WebSocketStream}}. : If the WebSocket connection is not yet [=established=] [[!WSP]] - :: [=Fail the WebSocket connection=] and set [=this=]'s [=WebSocket/ready state=] to + :: [=Fail the WebSocket connection=] and set |object|'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). [[!WSP]]

The [=fail the WebSocket connection=] algorithm invokes the [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket connection is - closed=], which fires the {{WebSocket/close}} event if [=this=] is a {{WebSocket}}, or settles - the {{WebSocketStream/closed}} promise if [=this=] is a {{WebSocketStream}}. + closed=], which fires the {{WebSocket/close}} event if |object| is a {{WebSocket}}, or settles + the {{WebSocketStream/closed}} promise if |object| is a {{WebSocketStream}}. : If the WebSocket closing handshake has not yet been started [[!WSP]] - :: [=Start the WebSocket closing handshake=] and set [=this=]'s [=WebSocket/ready state=] to + :: [=Start the WebSocket closing handshake=] and set |object|'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). [[!WSP]] - If |code| is null and |reason| is the empty string, the WebSocket Close message must not have a + If |code| is null and |reason| is the empty string, the WebSocket Close frame must not have a body.

The WebSocket Protocol erroneously states that the status code is @@ -1266,24 +1269,24 @@ To close the WebSocket for a {WebSocket} or {WebSocketStream} [=this= if |reason| is non-empty but |code| is null, then set |code| to 1000 ("Normal Closure"). If |code| is set, then the status code to use in the WebSocket Close - message must be the integer given by |code|. [[!WSP]] + frame must be the integer given by |code|. [[!WSP]] If |reason| is non-empty, then |reason|, encoded as UTF-8, must be - provided in the Close message after the status code. [[!WSP]] + provided in the Close frame after the status code. [[!WSP]]

The [=start the WebSocket closing handshake=] algorithm eventually invokes the [=close the WebSocket connection=] algorithm, which then establishes that [=the WebSocket - connection is closed=], which fires the {{WebSocket/close}} event if [=this=] is a - {{WebSocket}}, or settles the {{WebSocketStream/closed}} promise if [=this=] is a + connection is closed=], which fires the {{WebSocket/close}} event if |object| is a + {{WebSocket}}, or settles the {{WebSocketStream/closed}} promise if |object| is a {{WebSocketStream}}. : Otherwise - :: Set [=this=]'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2). + :: Set |object|'s [=WebSocket/ready state=] to {{WebSocket/CLOSING}} (2).

[=The WebSocket closing handshake is started=], and will eventually invoke the [=close the WebSocket connection=] algorithm, which will establish that [=the WebSocket connection is closed=], and thus the {{WebSocket/close}} event will fire or the - {{WebSocketStream/closed}} promise will resolve, depending on the type of [=this=]. + {{WebSocketStream/closed}} promise will resolve, depending on the type of |object|.

From 6da74e7d9823d63c460c6fdb649970160ad52edd Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 15:38:13 +0900 Subject: [PATCH 26/33] Add a missing comma Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index f0f0b6f..ad01a63 100644 --- a/index.bs +++ b/index.bs @@ -359,7 +359,7 @@ string. After [=the WebSocket connection is established=], its value might chang The close(|code|, |reason|) method steps are: 1. If |code| is the special value "missing", then set |code| to null. - 1. If |reason| is the special value "missing" then set |reason| to the empty string. + 1. If |reason| is the special value "missing", then set |reason| to the empty string. 1. [=Close the WebSocket=] with [=this=], |code|, and |reason|.
From 16a75fe2fb453ed4d331259a85006e5f91c4017e Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 15:38:44 +0900 Subject: [PATCH 27/33] Fix description of "was ever connected" Co-authored-by: Domenic Denicola --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index ad01a63..702b1fd 100644 --- a/index.bs +++ b/index.bs @@ -775,8 +775,8 @@ which is a {{ReadableStream}}. Each {{WebSocketStream}} object has an associated writable stream, which is a {{WritableStream}}. -Each {{WebSocketStream}} object has an associated boolean was ever -connected, which is initially false. +Each {{WebSocketStream}} object has an associated was ever +connected, which is a boolean, initially false. Each {{WebSocketStream}} object has an associated boolean handshake aborted, which is initially false. From 2bd7ad565887d7cbccbfb5a8dcd3ca396a67003c Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 15:39:13 +0900 Subject: [PATCH 28/33] Fix grammar in developer notes Co-authored-by: Domenic Denicola --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 702b1fd..3ad6f46 100644 --- a/index.bs +++ b/index.bs @@ -789,7 +789,7 @@ state=], but is not exposed to JavaScript.
: |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options|]) - :: Creates a new {{WebSocketStream}} object, immediately establishing the associating WebSocket + :: Creates a new {{WebSocketStream}} object, immediately establishing the associated WebSocket connection. |url| is a string giving the URL over which the connection is established. From b2e001c9aa7a36a2539cbf6d95c82949e6ec0cf1 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 15:51:45 +0900 Subject: [PATCH 29/33] Apply cleanups by @domenic. Co-authored-by: Domenic Denicola --- index.bs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/index.bs b/index.bs index 3ad6f46..4c9446b 100644 --- a/index.bs +++ b/index.bs @@ -788,7 +788,7 @@ state=], but is not exposed to JavaScript.
- : |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url| [, |options|]) + : |socket| = new {{WebSocketStream/constructor(url, options)|WebSocketStream}}(|url|[, |options|]) :: Creates a new {{WebSocketStream}} object, immediately establishing the associated WebSocket connection. @@ -813,7 +813,7 @@ state=], but is not exposed to JavaScript. :: Returns the [=WebSocketStream/url|URL=] that was used to establish the WebSocket connection. : |socket|.{{WebSocketStream/opened}} - :: Returns a {{promise}} which resolves when the handshake successfully completes, or rejects if + :: Returns a {{Promise}} which resolves when the handshake successfully completes, or rejects if the handshake fails. On success, it resolves to an object with the following properties: : {{WebSocketOpenInfo/readable}} @@ -823,7 +823,7 @@ state=], but is not exposed to JavaScript.

The original {{WebSocket}} API supplied {{ArrayBuffer}} objects, but modern practice is to prefer the {{Uint8Array}} type for binary data, particularly when using streams. - The underlying {{ArrayBuffer}} object can be recovered by calling chunk.buffer. The stream can be closed by calling {{ReadableStream/cancel()}} on @@ -858,7 +858,7 @@ state=], but is not exposed to JavaScript. :: The [=subprotocol in use=] for the connection. : |socket|.{{WebSocketStream/closed}} - :: A {{promise}} which resolves when the connection is closed. If the connection did not close + :: A {{Promise}} which resolves when the connection is closed. If the connection did not close [=cleanly=] then the promise is rejected with a {{WebSocketError}}. When the connection closes [=cleanly=] the promise is fulfilled with an object with properties {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}}, giving [=the WebSocket @@ -866,6 +866,7 @@ state=], but is not exposed to JavaScript. server. : |socket|.{{WebSocketStream/close()}} + : |socket|.{{WebSocketStream/close({ {{WebSocketCloseInfo/closeCode}}, {{WebSocketCloseInfo/reason}} })}} :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}} properties to indicate [=the WebSocket connection close code=] and [=the WebSocket connection close reason=] that will be sent to the remote server. If the handshake @@ -943,12 +944,12 @@ object=] to run these steps:

- 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/OPEN}} (1). + 1. Change |stream|'s [=WebSocketStream/ready state=] to {{WebSocket/OPEN}} (1). 1. Set |stream|'s [=WebSocketStream/was ever connected=] to true. 1. Let |extensions| be the [=extensions in use=]. 1. Let |protocol| be the [=subprotocol in use=]. 1. Let |pullAlgorithm| be an action that [=pulls bytes=] from |stream|. - 1. Let |cancelAlgorithm| be an action that [=cancels=] the {{WebSocketStream}} with |stream| and + 1. Let |cancelAlgorithm| be an action that [=cancels=] |stream| with |reason|, given |reason|. 1. Let |readable| be a [=new=] {{ReadableStream}}. 1. [=ReadableStream/Set up=] |readable| with |pullAlgorithm| and |cancelAlgorithm|. @@ -975,7 +976,7 @@ When [=a WebSocket message has been received=] for a {{WebSocketStream}} |stream and data |data|, the user agent must [=queue a global task=] on the [=WebSocket task source=] given |stream|'s [=relevant global object=] to follow these steps: [[!WSP]] - 1. If [=WebSocket/ready state=] is not {{WebSocket/OPEN}} (1), then return. + 1. If |stream|'s [=WebSocketStream/ready state=] is not {{WebSocket/OPEN}} (1), then return. 1. Let |chunk| be determined by switching on |type|:
@@ -1000,7 +1001,7 @@ and data |data|, the user agent must [=queue a global task=] on the [=WebSocket When [=the WebSocket closing handshake is started=] for a {{WebSocketStream}} |stream|, the user agent must [=queue a global task=] on the [=WebSocket task source=] given -|stream|'s [=relevant global object=] to change the |stream|'s [=WebSocketStream/ready state=] to +|stream|'s [=relevant global object=] to change |stream|'s [=WebSocketStream/ready state=] to {{WebSocket/CLOSING}} if it is not already. [[!WSP]]
@@ -1011,10 +1012,10 @@ When [=the WebSocket connection is closed=] for a {{WebSocketStream}} |stream|, [=cleanly=], the user agent must [=queue a global task=] on the [=WebSocket task source=] given |stream|'s [=relevant global object=] to run the following substeps: - 1. Change the [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). + 1. Change |stream|'s [=WebSocketStream/ready state=] to {{WebSocket/CLOSED}} (3). 1. If |stream|'s [=WebSocketStream/handshake aborted=] is true, then return. 1. If |stream|'s [=WebSocketStream/was ever connected=] is false, then [=reject=] |stream|'s - [=opened promise=] with a [=new=] WebSocketError. + [=opened promise=] with a [=new=] {{WebSocketError}}. 1. Let |code| be [=the WebSocket connection close code=]. 1. Let |reason| be the result of applying [=UTF-8 decode without BOM=] to [=the WebSocket connection close reason=]. @@ -1143,7 +1144,7 @@ Each {{WebSocketError}} object has an associated reaso string.
- : |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message| [, + : |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message|[, |init|]]) :: Creates a new {{WebSocketError}} object. @@ -1182,7 +1183,7 @@ WebSocketError(|message|, |init|) constructor steps are: 1. Let |reason| be |init|["{{WebSocketCloseInfo/reason}}"] if it [=map/exists=], or the empty string otherwise. 1. [=Validate close code and reason=] with |code| and |reason|. - 1. If |reason| is non-empty, but |code| is not set, then set |code| to 1000 ("Normal Closure"). + 1. If |reason| is non-empty, but |code| is null, then set |code| to 1000 ("Normal Closure"). 1. Set [=this=]'s [=WebSocketError/closeCode=] to |code|. 1. Set [=this=]'s [=WebSocketError/reason=] to |reason|.
From 1e2eae9549b16ea5f6f1445313da7f62f28addb8 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 16:43:46 +0900 Subject: [PATCH 30/33] Fixes requested by @domenic --- index.bs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 4c9446b..a6dada7 100644 --- a/index.bs +++ b/index.bs @@ -991,10 +991,10 @@ and data |data|, the user agent must [=queue a global task=] on the [=WebSocket 1. [=ReadableStream/Enqueue=] |chunk| into |stream|'s [=WebSocketStream/readable stream=]. 1. Apply backpressure to the WebSocket. -

Applying backpressure should result in no new WebSocket - messages being handled until the backpressure is released. After an implementation-defined amount - of data is buffered, the client will stop reading from the underlying network connection resulting - in network-layer backpressure being applied. +

Applying backpressure will result in no new WebSocket messages being handled until + the backpressure is released. After an implementation-defined amount of data is buffered, the + client will stop reading from the underlying network connection resulting in network-layer + backpressure being applied.


@@ -1002,7 +1002,7 @@ and data |data|, the user agent must [=queue a global task=] on the [=WebSocket When [=the WebSocket closing handshake is started=] for a {{WebSocketStream}} |stream|, the user agent must [=queue a global task=] on the [=WebSocket task source=] given |stream|'s [=relevant global object=] to change |stream|'s [=WebSocketStream/ready state=] to -{{WebSocket/CLOSING}} if it is not already. [[!WSP]] +{{WebSocket/CLOSING}} [[!WSP]]
@@ -1061,14 +1061,16 @@ To cancel a {{WebSocketStream}} |stream| given |reason|, [=close usin To write |chunk| to a {{WebSocketStream}} |stream|: 1. Let |promise| be [=a new promise=] created in |stream|'s [=relevant realm=]. + 1. Let |data| be null. + 1. Let |opcode| be null. 1. If |chunk| is a {{BufferSource}}, - 1. Let |data| be a [=get a copy of the buffer source|copy of the bytes=] given |chunk|. - 1. Let |opcode| be a binary frame opcode. + 1. Set |data| to a [=get a copy of the buffer source|copy of the bytes=] given |chunk|. + 1. Set |opcode| to a binary frame opcode. 1. Otherwise, 1. Let |string| be the result of [=Converted to an IDL value|converting=] |chunk| to an IDL {{USVString}}. If this throws an exception, return [=a promise rejected with=] the exception. - 1. Let |data| be the result of [=UTF-8 encoding=] |string|. - 1. Let |opcode| be a text frame opcode. + 1. Set |data| to the result of [=UTF-8 encoding=] |string|. + 1. Set |opcode| to a text frame opcode. 1. In parallel, 1. Wait until there is sufficient buffer space in |stream| to send the message. From 24f24f81d3c70a09724affc35a8c37538a3e95fb Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 16:53:56 +0900 Subject: [PATCH 31/33] Fix Bikeshed compile error Don't nest a link inside another link. --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index a6dada7..090bd95 100644 --- a/index.bs +++ b/index.bs @@ -866,7 +866,7 @@ state=], but is not exposed to JavaScript. server. : |socket|.{{WebSocketStream/close()}} - : |socket|.{{WebSocketStream/close({ {{WebSocketCloseInfo/closeCode}}, {{WebSocketCloseInfo/reason}} })}} + : |socket|.{{WebSocketStream/close()|close}}({ {{WebSocketCloseInfo/closeCode}}, {{WebSocketCloseInfo/reason}} }) :: Close the connection, optionally supplying an object with {{WebSocketCloseInfo/closeCode}} and {{WebSocketCloseInfo/reason}} properties to indicate [=the WebSocket connection close code=] and [=the WebSocket connection close reason=] that will be sent to the remote server. If the handshake From a6d426fe517978df0a87a7739ef9caae09a8e670 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 21 Feb 2024 17:00:03 +0900 Subject: [PATCH 32/33] Set defaults for WebSocketError's closeCode and reason --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 090bd95..a950641 100644 --- a/index.bs +++ b/index.bs @@ -1140,10 +1140,10 @@ interface WebSocketError : DOMException { Each {{WebSocketError}} object has an associated closeCode, which is -a number or null. +a number or null, and defaults to null. Each {{WebSocketError}} object has an associated reason, which is a -string. +string, and defaults to the empty string.
: |error| = new {{WebSocketError/constructor(message, init)|WebSocketError}}([|message|[, From d5570f3448ce165eacca2e0a519eeacd4d4a5123 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Thu, 28 Mar 2024 16:47:41 +0900 Subject: [PATCH 33/33] Add default arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebIDL requires explicit defaults for optional arguments. Co-authored-by: Philip Jägenstedt --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index a950641..9f4b8e7 100644 --- a/index.bs +++ b/index.bs @@ -752,11 +752,11 @@ dictionary WebSocketStreamOptions { [Exposed=(Window,Worker)] interface WebSocketStream { - constructor(USVString url, optional WebSocketStreamOptions options); + constructor(USVString url, optional WebSocketStreamOptions options = {}); readonly attribute USVString url; readonly attribute Promise opened; readonly attribute Promise closed; - undefined close(optional WebSocketCloseInfo closeInfo); + undefined close(optional WebSocketCloseInfo closeInfo = {}); };