deno/ext/broadcast_channel/01_broadcast_channel.js
Bartek Iwańczuk 462ce14a78
refactor: migrate extensions to virtual ops module (#22135)
First pass of migrating away from `Deno.core.ensureFastOps()`.

A few "tricky" ones have been left for a follow up.
2024-01-26 23:46:46 +01:00

166 lines
4.1 KiB
JavaScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/// <reference path="../../core/internal.d.ts" />
import { core, primordials } from "ext:core/mod.js";
import {
op_broadcast_recv,
op_broadcast_send,
op_broadcast_subscribe,
op_broadcast_unsubscribe,
} from "ext:core/ops";
const {
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
ObjectPrototypeIsPrototypeOf,
Symbol,
SymbolFor,
Uint8Array,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import {
defineEventHandler,
EventTarget,
setIsTrusted,
setTarget,
} from "ext:deno_web/02_event.js";
import { defer } from "ext:deno_web/02_timers.js";
import { DOMException } from "ext:deno_web/01_dom_exception.js";
const _name = Symbol("[[name]]");
const _closed = Symbol("[[closed]]");
const channels = [];
let rid = null;
async function recv() {
while (channels.length > 0) {
const message = await op_broadcast_recv(rid);
if (message === null) {
break;
}
const { 0: name, 1: data } = message;
dispatch(null, name, new Uint8Array(data));
}
core.close(rid);
rid = null;
}
function dispatch(source, name, data) {
for (let i = 0; i < channels.length; ++i) {
const channel = channels[i];
if (channel === source) continue; // Don't self-send.
if (channel[_name] !== name) continue;
if (channel[_closed]) continue;
const go = () => {
if (channel[_closed]) return;
const event = new MessageEvent("message", {
data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables.
origin: "http://127.0.0.1",
});
setIsTrusted(event, true);
setTarget(event, channel);
channel.dispatchEvent(event);
};
defer(go);
}
}
class BroadcastChannel extends EventTarget {
[_name];
[_closed] = false;
get name() {
return this[_name];
}
constructor(name) {
super();
const prefix = "Failed to construct 'BroadcastChannel'";
webidl.requiredArguments(arguments.length, 1, prefix);
this[_name] = webidl.converters["DOMString"](name, prefix, "Argument 1");
this[webidl.brand] = webidl.brand;
ArrayPrototypePush(channels, this);
if (rid === null) {
// Create the rid immediately, otherwise there is a time window (and a
// race condition) where messages can get lost, because recv() is async.
rid = op_broadcast_subscribe();
recv();
}
}
postMessage(message) {
webidl.assertBranded(this, BroadcastChannelPrototype);
const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'";
webidl.requiredArguments(arguments.length, 1, prefix);
if (this[_closed]) {
throw new DOMException("Already closed", "InvalidStateError");
}
if (typeof message === "function" || typeof message === "symbol") {
throw new DOMException("Uncloneable value", "DataCloneError");
}
const data = core.serialize(message);
// Send to other listeners in this VM.
dispatch(this, this[_name], new Uint8Array(data));
// Send to listeners in other VMs.
defer(() => {
if (!this[_closed]) {
op_broadcast_send(rid, this[_name], data);
}
});
}
close() {
webidl.assertBranded(this, BroadcastChannelPrototype);
this[_closed] = true;
const index = ArrayPrototypeIndexOf(channels, this);
if (index === -1) return;
ArrayPrototypeSplice(channels, index, 1);
if (channels.length === 0) {
op_broadcast_unsubscribe(rid);
}
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(BroadcastChannelPrototype, this),
keys: [
"name",
"onmessage",
"onmessageerror",
],
}),
inspectOptions,
);
}
}
defineEventHandler(BroadcastChannel.prototype, "message");
defineEventHandler(BroadcastChannel.prototype, "messageerror");
const BroadcastChannelPrototype = BroadcastChannel.prototype;
export { BroadcastChannel };