mirror of
https://github.com/denoland/deno.git
synced 2024-10-31 09:14:20 -04:00
b6400a25a0
Co-authored-by: Luca Casonato <hello@lcas.dev>
167 lines
4.8 KiB
JavaScript
167 lines
4.8 KiB
JavaScript
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
"use strict";
|
|
|
|
((window) => {
|
|
const core = window.Deno.core;
|
|
const webidl = window.__bootstrap.webidl;
|
|
const { setTarget } = window.__bootstrap.event;
|
|
|
|
const handlerSymbol = Symbol("eventHandlers");
|
|
function makeWrappedHandler(handler) {
|
|
function wrappedHandler(...args) {
|
|
if (typeof wrappedHandler.handler !== "function") {
|
|
return;
|
|
}
|
|
return wrappedHandler.handler.call(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}`, {
|
|
get() {
|
|
// TODO(bnoordhuis) The "BroadcastChannel should have an onmessage
|
|
// event" WPT test expects that .onmessage !== undefined. Returning
|
|
// null makes it pass but is perhaps not exactly in the spirit.
|
|
return this[handlerSymbol]?.get(name)?.handler ?? null;
|
|
},
|
|
set(value) {
|
|
if (!this[handlerSymbol]) {
|
|
this[handlerSymbol] = new Map();
|
|
}
|
|
let handlerWrapper = this[handlerSymbol]?.get(name);
|
|
if (handlerWrapper) {
|
|
handlerWrapper.handler = value;
|
|
} else {
|
|
handlerWrapper = makeWrappedHandler(value);
|
|
this.addEventListener(name, handlerWrapper);
|
|
}
|
|
this[handlerSymbol].set(name, handlerWrapper);
|
|
},
|
|
configurable: true,
|
|
enumerable: true,
|
|
});
|
|
}
|
|
|
|
const _name = Symbol("[[name]]");
|
|
const _closed = Symbol("[[closed]]");
|
|
|
|
const channels = [];
|
|
let rid = null;
|
|
|
|
async function recv() {
|
|
while (channels.length > 0) {
|
|
const message = await core.opAsync("op_broadcast_recv", rid);
|
|
|
|
if (message === null) {
|
|
break;
|
|
}
|
|
|
|
const [name, data] = message;
|
|
dispatch(null, name, new Uint8Array(data));
|
|
}
|
|
|
|
core.close(rid);
|
|
rid = null;
|
|
}
|
|
|
|
function dispatch(source, name, data) {
|
|
for (const channel of channels) {
|
|
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",
|
|
});
|
|
setTarget(event, channel);
|
|
channel.dispatchEvent(event);
|
|
};
|
|
|
|
defer(go);
|
|
}
|
|
}
|
|
|
|
// Defer to avoid starving the event loop. Not using queueMicrotask()
|
|
// for that reason: it lets promises make forward progress but can
|
|
// still starve other parts of the event loop.
|
|
function defer(go) {
|
|
setTimeout(go, 1);
|
|
}
|
|
|
|
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,
|
|
context: "Argument 1",
|
|
});
|
|
|
|
this[webidl.brand] = webidl.brand;
|
|
|
|
channels.push(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 = core.opSync("op_broadcast_subscribe");
|
|
recv();
|
|
}
|
|
}
|
|
|
|
postMessage(message) {
|
|
webidl.assertBranded(this, BroadcastChannel);
|
|
|
|
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(() => core.opAsync("op_broadcast_send", [rid, this[_name]], data));
|
|
}
|
|
|
|
close() {
|
|
webidl.assertBranded(this, BroadcastChannel);
|
|
this[_closed] = true;
|
|
|
|
const index = channels.indexOf(this);
|
|
if (index === -1) return;
|
|
|
|
channels.splice(index, 1);
|
|
if (channels.length === 0) core.opSync("op_broadcast_unsubscribe", rid);
|
|
}
|
|
}
|
|
|
|
defineEventHandler(BroadcastChannel.prototype, "message");
|
|
defineEventHandler(BroadcastChannel.prototype, "messageerror");
|
|
|
|
window.__bootstrap.broadcastChannel = { BroadcastChannel };
|
|
})(this);
|