summaryrefslogtreecommitdiff
path: root/extensions/websocket/01_websocket.js
diff options
context:
space:
mode:
authorLeo K <crowlkats@toaxl.com>2021-06-21 12:15:08 +0200
committerGitHub <noreply@github.com>2021-06-21 12:15:08 +0200
commitafe89e8850d9ca2adcf73c8d2d63f5054a594878 (patch)
tree3b0ff11ac204e76cfa3c83d486a40b122b4004d8 /extensions/websocket/01_websocket.js
parent952caa79b32e6c249977281ed494d4b1f98ed451 (diff)
fix(websocket): spec conformance & WPT (#11010)
Diffstat (limited to 'extensions/websocket/01_websocket.js')
-rw-r--r--extensions/websocket/01_websocket.js322
1 files changed, 206 insertions, 116 deletions
diff --git a/extensions/websocket/01_websocket.js b/extensions/websocket/01_websocket.js
index 3c3e12f83..225c6725b 100644
--- a/extensions/websocket/01_websocket.js
+++ b/extensions/websocket/01_websocket.js
@@ -3,28 +3,41 @@
((window) => {
const core = window.Deno.core;
-
- // provided by "deno_web"
const { URL } = window.__bootstrap.url;
+ const webidl = window.__bootstrap.webidl;
+ const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra;
+
+ webidl.converters["sequence<DOMString> or DOMString"] = (V, opts) => {
+ // Union for (sequence<DOMString> or DOMString)
+ if (webidl.type(V) === "Object" && V !== null) {
+ if (V[Symbol.iterator] !== undefined) {
+ return webidl.converters["sequence<DOMString>"](V, opts);
+ }
+ }
+ return webidl.converters.DOMString(V, opts);
+ };
+
+ webidl.converters["WebSocketSend"] = (V, opts) => {
+ // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString)
+ if (V instanceof Blob) {
+ return webidl.converters["Blob"](V, opts);
+ }
+ if (typeof V === "object") {
+ if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) {
+ return webidl.converters["ArrayBuffer"](V, opts);
+ }
+ if (ArrayBuffer.isView(V)) {
+ return webidl.converters["ArrayBufferView"](V, opts);
+ }
+ }
+ return webidl.converters["USVString"](V, opts);
+ };
const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
const CLOSED = 3;
- function requiredArguments(
- name,
- length,
- required,
- ) {
- if (length < required) {
- const errMsg = `${name} requires at least ${required} argument${
- required === 1 ? "" : "s"
- }, but only ${length} present`;
- throw new TypeError(errMsg);
- }
- }
-
/**
* Tries to close the resource (and ignores BadResource errors).
* @param {number} rid
@@ -74,14 +87,104 @@
});
}
+ const _readyState = Symbol("[[readyState]]");
+ const _url = Symbol("[[url]]");
+ const _rid = Symbol("[[rid]]");
+ const _extensions = Symbol("[[extensions]]");
+ const _protocol = Symbol("[[protocol]]");
+ const _binaryType = Symbol("[[binaryType]]");
+ const _bufferedAmount = Symbol("[[bufferedAmount]]");
class WebSocket extends EventTarget {
- #readyState = CONNECTING;
+ [_rid];
+
+ [_readyState] = CONNECTING;
+ get readyState() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_readyState];
+ }
+
+ get CONNECTING() {
+ webidl.assertBranded(this, WebSocket);
+ return CONNECTING;
+ }
+ get OPEN() {
+ webidl.assertBranded(this, WebSocket);
+ return OPEN;
+ }
+ get CLOSING() {
+ webidl.assertBranded(this, WebSocket);
+ return CLOSING;
+ }
+ get CLOSED() {
+ webidl.assertBranded(this, WebSocket);
+ return CLOSED;
+ }
+
+ [_extensions] = "";
+ get extensions() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_extensions];
+ }
+
+ [_protocol] = "";
+ get protocol() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_protocol];
+ }
+
+ [_url] = "";
+ get url() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_url];
+ }
+
+ [_binaryType] = "blob";
+ get binaryType() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_binaryType];
+ }
+ set binaryType(value) {
+ webidl.assertBranded(this, WebSocket);
+ value = webidl.converters.DOMString(value, {
+ prefix: "Failed to set 'binaryType' on 'WebSocket'",
+ });
+ if (value === "blob" || value === "arraybuffer") {
+ this[_binaryType] = value;
+ }
+ }
+
+ [_bufferedAmount] = 0;
+ get bufferedAmount() {
+ webidl.assertBranded(this, WebSocket);
+ return this[_bufferedAmount];
+ }
constructor(url, protocols = []) {
super();
- requiredArguments("WebSocket", arguments.length, 1);
-
- const wsURL = new URL(url);
+ this[webidl.brand] = webidl.brand;
+ const prefix = "Failed to construct 'WebSocket'";
+ webidl.requiredArguments(arguments.length, 1, {
+ prefix,
+ });
+ url = webidl.converters.USVString(url, {
+ prefix,
+ context: "Argument 1",
+ });
+ protocols = webidl.converters["sequence<DOMString> or DOMString"](
+ protocols,
+ {
+ prefix,
+ context: "Argument 2",
+ },
+ );
+
+ let wsURL;
+
+ try {
+ wsURL = new URL(url);
+ } catch (e) {
+ throw new DOMException(e.message, "SyntaxError");
+ }
if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") {
throw new DOMException(
@@ -97,16 +200,16 @@
);
}
- this.#url = wsURL.href;
+ this[_url] = wsURL.href;
- core.opSync("op_ws_check_permission", this.#url);
+ core.opSync("op_ws_check_permission", this[_url]);
- if (protocols && typeof protocols === "string") {
+ if (typeof protocols === "string") {
protocols = [protocols];
}
if (
- protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x))
+ protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size
) {
throw new DOMException(
"Can't supply multiple times the same protocol.",
@@ -114,19 +217,28 @@
);
}
+ if (
+ protocols.some((protocol) => !HTTP_TOKEN_CODE_POINT_RE.test(protocol))
+ ) {
+ throw new DOMException(
+ "Invalid protocol value.",
+ "SyntaxError",
+ );
+ }
+
core.opAsync("op_ws_create", {
url: wsURL.href,
protocols: protocols.join(", "),
}).then((create) => {
- this.#rid = create.rid;
- this.#extensions = create.extensions;
- this.#protocol = create.protocol;
+ this[_rid] = create.rid;
+ this[_extensions] = create.extensions;
+ this[_protocol] = create.protocol;
- if (this.#readyState === CLOSING) {
+ if (this[_readyState] === CLOSING) {
core.opAsync("op_ws_close", {
- rid: this.#rid,
+ rid: this[_rid],
}).then(() => {
- this.#readyState = CLOSED;
+ this[_readyState] = CLOSED;
const errEvent = new ErrorEvent("error");
errEvent.target = this;
@@ -135,10 +247,10 @@
const event = new CloseEvent("close");
event.target = this;
this.dispatchEvent(event);
- tryClose(this.#rid);
+ tryClose(this[_rid]);
});
} else {
- this.#readyState = OPEN;
+ this[_readyState] = OPEN;
const event = new Event("open");
event.target = this;
this.dispatchEvent(event);
@@ -146,7 +258,7 @@
this.#eventLoop();
}
}).catch((err) => {
- this.#readyState = CLOSED;
+ this[_readyState] = CLOSED;
const errorEv = new ErrorEvent(
"error",
@@ -161,67 +273,29 @@
});
}
- get CONNECTING() {
- return CONNECTING;
- }
- get OPEN() {
- return OPEN;
- }
- get CLOSING() {
- return CLOSING;
- }
- get CLOSED() {
- return CLOSED;
- }
-
- get readyState() {
- return this.#readyState;
- }
-
- #extensions = "";
- #protocol = "";
- #url = "";
- #rid;
-
- get extensions() {
- return this.#extensions;
- }
- get protocol() {
- return this.#protocol;
- }
-
- #binaryType = "blob";
- get binaryType() {
- return this.#binaryType;
- }
- set binaryType(value) {
- if (value === "blob" || value === "arraybuffer") {
- this.#binaryType = value;
- }
- }
- #bufferedAmount = 0;
- get bufferedAmount() {
- return this.#bufferedAmount;
- }
-
- get url() {
- return this.#url;
- }
-
send(data) {
- requiredArguments("WebSocket.send", arguments.length, 1);
+ webidl.assertBranded(this, WebSocket);
+ const prefix = "Failed to execute 'send' on 'WebSocket'";
+
+ webidl.requiredArguments(arguments.length, 1, {
+ prefix,
+ });
+ data = webidl.converters.WebSocketSend(data, {
+ prefix,
+ context: "Argument 1",
+ });
- if (this.#readyState != OPEN) {
- throw Error("readyState not OPEN");
+ if (this[_readyState] !== OPEN) {
+ throw new DOMException("readyState not OPEN", "InvalidStateError");
}
const sendTypedArray = (ta) => {
- this.#bufferedAmount += ta.size;
+ this[_bufferedAmount] += ta.byteLength;
core.opAsync("op_ws_send", {
- rid: this.#rid,
+ rid: this[_rid],
kind: "binary",
}, ta).then(() => {
- this.#bufferedAmount -= ta.size;
+ this[_bufferedAmount] -= ta.byteLength;
});
};
@@ -229,80 +303,94 @@
data.slice().arrayBuffer().then((ab) =>
sendTypedArray(new DataView(ab))
);
- } else if (
- data instanceof Int8Array || data instanceof Int16Array ||
- data instanceof Int32Array || data instanceof Uint8Array ||
- data instanceof Uint16Array || data instanceof Uint32Array ||
- data instanceof Uint8ClampedArray || data instanceof Float32Array ||
- data instanceof Float64Array || data instanceof DataView
- ) {
+ } else if (ArrayBuffer.isView(data)) {
sendTypedArray(data);
} else if (data instanceof ArrayBuffer) {
sendTypedArray(new DataView(data));
} else {
const string = String(data);
const d = core.encode(string);
- this.#bufferedAmount += d.size;
+ this[_bufferedAmount] += d.byteLength;
core.opAsync("op_ws_send", {
- rid: this.#rid,
+ rid: this[_rid],
kind: "text",
text: string,
}).then(() => {
- this.#bufferedAmount -= d.size;
+ this[_bufferedAmount] -= d.byteLength;
});
}
}
- close(code, reason) {
- if (code && !(code === 1000 || (3000 <= code && code < 5000))) {
+ close(code = undefined, reason = undefined) {
+ webidl.assertBranded(this, WebSocket);
+ const prefix = "Failed to execute 'close' on 'WebSocket'";
+
+ if (code !== undefined) {
+ code = webidl.converters["unsigned short"](code, {
+ prefix,
+ clamp: true,
+ context: "Argument 1",
+ });
+ }
+
+ if (reason !== undefined) {
+ reason = webidl.converters.USVString(reason, {
+ prefix,
+ context: "Argument 2",
+ });
+ }
+
+ if (
+ code !== undefined && !(code === 1000 || (3000 <= code && code < 5000))
+ ) {
throw new DOMException(
"The close code must be either 1000 or in the range of 3000 to 4999.",
- "NotSupportedError",
+ "InvalidAccessError",
);
}
- if (reason && core.encode(reason).byteLength > 123) {
+ if (reason !== undefined && core.encode(reason).byteLength > 123) {
throw new DOMException(
"The close reason may not be longer than 123 bytes.",
"SyntaxError",
);
}
- if (this.#readyState === CONNECTING) {
- this.#readyState = CLOSING;
- } else if (this.#readyState === OPEN) {
- this.#readyState = CLOSING;
+ if (this[_readyState] === CONNECTING) {
+ this[_readyState] = CLOSING;
+ } else if (this[_readyState] === OPEN) {
+ this[_readyState] = CLOSING;
core.opAsync("op_ws_close", {
- rid: this.#rid,
+ rid: this[_rid],
code,
reason,
}).then(() => {
- this.#readyState = CLOSED;
+ this[_readyState] = CLOSED;
const event = new CloseEvent("close", {
wasClean: true,
- code,
+ code: code ?? 1005,
reason,
});
event.target = this;
this.dispatchEvent(event);
- tryClose(this.#rid);
+ tryClose(this[_rid]);
});
}
}
async #eventLoop() {
- while (this.#readyState === OPEN) {
+ while (this[_readyState] === OPEN) {
const { kind, value } = await core.opAsync(
"op_ws_next_event",
- this.#rid,
+ this[_rid],
);
switch (kind) {
case "string": {
const event = new MessageEvent("message", {
data: value,
- origin: this.#url,
+ origin: this[_url],
});
event.target = this;
this.dispatchEvent(event);
@@ -319,7 +407,7 @@
const event = new MessageEvent("message", {
data,
- origin: this.#url,
+ origin: this[_url],
});
event.target = this;
this.dispatchEvent(event);
@@ -327,13 +415,13 @@
}
case "ping": {
core.opAsync("op_ws_send", {
- rid: this.#rid,
+ rid: this[_rid],
kind: "pong",
});
break;
}
case "close": {
- this.#readyState = CLOSED;
+ this[_readyState] = CLOSED;
const event = new CloseEvent("close", {
wasClean: true,
@@ -342,11 +430,11 @@
});
event.target = this;
this.dispatchEvent(event);
- tryClose(this.#rid);
+ tryClose(this[_rid]);
break;
}
case "error": {
- this.#readyState = CLOSED;
+ this[_readyState] = CLOSED;
const errorEv = new ErrorEvent("error", {
message: value,
@@ -357,7 +445,7 @@
const closeEv = new CloseEvent("close");
closeEv.target = this;
this.dispatchEvent(closeEv);
- tryClose(this.#rid);
+ tryClose(this[_rid]);
break;
}
}
@@ -385,5 +473,7 @@
defineEventHandler(WebSocket.prototype, "close");
defineEventHandler(WebSocket.prototype, "open");
+ webidl.configurePrototype(WebSocket);
+
window.__bootstrap.webSocket = { WebSocket };
})(this);