diff --git a/Cargo.lock b/Cargo.lock
index 80eb45ffbd..c46c6ac9fb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -591,6 +591,14 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "deno_broadcast_channel"
+version = "0.1.0"
+dependencies = [
+ "deno_core",
+ "tokio",
+]
+
[[package]]
name = "deno_console"
version = "0.7.0"
@@ -691,6 +699,7 @@ version = "0.15.0"
dependencies = [
"atty",
"bytes",
+ "deno_broadcast_channel",
"deno_console",
"deno_core",
"deno_crypto",
diff --git a/Cargo.toml b/Cargo.toml
index 31e2ee0915..98ea64293c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ members = [
"serde_v8",
"test_plugin",
"test_util",
+ "extensions/broadcast_channel",
"extensions/console",
"extensions/crypto",
"extensions/fetch",
diff --git a/cli/build.rs b/cli/build.rs
index 116ce8167a..ab2a7d0f7d 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -8,6 +8,7 @@ use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::JsRuntime;
use deno_core::RuntimeOptions;
+use deno_runtime::deno_broadcast_channel;
use deno_runtime::deno_console;
use deno_runtime::deno_crypto;
use deno_runtime::deno_fetch;
@@ -74,6 +75,10 @@ fn create_compiler_snapshot(
op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
+ op_crate_libs.insert(
+ "deno.broadcast_channel",
+ deno_broadcast_channel::get_declaration(),
+ );
// ensure we invalidate the build properly.
for (_, path) in op_crate_libs.iter() {
@@ -300,6 +305,10 @@ fn main() {
"cargo:rustc-env=DENO_CRYPTO_LIB_PATH={}",
deno_crypto::get_declaration().display()
);
+ println!(
+ "cargo:rustc-env=DENO_BROADCAST_CHANNEL_LIB_PATH={}",
+ deno_broadcast_channel::get_declaration().display()
+ );
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
diff --git a/cli/main.rs b/cli/main.rs
index aa144d1ded..868805e926 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -302,7 +302,7 @@ fn print_cache_info(
pub fn get_types(unstable: bool) -> String {
let mut types = format!(
- "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
+ "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
crate::tsc::DENO_NS_LIB,
crate::tsc::DENO_CONSOLE_LIB,
crate::tsc::DENO_URL_LIB,
@@ -313,6 +313,7 @@ pub fn get_types(unstable: bool) -> String {
crate::tsc::DENO_WEBSOCKET_LIB,
crate::tsc::DENO_WEBSTORAGE_LIB,
crate::tsc::DENO_CRYPTO_LIB,
+ crate::tsc::DENO_BROADCAST_CHANNEL_LIB,
crate::tsc::SHARED_GLOBALS_LIB,
crate::tsc::WINDOW_LIB,
);
diff --git a/cli/tsc.rs b/cli/tsc.rs
index 6d2e297db8..203bf794b0 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -41,6 +41,8 @@ pub static DENO_WEBSOCKET_LIB: &str =
pub static DENO_WEBSTORAGE_LIB: &str =
include_str!(env!("DENO_WEBSTORAGE_LIB_PATH"));
pub static DENO_CRYPTO_LIB: &str = include_str!(env!("DENO_CRYPTO_LIB_PATH"));
+pub static DENO_BROADCAST_CHANNEL_LIB: &str =
+ include_str!(env!("DENO_BROADCAST_CHANNEL_LIB_PATH"));
pub static SHARED_GLOBALS_LIB: &str =
include_str!("dts/lib.deno.shared_globals.d.ts");
pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts");
diff --git a/extensions/broadcast_channel/01_broadcast_channel.js b/extensions/broadcast_channel/01_broadcast_channel.js
new file mode 100644
index 0000000000..34f8b9e196
--- /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 0000000000..72c29f651f
--- /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 0000000000..5b5034ef7a
--- /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 0000000000..c8efef778a
--- /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
+
+///
+///
+
+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(
+ type: K,
+ listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ removeEventListener(
+ 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 0000000000..cee9c3e0c1
--- /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);
+
+impl Resource for BroadcastChannelResource {
+ fn name(&self) -> Cow {
+ "broadcastChannel".into()
+ }
+}
+
+pub fn op_broadcast_open(
+ state: &mut OpState,
+ name: String,
+ _bufs: Option,
+) -> Result {
+ 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>,
+ rid: ResourceId,
+ buf: Option,
+) -> Result<(), AnyError> {
+ let state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get::(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>,
+ rid: ResourceId,
+ _bufs: Option,
+) -> Result, AnyError> {
+ let resource = {
+ let state = state.borrow_mut();
+ state
+ .resource_table
+ .get::(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")
+}
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 5758219d80..c7805b1b16 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -18,6 +18,7 @@ name = "hello_runtime"
path = "examples/hello_runtime.rs"
[build-dependencies]
+deno_broadcast_channel = { path = "../extensions/broadcast_channel", version = "0.1.0" }
deno_console = { version = "0.7.0", path = "../extensions/console" }
deno_core = { version = "0.88.0", path = "../core" }
deno_crypto = { version = "0.21.0", path = "../extensions/crypto" }
@@ -36,6 +37,7 @@ winres = "0.1.11"
winapi = "0.3.9"
[dependencies]
+deno_broadcast_channel = { path = "../extensions/broadcast_channel", version = "0.1.0" }
deno_console = { version = "0.7.0", path = "../extensions/console" }
deno_core = { version = "0.88.0", path = "../core" }
deno_crypto = { version = "0.21.0", path = "../extensions/crypto" }
diff --git a/runtime/build.rs b/runtime/build.rs
index e1cae71954..4fe89af3ec 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -52,6 +52,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec) {
deno_crypto::init(None),
deno_webgpu::init(false),
deno_timers::init::(),
+ deno_broadcast_channel::init(),
];
let js_runtime = JsRuntime::new(RuntimeOptions {
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 796d5178f3..e1c474c282 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -29,6 +29,7 @@ delete Object.prototype.__proto__;
const webgpu = window.__bootstrap.webgpu;
const webSocket = window.__bootstrap.webSocket;
const webStorage = window.__bootstrap.webStorage;
+ const broadcastChannel = window.__bootstrap.broadcastChannel;
const file = window.__bootstrap.file;
const formData = window.__bootstrap.formData;
const fetch = window.__bootstrap.fetch;
@@ -282,6 +283,7 @@ delete Object.prototype.__proto__;
URL: util.nonEnumerable(url.URL),
URLSearchParams: util.nonEnumerable(url.URLSearchParams),
WebSocket: util.nonEnumerable(webSocket.WebSocket),
+ BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel),
Worker: util.nonEnumerable(worker.Worker),
WritableStream: util.nonEnumerable(streams.WritableStream),
WritableStreamDefaultWriter: util.nonEnumerable(
diff --git a/runtime/lib.rs b/runtime/lib.rs
index d45a727275..3cc73bffff 100644
--- a/runtime/lib.rs
+++ b/runtime/lib.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+pub use deno_broadcast_channel;
pub use deno_console;
pub use deno_crypto;
pub use deno_fetch;
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 5b731a0f51..172d24dea2 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -268,6 +268,7 @@ impl WebWorker {
options.user_agent.clone(),
options.ca_data.clone(),
),
+ deno_broadcast_channel::init(),
deno_crypto::init(options.seed),
deno_webgpu::init(options.unstable),
deno_timers::init::(),
diff --git a/runtime/worker.rs b/runtime/worker.rs
index c75f09dc81..b41f0291c5 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -107,6 +107,7 @@ impl MainWorker {
),
deno_webstorage::init(options.location_data_dir.clone()),
deno_crypto::init(options.seed),
+ deno_broadcast_channel::init(),
deno_webgpu::init(options.unstable),
deno_timers::init::(),
// Metrics