summaryrefslogtreecommitdiff
path: root/ext/web/13_message_port.js
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2021-08-11 12:27:05 +0200
committerGitHub <noreply@github.com>2021-08-11 12:27:05 +0200
commita0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch)
tree90671b004537e20f9493fd3277ffd21d30b39a0e /ext/web/13_message_port.js
parent3a6994115176781b3a93d70794b1b81bc95e42b4 (diff)
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/web/13_message_port.js')
-rw-r--r--ext/web/13_message_port.js286
1 files changed, 286 insertions, 0 deletions
diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js
new file mode 100644
index 000000000..d5014fdb9
--- /dev/null
+++ b/ext/web/13_message_port.js
@@ -0,0 +1,286 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+// @ts-check
+/// <reference path="../../core/lib.deno_core.d.ts" />
+/// <reference path="../webidl/internal.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./lib.deno_web.d.ts" />
+
+"use strict";
+
+((window) => {
+ const core = window.Deno.core;
+ const webidl = window.__bootstrap.webidl;
+ const { setEventTargetData } = window.__bootstrap.eventTarget;
+ const { defineEventHandler } = window.__bootstrap.event;
+ const { DOMException } = window.__bootstrap.domException;
+ const {
+ ObjectSetPrototypeOf,
+ Symbol,
+ SymbolFor,
+ SymbolToStringTag,
+ TypeError,
+ } = window.__bootstrap.primordials;
+
+ class MessageChannel {
+ /** @type {MessagePort} */
+ #port1;
+ /** @type {MessagePort} */
+ #port2;
+
+ constructor() {
+ this[webidl.brand] = webidl.brand;
+ const [port1Id, port2Id] = opCreateEntangledMessagePort();
+ const port1 = createMessagePort(port1Id);
+ const port2 = createMessagePort(port2Id);
+ this.#port1 = port1;
+ this.#port2 = port2;
+ }
+
+ get port1() {
+ webidl.assertBranded(this, MessageChannel);
+ return this.#port1;
+ }
+
+ get port2() {
+ webidl.assertBranded(this, MessageChannel);
+ return this.#port2;
+ }
+
+ [SymbolFor("Deno.inspect")](inspect) {
+ return `MessageChannel ${
+ inspect({ port1: this.port1, port2: this.port2 })
+ }`;
+ }
+
+ get [SymbolToStringTag]() {
+ return "MessageChannel";
+ }
+ }
+
+ webidl.configurePrototype(MessageChannel);
+
+ const _id = Symbol("id");
+ const _enabled = Symbol("enabled");
+
+ /**
+ * @param {number} id
+ * @returns {MessagePort}
+ */
+ function createMessagePort(id) {
+ const port = core.createHostObject();
+ ObjectSetPrototypeOf(port, MessagePort.prototype);
+ port[webidl.brand] = webidl.brand;
+ setEventTargetData(port);
+ port[_id] = id;
+ return port;
+ }
+
+ class MessagePort extends EventTarget {
+ /** @type {number | null} */
+ [_id] = null;
+ /** @type {boolean} */
+ [_enabled] = false;
+
+ constructor() {
+ super();
+ webidl.illegalConstructor();
+ }
+
+ /**
+ * @param {any} message
+ * @param {object[] | StructuredSerializeOptions} transferOrOptions
+ */
+ postMessage(message, transferOrOptions = {}) {
+ webidl.assertBranded(this, MessagePort);
+ const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ message = webidl.converters.any(message);
+ let options;
+ if (
+ webidl.type(transferOrOptions) === "Object" &&
+ transferOrOptions !== undefined &&
+ transferOrOptions[Symbol.iterator] !== undefined
+ ) {
+ const transfer = webidl.converters["sequence<object>"](
+ transferOrOptions,
+ { prefix, context: "Argument 2" },
+ );
+ options = { transfer };
+ } else {
+ options = webidl.converters.StructuredSerializeOptions(
+ transferOrOptions,
+ {
+ prefix,
+ context: "Argument 2",
+ },
+ );
+ }
+ const { transfer } = options;
+ if (transfer.includes(this)) {
+ throw new DOMException("Can not tranfer self", "DataCloneError");
+ }
+ const data = serializeJsMessageData(message, transfer);
+ if (this[_id] === null) return;
+ core.opSync("op_message_port_post_message", this[_id], data);
+ }
+
+ start() {
+ webidl.assertBranded(this, MessagePort);
+ if (this[_enabled]) return;
+ (async () => {
+ this[_enabled] = true;
+ while (true) {
+ if (this[_id] === null) break;
+ const data = await core.opAsync(
+ "op_message_port_recv_message",
+ this[_id],
+ );
+ if (data === null) break;
+ let message, transfer;
+ try {
+ const v = deserializeJsMessageData(data);
+ message = v[0];
+ transfer = v[1];
+ } catch (err) {
+ const event = new MessageEvent("messageerror", { data: err });
+ this.dispatchEvent(event);
+ return;
+ }
+ const event = new MessageEvent("message", {
+ data: message,
+ ports: transfer,
+ });
+ this.dispatchEvent(event);
+ }
+ this[_enabled] = false;
+ })();
+ }
+
+ close() {
+ webidl.assertBranded(this, MessagePort);
+ if (this[_id] !== null) {
+ core.close(this[_id]);
+ this[_id] = null;
+ }
+ }
+
+ get [SymbolToStringTag]() {
+ return "MessagePort";
+ }
+ }
+
+ defineEventHandler(MessagePort.prototype, "message", function (self) {
+ self.start();
+ });
+ defineEventHandler(MessagePort.prototype, "messageerror");
+
+ webidl.configurePrototype(MessagePort);
+
+ /**
+ * @returns {[number, number]}
+ */
+ function opCreateEntangledMessagePort() {
+ return core.opSync("op_message_port_create_entangled");
+ }
+
+ /**
+ * @param {globalThis.__bootstrap.messagePort.MessageData} messageData
+ * @returns {[any, object[]]}
+ */
+ function deserializeJsMessageData(messageData) {
+ /** @type {object[]} */
+ const transferables = [];
+
+ for (const transferable of messageData.transferables) {
+ switch (transferable.kind) {
+ case "messagePort": {
+ const port = createMessagePort(transferable.data);
+ transferables.push(port);
+ break;
+ }
+ default:
+ throw new TypeError("Unreachable");
+ }
+ }
+
+ const data = core.deserialize(messageData.data, {
+ hostObjects: transferables,
+ });
+
+ return [data, transferables];
+ }
+
+ /**
+ * @param {any} data
+ * @param {object[]} tranferables
+ * @returns {globalThis.__bootstrap.messagePort.MessageData}
+ */
+ function serializeJsMessageData(data, tranferables) {
+ let serializedData;
+ try {
+ serializedData = core.serialize(data, { hostObjects: tranferables });
+ } catch (err) {
+ throw new DOMException(err.message, "DataCloneError");
+ }
+
+ /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */
+ const serializedTransferables = [];
+
+ for (const transferable of tranferables) {
+ if (transferable instanceof MessagePort) {
+ webidl.assertBranded(transferable, MessagePort);
+ const id = transferable[_id];
+ if (id === null) {
+ throw new DOMException(
+ "Can not transfer disentangled message port",
+ "DataCloneError",
+ );
+ }
+ transferable[_id] = null;
+ serializedTransferables.push({ kind: "messagePort", data: id });
+ } else {
+ throw new DOMException("Value not transferable", "DataCloneError");
+ }
+ }
+
+ return {
+ data: serializedData,
+ transferables: serializedTransferables,
+ };
+ }
+
+ webidl.converters.StructuredSerializeOptions = webidl
+ .createDictionaryConverter(
+ "StructuredSerializeOptions",
+ [
+ {
+ key: "transfer",
+ converter: webidl.converters["sequence<object>"],
+ get defaultValue() {
+ return [];
+ },
+ },
+ ],
+ );
+
+ function structuredClone(value, options) {
+ const prefix = "Failed to execute 'structuredClone'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ options = webidl.converters.StructuredSerializeOptions(options, {
+ prefix,
+ context: "Argument 2",
+ });
+ const messageData = serializeJsMessageData(value, options.transfer);
+ const [data] = deserializeJsMessageData(messageData);
+ return data;
+ }
+
+ window.__bootstrap.messagePort = {
+ MessageChannel,
+ MessagePort,
+ deserializeJsMessageData,
+ serializeJsMessageData,
+ structuredClone,
+ };
+})(globalThis);