diff --git a/lib/web/websocket/connection.js b/lib/web/websocket/connection.js index 4ecc8a195fc..10dcec6a0cc 100644 --- a/lib/web/websocket/connection.js +++ b/lib/web/websocket/connection.js @@ -1,6 +1,6 @@ 'use strict' -const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants') +const { uid, states, sentCloseFrameState, closingHandshakeStates, emptyBuffer, opcodes } = require('./constants') const { parseExtensions, isClosed, isClosing, isEstablished, isConnecting, validateCloseCodeAndReason } = require('./util') const { makeRequest } = require('../fetch/request') const { fetching } = require('../fetch/index') @@ -245,7 +245,7 @@ function closeWebSocketConnection (object, code, reason, validate = false) { // Fail the WebSocket connection and set object’s ready state to CLOSING (2). [WSP] failWebsocketConnection(object) object.readyState = states.CLOSING - } else if (!object.closeState.has(sentCloseFrameState.SENT) && !object.closeState.has(sentCloseFrameState.RECEIVED)) { + } else if (object.closeState.isDisjointFrom(closingHandshakeStates)) { // Upon either sending or receiving a Close control frame, it is said // that _The WebSocket Closing Handshake is Started_ and that the // WebSocket connection is in the CLOSING state. diff --git a/lib/web/websocket/constants.js b/lib/web/websocket/constants.js index e4e69901c96..e12992a5224 100644 --- a/lib/web/websocket/constants.js +++ b/lib/web/websocket/constants.js @@ -46,6 +46,19 @@ const sentCloseFrameState = { RECEIVED: 2 } +/** + * Tracks whether the closing handshake has started or completed. + * + * A close state that is disjoint from this set has not started the closing + * handshake yet. A close state that is a superset of this set has completed it. + * + * @type {ReadonlySet} + */ +const closingHandshakeStates = new Set([ + sentCloseFrameState.SENT, + sentCloseFrameState.RECEIVED +]) + /** * The WebSocket opcodes. * @@ -116,6 +129,7 @@ const sendHints = { module.exports = { uid, sentCloseFrameState, + closingHandshakeStates, staticPropertyDescriptors, states, opcodes, diff --git a/lib/web/websocket/receiver.js b/lib/web/websocket/receiver.js index 384808d1b7e..a11264a0c43 100644 --- a/lib/web/websocket/receiver.js +++ b/lib/web/websocket/receiver.js @@ -2,7 +2,7 @@ const { Writable } = require('node:stream') const assert = require('node:assert') -const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants') +const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState, closingHandshakeStates } = require('./constants') const { isValidStatusCode, isValidOpcode, @@ -394,7 +394,7 @@ class ByteParser extends Writable { // Upon receiving such a frame, the other peer sends a // Close frame in response, if it hasn't already sent one. - if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) { + if (this.#handler.closeState.isDisjointFrom(closingHandshakeStates)) { // If an endpoint receives a Close frame and did not previously send a // Close frame, the endpoint MUST send a Close frame in response. (When // sending a Close frame in response, the endpoint typically echos the diff --git a/lib/web/websocket/stream/websocketstream.js b/lib/web/websocket/stream/websocketstream.js index ca40ad08dae..f933b855e20 100644 --- a/lib/web/websocket/stream/websocketstream.js +++ b/lib/web/websocket/stream/websocketstream.js @@ -2,7 +2,7 @@ const { createDeferredPromise } = require('../../../util/promise') const { environmentSettingsObject } = require('../../fetch/util') -const { states, opcodes, sentCloseFrameState } = require('../constants') +const { states, opcodes, closingHandshakeStates } = require('../constants') const { webidl } = require('../../webidl') const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util') const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection') @@ -242,7 +242,7 @@ class WebSocketStream { // 6.1. Wait until there is sufficient buffer space in stream to send the message. // 6.2. If the closing handshake has not yet started , Send a WebSocket Message to stream comprised of data using opcode . - if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) { + if (this.#handler.closeState.isDisjointFrom(closingHandshakeStates)) { const frame = new WebsocketFrameSend(data) this.#handler.socket.write(frame.createFrame(opcode), () => { @@ -347,9 +347,7 @@ class WebSocketStream { /** @type {import('../websocket').Handler['onSocketClose']} */ #onSocketClose () { - const wasClean = - this.#handler.closeState.has(sentCloseFrameState.SENT) && - this.#handler.closeState.has(sentCloseFrameState.RECEIVED) + const wasClean = this.#handler.closeState.isSupersetOf(closingHandshakeStates) // 1. Change the ready state to CLOSED (3). this.#handler.readyState = states.CLOSED @@ -376,7 +374,7 @@ class WebSocketStream { // 1006. let code = result?.code ?? 1005 - if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) { + if (this.#handler.closeState.isDisjointFrom(closingHandshakeStates)) { code = 1006 } diff --git a/lib/web/websocket/websocket.js b/lib/web/websocket/websocket.js index da94ab5b352..0ebc698a66e 100644 --- a/lib/web/websocket/websocket.js +++ b/lib/web/websocket/websocket.js @@ -4,7 +4,7 @@ const { isArrayBuffer } = require('node:util/types') const { webidl } = require('../webidl') const { URLSerializer } = require('../fetch/data-url') const { environmentSettingsObject } = require('../fetch/util') -const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants') +const { staticPropertyDescriptors, states, sentCloseFrameState, closingHandshakeStates, sendHints, opcodes } = require('./constants') const { isConnecting, isEstablished, @@ -569,9 +569,7 @@ class WebSocket extends EventTarget { // If the TCP connection was closed after the // WebSocket closing handshake was completed, the WebSocket connection // is said to have been closed _cleanly_. - const wasClean = - this.#handler.closeState.has(sentCloseFrameState.SENT) && - this.#handler.closeState.has(sentCloseFrameState.RECEIVED) + const wasClean = this.#handler.closeState.isSupersetOf(closingHandshakeStates) let code = 1005 let reason = '' diff --git a/test/web-platform-tests/wpt-runner.mjs b/test/web-platform-tests/wpt-runner.mjs index 702ad159cb0..a84d8102378 100644 --- a/test/web-platform-tests/wpt-runner.mjs +++ b/test/web-platform-tests/wpt-runner.mjs @@ -32,6 +32,7 @@ const SERVER_READY_CHECKS = [ ['wss', (line) => line.includes('wss on port') && line.includes('Listen on:')], ['h2', (line) => line.includes('h2 on port 9000') && line.includes('Starting http2 server')] ] +const SERVER_READY_CHECK_NAMES = new Set(SERVER_READY_CHECKS.map(([name]) => name)) function streamServerLogs (stream, target, onLine) { let buffer = '' @@ -174,10 +175,7 @@ async function runWithTestUtil (testFunction) { const readinessTimeout = setTimeout(() => { if (!readySettled) { readySettled = true - const missing = SERVER_READY_CHECKS - .map(([name]) => name) - .filter((name) => !readyChecks.has(name)) - .join(', ') + const missing = [...SERVER_READY_CHECK_NAMES.difference(readyChecks)].join(', ') rejectReady(new Error(`Timed out waiting for WPT server readiness. Missing: ${missing}`)) } }, 30_000)