Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use primordials for extensions/websocket #11240

Merged
merged 8 commits into from
Jul 4, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 128 additions & 75 deletions extensions/websocket/01_websocket.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";

/// <reference path="../../core/internal.d.ts" />

((window) => {
const core = window.Deno.core;
const { URL } = window.__bootstrap.url;
const webidl = window.__bootstrap.webidl;
const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra;
const { DOMException } = window.__bootstrap.domException;
const { Blob } = globalThis.__bootstrap.file;
const {
ArrayBuffer,
ArrayBufferIsView,
ArrayPrototypeJoin,
DataView,
ErrorPrototypeToString,
ObjectDefineProperty,
Map,
MapPrototypeGet,
MapPrototypeSet,
Set,
Symbol,
String,
StringPrototypeToLowerCase,
StringPrototypeEndsWith,
FunctionPrototypeCall,
RegExpPrototypeTest,
ObjectDefineProperties,
ArrayPrototypeMap,
ArrayPrototypeSome,
PromisePrototypeThen,
} = window.__bootstrap.primordials;

webidl.converters["sequence<DOMString> or DOMString"] = (V, opts) => {
// Union for (sequence<DOMString> or DOMString)
Expand All @@ -24,10 +49,11 @@
return webidl.converters["Blob"](V, opts);
}
if (typeof V === "object") {
// TODO(littledivy): use primordial for SharedArrayBuffer
if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) {
return webidl.converters["ArrayBuffer"](V, opts);
}
if (ArrayBuffer.isView(V)) {
if (ArrayBufferIsView(V)) {
return webidl.converters["ArrayBufferView"](V, opts);
}
}
Expand Down Expand Up @@ -58,30 +84,33 @@
if (typeof wrappedHandler.handler !== "function") {
return;
}
return wrappedHandler.handler.call(this, ...args);
return FunctionPrototypeCall(wrappedHandler.handler, this, ...args);
}
wrappedHandler.handler = handler;
return wrappedHandler;
}
// TODO(lucacasonato) reuse when we can reuse code between web crates
function defineEventHandler(emitter, name) {
// HTML specification section 8.1.5.1
Object.defineProperty(emitter, `on${name}`, {
ObjectDefineProperty(emitter, `on${name}`, {
get() {
return this[handlerSymbol]?.get(name)?.handler;
if (!this[handlerSymbol]) {
return null;
}
return MapPrototypeGet(this[handlerSymbol], name)?.handler;
},
set(value) {
if (!this[handlerSymbol]) {
this[handlerSymbol] = new Map();
}
let handlerWrapper = this[handlerSymbol]?.get(name);
let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name);
if (handlerWrapper) {
handlerWrapper.handler = value;
} else {
handlerWrapper = makeWrappedHandler(value);
this.addEventListener(name, handlerWrapper);
}
this[handlerSymbol].set(name, handlerWrapper);
MapPrototypeSet(this[handlerSymbol], name, handlerWrapper);
},
configurable: true,
enumerable: true,
Expand Down Expand Up @@ -194,7 +223,7 @@
);
}

if (wsURL.hash !== "" || wsURL.href.endsWith("#")) {
if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) {
throw new DOMException(
"Fragments are not allowed in a WebSocket URL.",
"SyntaxError",
Expand All @@ -210,7 +239,10 @@
}

if (
protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size
protocols.length !==
new Set(
ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)),
).size
) {
throw new DOMException(
"Can't supply multiple times the same protocol.",
Expand All @@ -219,54 +251,65 @@
}

if (
protocols.some((protocol) => !HTTP_TOKEN_CODE_POINT_RE.test(protocol))
ArrayPrototypeSome(
protocols,
(protocol) =>
!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, 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;

if (this[_readyState] === CLOSING) {
core.opAsync("op_ws_close", {
rid: this[_rid],
}).then(() => {
this[_readyState] = CLOSED;

const errEvent = new ErrorEvent("error");
this.dispatchEvent(errEvent);

const event = new CloseEvent("close");
PromisePrototypeThen(
core.opAsync("op_ws_create", {
url: wsURL.href,
protocols: ArrayPrototypeJoin(protocols, ", "),
}),
(create) => {
this[_rid] = create.rid;
this[_extensions] = create.extensions;
this[_protocol] = create.protocol;

if (this[_readyState] === CLOSING) {
PromisePrototypeThen(
core.opAsync("op_ws_close", {
rid: this[_rid],
}),
() => {
this[_readyState] = CLOSED;

const errEvent = new ErrorEvent("error");
this.dispatchEvent(errEvent);

const event = new CloseEvent("close");
this.dispatchEvent(event);
tryClose(this[_rid]);
},
);
} else {
this[_readyState] = OPEN;
const event = new Event("open");
this.dispatchEvent(event);
tryClose(this[_rid]);
});
} else {
this[_readyState] = OPEN;
const event = new Event("open");
this.dispatchEvent(event);

this.#eventLoop();
}
}).catch((err) => {
this[_readyState] = CLOSED;
this.#eventLoop();
}
},
(err) => {
this[_readyState] = CLOSED;

const errorEv = new ErrorEvent(
"error",
{ error: err, message: err.toString() },
);
this.dispatchEvent(errorEv);
const errorEv = new ErrorEvent(
"error",
{ error: err, message: ErrorPrototypeToString(err) },
);
this.dispatchEvent(errorEv);

const closeEv = new CloseEvent("close");
this.dispatchEvent(closeEv);
});
const closeEv = new CloseEvent("close");
this.dispatchEvent(closeEv);
},
);
}

send(data) {
Expand All @@ -287,33 +330,40 @@

const sendTypedArray = (ta) => {
this[_bufferedAmount] += ta.byteLength;
core.opAsync("op_ws_send", {
rid: this[_rid],
kind: "binary",
}, ta).then(() => {
this[_bufferedAmount] -= ta.byteLength;
});
PromisePrototypeThen(
core.opAsync("op_ws_send", {
rid: this[_rid],
kind: "binary",
}, ta),
() => {
this[_bufferedAmount] -= ta.byteLength;
},
);
};

if (data instanceof Blob) {
data.slice().arrayBuffer().then((ab) =>
sendTypedArray(new DataView(ab))
PromisePrototypeThen(
data.slice().arrayBuffer(),
(ab) => sendTypedArray(new DataView(ab)),
);
} else if (ArrayBuffer.isView(data)) {
} else if (ArrayBufferIsView(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.byteLength;
core.opAsync("op_ws_send", {
rid: this[_rid],
kind: "text",
text: string,
}).then(() => {
this[_bufferedAmount] -= d.byteLength;
});
PromisePrototypeThen(
core.opAsync("op_ws_send", {
rid: this[_rid],
kind: "text",
text: string,
}),
() => {
this[_bufferedAmount] -= d.byteLength;
},
);
}
}

Expand Down Expand Up @@ -357,20 +407,23 @@
} else if (this[_readyState] === OPEN) {
this[_readyState] = CLOSING;

core.opAsync("op_ws_close", {
rid: this[_rid],
code,
reason,
}).then(() => {
this[_readyState] = CLOSED;
const event = new CloseEvent("close", {
wasClean: true,
code: code ?? 1005,
PromisePrototypeThen(
core.opAsync("op_ws_close", {
rid: this[_rid],
code,
reason,
});
this.dispatchEvent(event);
tryClose(this[_rid]);
});
}),
() => {
this[_readyState] = CLOSED;
const event = new CloseEvent("close", {
wasClean: true,
code: code ?? 1005,
reason,
});
this.dispatchEvent(event);
tryClose(this[_rid]);
},
);
}
}

Expand Down Expand Up @@ -443,7 +496,7 @@
}
}

Object.defineProperties(WebSocket, {
ObjectDefineProperties(WebSocket, {
CONNECTING: {
value: 0,
},
Expand Down