summaryrefslogtreecommitdiff
path: root/ext/broadcast_channel/01_broadcast_channel.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/broadcast_channel/01_broadcast_channel.js
parent3a6994115176781b3a93d70794b1b81bc95e42b4 (diff)
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/broadcast_channel/01_broadcast_channel.js')
-rw-r--r--ext/broadcast_channel/01_broadcast_channel.js186
1 files changed, 186 insertions, 0 deletions
diff --git a/ext/broadcast_channel/01_broadcast_channel.js b/ext/broadcast_channel/01_broadcast_channel.js
new file mode 100644
index 000000000..42112c014
--- /dev/null
+++ b/ext/broadcast_channel/01_broadcast_channel.js
@@ -0,0 +1,186 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+/// <reference path="../../core/internal.d.ts" />
+
+"use strict";
+
+((window) => {
+ const core = window.Deno.core;
+ const webidl = window.__bootstrap.webidl;
+ const { setTarget } = window.__bootstrap.event;
+ const { DOMException } = window.__bootstrap.domException;
+ const {
+ ArrayPrototypeIndexOf,
+ ArrayPrototypeSplice,
+ ArrayPrototypePush,
+ Symbol,
+ Uint8Array,
+ ObjectDefineProperty,
+ Map,
+ MapPrototypeSet,
+ MapPrototypeGet,
+ FunctionPrototypeCall,
+ } = window.__bootstrap.primordials;
+
+ const handlerSymbol = Symbol("eventHandlers");
+ function makeWrappedHandler(handler) {
+ function wrappedHandler(...args) {
+ if (typeof wrappedHandler.handler !== "function") {
+ return;
+ }
+ return FunctionPrototypeCall(wrappedHandler.handler, 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
+ ObjectDefineProperty(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.
+ if (!this[handlerSymbol]) {
+ return null;
+ }
+ return MapPrototypeGet(this[handlerSymbol], name)?.handler ?? null;
+ },
+ set(value) {
+ if (!this[handlerSymbol]) {
+ this[handlerSymbol] = new Map();
+ }
+ let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name);
+ if (handlerWrapper) {
+ handlerWrapper.handler = value;
+ } else {
+ handlerWrapper = makeWrappedHandler(value);
+ this.addEventListener(name, handlerWrapper);
+ }
+ MapPrototypeSet(this[handlerSymbol], 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;
+
+ 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 = 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 = ArrayPrototypeIndexOf(channels, this);
+ if (index === -1) return;
+
+ ArrayPrototypeSplice(channels, 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);