diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2021-08-11 12:27:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-11 12:27:05 +0200 |
commit | a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch) | |
tree | 90671b004537e20f9493fd3277ffd21d30b39a0e /ext/web/13_message_port.js | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (diff) |
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/web/13_message_port.js')
-rw-r--r-- | ext/web/13_message_port.js | 286 |
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); |