deno/ext/web/13_message_port.js
李瑞丰 46245b830a
fix(ext/webidl): correctly apply [SymbolToStringTag] to interfaces (#11851)
Co-authored-by: Luca Casonato <hello@lcas.dev>
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
2021-09-25 02:07:22 +09:00

340 lines
9.4 KiB
JavaScript

// 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 {
ArrayBuffer,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ObjectSetPrototypeOf,
Symbol,
SymbolFor,
TypeError,
WeakSet,
WeakSetPrototypeAdd,
WeakSetPrototypeHas,
} = 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 })
}`;
}
}
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 (ArrayPrototypeIncludes(transfer, 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, transferables;
try {
const v = deserializeJsMessageData(data);
message = v[0];
transferables = v[1];
} catch (err) {
const event = new MessageEvent("messageerror", { data: err });
this.dispatchEvent(event);
return;
}
const event = new MessageEvent("message", {
data: message,
ports: ArrayPrototypeFilter(
transferables,
(t) => t instanceof MessagePort,
),
});
this.dispatchEvent(event);
}
this[_enabled] = false;
})();
}
close() {
webidl.assertBranded(this, MessagePort);
if (this[_id] !== null) {
core.close(this[_id]);
this[_id] = null;
}
}
}
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 = [];
const hostObjects = [];
const arrayBufferIdsInTransferables = [];
const transferedArrayBuffers = [];
for (const transferable of messageData.transferables) {
switch (transferable.kind) {
case "messagePort": {
const port = createMessagePort(transferable.data);
ArrayPrototypePush(transferables, port);
ArrayPrototypePush(hostObjects, port);
break;
}
case "arrayBuffer": {
ArrayPrototypePush(transferedArrayBuffers, transferable.data);
const i = ArrayPrototypePush(transferables, null);
ArrayPrototypePush(arrayBufferIdsInTransferables, i);
break;
}
default:
throw new TypeError("Unreachable");
}
}
const data = core.deserialize(messageData.data, {
hostObjects,
transferedArrayBuffers,
});
for (const i in arrayBufferIdsInTransferables) {
const id = arrayBufferIdsInTransferables[i];
transferables[id] = transferedArrayBuffers[i];
}
return [data, transferables];
}
const detachedArrayBuffers = new WeakSet();
/**
* @param {any} data
* @param {object[]} transferables
* @returns {globalThis.__bootstrap.messagePort.MessageData}
*/
function serializeJsMessageData(data, transferables) {
const transferedArrayBuffers = ArrayPrototypeFilter(
transferables,
(a) => a instanceof ArrayBuffer,
);
for (const arrayBuffer of transferedArrayBuffers) {
// This is hacky with both false positives and false negatives for
// detecting detached array buffers. V8 needs to add a way to tell if a
// buffer is detached or not.
if (WeakSetPrototypeHas(detachedArrayBuffers, arrayBuffer)) {
throw new DOMException(
"Can not transfer detached ArrayBuffer",
"DataCloneError",
);
}
WeakSetPrototypeAdd(detachedArrayBuffers, arrayBuffer);
}
let serializedData;
try {
serializedData = core.serialize(data, {
hostObjects: ArrayPrototypeFilter(
transferables,
(a) => a instanceof MessagePort,
),
transferedArrayBuffers,
});
} catch (err) {
throw new DOMException(err.message, "DataCloneError");
}
/** @type {globalThis.__bootstrap.messagePort.Transferable[]} */
const serializedTransferables = [];
let arrayBufferI = 0;
for (const transferable of transferables) {
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;
ArrayPrototypePush(serializedTransferables, {
kind: "messagePort",
data: id,
});
} else if (transferable instanceof ArrayBuffer) {
ArrayPrototypePush(serializedTransferables, {
kind: "arrayBuffer",
data: transferedArrayBuffers[arrayBufferI],
});
arrayBufferI++;
} 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);