summaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorBen Noordhuis <info@bnoordhuis.nl>2021-05-22 18:08:24 +0200
committerBen Noordhuis <info@bnoordhuis.nl>2021-05-23 15:16:42 +0200
commit8cf7f966f24d0fb996b41d92b04ad9647337a8f6 (patch)
tree306f60a892186dc14e06f65f7dd5c7b7dbfad147 /extensions
parent5f0d91497b03795250c55340c7e7c7de66d4607b (diff)
feat(extensions): add BroadcastChannel
Co-Authored-By: Ben Noordhuis <info@bnoordhuis.nl> Fixes: #10354
Diffstat (limited to 'extensions')
-rw-r--r--extensions/broadcast_channel/01_broadcast_channel.js117
-rw-r--r--extensions/broadcast_channel/Cargo.toml18
-rw-r--r--extensions/broadcast_channel/README.md5
-rw-r--r--extensions/broadcast_channel/lib.deno_broadcast_channel.d.ts55
-rw-r--r--extensions/broadcast_channel/lib.rs131
5 files changed, 326 insertions, 0 deletions
diff --git a/extensions/broadcast_channel/01_broadcast_channel.js b/extensions/broadcast_channel/01_broadcast_channel.js
new file mode 100644
index 000000000..34f8b9e19
--- /dev/null
+++ b/extensions/broadcast_channel/01_broadcast_channel.js
@@ -0,0 +1,117 @@
+// 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 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() {
+ return this[handlerSymbol]?.get(name)?.handler;
+ },
+ 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 _rid = Symbol("[[rid]]");
+
+ class BroadcastChannel extends EventTarget {
+ [_name];
+ [_closed] = false;
+ [_rid];
+
+ get name() {
+ return this[_name];
+ }
+
+ constructor(name) {
+ super();
+
+ window.location;
+
+ const prefix = "Failed to construct 'broadcastChannel'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+
+ this[_name] = webidl.converters["DOMString"](name, {
+ prefix,
+ context: "Argument 1",
+ });
+
+ this[_rid] = core.opSync("op_broadcast_open", this[_name]);
+
+ this[webidl.brand] = webidl.brand;
+
+ this.#eventLoop();
+ }
+
+ postMessage(message) {
+ webidl.assertBranded(this, BroadcastChannel);
+
+ if (this[_closed]) {
+ throw new DOMException("Already closed", "InvalidStateError");
+ }
+
+ core.opAsync("op_broadcast_send", this[_rid], core.serialize(message));
+ }
+
+ close() {
+ webidl.assertBranded(this, BroadcastChannel);
+
+ this[_closed] = true;
+ core.close(this[_rid]);
+ }
+
+ async #eventLoop() {
+ while (!this[_closed]) {
+ const message = await core.opAsync(
+ "op_broadcast_next_event",
+ this[_rid],
+ );
+
+ if (message.length !== 0) {
+ const event = new MessageEvent("message", {
+ data: core.deserialize(message),
+ origin: window.location,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+ }
+ }
+ }
+ }
+
+ defineEventHandler(BroadcastChannel.prototype, "message");
+ defineEventHandler(BroadcastChannel.prototype, "messageerror");
+
+ window.__bootstrap.broadcastChannel = { BroadcastChannel };
+})(this);
diff --git a/extensions/broadcast_channel/Cargo.toml b/extensions/broadcast_channel/Cargo.toml
new file mode 100644
index 000000000..72c29f651
--- /dev/null
+++ b/extensions/broadcast_channel/Cargo.toml
@@ -0,0 +1,18 @@
+# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "deno_broadcast_channel"
+version = "0.1.0"
+edition = "2018"
+description = "Implementation of BroadcastChannel API for Deno"
+authors = ["the Deno authors"]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/denoland/deno"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+deno_core = { version = "0.88.0", path = "../../core" }
+tokio = { version = "1.4.0", features = ["full"] }
diff --git a/extensions/broadcast_channel/README.md b/extensions/broadcast_channel/README.md
new file mode 100644
index 000000000..5b5034ef7
--- /dev/null
+++ b/extensions/broadcast_channel/README.md
@@ -0,0 +1,5 @@
+# deno_broadcast_channel
+
+This crate implements the BroadcastChannel functions of Deno.
+
+Spec: https://html.spec.whatwg.org/multipage/web-messaging.html
diff --git a/extensions/broadcast_channel/lib.deno_broadcast_channel.d.ts b/extensions/broadcast_channel/lib.deno_broadcast_channel.d.ts
new file mode 100644
index 000000000..c8efef778
--- /dev/null
+++ b/extensions/broadcast_channel/lib.deno_broadcast_channel.d.ts
@@ -0,0 +1,55 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+// deno-lint-ignore-file no-explicit-any
+
+/// <reference no-default-lib="true" />
+/// <reference lib="esnext" />
+
+interface BroadcastChannelEventMap {
+ "message": MessageEvent;
+ "messageerror": MessageEvent;
+}
+
+interface BroadcastChannel extends EventTarget {
+ /**
+ * Returns the channel name (as passed to the constructor).
+ */
+ readonly name: string;
+ onmessage: ((this: BroadcastChannel, ev: MessageEvent) => any) | null;
+ onmessageerror: ((this: BroadcastChannel, ev: MessageEvent) => any) | null;
+ /**
+ * Closes the BroadcastChannel object, opening it up to garbage collection.
+ */
+ close(): void;
+ /**
+ * Sends the given message to other BroadcastChannel objects set up for
+ * this channel. Messages can be structured objects, e.g. nested objects
+ * and arrays.
+ */
+ postMessage(message: any): void;
+ addEventListener<K extends keyof BroadcastChannelEventMap>(
+ type: K,
+ listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ removeEventListener<K extends keyof BroadcastChannelEventMap>(
+ type: K,
+ listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any,
+ options?: boolean | EventListenerOptions,
+ ): void;
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions,
+ ): void;
+}
+
+declare var BroadcastChannel: {
+ prototype: BroadcastChannel;
+ new (name: string): BroadcastChannel;
+};
diff --git a/extensions/broadcast_channel/lib.rs b/extensions/broadcast_channel/lib.rs
new file mode 100644
index 000000000..cee9c3e0c
--- /dev/null
+++ b/extensions/broadcast_channel/lib.rs
@@ -0,0 +1,131 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::bad_resource_id;
+use deno_core::error::AnyError;
+use deno_core::include_js_files;
+use deno_core::op_async;
+use deno_core::op_sync;
+use deno_core::AsyncRefCell;
+use deno_core::Extension;
+use deno_core::OpState;
+use deno_core::RcRef;
+use deno_core::Resource;
+use deno_core::ResourceId;
+use deno_core::ZeroCopyBuf;
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::path::PathBuf;
+use std::rc::Rc;
+use tokio::io::AsyncReadExt;
+use tokio::io::AsyncWriteExt;
+
+struct BroadcastChannelResource(AsyncRefCell<tokio::fs::File>);
+
+impl Resource for BroadcastChannelResource {
+ fn name(&self) -> Cow<str> {
+ "broadcastChannel".into()
+ }
+}
+
+pub fn op_broadcast_open(
+ state: &mut OpState,
+ name: String,
+ _bufs: Option<ZeroCopyBuf>,
+) -> Result<ResourceId, AnyError> {
+ let path = PathBuf::from("./");
+ std::fs::create_dir_all(&path)?;
+ let file = std::fs::OpenOptions::new()
+ .create(true)
+ .append(true)
+ .read(true)
+ .open(path.join(format!("broadcast_{}", name)))?;
+
+ let rid =
+ state
+ .resource_table
+ .add(BroadcastChannelResource(AsyncRefCell::new(
+ tokio::fs::File::from_std(file),
+ )));
+
+ Ok(rid)
+}
+
+pub async fn op_broadcast_send(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ buf: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ let state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get::<BroadcastChannelResource>(rid)
+ .ok_or_else(bad_resource_id)?;
+
+ let mut file = RcRef::map(&resource, |r| &r.0).borrow_mut().await;
+
+ let buffer_data = buf.unwrap();
+ let mut data = vec![];
+ data.extend_from_slice(&(buffer_data.len() as u64).to_ne_bytes());
+ data.extend_from_slice(&buffer_data);
+
+ file.write_all(&data).await?;
+
+ Ok(())
+}
+
+pub async fn op_broadcast_next_event(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ _bufs: Option<ZeroCopyBuf>,
+) -> Result<Vec<u8>, AnyError> {
+ let resource = {
+ let state = state.borrow_mut();
+ state
+ .resource_table
+ .get::<BroadcastChannelResource>(rid)
+ .ok_or_else(bad_resource_id)?
+ };
+
+ let mut file = RcRef::map(&resource, |r| &r.0).borrow_mut().await;
+
+ let size = match file.read_u64().await {
+ Ok(s) => s,
+ Err(e) => {
+ return match e.kind() {
+ deno_core::futures::io::ErrorKind::UnexpectedEof => Ok(vec![]),
+ _ => Err(e.into()),
+ }
+ }
+ };
+ let mut data = vec![0u8; size as usize];
+ match file.read_exact(&mut data).await {
+ Ok(s) => s,
+ Err(e) => {
+ return match e.kind() {
+ deno_core::futures::io::ErrorKind::UnexpectedEof => Ok(vec![]),
+ _ => Err(e.into()),
+ }
+ }
+ };
+
+ Ok(data)
+}
+
+pub fn init() -> Extension {
+ Extension::builder()
+ .js(include_js_files!(
+ prefix "deno:extensions/broadcast_channel",
+ "01_broadcast_channel.js",
+ ))
+ .ops(vec![
+ ("op_broadcast_open", op_sync(op_broadcast_open)),
+ ("op_broadcast_send", op_async(op_broadcast_send)),
+ ("op_broadcast_next_event", op_async(op_broadcast_next_event)),
+ ])
+ .build()
+}
+
+pub fn get_declaration() -> PathBuf {
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("lib.deno_broadcast_channel.d.ts")
+}