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/broadcast_channel/01_broadcast_channel.js | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (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.js | 186 |
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); |