From 3c97bbe165ef0c5e4fa9b9eb637ff481d8c86884 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 23 May 2022 13:21:11 +0200 Subject: [PATCH] fix(ext/websocket): WebSocket dispatch single close event (#13443) --- cli/tests/integration/mod.rs | 8 +++--- ext/http/01_http.js | 34 ++++++++----------------- ext/websocket/01_websocket.js | 48 +++++++++++++++++++++++++++++------ ext/websocket/lib.rs | 3 +++ tools/wpt/expectation.json | 8 +++--- 5 files changed, 62 insertions(+), 39 deletions(-) diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 7de4418743..d0c693a377 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -738,10 +738,12 @@ fn websocket_server_multi_field_connection_header() { .uri("ws://localhost:4319") .body(()) .unwrap(); - assert!( + let (mut socket, _) = deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::connect(req) - .is_ok() - ); + .unwrap(); + let message = socket.read_message().unwrap(); + assert_eq!(message, deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::Message::Close(None)); + socket.close(None).unwrap(); assert!(child.wait().unwrap().success()); } diff --git a/ext/http/01_http.js b/ext/http/01_http.js index c9fe8220d3..de546285d3 100644 --- a/ext/http/01_http.js +++ b/ext/http/01_http.js @@ -357,32 +357,18 @@ httpConn.close(); - if (ws[_readyState] === WebSocket.CLOSING) { - await core.opAsync("op_ws_close", wsRid); + ws[_readyState] = WebSocket.OPEN; + const event = new Event("open"); + ws.dispatchEvent(event); - ws[_readyState] = WebSocket.CLOSED; - - const errEvent = new ErrorEvent("error"); - ws.dispatchEvent(errEvent); - - const event = new CloseEvent("close"); - ws.dispatchEvent(event); - - core.tryClose(wsRid); - } else { - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); - - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); + ws[_eventLoop](); + if (ws[_idleTimeoutDuration]) { + ws.addEventListener( + "close", + () => clearTimeout(ws[_idleTimeoutTimeout]), + ); } + ws[_serverHandleIdleTimeout](); } } finally { if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index 19c5e28758..3585256fc6 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -29,6 +29,8 @@ StringPrototypeToLowerCase, Symbol, SymbolIterator, + PromisePrototypeCatch, + SymbolFor, } = window.__bootstrap.primordials; webidl.converters["sequence or DOMString"] = (V, opts) => { @@ -367,16 +369,19 @@ } else if (this[_readyState] === OPEN) { this[_readyState] = CLOSING; - PromisePrototypeThen( + PromisePrototypeCatch( core.opAsync("op_ws_close", this[_rid], code, reason), - () => { + (err) => { this[_readyState] = CLOSED; - const event = new CloseEvent("close", { - wasClean: true, - code: code ?? 1005, - reason, + + const errorEv = new ErrorEvent("error", { + error: err, + message: ErrorPrototypeToString(err), }); - this.dispatchEvent(event); + this.dispatchEvent(errorEv); + + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); core.tryClose(this[_rid]); }, ); @@ -384,7 +389,7 @@ } async [_eventLoop]() { - while (this[_readyState] === OPEN) { + while (this[_readyState] !== CLOSED) { const { kind, value } = await core.opAsync( "op_ws_next_event", this[_rid], @@ -429,9 +434,23 @@ } case "closed": case "close": { + const prevState = this[_readyState]; this[_readyState] = CLOSED; clearTimeout(this[_idleTimeoutTimeout]); + if (prevState === OPEN) { + try { + await core.opAsync( + "op_ws_close", + this[_rid], + value.code, + value.reason, + ); + } catch { + // ignore failures + } + } + const event = new CloseEvent("close", { wasClean: true, code: value.code, @@ -487,6 +506,19 @@ }, (this[_idleTimeoutDuration] / 2) * 1000); } } + + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + readyState: this.readyState, + extensions: this.extensions, + protocol: this.protocol, + binaryType: this.binaryType, + bufferedAmount: this.bufferedAmount, + }) + }`; + } } ObjectDefineProperties(WebSocket, { diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 163ba46dd0..267231238b 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -136,6 +136,9 @@ impl WsStreamResource { match res { Ok(()) => Ok(()), Err(Error::ConnectionClosed) => Ok(()), + Err(tokio_tungstenite::tungstenite::Error::Protocol( + tokio_tungstenite::tungstenite::error::ProtocolError::SendAfterClosing, + )) => Ok(()), Err(err) => Err(err.into()), } } diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 018b3ebd33..1caf96c9d1 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -4184,12 +4184,12 @@ "eventhandlers.any.worker.html?wss": true, "referrer.any.html": true, "referrer.any.worker.html": true, - "Close-delayed.any.html": false, + "Close-delayed.any.html": true, "Close-delayed.any.html?wpt_flags=h2": false, - "Close-delayed.any.html?wss": false, - "Close-delayed.any.worker.html": false, + "Close-delayed.any.html?wss": true, + "Close-delayed.any.worker.html": true, "Close-delayed.any.worker.html?wpt_flags=h2": false, - "Close-delayed.any.worker.html?wss": false, + "Close-delayed.any.worker.html?wss": true, "stream": { "tentative": { "abort.any.html?wss": true,