1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 08:33:43 -05:00

feat(extensions): add BroadcastChannel

Co-Authored-By: Ben Noordhuis <info@bnoordhuis.nl>
Fixes: #10354
This commit is contained in:
Ben Noordhuis 2021-05-22 18:08:24 +02:00
parent 5f0d91497b
commit 8cf7f966f2
16 changed files with 357 additions and 1 deletions

9
Cargo.lock generated
View file

@ -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",

View file

@ -9,6 +9,7 @@ members = [
"serde_v8",
"test_plugin",
"test_util",
"extensions/broadcast_channel",
"extensions/console",
"extensions/crypto",
"extensions/fetch",

View file

@ -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());

View file

@ -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,
);

View file

@ -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");

View file

@ -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);

View file

@ -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"] }

View file

@ -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

View file

@ -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;
};

View file

@ -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")
}

View file

@ -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" }

View file

@ -52,6 +52,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec<PathBuf>) {
deno_crypto::init(None),
deno_webgpu::init(false),
deno_timers::init::<deno_timers::NoTimersPermission>(),
deno_broadcast_channel::init(),
];
let js_runtime = JsRuntime::new(RuntimeOptions {

View file

@ -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(

View file

@ -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;

View file

@ -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::<Permissions>(),

View file

@ -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::<Permissions>(),
// Metrics