summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Cargo.toml74
-rw-r--r--runtime/README.md44
-rw-r--r--runtime/build.rs81
-rw-r--r--runtime/colors.rs130
-rw-r--r--runtime/errors.rs209
-rw-r--r--runtime/examples/hello_runtime.js2
-rw-r--r--runtime/examples/hello_runtime.rs55
-rw-r--r--runtime/fs_util.rs80
-rw-r--r--runtime/http_util.rs46
-rw-r--r--runtime/inspector.rs952
-rw-r--r--runtime/js.rs31
-rw-r--r--runtime/lib.rs26
-rw-r--r--runtime/metrics.rs131
-rw-r--r--runtime/ops/crypto.rs14
-rw-r--r--runtime/ops/dispatch_minimal.rs205
-rw-r--r--runtime/ops/fetch.rs25
-rw-r--r--runtime/ops/fs.rs1702
-rw-r--r--runtime/ops/fs_events.rs133
-rw-r--r--runtime/ops/io.rs473
-rw-r--r--runtime/ops/mod.rs89
-rw-r--r--runtime/ops/net.rs566
-rw-r--r--runtime/ops/net_unix.rs151
-rw-r--r--runtime/ops/os.rs192
-rw-r--r--runtime/ops/permissions.rs103
-rw-r--r--runtime/ops/plugin.rs156
-rw-r--r--runtime/ops/process.rs290
-rw-r--r--runtime/ops/runtime.rs118
-rw-r--r--runtime/ops/signal.rs142
-rw-r--r--runtime/ops/timers.rs193
-rw-r--r--runtime/ops/tls.rs431
-rw-r--r--runtime/ops/tty.rs334
-rw-r--r--runtime/ops/web_worker.rs37
-rw-r--r--runtime/ops/websocket.rs326
-rw-r--r--runtime/ops/worker_host.rs318
-rw-r--r--runtime/permissions.rs1108
-rw-r--r--runtime/resolve_addr.rs72
-rw-r--r--runtime/rt/00_bootstrap_namespace.js9
-rw-r--r--runtime/rt/01_build.js26
-rw-r--r--runtime/rt/01_colors.js92
-rw-r--r--runtime/rt/01_errors.js162
-rw-r--r--runtime/rt/01_internals.js23
-rw-r--r--runtime/rt/01_version.js26
-rw-r--r--runtime/rt/01_web_util.js160
-rw-r--r--runtime/rt/02_console.js1732
-rw-r--r--runtime/rt/06_util.js148
-rw-r--r--runtime/rt/10_dispatch_minimal.js114
-rw-r--r--runtime/rt/11_timers.js557
-rw-r--r--runtime/rt/11_workers.js206
-rw-r--r--runtime/rt/12_io.js135
-rw-r--r--runtime/rt/13_buffer.js241
-rw-r--r--runtime/rt/27_websocket.js316
-rw-r--r--runtime/rt/30_files.js209
-rw-r--r--runtime/rt/30_fs.js425
-rw-r--r--runtime/rt/30_metrics.js13
-rw-r--r--runtime/rt/30_net.js245
-rw-r--r--runtime/rt/30_os.js66
-rw-r--r--runtime/rt/40_compiler_api.js97
-rw-r--r--runtime/rt/40_diagnostics.js23
-rw-r--r--runtime/rt/40_error_stack.js23
-rw-r--r--runtime/rt/40_fs_events.js52
-rw-r--r--runtime/rt/40_net_unstable.js48
-rw-r--r--runtime/rt/40_performance.js341
-rw-r--r--runtime/rt/40_permissions.js65
-rw-r--r--runtime/rt/40_plugins.js13
-rw-r--r--runtime/rt/40_process.js122
-rw-r--r--runtime/rt/40_read_file.js43
-rw-r--r--runtime/rt/40_signals.js256
-rw-r--r--runtime/rt/40_testing.js350
-rw-r--r--runtime/rt/40_tls.js82
-rw-r--r--runtime/rt/40_tty.js28
-rw-r--r--runtime/rt/40_write_file.js92
-rw-r--r--runtime/rt/41_prompt.js80
-rw-r--r--runtime/rt/90_deno_ns.js137
-rw-r--r--runtime/rt/99_main.js395
-rw-r--r--runtime/rt/README.md59
-rw-r--r--runtime/tokio_util.rs25
-rw-r--r--runtime/web_worker.rs595
-rw-r--r--runtime/worker.rs349
78 files changed, 17219 insertions, 0 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
new file mode 100644
index 000000000..fa097064e
--- /dev/null
+++ b/runtime/Cargo.toml
@@ -0,0 +1,74 @@
+# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "deno_runtime"
+version = "0.1.0"
+license = "MIT"
+authors = ["the Deno authors"]
+edition = "2018"
+description = "Provides the deno runtime library"
+repository = "https://github.com/denoland/deno"
+
+[lib]
+name = "deno_runtime"
+path = "lib.rs"
+
+[[example]]
+name = "hello_runtime"
+path = "examples/hello_runtime.rs"
+
+[build-dependencies]
+deno_crypto = { path = "../op_crates/crypto", version = "0.4.0" }
+deno_core = { path = "../core", version = "0.70.0" }
+deno_web = { path = "../op_crates/web", version = "0.21.0" }
+deno_fetch = { path = "../op_crates/fetch", version = "0.13.0" }
+
+[target.'cfg(windows)'.build-dependencies]
+winres = "0.1.11"
+winapi = "0.3.9"
+
+[dependencies]
+deno_core = { path = "../core", version = "0.70.0" }
+deno_crypto = { path = "../op_crates/crypto", version = "0.4.0" }
+deno_fetch = { path = "../op_crates/fetch", version = "0.13.0" }
+deno_web = { path = "../op_crates/web", version = "0.21.0" }
+
+atty = "0.2.14"
+dlopen = "0.1.8"
+encoding_rs = "0.8.24"
+env_logger = "0.7.1"
+filetime = "0.2.12"
+http = "0.2.1"
+indexmap = "1.6.0"
+lazy_static = "1.4.0"
+libc = "0.2.77"
+log = "0.4.11"
+notify = "5.0.0-pre.3"
+percent-encoding = "2.1.0"
+regex = "1.3.9"
+ring = "0.16.19"
+rustyline = { version = "7.0.0", default-features = false }
+rustyline-derive = "0.4.0"
+serde = { version = "1.0.116", features = ["derive"] }
+shell-escape = "0.1.5"
+sys-info = "0.7.0"
+termcolor = "1.1.0"
+tokio = { version = "0.2.22", features = ["full"] }
+tokio-rustls = "0.14.1"
+# Keep in-sync with warp.
+tokio-tungstenite = "0.11.0"
+uuid = { version = "0.8.1", features = ["v4"] }
+warp = { version = "0.2.5", features = ["tls"] }
+webpki = "0.21.3"
+webpki-roots = "=0.19.0" # Pinned to v0.19.0 to match 'reqwest'.
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3.9", features = ["knownfolders", "mswsock", "objbase", "shlobj", "tlhelp32", "winbase", "winerror", "winsock2"] }
+fwdansi = "1.1.0"
+
+[target.'cfg(unix)'.dependencies]
+nix = "0.19.0"
+
+[dev-dependencies]
+# Used in benchmark
+test_util = { path = "../test_util" }
diff --git a/runtime/README.md b/runtime/README.md
new file mode 100644
index 000000000..1056ac5b6
--- /dev/null
+++ b/runtime/README.md
@@ -0,0 +1,44 @@
+# `deno_runtime` crate
+
+[![crates](https://img.shields.io/crates/v/deno_runtime.svg)](https://crates.io/crates/deno_runtime)
+[![docs](https://docs.rs/deno_runtime/badge.svg)](https://docs.rs/deno_runtime)
+
+This is a slim version of the Deno CLI which removes typescript integration and
+various tooling (like lint and doc). Basically only JavaScript execution with
+Deno's operating system bindings (ops).
+
+## Stability
+
+This crate is built using battle-tested modules that were originally in `deno`
+crate, however the API of this crate is subject to rapid and breaking changes.
+
+## `MainWorker`
+
+The main API of this crate is `MainWorker`. `MainWorker` is a structure
+encapsulating `deno_core::JsRuntime` with a set of ops used to implement `Deno`
+namespace.
+
+When creating a `MainWorker` implementors must call `MainWorker::bootstrap` to
+prepare JS runtime for use.
+
+`MainWorker` is highly configurable and allows to customize many of the
+runtime's properties:
+
+- module loading implementation
+- error formatting
+- support for source maps
+- support for V8 inspector and Chrome Devtools debugger
+- HTTP client user agent, CA certificate
+- random number generator seed
+
+## `Worker` Web API
+
+`deno_runtime` comes with support for `Worker` Web API. The `Worker` API is
+implemented using `WebWorker` structure.
+
+When creating a new instance of `MainWorker` implementors must provide a
+callback function that is used when creating a new instance of `Worker`.
+
+All `WebWorker` instances are decendents of `MainWorker` which is responsible
+for setting up communication with child worker. Each `WebWorker` spawns a new OS
+thread that is dedicated solely to that worker.
diff --git a/runtime/build.rs b/runtime/build.rs
new file mode 100644
index 000000000..7c74c9793
--- /dev/null
+++ b/runtime/build.rs
@@ -0,0 +1,81 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::JsRuntime;
+use deno_core::RuntimeOptions;
+use std::env;
+use std::path::Path;
+use std::path::PathBuf;
+
+// TODO(bartlomieju): this module contains a lot of duplicated
+// logic with `cli/build.rs`, factor out to `deno_core`.
+fn create_snapshot(
+ mut js_runtime: JsRuntime,
+ snapshot_path: &Path,
+ files: Vec<PathBuf>,
+) {
+ deno_web::init(&mut js_runtime);
+ deno_fetch::init(&mut js_runtime);
+ deno_crypto::init(&mut js_runtime);
+ // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
+ // workspace root.
+ let display_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
+ for file in files {
+ println!("cargo:rerun-if-changed={}", file.display());
+ let display_path = file.strip_prefix(display_root).unwrap();
+ let display_path_str = display_path.display().to_string();
+ js_runtime
+ .execute(
+ &("deno:".to_string() + &display_path_str.replace('\\', "/")),
+ &std::fs::read_to_string(&file).unwrap(),
+ )
+ .unwrap();
+ }
+
+ let snapshot = js_runtime.snapshot();
+ let snapshot_slice: &[u8] = &*snapshot;
+ println!("Snapshot size: {}", snapshot_slice.len());
+ std::fs::write(&snapshot_path, snapshot_slice).unwrap();
+ println!("Snapshot written to: {} ", snapshot_path.display());
+}
+
+fn create_runtime_snapshot(snapshot_path: &Path, files: Vec<PathBuf>) {
+ let js_runtime = JsRuntime::new(RuntimeOptions {
+ will_snapshot: true,
+ ..Default::default()
+ });
+ create_snapshot(js_runtime, snapshot_path, files);
+}
+
+fn main() {
+ // Don't build V8 if "cargo doc" is being run. This is to support docs.rs.
+ if env::var_os("RUSTDOCFLAGS").is_some() {
+ return;
+ }
+
+ // To debug snapshot issues uncomment:
+ // op_fetch_asset::trace_serializer();
+
+ println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
+ println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
+ let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+
+ // Main snapshot
+ let runtime_snapshot_path = o.join("CLI_SNAPSHOT.bin");
+
+ let js_files = get_js_files("rt");
+ create_runtime_snapshot(&runtime_snapshot_path, js_files);
+}
+
+fn get_js_files(d: &str) -> Vec<PathBuf> {
+ let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
+ let mut js_files = std::fs::read_dir(d)
+ .unwrap()
+ .map(|dir_entry| {
+ let file = dir_entry.unwrap();
+ manifest_dir.join(file.path())
+ })
+ .filter(|path| path.extension().unwrap_or_default() == "js")
+ .collect::<Vec<PathBuf>>();
+ js_files.sort();
+ js_files
+}
diff --git a/runtime/colors.rs b/runtime/colors.rs
new file mode 100644
index 000000000..93f252716
--- /dev/null
+++ b/runtime/colors.rs
@@ -0,0 +1,130 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use regex::Regex;
+use std::env;
+use std::fmt;
+use std::io::Write;
+use termcolor::Color::{Ansi256, Black, Blue, Cyan, Green, Red, White, Yellow};
+use termcolor::{Ansi, ColorSpec, WriteColor};
+
+#[cfg(windows)]
+use termcolor::{BufferWriter, ColorChoice};
+
+lazy_static! {
+ // STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
+ // Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
+ static ref STRIP_ANSI_RE: Regex = Regex::new(
+ r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
+ ).unwrap();
+ static ref NO_COLOR: bool = {
+ env::var_os("NO_COLOR").is_some()
+ };
+}
+
+/// Helper function to strip ansi codes.
+#[cfg(test)]
+pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
+ STRIP_ANSI_RE.replace_all(s, "")
+}
+
+pub fn use_color() -> bool {
+ !(*NO_COLOR)
+}
+
+#[cfg(windows)]
+pub fn enable_ansi() {
+ BufferWriter::stdout(ColorChoice::AlwaysAnsi);
+}
+
+fn style(s: &str, colorspec: ColorSpec) -> impl fmt::Display {
+ if !use_color() {
+ return String::from(s);
+ }
+ let mut v = Vec::new();
+ let mut ansi_writer = Ansi::new(&mut v);
+ ansi_writer.set_color(&colorspec).unwrap();
+ ansi_writer.write_all(s.as_bytes()).unwrap();
+ ansi_writer.reset().unwrap();
+ String::from_utf8_lossy(&v).into_owned()
+}
+
+pub fn red_bold(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Red)).set_bold(true);
+ style(&s, style_spec)
+}
+
+pub fn green_bold(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Green)).set_bold(true);
+ style(&s, style_spec)
+}
+
+pub fn italic_bold(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bold(true).set_italic(true);
+ style(&s, style_spec)
+}
+
+pub fn white_on_red(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bg(Some(Red)).set_fg(Some(White));
+ style(&s, style_spec)
+}
+
+pub fn black_on_green(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bg(Some(Green)).set_fg(Some(Black));
+ style(&s, style_spec)
+}
+
+pub fn yellow(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Yellow));
+ style(&s, style_spec)
+}
+
+pub fn cyan(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Cyan));
+ style(&s, style_spec)
+}
+
+pub fn red(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Red));
+ style(&s, style_spec)
+}
+
+pub fn green(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Green));
+ style(&s, style_spec)
+}
+
+pub fn bold(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bold(true);
+ style(&s, style_spec)
+}
+
+pub fn gray(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Ansi256(8)));
+ style(&s, style_spec)
+}
+
+pub fn italic_bold_gray(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec
+ .set_fg(Some(Ansi256(8)))
+ .set_bold(true)
+ .set_italic(true);
+ style(&s, style_spec)
+}
+
+pub fn intense_blue(s: &str) -> impl fmt::Display {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(Blue)).set_intense(true);
+ style(&s, style_spec)
+}
diff --git a/runtime/errors.rs b/runtime/errors.rs
new file mode 100644
index 000000000..f8f71a859
--- /dev/null
+++ b/runtime/errors.rs
@@ -0,0 +1,209 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+//! There are many types of errors in Deno:
+//! - AnyError: a generic wrapper that can encapsulate any type of error.
+//! - JsError: a container for the error message and stack trace for exceptions
+//! thrown in JavaScript code. We use this to pretty-print stack traces.
+//! - Diagnostic: these are errors that originate in TypeScript's compiler.
+//! They're similar to JsError, in that they have line numbers. But
+//! Diagnostics are compile-time type errors, whereas JsErrors are runtime
+//! exceptions.
+
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::url;
+use deno_core::ModuleResolutionError;
+use deno_fetch::reqwest;
+use rustyline::error::ReadlineError;
+use std::env;
+use std::error::Error;
+use std::io;
+
+fn get_dlopen_error_class(error: &dlopen::Error) -> &'static str {
+ use dlopen::Error::*;
+ match error {
+ NullCharacter(_) => "InvalidData",
+ OpeningLibraryError(ref e) => get_io_error_class(e),
+ SymbolGettingError(ref e) => get_io_error_class(e),
+ AddrNotMatchingDll(ref e) => get_io_error_class(e),
+ NullSymbol => "NotFound",
+ }
+}
+
+fn get_env_var_error_class(error: &env::VarError) -> &'static str {
+ use env::VarError::*;
+ match error {
+ NotPresent => "NotFound",
+ NotUnicode(..) => "InvalidData",
+ }
+}
+
+fn get_io_error_class(error: &io::Error) -> &'static str {
+ use io::ErrorKind::*;
+ match error.kind() {
+ NotFound => "NotFound",
+ PermissionDenied => "PermissionDenied",
+ ConnectionRefused => "ConnectionRefused",
+ ConnectionReset => "ConnectionReset",
+ ConnectionAborted => "ConnectionAborted",
+ NotConnected => "NotConnected",
+ AddrInUse => "AddrInUse",
+ AddrNotAvailable => "AddrNotAvailable",
+ BrokenPipe => "BrokenPipe",
+ AlreadyExists => "AlreadyExists",
+ InvalidInput => "TypeError",
+ InvalidData => "InvalidData",
+ TimedOut => "TimedOut",
+ Interrupted => "Interrupted",
+ WriteZero => "WriteZero",
+ UnexpectedEof => "UnexpectedEof",
+ Other => "Error",
+ WouldBlock => unreachable!(),
+ // Non-exhaustive enum - might add new variants
+ // in the future
+ _ => unreachable!(),
+ }
+}
+
+fn get_module_resolution_error_class(
+ _: &ModuleResolutionError,
+) -> &'static str {
+ "URIError"
+}
+
+fn get_notify_error_class(error: &notify::Error) -> &'static str {
+ use notify::ErrorKind::*;
+ match error.kind {
+ Generic(_) => "Error",
+ Io(ref e) => get_io_error_class(e),
+ PathNotFound => "NotFound",
+ WatchNotFound => "NotFound",
+ InvalidConfig(_) => "InvalidData",
+ }
+}
+
+fn get_readline_error_class(error: &ReadlineError) -> &'static str {
+ use ReadlineError::*;
+ match error {
+ Io(err) => get_io_error_class(err),
+ Eof => "UnexpectedEof",
+ Interrupted => "Interrupted",
+ #[cfg(unix)]
+ Errno(err) => get_nix_error_class(err),
+ _ => unimplemented!(),
+ }
+}
+
+fn get_regex_error_class(error: &regex::Error) -> &'static str {
+ use regex::Error::*;
+ match error {
+ Syntax(_) => "SyntaxError",
+ CompiledTooBig(_) => "RangeError",
+ _ => "Error",
+ }
+}
+
+fn get_request_error_class(error: &reqwest::Error) -> &'static str {
+ error
+ .source()
+ .and_then(|inner_err| {
+ (inner_err
+ .downcast_ref::<io::Error>()
+ .map(get_io_error_class))
+ .or_else(|| {
+ inner_err
+ .downcast_ref::<serde_json::error::Error>()
+ .map(get_serde_json_error_class)
+ })
+ .or_else(|| {
+ inner_err
+ .downcast_ref::<url::ParseError>()
+ .map(get_url_parse_error_class)
+ })
+ })
+ .unwrap_or("Http")
+}
+
+fn get_serde_json_error_class(
+ error: &serde_json::error::Error,
+) -> &'static str {
+ use deno_core::serde_json::error::*;
+ match error.classify() {
+ Category::Io => error
+ .source()
+ .and_then(|e| e.downcast_ref::<io::Error>())
+ .map(get_io_error_class)
+ .unwrap(),
+ Category::Syntax => "SyntaxError",
+ Category::Data => "InvalidData",
+ Category::Eof => "UnexpectedEof",
+ }
+}
+
+fn get_url_parse_error_class(_error: &url::ParseError) -> &'static str {
+ "URIError"
+}
+
+#[cfg(unix)]
+fn get_nix_error_class(error: &nix::Error) -> &'static str {
+ use nix::errno::Errno::*;
+ match error {
+ nix::Error::Sys(ECHILD) => "NotFound",
+ nix::Error::Sys(EINVAL) => "TypeError",
+ nix::Error::Sys(ENOENT) => "NotFound",
+ nix::Error::Sys(ENOTTY) => "BadResource",
+ nix::Error::Sys(EPERM) => "PermissionDenied",
+ nix::Error::Sys(ESRCH) => "NotFound",
+ nix::Error::Sys(UnknownErrno) => "Error",
+ nix::Error::Sys(_) => "Error",
+ nix::Error::InvalidPath => "TypeError",
+ nix::Error::InvalidUtf8 => "InvalidData",
+ nix::Error::UnsupportedOperation => unreachable!(),
+ }
+}
+
+pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
+ deno_core::error::get_custom_error_class(e)
+ .or_else(|| {
+ e.downcast_ref::<dlopen::Error>()
+ .map(get_dlopen_error_class)
+ })
+ .or_else(|| {
+ e.downcast_ref::<env::VarError>()
+ .map(get_env_var_error_class)
+ })
+ .or_else(|| e.downcast_ref::<io::Error>().map(get_io_error_class))
+ .or_else(|| {
+ e.downcast_ref::<ModuleResolutionError>()
+ .map(get_module_resolution_error_class)
+ })
+ .or_else(|| {
+ e.downcast_ref::<notify::Error>()
+ .map(get_notify_error_class)
+ })
+ .or_else(|| {
+ e.downcast_ref::<ReadlineError>()
+ .map(get_readline_error_class)
+ })
+ .or_else(|| {
+ e.downcast_ref::<reqwest::Error>()
+ .map(get_request_error_class)
+ })
+ .or_else(|| e.downcast_ref::<regex::Error>().map(get_regex_error_class))
+ .or_else(|| {
+ e.downcast_ref::<serde_json::error::Error>()
+ .map(get_serde_json_error_class)
+ })
+ .or_else(|| {
+ e.downcast_ref::<url::ParseError>()
+ .map(get_url_parse_error_class)
+ })
+ .or_else(|| {
+ #[cfg(unix)]
+ let maybe_get_nix_error_class =
+ || e.downcast_ref::<nix::Error>().map(get_nix_error_class);
+ #[cfg(not(unix))]
+ let maybe_get_nix_error_class = || Option::<&'static str>::None;
+ (maybe_get_nix_error_class)()
+ })
+}
diff --git a/runtime/examples/hello_runtime.js b/runtime/examples/hello_runtime.js
new file mode 100644
index 000000000..46609c7a0
--- /dev/null
+++ b/runtime/examples/hello_runtime.js
@@ -0,0 +1,2 @@
+console.log("Hello world!");
+console.log(Deno);
diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs
new file mode 100644
index 000000000..dbe539281
--- /dev/null
+++ b/runtime/examples/hello_runtime.rs
@@ -0,0 +1,55 @@
+// Copyright 2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_core::FsModuleLoader;
+use deno_core::ModuleSpecifier;
+use deno_runtime::permissions::Permissions;
+use deno_runtime::worker::MainWorker;
+use deno_runtime::worker::WorkerOptions;
+use std::path::Path;
+use std::rc::Rc;
+use std::sync::Arc;
+
+fn get_error_class_name(e: &AnyError) -> &'static str {
+ deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
+}
+
+#[tokio::main]
+async fn main() -> Result<(), AnyError> {
+ let module_loader = Rc::new(FsModuleLoader);
+ let create_web_worker_cb = Arc::new(|_| {
+ todo!("Web workers are not supported in the example");
+ });
+
+ let options = WorkerOptions {
+ apply_source_maps: false,
+ args: vec![],
+ debug_flag: false,
+ unstable: false,
+ ca_filepath: None,
+ user_agent: "hello_runtime".to_string(),
+ seed: None,
+ js_error_create_fn: None,
+ create_web_worker_cb,
+ attach_inspector: false,
+ maybe_inspector_server: None,
+ should_break_on_first_statement: false,
+ module_loader,
+ runtime_version: "x".to_string(),
+ ts_version: "x".to_string(),
+ no_color: false,
+ get_error_class_fn: Some(&get_error_class_name),
+ };
+
+ let js_path =
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/hello_runtime.js");
+ let main_module = ModuleSpecifier::resolve_path(&js_path.to_string_lossy())?;
+ let permissions = Permissions::allow_all();
+
+ let mut worker =
+ MainWorker::from_options(main_module.clone(), permissions, &options);
+ worker.bootstrap(&options);
+ worker.execute_module(&main_module).await?;
+ worker.run_event_loop().await?;
+ Ok(())
+}
diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs
new file mode 100644
index 000000000..028538d4f
--- /dev/null
+++ b/runtime/fs_util.rs
@@ -0,0 +1,80 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+pub use deno_core::normalize_path;
+use std::env::current_dir;
+use std::io::Error;
+use std::path::{Path, PathBuf};
+
+/// Similar to `std::fs::canonicalize()` but strips UNC prefixes on Windows.
+pub fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> {
+ let mut canonicalized_path = path.canonicalize()?;
+ if cfg!(windows) {
+ canonicalized_path = PathBuf::from(
+ canonicalized_path
+ .display()
+ .to_string()
+ .trim_start_matches("\\\\?\\"),
+ );
+ }
+ Ok(canonicalized_path)
+}
+
+pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
+ let resolved_path = if path.is_absolute() {
+ path.to_owned()
+ } else {
+ let cwd = current_dir().unwrap();
+ cwd.join(path)
+ };
+
+ Ok(normalize_path(&resolved_path))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn resolve_from_cwd_child() {
+ let cwd = current_dir().unwrap();
+ assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a"));
+ }
+
+ #[test]
+ fn resolve_from_cwd_dot() {
+ let cwd = current_dir().unwrap();
+ assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd);
+ }
+
+ #[test]
+ fn resolve_from_cwd_parent() {
+ let cwd = current_dir().unwrap();
+ assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd);
+ }
+
+ #[test]
+ fn test_normalize_path() {
+ assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b"));
+ assert_eq!(normalize_path(Path::new("a/./b/")), PathBuf::from("a/b/"));
+ assert_eq!(
+ normalize_path(Path::new("a/./b/../c")),
+ PathBuf::from("a/c")
+ );
+
+ if cfg!(windows) {
+ assert_eq!(
+ normalize_path(Path::new("C:\\a\\.\\b\\..\\c")),
+ PathBuf::from("C:\\a\\c")
+ );
+ }
+ }
+
+ // TODO: Get a good expected value here for Windows.
+ #[cfg(not(windows))]
+ #[test]
+ fn resolve_from_cwd_absolute() {
+ let expected = Path::new("/a");
+ assert_eq!(resolve_from_cwd(expected).unwrap(), expected);
+ }
+}
diff --git a/runtime/http_util.rs b/runtime/http_util.rs
new file mode 100644
index 000000000..67703c214
--- /dev/null
+++ b/runtime/http_util.rs
@@ -0,0 +1,46 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use deno_fetch::reqwest;
+use deno_fetch::reqwest::header::HeaderMap;
+use deno_fetch::reqwest::header::USER_AGENT;
+use deno_fetch::reqwest::redirect::Policy;
+use deno_fetch::reqwest::Client;
+use std::fs::File;
+use std::io::Read;
+
+/// Create new instance of async reqwest::Client. This client supports
+/// proxies and doesn't follow redirects.
+pub fn create_http_client(
+ user_agent: String,
+ ca_file: Option<&str>,
+) -> Result<Client, AnyError> {
+ let mut headers = HeaderMap::new();
+ headers.insert(USER_AGENT, user_agent.parse().unwrap());
+ let mut builder = Client::builder()
+ .redirect(Policy::none())
+ .default_headers(headers)
+ .use_rustls_tls();
+
+ if let Some(ca_file) = ca_file {
+ let mut buf = Vec::new();
+ File::open(ca_file)?.read_to_end(&mut buf)?;
+ let cert = reqwest::Certificate::from_pem(&buf)?;
+ builder = builder.add_root_certificate(cert);
+ }
+
+ builder
+ .build()
+ .map_err(|_| generic_error("Unable to build http client"))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn create_test_client() {
+ create_http_client("test_client".to_string(), None).unwrap();
+ }
+}
diff --git a/runtime/inspector.rs b/runtime/inspector.rs
new file mode 100644
index 000000000..89fd5bf57
--- /dev/null
+++ b/runtime/inspector.rs
@@ -0,0 +1,952 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+//! The documentation for the inspector API is sparse, but these are helpful:
+//! https://chromedevtools.github.io/devtools-protocol/
+//! https://hyperandroid.com/2020/02/12/v8-inspector-from-an-embedder-standpoint/
+
+use core::convert::Infallible as Never; // Alias for the future `!` type.
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use deno_core::futures::channel::mpsc;
+use deno_core::futures::channel::mpsc::UnboundedReceiver;
+use deno_core::futures::channel::mpsc::UnboundedSender;
+use deno_core::futures::channel::oneshot;
+use deno_core::futures::future::Future;
+use deno_core::futures::pin_mut;
+use deno_core::futures::prelude::*;
+use deno_core::futures::select;
+use deno_core::futures::stream::FuturesUnordered;
+use deno_core::futures::task;
+use deno_core::futures::task::Context;
+use deno_core::futures::task::Poll;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::v8;
+use std::cell::BorrowMutError;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::ffi::c_void;
+use std::mem::replace;
+use std::mem::take;
+use std::mem::MaybeUninit;
+use std::net::SocketAddr;
+use std::ops::Deref;
+use std::ops::DerefMut;
+use std::pin::Pin;
+use std::process;
+use std::ptr;
+use std::ptr::NonNull;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::thread;
+use uuid::Uuid;
+use warp::filters::ws;
+use warp::Filter;
+
+pub struct InspectorServer {
+ pub host: SocketAddr,
+ register_inspector_tx: UnboundedSender<InspectorInfo>,
+ shutdown_server_tx: Option<oneshot::Sender<()>>,
+ thread_handle: Option<thread::JoinHandle<()>>,
+}
+
+impl InspectorServer {
+ pub fn new(host: SocketAddr, name: String) -> Self {
+ let (register_inspector_tx, register_inspector_rx) =
+ mpsc::unbounded::<InspectorInfo>();
+
+ let (shutdown_server_tx, shutdown_server_rx) = oneshot::channel();
+
+ let thread_handle = thread::spawn(move || {
+ crate::tokio_util::run_basic(server(
+ host,
+ register_inspector_rx,
+ shutdown_server_rx,
+ name,
+ ))
+ });
+
+ Self {
+ host,
+ register_inspector_tx,
+ shutdown_server_tx: Some(shutdown_server_tx),
+ thread_handle: Some(thread_handle),
+ }
+ }
+
+ fn register_inspector(&self, info: InspectorInfo) {
+ self.register_inspector_tx.unbounded_send(info).unwrap();
+ }
+}
+
+impl Drop for InspectorServer {
+ fn drop(&mut self) {
+ if let Some(shutdown_server_tx) = self.shutdown_server_tx.take() {
+ shutdown_server_tx
+ .send(())
+ .expect("unable to send shutdown signal");
+ }
+
+ if let Some(thread_handle) = self.thread_handle.take() {
+ thread_handle.join().expect("unable to join thread");
+ }
+ }
+}
+
+/// Inspector information that is sent from the isolate thread to the server
+/// thread when a new inspector is created.
+struct InspectorInfo {
+ host: SocketAddr,
+ uuid: Uuid,
+ thread_name: Option<String>,
+ new_websocket_tx: UnboundedSender<WebSocketProxy>,
+ canary_rx: oneshot::Receiver<Never>,
+}
+
+impl InspectorInfo {
+ fn get_json_metadata(&self) -> Value {
+ json!({
+ "description": "deno",
+ "devtoolsFrontendUrl": self.get_frontend_url(),
+ "faviconUrl": "https://deno.land/favicon.ico",
+ "id": self.uuid.to_string(),
+ "title": self.get_title(),
+ "type": "deno",
+ // TODO(ry): "url": "file://",
+ "webSocketDebuggerUrl": self.get_websocket_debugger_url(),
+ })
+ }
+
+ fn get_websocket_debugger_url(&self) -> String {
+ format!("ws://{}/ws/{}", &self.host, &self.uuid)
+ }
+
+ fn get_frontend_url(&self) -> String {
+ format!(
+ "devtools://devtools/bundled/inspector.html?v8only=true&ws={}/ws/{}",
+ &self.host, &self.uuid
+ )
+ }
+
+ fn get_title(&self) -> String {
+ format!(
+ "[{}] deno{}",
+ process::id(),
+ self
+ .thread_name
+ .as_ref()
+ .map(|n| format!(" - {}", n))
+ .unwrap_or_default()
+ )
+ }
+}
+
+async fn server(
+ host: SocketAddr,
+ register_inspector_rx: UnboundedReceiver<InspectorInfo>,
+ shutdown_server_rx: oneshot::Receiver<()>,
+ name: String,
+) {
+ // TODO: put the `inspector_map` in an `Rc<RefCell<_>>` instead. This is
+ // currently not possible because warp requires all filters to implement
+ // `Send`, which should not be necessary because we are using the
+ // single-threaded Tokio runtime.
+ let inspector_map = HashMap::<Uuid, InspectorInfo>::new();
+ let inspector_map = Arc::new(Mutex::new(inspector_map));
+
+ let inspector_map_ = inspector_map.clone();
+ let register_inspector_handler = register_inspector_rx
+ .map(|info| {
+ eprintln!(
+ "Debugger listening on {}",
+ info.get_websocket_debugger_url()
+ );
+ let mut g = inspector_map_.lock().unwrap();
+ if g.insert(info.uuid, info).is_some() {
+ panic!("Inspector UUID already in map");
+ }
+ })
+ .collect::<()>();
+
+ let inspector_map_ = inspector_map_.clone();
+ let deregister_inspector_handler = future::poll_fn(|cx| {
+ let mut g = inspector_map_.lock().unwrap();
+ g.retain(|_, info| info.canary_rx.poll_unpin(cx) == Poll::Pending);
+ Poll::<Never>::Pending
+ })
+ .fuse();
+
+ let inspector_map_ = inspector_map.clone();
+ let websocket_route = warp::path("ws")
+ .and(warp::path::param())
+ .and(warp::ws())
+ .and_then(move |uuid: String, ws: warp::ws::Ws| {
+ future::ready(
+ Uuid::parse_str(&uuid)
+ .ok()
+ .and_then(|uuid| {
+ let g = inspector_map_.lock().unwrap();
+ g.get(&uuid).map(|info| info.new_websocket_tx.clone()).map(
+ |new_websocket_tx| {
+ ws.on_upgrade(move |websocket| async move {
+ let (proxy, pump) = create_websocket_proxy(websocket);
+ let _ = new_websocket_tx.unbounded_send(proxy);
+ pump.await;
+ })
+ },
+ )
+ })
+ .ok_or_else(warp::reject::not_found),
+ )
+ });
+
+ let json_version_response = json!({
+ "Browser": name,
+ "Protocol-Version": "1.3",
+ "V8-Version": deno_core::v8_version(),
+ });
+ let json_version_route = warp::path!("json" / "version")
+ .map(move || warp::reply::json(&json_version_response));
+
+ let inspector_map_ = inspector_map.clone();
+ let json_list_route = warp::path("json").map(move || {
+ let g = inspector_map_.lock().unwrap();
+ let json_values = g
+ .values()
+ .map(|info| info.get_json_metadata())
+ .collect::<Vec<_>>();
+ warp::reply::json(&json!(json_values))
+ });
+
+ let server_routes =
+ websocket_route.or(json_version_route).or(json_list_route);
+ let server_handler = warp::serve(server_routes)
+ .try_bind_with_graceful_shutdown(host, async {
+ shutdown_server_rx.await.ok();
+ })
+ .map(|(_, fut)| fut)
+ .unwrap_or_else(|err| {
+ eprintln!("Cannot start inspector server: {}.", err);
+ process::exit(1);
+ })
+ .fuse();
+
+ pin_mut!(register_inspector_handler);
+ pin_mut!(deregister_inspector_handler);
+ pin_mut!(server_handler);
+
+ select! {
+ _ = register_inspector_handler => {},
+ _ = deregister_inspector_handler => unreachable!(),
+ _ = server_handler => {},
+ }
+}
+
+type WebSocketProxySender = UnboundedSender<ws::Message>;
+type WebSocketProxyReceiver =
+ UnboundedReceiver<Result<ws::Message, warp::Error>>;
+
+/// Encapsulates an UnboundedSender/UnboundedReceiver pair that together form
+/// a duplex channel for sending/receiving websocket messages.
+struct WebSocketProxy {
+ tx: WebSocketProxySender,
+ rx: WebSocketProxyReceiver,
+}
+
+impl WebSocketProxy {
+ pub fn split(self) -> (WebSocketProxySender, WebSocketProxyReceiver) {
+ (self.tx, self.rx)
+ }
+}
+
+/// Creates a future that proxies messages sent and received on a warp WebSocket
+/// to a UnboundedSender/UnboundedReceiver pair. We need this to sidestep
+/// Tokio's task budget, which causes issues when DenoInspector::poll_sessions()
+/// needs to block the thread because JavaScript execution is paused.
+///
+/// This works because UnboundedSender/UnboundedReceiver are implemented in the
+/// 'futures' crate, therefore they can't participate in Tokio's cooperative
+/// task yielding.
+///
+/// A tuple is returned, where the first element is a duplex channel that can
+/// be used to send/receive messages on the websocket, and the second element
+/// is a future that does the forwarding.
+fn create_websocket_proxy(
+ websocket: ws::WebSocket,
+) -> (WebSocketProxy, impl Future<Output = ()> + Send) {
+ // The 'outbound' channel carries messages sent to the websocket.
+ let (outbound_tx, outbound_rx) = mpsc::unbounded();
+
+ // The 'inbound' channel carries messages received from the websocket.
+ let (inbound_tx, inbound_rx) = mpsc::unbounded();
+
+ let proxy = WebSocketProxy {
+ tx: outbound_tx,
+ rx: inbound_rx,
+ };
+
+ // The pump future takes care of forwarding messages between the websocket
+ // and channels. It resolves to () when either side disconnects, ignoring any
+ // errors.
+ let pump = async move {
+ let (websocket_tx, websocket_rx) = websocket.split();
+
+ let outbound_pump =
+ outbound_rx.map(Ok).forward(websocket_tx).map_err(|_| ());
+
+ let inbound_pump = websocket_rx
+ .map(|msg| inbound_tx.unbounded_send(msg))
+ .map_err(|_| ())
+ .try_collect::<()>();
+
+ let _ = future::try_join(outbound_pump, inbound_pump).await;
+ };
+
+ (proxy, pump)
+}
+
+#[derive(Clone, Copy)]
+enum PollState {
+ Idle,
+ Woken,
+ Polling,
+ Parked,
+ Dropped,
+}
+
+pub struct DenoInspector {
+ v8_inspector_client: v8::inspector::V8InspectorClientBase,
+ v8_inspector: v8::UniqueRef<v8::inspector::V8Inspector>,
+ sessions: RefCell<InspectorSessions>,
+ flags: RefCell<InspectorFlags>,
+ waker: Arc<InspectorWaker>,
+ _canary_tx: oneshot::Sender<Never>,
+ pub server: Option<Arc<InspectorServer>>,
+ pub debugger_url: Option<String>,
+}
+
+impl Deref for DenoInspector {
+ type Target = v8::inspector::V8Inspector;
+ fn deref(&self) -> &Self::Target {
+ &self.v8_inspector
+ }
+}
+
+impl DerefMut for DenoInspector {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.v8_inspector
+ }
+}
+
+impl Drop for DenoInspector {
+ fn drop(&mut self) {
+ // Since the waker is cloneable, it might outlive the inspector itself.
+ // Set the poll state to 'dropped' so it doesn't attempt to request an
+ // interrupt from the isolate.
+ self.waker.update(|w| w.poll_state = PollState::Dropped);
+ // V8 automatically deletes all sessions when an Inspector instance is
+ // deleted, however InspectorSession also has a drop handler that cleans
+ // up after itself. To avoid a double free, make sure the inspector is
+ // dropped last.
+ take(&mut *self.sessions.borrow_mut());
+ }
+}
+
+impl v8::inspector::V8InspectorClientImpl for DenoInspector {
+ fn base(&self) -> &v8::inspector::V8InspectorClientBase {
+ &self.v8_inspector_client
+ }
+
+ fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase {
+ &mut self.v8_inspector_client
+ }
+
+ fn run_message_loop_on_pause(&mut self, context_group_id: i32) {
+ assert_eq!(context_group_id, DenoInspectorSession::CONTEXT_GROUP_ID);
+ self.flags.borrow_mut().on_pause = true;
+ let _ = self.poll_sessions(None);
+ }
+
+ fn quit_message_loop_on_pause(&mut self) {
+ self.flags.borrow_mut().on_pause = false;
+ }
+
+ fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {
+ assert_eq!(context_group_id, DenoInspectorSession::CONTEXT_GROUP_ID);
+ self.flags.borrow_mut().session_handshake_done = true;
+ }
+}
+
+/// DenoInspector implements a Future so that it can poll for new incoming
+/// connections and messages from the WebSocket server. The Worker that owns
+/// this DenoInspector will call our poll function from Worker::poll().
+impl Future for DenoInspector {
+ type Output = ();
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
+ self.poll_sessions(Some(cx)).unwrap()
+ }
+}
+
+impl DenoInspector {
+ const CONTEXT_GROUP_ID: i32 = 1;
+
+ pub fn new(
+ js_runtime: &mut deno_core::JsRuntime,
+ server: Option<Arc<InspectorServer>>,
+ ) -> Box<Self> {
+ let context = js_runtime.global_context();
+ let scope = &mut v8::HandleScope::new(js_runtime.v8_isolate());
+
+ let (new_websocket_tx, new_websocket_rx) =
+ mpsc::unbounded::<WebSocketProxy>();
+ let (canary_tx, canary_rx) = oneshot::channel::<Never>();
+
+ // Create DenoInspector instance.
+ let mut self_ = new_box_with(|self_ptr| {
+ let v8_inspector_client =
+ v8::inspector::V8InspectorClientBase::new::<Self>();
+ let v8_inspector =
+ v8::inspector::V8Inspector::create(scope, unsafe { &mut *self_ptr });
+
+ let sessions = InspectorSessions::new(self_ptr, new_websocket_rx);
+ let flags = InspectorFlags::new();
+ let waker = InspectorWaker::new(scope.thread_safe_handle());
+
+ let debugger_url = if let Some(server) = server.clone() {
+ let info = InspectorInfo {
+ host: server.host,
+ uuid: Uuid::new_v4(),
+ thread_name: thread::current().name().map(|n| n.to_owned()),
+ new_websocket_tx,
+ canary_rx,
+ };
+
+ let debugger_url = info.get_websocket_debugger_url();
+ server.register_inspector(info);
+ Some(debugger_url)
+ } else {
+ None
+ };
+
+ Self {
+ v8_inspector_client,
+ v8_inspector,
+ sessions,
+ flags,
+ waker,
+ _canary_tx: canary_tx,
+ server,
+ debugger_url,
+ }
+ });
+
+ // Tell the inspector about the global context.
+ let context = v8::Local::new(scope, context);
+ let context_name = v8::inspector::StringView::from(&b"global context"[..]);
+ self_.context_created(context, Self::CONTEXT_GROUP_ID, context_name);
+
+ // Poll the session handler so we will get notified whenever there is
+ // new_incoming debugger activity.
+ let _ = self_.poll_sessions(None).unwrap();
+
+ self_
+ }
+
+ fn poll_sessions(
+ &self,
+ mut invoker_cx: Option<&mut Context>,
+ ) -> Result<Poll<()>, BorrowMutError> {
+ // Short-circuit if there is no server
+ if self.server.is_none() {
+ return Ok(Poll::Ready(()));
+ }
+
+ // The futures this function uses do not have re-entrant poll() functions.
+ // However it is can happpen that poll_sessions() gets re-entered, e.g.
+ // when an interrupt request is honored while the inspector future is polled
+ // by the task executor. We let the caller know by returning some error.
+ let mut sessions = self.sessions.try_borrow_mut()?;
+
+ self.waker.update(|w| {
+ match w.poll_state {
+ PollState::Idle | PollState::Woken => w.poll_state = PollState::Polling,
+ _ => unreachable!(),
+ };
+ });
+
+ // Create a new Context object that will make downstream futures
+ // use the InspectorWaker when they are ready to be polled again.
+ let waker_ref = task::waker_ref(&self.waker);
+ let cx = &mut Context::from_waker(&waker_ref);
+
+ loop {
+ loop {
+ // Do one "handshake" with a newly connected session at a time.
+ if let Some(session) = &mut sessions.handshake {
+ let poll_result = session.poll_unpin(cx);
+ let handshake_done =
+ replace(&mut self.flags.borrow_mut().session_handshake_done, false);
+ match poll_result {
+ Poll::Pending if handshake_done => {
+ let session = sessions.handshake.take().unwrap();
+ sessions.established.push(session);
+ take(&mut self.flags.borrow_mut().waiting_for_session);
+ }
+ Poll::Ready(_) => sessions.handshake = None,
+ Poll::Pending => break,
+ };
+ }
+
+ // Accept new connections.
+ match sessions.new_incoming.poll_next_unpin(cx) {
+ Poll::Ready(Some(session)) => {
+ let prev = sessions.handshake.replace(session);
+ assert!(prev.is_none());
+ continue;
+ }
+ Poll::Ready(None) => {}
+ Poll::Pending => {}
+ }
+
+ // Poll established sessions.
+ match sessions.established.poll_next_unpin(cx) {
+ Poll::Ready(Some(_)) => continue,
+ Poll::Ready(None) => break,
+ Poll::Pending => break,
+ };
+ }
+
+ let should_block = sessions.handshake.is_some()
+ || self.flags.borrow().on_pause
+ || self.flags.borrow().waiting_for_session;
+
+ let new_state = self.waker.update(|w| {
+ match w.poll_state {
+ PollState::Woken => {
+ // The inspector was woken while the session handler was being
+ // polled, so we poll it another time.
+ w.poll_state = PollState::Polling;
+ }
+ PollState::Polling if !should_block => {
+ // The session handler doesn't need to be polled any longer, and
+ // there's no reason to block (execution is not paused), so this
+ // function is about to return.
+ w.poll_state = PollState::Idle;
+ // Register the task waker that can be used to wake the parent
+ // task that will poll the inspector future.
+ if let Some(cx) = invoker_cx.take() {
+ w.task_waker.replace(cx.waker().clone());
+ }
+ // Register the address of the inspector, which allows the waker
+ // to request an interrupt from the isolate.
+ w.inspector_ptr = NonNull::new(self as *const _ as *mut Self);
+ }
+ PollState::Polling if should_block => {
+ // Isolate execution has been paused but there are no more
+ // events to process, so this thread will be parked. Therefore,
+ // store the current thread handle in the waker so it knows
+ // which thread to unpark when new events arrive.
+ w.poll_state = PollState::Parked;
+ w.parked_thread.replace(thread::current());
+ }
+ _ => unreachable!(),
+ };
+ w.poll_state
+ });
+ match new_state {
+ PollState::Idle => break Ok(Poll::Pending), // Yield to task.
+ PollState::Polling => {} // Poll the session handler again.
+ PollState::Parked => thread::park(), // Park the thread.
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ /// This function blocks the thread until at least one inspector client has
+ /// established a websocket connection and successfully completed the
+ /// handshake. After that, it instructs V8 to pause at the next statement.
+ pub fn wait_for_session_and_break_on_next_statement(&mut self) {
+ loop {
+ match self.sessions.get_mut().established.iter_mut().next() {
+ Some(session) => break session.break_on_next_statement(),
+ None => {
+ self.flags.get_mut().waiting_for_session = true;
+ let _ = self.poll_sessions(None).unwrap();
+ }
+ };
+ }
+ }
+}
+
+#[derive(Default)]
+struct InspectorFlags {
+ waiting_for_session: bool,
+ session_handshake_done: bool,
+ on_pause: bool,
+}
+
+impl InspectorFlags {
+ fn new() -> RefCell<Self> {
+ let self_ = Self::default();
+ RefCell::new(self_)
+ }
+}
+
+struct InspectorSessions {
+ new_incoming:
+ Pin<Box<dyn Stream<Item = Box<DenoInspectorSession>> + 'static>>,
+ handshake: Option<Box<DenoInspectorSession>>,
+ established: FuturesUnordered<Box<DenoInspectorSession>>,
+}
+
+impl InspectorSessions {
+ fn new(
+ inspector_ptr: *mut DenoInspector,
+ new_websocket_rx: UnboundedReceiver<WebSocketProxy>,
+ ) -> RefCell<Self> {
+ let new_incoming = new_websocket_rx
+ .map(move |websocket| DenoInspectorSession::new(inspector_ptr, websocket))
+ .boxed_local();
+ let self_ = Self {
+ new_incoming,
+ ..Default::default()
+ };
+ RefCell::new(self_)
+ }
+}
+
+impl Default for InspectorSessions {
+ fn default() -> Self {
+ Self {
+ new_incoming: stream::empty().boxed_local(),
+ handshake: None,
+ established: FuturesUnordered::new(),
+ }
+ }
+}
+
+struct InspectorWakerInner {
+ poll_state: PollState,
+ task_waker: Option<task::Waker>,
+ parked_thread: Option<thread::Thread>,
+ inspector_ptr: Option<NonNull<DenoInspector>>,
+ isolate_handle: v8::IsolateHandle,
+}
+
+unsafe impl Send for InspectorWakerInner {}
+
+struct InspectorWaker(Mutex<InspectorWakerInner>);
+
+impl InspectorWaker {
+ fn new(isolate_handle: v8::IsolateHandle) -> Arc<Self> {
+ let inner = InspectorWakerInner {
+ poll_state: PollState::Idle,
+ task_waker: None,
+ parked_thread: None,
+ inspector_ptr: None,
+ isolate_handle,
+ };
+ Arc::new(Self(Mutex::new(inner)))
+ }
+
+ fn update<F, R>(&self, update_fn: F) -> R
+ where
+ F: FnOnce(&mut InspectorWakerInner) -> R,
+ {
+ let mut g = self.0.lock().unwrap();
+ update_fn(&mut g)
+ }
+}
+
+impl task::ArcWake for InspectorWaker {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ arc_self.update(|w| {
+ match w.poll_state {
+ PollState::Idle => {
+ // Wake the task, if any, that has polled the Inspector future last.
+ if let Some(waker) = w.task_waker.take() {
+ waker.wake()
+ }
+ // Request an interrupt from the isolate if it's running and there's
+ // not unhandled interrupt request in flight.
+ if let Some(arg) = w
+ .inspector_ptr
+ .take()
+ .map(|ptr| ptr.as_ptr() as *mut c_void)
+ {
+ w.isolate_handle.request_interrupt(handle_interrupt, arg);
+ }
+ extern "C" fn handle_interrupt(
+ _isolate: &mut v8::Isolate,
+ arg: *mut c_void,
+ ) {
+ let inspector = unsafe { &*(arg as *mut DenoInspector) };
+ let _ = inspector.poll_sessions(None);
+ }
+ }
+ PollState::Parked => {
+ // Unpark the isolate thread.
+ let parked_thread = w.parked_thread.take().unwrap();
+ assert_ne!(parked_thread.id(), thread::current().id());
+ parked_thread.unpark();
+ }
+ _ => {}
+ };
+ w.poll_state = PollState::Woken;
+ });
+ }
+}
+
+struct DenoInspectorSession {
+ v8_channel: v8::inspector::ChannelBase,
+ v8_session: v8::UniqueRef<v8::inspector::V8InspectorSession>,
+ websocket_tx: WebSocketProxySender,
+ websocket_rx_handler: Pin<Box<dyn Future<Output = ()> + 'static>>,
+}
+
+impl Deref for DenoInspectorSession {
+ type Target = v8::inspector::V8InspectorSession;
+ fn deref(&self) -> &Self::Target {
+ &self.v8_session
+ }
+}
+
+impl DerefMut for DenoInspectorSession {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.v8_session
+ }
+}
+
+impl DenoInspectorSession {
+ const CONTEXT_GROUP_ID: i32 = 1;
+
+ pub fn new(
+ inspector_ptr: *mut DenoInspector,
+ websocket: WebSocketProxy,
+ ) -> Box<Self> {
+ new_box_with(move |self_ptr| {
+ let v8_channel = v8::inspector::ChannelBase::new::<Self>();
+ let v8_session = unsafe { &mut *inspector_ptr }.connect(
+ Self::CONTEXT_GROUP_ID,
+ // Todo(piscisaureus): V8Inspector::connect() should require that
+ // the 'v8_channel' argument cannot move.
+ unsafe { &mut *self_ptr },
+ v8::inspector::StringView::empty(),
+ );
+
+ let (websocket_tx, websocket_rx) = websocket.split();
+ let websocket_rx_handler =
+ Self::receive_from_websocket(self_ptr, websocket_rx);
+
+ Self {
+ v8_channel,
+ v8_session,
+ websocket_tx,
+ websocket_rx_handler,
+ }
+ })
+ }
+
+ /// Returns a future that receives messages from the websocket and dispatches
+ /// them to the V8 session.
+ fn receive_from_websocket(
+ self_ptr: *mut Self,
+ websocket_rx: WebSocketProxyReceiver,
+ ) -> Pin<Box<dyn Future<Output = ()> + 'static>> {
+ async move {
+ eprintln!("Debugger session started.");
+
+ let result = websocket_rx
+ .map_ok(move |msg| {
+ let msg = msg.as_bytes();
+ let msg = v8::inspector::StringView::from(msg);
+ unsafe { &mut *self_ptr }.dispatch_protocol_message(msg);
+ })
+ .try_collect::<()>()
+ .await;
+
+ match result {
+ Ok(_) => eprintln!("Debugger session ended."),
+ Err(err) => eprintln!("Debugger session ended: {}.", err),
+ };
+ }
+ .boxed_local()
+ }
+
+ fn send_to_websocket(&self, msg: v8::UniquePtr<v8::inspector::StringBuffer>) {
+ let msg = msg.unwrap().string().to_string();
+ let msg = ws::Message::text(msg);
+ let _ = self.websocket_tx.unbounded_send(msg);
+ }
+
+ pub fn break_on_next_statement(&mut self) {
+ let reason = v8::inspector::StringView::from(&b"debugCommand"[..]);
+ let detail = v8::inspector::StringView::empty();
+ self.schedule_pause_on_next_statement(reason, detail);
+ }
+}
+
+impl v8::inspector::ChannelImpl for DenoInspectorSession {
+ fn base(&self) -> &v8::inspector::ChannelBase {
+ &self.v8_channel
+ }
+
+ fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase {
+ &mut self.v8_channel
+ }
+
+ fn send_response(
+ &mut self,
+ _call_id: i32,
+ message: v8::UniquePtr<v8::inspector::StringBuffer>,
+ ) {
+ self.send_to_websocket(message);
+ }
+
+ fn send_notification(
+ &mut self,
+ message: v8::UniquePtr<v8::inspector::StringBuffer>,
+ ) {
+ self.send_to_websocket(message);
+ }
+
+ fn flush_protocol_notifications(&mut self) {}
+}
+
+impl Future for DenoInspectorSession {
+ type Output = ();
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ self.websocket_rx_handler.poll_unpin(cx)
+ }
+}
+
+/// A local inspector session that can be used to send and receive protocol messages directly on
+/// the same thread as an isolate.
+pub struct InspectorSession {
+ v8_channel: v8::inspector::ChannelBase,
+ v8_session: v8::UniqueRef<v8::inspector::V8InspectorSession>,
+ response_tx_map: HashMap<i32, oneshot::Sender<serde_json::Value>>,
+ next_message_id: i32,
+ notification_queue: Vec<Value>,
+}
+
+impl Deref for InspectorSession {
+ type Target = v8::inspector::V8InspectorSession;
+ fn deref(&self) -> &Self::Target {
+ &self.v8_session
+ }
+}
+
+impl DerefMut for InspectorSession {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.v8_session
+ }
+}
+
+impl v8::inspector::ChannelImpl for InspectorSession {
+ fn base(&self) -> &v8::inspector::ChannelBase {
+ &self.v8_channel
+ }
+
+ fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase {
+ &mut self.v8_channel
+ }
+
+ fn send_response(
+ &mut self,
+ call_id: i32,
+ message: v8::UniquePtr<v8::inspector::StringBuffer>,
+ ) {
+ let raw_message = message.unwrap().string().to_string();
+ let message = serde_json::from_str(&raw_message).unwrap();
+
+ self
+ .response_tx_map
+ .remove(&call_id)
+ .unwrap()
+ .send(message)
+ .unwrap();
+ }
+
+ fn send_notification(
+ &mut self,
+ message: v8::UniquePtr<v8::inspector::StringBuffer>,
+ ) {
+ let raw_message = message.unwrap().string().to_string();
+ let message = serde_json::from_str(&raw_message).unwrap();
+
+ self.notification_queue.push(message);
+ }
+
+ fn flush_protocol_notifications(&mut self) {}
+}
+
+impl InspectorSession {
+ const CONTEXT_GROUP_ID: i32 = 1;
+
+ pub fn new(inspector_ptr: *mut DenoInspector) -> Box<Self> {
+ new_box_with(move |self_ptr| {
+ let v8_channel = v8::inspector::ChannelBase::new::<Self>();
+ let v8_session = unsafe { &mut *inspector_ptr }.connect(
+ Self::CONTEXT_GROUP_ID,
+ unsafe { &mut *self_ptr },
+ v8::inspector::StringView::empty(),
+ );
+
+ let response_tx_map = HashMap::new();
+ let next_message_id = 0;
+
+ let notification_queue = Vec::new();
+
+ Self {
+ v8_channel,
+ v8_session,
+ response_tx_map,
+ next_message_id,
+ notification_queue,
+ }
+ })
+ }
+
+ pub fn notifications(&mut self) -> Vec<Value> {
+ self.notification_queue.split_off(0)
+ }
+
+ pub async fn post_message(
+ &mut self,
+ method: &str,
+ params: Option<serde_json::Value>,
+ ) -> Result<serde_json::Value, AnyError> {
+ let id = self.next_message_id;
+ self.next_message_id += 1;
+
+ let (response_tx, response_rx) = oneshot::channel::<serde_json::Value>();
+ self.response_tx_map.insert(id, response_tx);
+
+ let message = json!({
+ "id": id,
+ "method": method,
+ "params": params,
+ });
+
+ let raw_message = serde_json::to_string(&message).unwrap();
+ let raw_message = v8::inspector::StringView::from(raw_message.as_bytes());
+ self.v8_session.dispatch_protocol_message(raw_message);
+
+ let response = response_rx.await.unwrap();
+ if let Some(error) = response.get("error") {
+ return Err(generic_error(error.to_string()));
+ }
+
+ let result = response.get("result").unwrap().clone();
+ Ok(result)
+ }
+}
+
+fn new_box_with<T>(new_fn: impl FnOnce(*mut T) -> T) -> Box<T> {
+ let b = Box::new(MaybeUninit::<T>::uninit());
+ let p = Box::into_raw(b) as *mut T;
+ unsafe { ptr::write(p, new_fn(p)) };
+ unsafe { Box::from_raw(p) }
+}
diff --git a/runtime/js.rs b/runtime/js.rs
new file mode 100644
index 000000000..efbc958c7
--- /dev/null
+++ b/runtime/js.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::Snapshot;
+
+pub static CLI_SNAPSHOT: &[u8] =
+ include_bytes!(concat!(env!("OUT_DIR"), "/CLI_SNAPSHOT.bin"));
+
+pub fn deno_isolate_init() -> Snapshot {
+ debug!("Deno isolate init with snapshots.");
+ let data = CLI_SNAPSHOT;
+ Snapshot::Static(data)
+}
+
+#[test]
+fn cli_snapshot() {
+ let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
+ startup_snapshot: Some(deno_isolate_init()),
+ ..Default::default()
+ });
+ js_runtime
+ .execute(
+ "<anon>",
+ r#"
+ if (!(bootstrap.mainRuntime && bootstrap.workerRuntime)) {
+ throw Error("bad");
+ }
+ console.log("we have console.log!!!");
+ "#,
+ )
+ .unwrap();
+}
diff --git a/runtime/lib.rs b/runtime/lib.rs
new file mode 100644
index 000000000..6745f3ec8
--- /dev/null
+++ b/runtime/lib.rs
@@ -0,0 +1,26 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+#![deny(warnings)]
+
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+
+pub use deno_crypto;
+pub use deno_fetch;
+pub use deno_web;
+
+pub mod colors;
+pub mod errors;
+pub mod fs_util;
+pub mod http_util;
+pub mod inspector;
+pub mod js;
+pub mod metrics;
+pub mod ops;
+pub mod permissions;
+pub mod resolve_addr;
+pub mod tokio_util;
+pub mod web_worker;
+pub mod worker;
diff --git a/runtime/metrics.rs b/runtime/metrics.rs
new file mode 100644
index 000000000..c70e0dab9
--- /dev/null
+++ b/runtime/metrics.rs
@@ -0,0 +1,131 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+#[derive(Default, Debug)]
+pub struct Metrics {
+ pub ops_dispatched: u64,
+ pub ops_dispatched_sync: u64,
+ pub ops_dispatched_async: u64,
+ pub ops_dispatched_async_unref: u64,
+ pub ops_completed: u64,
+ pub ops_completed_sync: u64,
+ pub ops_completed_async: u64,
+ pub ops_completed_async_unref: u64,
+ pub bytes_sent_control: u64,
+ pub bytes_sent_data: u64,
+ pub bytes_received: u64,
+}
+
+impl Metrics {
+ fn op_dispatched(
+ &mut self,
+ bytes_sent_control: usize,
+ bytes_sent_data: usize,
+ ) {
+ self.ops_dispatched += 1;
+ self.bytes_sent_control += bytes_sent_control as u64;
+ self.bytes_sent_data += bytes_sent_data as u64;
+ }
+
+ fn op_completed(&mut self, bytes_received: usize) {
+ self.ops_completed += 1;
+ self.bytes_received += bytes_received as u64;
+ }
+
+ pub fn op_sync(
+ &mut self,
+ bytes_sent_control: usize,
+ bytes_sent_data: usize,
+ bytes_received: usize,
+ ) {
+ self.ops_dispatched_sync += 1;
+ self.op_dispatched(bytes_sent_control, bytes_sent_data);
+ self.ops_completed_sync += 1;
+ self.op_completed(bytes_received);
+ }
+
+ pub fn op_dispatched_async(
+ &mut self,
+ bytes_sent_control: usize,
+ bytes_sent_data: usize,
+ ) {
+ self.ops_dispatched_async += 1;
+ self.op_dispatched(bytes_sent_control, bytes_sent_data)
+ }
+
+ pub fn op_dispatched_async_unref(
+ &mut self,
+ bytes_sent_control: usize,
+ bytes_sent_data: usize,
+ ) {
+ self.ops_dispatched_async_unref += 1;
+ self.op_dispatched(bytes_sent_control, bytes_sent_data)
+ }
+
+ pub fn op_completed_async(&mut self, bytes_received: usize) {
+ self.ops_completed_async += 1;
+ self.op_completed(bytes_received);
+ }
+
+ pub fn op_completed_async_unref(&mut self, bytes_received: usize) {
+ self.ops_completed_async_unref += 1;
+ self.op_completed(bytes_received);
+ }
+}
+
+use deno_core::BufVec;
+use deno_core::Op;
+use deno_core::OpFn;
+use deno_core::OpState;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+pub fn metrics_op(op_fn: Box<OpFn>) -> Box<OpFn> {
+ Box::new(move |op_state: Rc<RefCell<OpState>>, bufs: BufVec| -> Op {
+ // TODOs:
+ // * The 'bytes' metrics seem pretty useless, especially now that the
+ // distinction between 'control' and 'data' buffers has become blurry.
+ // * Tracking completion of async ops currently makes us put the boxed
+ // future into _another_ box. Keeping some counters may not be expensive
+ // in itself, but adding a heap allocation for every metric seems bad.
+ let mut buf_len_iter = bufs.iter().map(|buf| buf.len());
+ let bytes_sent_control = buf_len_iter.next().unwrap_or(0);
+ let bytes_sent_data = buf_len_iter.sum();
+
+ let op = (op_fn)(op_state.clone(), bufs);
+
+ let op_state_ = op_state.clone();
+ let mut s = op_state.borrow_mut();
+ let metrics = s.borrow_mut::<Metrics>();
+
+ use deno_core::futures::future::FutureExt;
+
+ match op {
+ Op::Sync(buf) => {
+ metrics.op_sync(bytes_sent_control, bytes_sent_data, buf.len());
+ Op::Sync(buf)
+ }
+ Op::Async(fut) => {
+ metrics.op_dispatched_async(bytes_sent_control, bytes_sent_data);
+ let fut = fut
+ .inspect(move |buf| {
+ let mut s = op_state_.borrow_mut();
+ let metrics = s.borrow_mut::<Metrics>();
+ metrics.op_completed_async(buf.len());
+ })
+ .boxed_local();
+ Op::Async(fut)
+ }
+ Op::AsyncUnref(fut) => {
+ metrics.op_dispatched_async_unref(bytes_sent_control, bytes_sent_data);
+ let fut = fut
+ .inspect(move |buf| {
+ let mut s = op_state_.borrow_mut();
+ let metrics = s.borrow_mut::<Metrics>();
+ metrics.op_completed_async_unref(buf.len());
+ })
+ .boxed_local();
+ Op::AsyncUnref(fut)
+ }
+ other => other,
+ }
+ })
+}
diff --git a/runtime/ops/crypto.rs b/runtime/ops/crypto.rs
new file mode 100644
index 000000000..a73843a33
--- /dev/null
+++ b/runtime/ops/crypto.rs
@@ -0,0 +1,14 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use deno_crypto::op_get_random_values;
+use deno_crypto::rand::rngs::StdRng;
+use deno_crypto::rand::SeedableRng;
+
+pub fn init(rt: &mut deno_core::JsRuntime, maybe_seed: Option<u64>) {
+ if let Some(seed) = maybe_seed {
+ let rng = StdRng::seed_from_u64(seed);
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ state.put::<StdRng>(rng);
+ }
+ super::reg_json_sync(rt, "op_get_random_values", op_get_random_values);
+}
diff --git a/runtime/ops/dispatch_minimal.rs b/runtime/ops/dispatch_minimal.rs
new file mode 100644
index 000000000..ae8fa819d
--- /dev/null
+++ b/runtime/ops/dispatch_minimal.rs
@@ -0,0 +1,205 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_core::futures::future::FutureExt;
+use deno_core::BufVec;
+use deno_core::Op;
+use deno_core::OpFn;
+use deno_core::OpState;
+use std::cell::RefCell;
+use std::future::Future;
+use std::iter::repeat;
+use std::mem::size_of_val;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::slice;
+
+pub enum MinimalOp {
+ Sync(Result<i32, AnyError>),
+ Async(Pin<Box<dyn Future<Output = Result<i32, AnyError>>>>),
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+// This corresponds to RecordMinimal on the TS side.
+pub struct Record {
+ pub promise_id: i32,
+ pub arg: i32,
+ pub result: i32,
+}
+
+impl Into<Box<[u8]>> for Record {
+ fn into(self) -> Box<[u8]> {
+ let vec = vec![self.promise_id, self.arg, self.result];
+ let buf32 = vec.into_boxed_slice();
+ let ptr = Box::into_raw(buf32) as *mut [u8; 3 * 4];
+ unsafe { Box::from_raw(ptr) }
+ }
+}
+
+pub struct ErrorRecord {
+ pub promise_id: i32,
+ pub arg: i32,
+ pub error_len: i32,
+ pub error_class: &'static [u8],
+ pub error_message: Vec<u8>,
+}
+
+impl Into<Box<[u8]>> for ErrorRecord {
+ fn into(self) -> Box<[u8]> {
+ let Self {
+ promise_id,
+ arg,
+ error_len,
+ error_class,
+ error_message,
+ ..
+ } = self;
+ let header_i32 = [promise_id, arg, error_len];
+ let header_u8 = unsafe {
+ slice::from_raw_parts(
+ &header_i32 as *const _ as *const u8,
+ size_of_val(&header_i32),
+ )
+ };
+ let padded_len =
+ (header_u8.len() + error_class.len() + error_message.len() + 3usize)
+ & !3usize;
+ header_u8
+ .iter()
+ .cloned()
+ .chain(error_class.iter().cloned())
+ .chain(error_message.into_iter())
+ .chain(repeat(b' '))
+ .take(padded_len)
+ .collect()
+ }
+}
+
+#[test]
+fn test_error_record() {
+ let expected = vec![
+ 1, 0, 0, 0, 255, 255, 255, 255, 11, 0, 0, 0, 66, 97, 100, 82, 101, 115,
+ 111, 117, 114, 99, 101, 69, 114, 114, 111, 114,
+ ];
+ let err_record = ErrorRecord {
+ promise_id: 1,
+ arg: -1,
+ error_len: 11,
+ error_class: b"BadResource",
+ error_message: b"Error".to_vec(),
+ };
+ let buf: Box<[u8]> = err_record.into();
+ assert_eq!(buf, expected.into_boxed_slice());
+}
+
+pub fn parse_min_record(bytes: &[u8]) -> Option<Record> {
+ if bytes.len() % std::mem::size_of::<i32>() != 0 {
+ return None;
+ }
+ let p = bytes.as_ptr();
+ #[allow(clippy::cast_ptr_alignment)]
+ let p32 = p as *const i32;
+ let s = unsafe { std::slice::from_raw_parts(p32, bytes.len() / 4) };
+
+ if s.len() != 3 {
+ return None;
+ }
+ let ptr = s.as_ptr();
+ let ints = unsafe { std::slice::from_raw_parts(ptr, 3) };
+ Some(Record {
+ promise_id: ints[0],
+ arg: ints[1],
+ result: ints[2],
+ })
+}
+
+#[test]
+fn test_parse_min_record() {
+ let buf = vec![1, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0];
+ assert_eq!(
+ parse_min_record(&buf),
+ Some(Record {
+ promise_id: 1,
+ arg: 3,
+ result: 4
+ })
+ );
+
+ let buf = vec![];
+ assert_eq!(parse_min_record(&buf), None);
+
+ let buf = vec![5];
+ assert_eq!(parse_min_record(&buf), None);
+}
+
+pub fn minimal_op<F>(op_fn: F) -> Box<OpFn>
+where
+ F: Fn(Rc<RefCell<OpState>>, bool, i32, BufVec) -> MinimalOp + 'static,
+{
+ Box::new(move |state: Rc<RefCell<OpState>>, bufs: BufVec| {
+ let mut bufs_iter = bufs.into_iter();
+ let record_buf = bufs_iter.next().expect("Expected record at position 0");
+ let zero_copy = bufs_iter.collect::<BufVec>();
+
+ let mut record = match parse_min_record(&record_buf) {
+ Some(r) => r,
+ None => {
+ let error_class = b"TypeError";
+ let error_message = b"Unparsable control buffer";
+ let error_record = ErrorRecord {
+ promise_id: 0,
+ arg: -1,
+ error_len: error_class.len() as i32,
+ error_class,
+ error_message: error_message[..].to_owned(),
+ };
+ return Op::Sync(error_record.into());
+ }
+ };
+ let is_sync = record.promise_id == 0;
+ let rid = record.arg;
+ let min_op = op_fn(state.clone(), is_sync, rid, zero_copy);
+
+ match min_op {
+ MinimalOp::Sync(sync_result) => Op::Sync(match sync_result {
+ Ok(r) => {
+ record.result = r;
+ record.into()
+ }
+ Err(err) => {
+ let error_class = (state.borrow().get_error_class_fn)(&err);
+ let error_record = ErrorRecord {
+ promise_id: record.promise_id,
+ arg: -1,
+ error_len: error_class.len() as i32,
+ error_class: error_class.as_bytes(),
+ error_message: err.to_string().as_bytes().to_owned(),
+ };
+ error_record.into()
+ }
+ }),
+ MinimalOp::Async(min_fut) => {
+ let fut = async move {
+ match min_fut.await {
+ Ok(r) => {
+ record.result = r;
+ record.into()
+ }
+ Err(err) => {
+ let error_class = (state.borrow().get_error_class_fn)(&err);
+ let error_record = ErrorRecord {
+ promise_id: record.promise_id,
+ arg: -1,
+ error_len: error_class.len() as i32,
+ error_class: error_class.as_bytes(),
+ error_message: err.to_string().as_bytes().to_owned(),
+ };
+ error_record.into()
+ }
+ }
+ };
+ Op::Async(fut.boxed_local())
+ }
+ }
+ })
+}
diff --git a/runtime/ops/fetch.rs b/runtime/ops/fetch.rs
new file mode 100644
index 000000000..0ef99f73d
--- /dev/null
+++ b/runtime/ops/fetch.rs
@@ -0,0 +1,25 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::http_util;
+use crate::permissions::Permissions;
+use deno_fetch::reqwest;
+
+pub fn init(
+ rt: &mut deno_core::JsRuntime,
+ user_agent: String,
+ maybe_ca_file: Option<&str>,
+) {
+ {
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ state.put::<reqwest::Client>({
+ http_util::create_http_client(user_agent, maybe_ca_file).unwrap()
+ });
+ }
+ super::reg_json_async(rt, "op_fetch", deno_fetch::op_fetch::<Permissions>);
+ super::reg_json_async(rt, "op_fetch_read", deno_fetch::op_fetch_read);
+ super::reg_json_sync(
+ rt,
+ "op_create_http_client",
+ deno_fetch::op_create_http_client::<Permissions>,
+ );
+}
diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs
new file mode 100644
index 000000000..865c5bcca
--- /dev/null
+++ b/runtime/ops/fs.rs
@@ -0,0 +1,1702 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+// Some deserializer fields are only used on Unix and Windows build fails without it
+use super::io::std_file_resource;
+use super::io::{FileMetadata, StreamResource, StreamResourceHolder};
+use crate::fs_util::canonicalize_path;
+use crate::permissions::Permissions;
+use deno_core::error::custom_error;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use deno_crypto::rand::thread_rng;
+use deno_crypto::rand::Rng;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::convert::From;
+use std::env::{current_dir, set_current_dir, temp_dir};
+use std::io;
+use std::io::{Seek, SeekFrom};
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
+
+#[cfg(not(unix))]
+use deno_core::error::generic_error;
+#[cfg(not(unix))]
+use deno_core::error::not_supported;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_open_sync", op_open_sync);
+ super::reg_json_async(rt, "op_open_async", op_open_async);
+
+ super::reg_json_sync(rt, "op_seek_sync", op_seek_sync);
+ super::reg_json_async(rt, "op_seek_async", op_seek_async);
+
+ super::reg_json_sync(rt, "op_fdatasync_sync", op_fdatasync_sync);
+ super::reg_json_async(rt, "op_fdatasync_async", op_fdatasync_async);
+
+ super::reg_json_sync(rt, "op_fsync_sync", op_fsync_sync);
+ super::reg_json_async(rt, "op_fsync_async", op_fsync_async);
+
+ super::reg_json_sync(rt, "op_fstat_sync", op_fstat_sync);
+ super::reg_json_async(rt, "op_fstat_async", op_fstat_async);
+
+ super::reg_json_sync(rt, "op_umask", op_umask);
+ super::reg_json_sync(rt, "op_chdir", op_chdir);
+
+ super::reg_json_sync(rt, "op_mkdir_sync", op_mkdir_sync);
+ super::reg_json_async(rt, "op_mkdir_async", op_mkdir_async);
+
+ super::reg_json_sync(rt, "op_chmod_sync", op_chmod_sync);
+ super::reg_json_async(rt, "op_chmod_async", op_chmod_async);
+
+ super::reg_json_sync(rt, "op_chown_sync", op_chown_sync);
+ super::reg_json_async(rt, "op_chown_async", op_chown_async);
+
+ super::reg_json_sync(rt, "op_remove_sync", op_remove_sync);
+ super::reg_json_async(rt, "op_remove_async", op_remove_async);
+
+ super::reg_json_sync(rt, "op_copy_file_sync", op_copy_file_sync);
+ super::reg_json_async(rt, "op_copy_file_async", op_copy_file_async);
+
+ super::reg_json_sync(rt, "op_stat_sync", op_stat_sync);
+ super::reg_json_async(rt, "op_stat_async", op_stat_async);
+
+ super::reg_json_sync(rt, "op_realpath_sync", op_realpath_sync);
+ super::reg_json_async(rt, "op_realpath_async", op_realpath_async);
+
+ super::reg_json_sync(rt, "op_read_dir_sync", op_read_dir_sync);
+ super::reg_json_async(rt, "op_read_dir_async", op_read_dir_async);
+
+ super::reg_json_sync(rt, "op_rename_sync", op_rename_sync);
+ super::reg_json_async(rt, "op_rename_async", op_rename_async);
+
+ super::reg_json_sync(rt, "op_link_sync", op_link_sync);
+ super::reg_json_async(rt, "op_link_async", op_link_async);
+
+ super::reg_json_sync(rt, "op_symlink_sync", op_symlink_sync);
+ super::reg_json_async(rt, "op_symlink_async", op_symlink_async);
+
+ super::reg_json_sync(rt, "op_read_link_sync", op_read_link_sync);
+ super::reg_json_async(rt, "op_read_link_async", op_read_link_async);
+
+ super::reg_json_sync(rt, "op_ftruncate_sync", op_ftruncate_sync);
+ super::reg_json_async(rt, "op_ftruncate_async", op_ftruncate_async);
+
+ super::reg_json_sync(rt, "op_truncate_sync", op_truncate_sync);
+ super::reg_json_async(rt, "op_truncate_async", op_truncate_async);
+
+ super::reg_json_sync(rt, "op_make_temp_dir_sync", op_make_temp_dir_sync);
+ super::reg_json_async(rt, "op_make_temp_dir_async", op_make_temp_dir_async);
+
+ super::reg_json_sync(rt, "op_make_temp_file_sync", op_make_temp_file_sync);
+ super::reg_json_async(rt, "op_make_temp_file_async", op_make_temp_file_async);
+
+ super::reg_json_sync(rt, "op_cwd", op_cwd);
+
+ super::reg_json_sync(rt, "op_futime_sync", op_futime_sync);
+ super::reg_json_async(rt, "op_futime_async", op_futime_async);
+
+ super::reg_json_sync(rt, "op_utime_sync", op_utime_sync);
+ super::reg_json_async(rt, "op_utime_async", op_utime_async);
+}
+
+fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> {
+ s.into_string().map_err(|s| {
+ let message = format!("File name or path {:?} is not valid UTF-8", s);
+ custom_error("InvalidData", message)
+ })
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct OpenArgs {
+ path: String,
+ mode: Option<u32>,
+ options: OpenOptions,
+}
+
+#[derive(Deserialize, Default, Debug)]
+#[serde(rename_all = "camelCase")]
+#[serde(default)]
+struct OpenOptions {
+ read: bool,
+ write: bool,
+ create: bool,
+ truncate: bool,
+ append: bool,
+ create_new: bool,
+}
+
+fn open_helper(
+ state: &mut OpState,
+ args: Value,
+) -> Result<(PathBuf, std::fs::OpenOptions), AnyError> {
+ let args: OpenArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+
+ let mut open_options = std::fs::OpenOptions::new();
+
+ if let Some(mode) = args.mode {
+ // mode only used if creating the file on Unix
+ // if not specified, defaults to 0o666
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::OpenOptionsExt;
+ open_options.mode(mode & 0o777);
+ }
+ #[cfg(not(unix))]
+ let _ = mode; // avoid unused warning
+ }
+
+ let permissions = state.borrow::<Permissions>();
+ let options = args.options;
+
+ if options.read {
+ permissions.check_read(&path)?;
+ }
+
+ if options.write || options.append {
+ permissions.check_write(&path)?;
+ }
+
+ open_options
+ .read(options.read)
+ .create(options.create)
+ .write(options.write)
+ .truncate(options.truncate)
+ .append(options.append)
+ .create_new(options.create_new);
+
+ Ok((path, open_options))
+}
+
+fn op_open_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let (path, open_options) = open_helper(state, args)?;
+ let std_file = open_options.open(path)?;
+ let tokio_file = tokio::fs::File::from_std(std_file);
+ let rid = state.resource_table.add(
+ "fsFile",
+ Box::new(StreamResourceHolder::new(StreamResource::FsFile(Some((
+ tokio_file,
+ FileMetadata::default(),
+ ))))),
+ );
+ Ok(json!(rid))
+}
+
+async fn op_open_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let (path, open_options) = open_helper(&mut state.borrow_mut(), args)?;
+ let tokio_file = tokio::fs::OpenOptions::from(open_options)
+ .open(path)
+ .await?;
+ let rid = state.borrow_mut().resource_table.add(
+ "fsFile",
+ Box::new(StreamResourceHolder::new(StreamResource::FsFile(Some((
+ tokio_file,
+ FileMetadata::default(),
+ ))))),
+ );
+ Ok(json!(rid))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SeekArgs {
+ rid: i32,
+ offset: i64,
+ whence: i32,
+}
+
+fn seek_helper(args: Value) -> Result<(u32, SeekFrom), AnyError> {
+ let args: SeekArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let offset = args.offset;
+ let whence = args.whence as u32;
+ // Translate seek mode to Rust repr.
+ let seek_from = match whence {
+ 0 => SeekFrom::Start(offset as u64),
+ 1 => SeekFrom::Current(offset),
+ 2 => SeekFrom::End(offset),
+ _ => {
+ return Err(type_error(format!("Invalid seek mode: {}", whence)));
+ }
+ };
+
+ Ok((rid, seek_from))
+}
+
+fn op_seek_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let (rid, seek_from) = seek_helper(args)?;
+ let pos = std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => std_file.seek(seek_from).map_err(AnyError::from),
+ Err(_) => Err(type_error(
+ "cannot seek on this type of resource".to_string(),
+ )),
+ })?;
+ Ok(json!(pos))
+}
+
+async fn op_seek_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let (rid, seek_from) = seek_helper(args)?;
+ // TODO(ry) This is a fake async op. We need to use poll_fn,
+ // tokio::fs::File::start_seek and tokio::fs::File::poll_complete
+ let pos = std_file_resource(&mut state.borrow_mut(), rid, |r| match r {
+ Ok(std_file) => std_file.seek(seek_from).map_err(AnyError::from),
+ Err(_) => Err(type_error(
+ "cannot seek on this type of resource".to_string(),
+ )),
+ })?;
+ Ok(json!(pos))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FdatasyncArgs {
+ rid: i32,
+}
+
+fn op_fdatasync_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: FdatasyncArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => std_file.sync_data().map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot sync this type of resource".to_string())),
+ })?;
+ Ok(json!({}))
+}
+
+async fn op_fdatasync_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: FdatasyncArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ std_file_resource(&mut state.borrow_mut(), rid, |r| match r {
+ Ok(std_file) => std_file.sync_data().map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot sync this type of resource".to_string())),
+ })?;
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FsyncArgs {
+ rid: i32,
+}
+
+fn op_fsync_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: FsyncArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => std_file.sync_all().map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot sync this type of resource".to_string())),
+ })?;
+ Ok(json!({}))
+}
+
+async fn op_fsync_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: FsyncArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ std_file_resource(&mut state.borrow_mut(), rid, |r| match r {
+ Ok(std_file) => std_file.sync_all().map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot sync this type of resource".to_string())),
+ })?;
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FstatArgs {
+ rid: i32,
+}
+
+fn op_fstat_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.fstat");
+ let args: FstatArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let metadata = std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => std_file.metadata().map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot stat this type of resource".to_string())),
+ })?;
+ Ok(get_stat_json(metadata))
+}
+
+async fn op_fstat_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ super::check_unstable2(&state, "Deno.fstat");
+
+ let args: FstatArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let metadata =
+ std_file_resource(&mut state.borrow_mut(), rid, |r| match r {
+ Ok(std_file) => std_file.metadata().map_err(AnyError::from),
+ Err(_) => {
+ Err(type_error("cannot stat this type of resource".to_string()))
+ }
+ })?;
+ Ok(get_stat_json(metadata))
+}
+
+#[derive(Deserialize)]
+struct UmaskArgs {
+ mask: Option<u32>,
+}
+
+fn op_umask(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.umask");
+ let args: UmaskArgs = serde_json::from_value(args)?;
+ // TODO implement umask for Windows
+ // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
+ // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
+ #[cfg(not(unix))]
+ {
+ let _ = args.mask; // avoid unused warning.
+ Err(not_supported())
+ }
+ #[cfg(unix)]
+ {
+ use nix::sys::stat::mode_t;
+ use nix::sys::stat::umask;
+ use nix::sys::stat::Mode;
+ let r = if let Some(mask) = args.mask {
+ // If mask provided, return previous.
+ umask(Mode::from_bits_truncate(mask as mode_t))
+ } else {
+ // If no mask provided, we query the current. Requires two syscalls.
+ let prev = umask(Mode::from_bits_truncate(0o777));
+ let _ = umask(prev);
+ prev
+ };
+ Ok(json!(r.bits() as u32))
+ }
+}
+
+#[derive(Deserialize)]
+struct ChdirArgs {
+ directory: String,
+}
+
+fn op_chdir(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ChdirArgs = serde_json::from_value(args)?;
+ let d = PathBuf::from(&args.directory);
+ state.borrow::<Permissions>().check_read(&d)?;
+ set_current_dir(&d)?;
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct MkdirArgs {
+ path: String,
+ recursive: bool,
+ mode: Option<u32>,
+}
+
+fn op_mkdir_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: MkdirArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+ let mode = args.mode.unwrap_or(0o777) & 0o777;
+ state.borrow::<Permissions>().check_write(&path)?;
+ debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
+ let mut builder = std::fs::DirBuilder::new();
+ builder.recursive(args.recursive);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::DirBuilderExt;
+ builder.mode(mode);
+ }
+ builder.create(path)?;
+ Ok(json!({}))
+}
+
+async fn op_mkdir_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: MkdirArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+ let mode = args.mode.unwrap_or(0o777) & 0o777;
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&path)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
+ let mut builder = std::fs::DirBuilder::new();
+ builder.recursive(args.recursive);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::DirBuilderExt;
+ builder.mode(mode);
+ }
+ builder.create(path)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ChmodArgs {
+ path: String,
+ mode: u32,
+}
+
+fn op_chmod_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ChmodArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+ let mode = args.mode & 0o777;
+
+ state.borrow::<Permissions>().check_write(&path)?;
+ debug!("op_chmod_sync {} {:o}", path.display(), mode);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ let permissions = PermissionsExt::from_mode(mode);
+ std::fs::set_permissions(&path, permissions)?;
+ Ok(json!({}))
+ }
+ // TODO Implement chmod for Windows (#4357)
+ #[cfg(not(unix))]
+ {
+ // Still check file/dir exists on Windows
+ let _metadata = std::fs::metadata(&path)?;
+ Err(generic_error("Not implemented"))
+ }
+}
+
+async fn op_chmod_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: ChmodArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+ let mode = args.mode & 0o777;
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&path)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_chmod_async {} {:o}", path.display(), mode);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ let permissions = PermissionsExt::from_mode(mode);
+ std::fs::set_permissions(&path, permissions)?;
+ Ok(json!({}))
+ }
+ // TODO Implement chmod for Windows (#4357)
+ #[cfg(not(unix))]
+ {
+ // Still check file/dir exists on Windows
+ let _metadata = std::fs::metadata(&path)?;
+ Err(not_supported())
+ }
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ChownArgs {
+ path: String,
+ uid: Option<u32>,
+ gid: Option<u32>,
+}
+
+fn op_chown_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ChownArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+ state.borrow::<Permissions>().check_write(&path)?;
+ debug!(
+ "op_chown_sync {} {:?} {:?}",
+ path.display(),
+ args.uid,
+ args.gid,
+ );
+ #[cfg(unix)]
+ {
+ use nix::unistd::{chown, Gid, Uid};
+ let nix_uid = args.uid.map(Uid::from_raw);
+ let nix_gid = args.gid.map(Gid::from_raw);
+ chown(&path, nix_uid, nix_gid)?;
+ Ok(json!({}))
+ }
+ // TODO Implement chown for Windows
+ #[cfg(not(unix))]
+ {
+ Err(generic_error("Not implemented"))
+ }
+}
+
+async fn op_chown_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: ChownArgs = serde_json::from_value(args)?;
+ let path = Path::new(&args.path).to_path_buf();
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&path)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!(
+ "op_chown_async {} {:?} {:?}",
+ path.display(),
+ args.uid,
+ args.gid,
+ );
+ #[cfg(unix)]
+ {
+ use nix::unistd::{chown, Gid, Uid};
+ let nix_uid = args.uid.map(Uid::from_raw);
+ let nix_gid = args.gid.map(Gid::from_raw);
+ chown(&path, nix_uid, nix_gid)?;
+ Ok(json!({}))
+ }
+ // TODO Implement chown for Windows
+ #[cfg(not(unix))]
+ Err(not_supported())
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct RemoveArgs {
+ path: String,
+ recursive: bool,
+}
+
+fn op_remove_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: RemoveArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let recursive = args.recursive;
+
+ state.borrow::<Permissions>().check_write(&path)?;
+
+ #[cfg(not(unix))]
+ use std::os::windows::prelude::MetadataExt;
+
+ let metadata = std::fs::symlink_metadata(&path)?;
+
+ debug!("op_remove_sync {} {}", path.display(), recursive);
+ let file_type = metadata.file_type();
+ if file_type.is_file() {
+ std::fs::remove_file(&path)?;
+ } else if recursive {
+ std::fs::remove_dir_all(&path)?;
+ } else if file_type.is_symlink() {
+ #[cfg(unix)]
+ std::fs::remove_file(&path)?;
+ #[cfg(not(unix))]
+ {
+ use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
+ if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
+ std::fs::remove_dir(&path)?;
+ } else {
+ std::fs::remove_file(&path)?;
+ }
+ }
+ } else if file_type.is_dir() {
+ std::fs::remove_dir(&path)?;
+ } else {
+ // pipes, sockets, etc...
+ std::fs::remove_file(&path)?;
+ }
+ Ok(json!({}))
+}
+
+async fn op_remove_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: RemoveArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let recursive = args.recursive;
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&path)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ #[cfg(not(unix))]
+ use std::os::windows::prelude::MetadataExt;
+
+ let metadata = std::fs::symlink_metadata(&path)?;
+
+ debug!("op_remove_async {} {}", path.display(), recursive);
+ let file_type = metadata.file_type();
+ if file_type.is_file() {
+ std::fs::remove_file(&path)?;
+ } else if recursive {
+ std::fs::remove_dir_all(&path)?;
+ } else if file_type.is_symlink() {
+ #[cfg(unix)]
+ std::fs::remove_file(&path)?;
+ #[cfg(not(unix))]
+ {
+ use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
+ if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
+ std::fs::remove_dir(&path)?;
+ } else {
+ std::fs::remove_file(&path)?;
+ }
+ }
+ } else if file_type.is_dir() {
+ std::fs::remove_dir(&path)?;
+ } else {
+ // pipes, sockets, etc...
+ std::fs::remove_file(&path)?;
+ }
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CopyFileArgs {
+ from: String,
+ to: String,
+}
+
+fn op_copy_file_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: CopyFileArgs = serde_json::from_value(args)?;
+ let from = PathBuf::from(&args.from);
+ let to = PathBuf::from(&args.to);
+
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&from)?;
+ permissions.check_write(&to)?;
+
+ debug!("op_copy_file_sync {} {}", from.display(), to.display());
+ // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
+ // See https://github.com/rust-lang/rust/issues/54800
+ // Once the issue is resolved, we should remove this workaround.
+ if cfg!(unix) && !from.is_file() {
+ return Err(custom_error("NotFound", "File not found"));
+ }
+
+ // returns size of from as u64 (we ignore)
+ std::fs::copy(&from, &to)?;
+ Ok(json!({}))
+}
+
+async fn op_copy_file_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: CopyFileArgs = serde_json::from_value(args)?;
+ let from = PathBuf::from(&args.from);
+ let to = PathBuf::from(&args.to);
+
+ {
+ let state = state.borrow();
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&from)?;
+ permissions.check_write(&to)?;
+ }
+
+ debug!("op_copy_file_async {} {}", from.display(), to.display());
+ tokio::task::spawn_blocking(move || {
+ // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
+ // See https://github.com/rust-lang/rust/issues/54800
+ // Once the issue is resolved, we should remove this workaround.
+ if cfg!(unix) && !from.is_file() {
+ return Err(custom_error("NotFound", "File not found"));
+ }
+
+ // returns size of from as u64 (we ignore)
+ std::fs::copy(&from, &to)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Value {
+ match maybe_time {
+ Ok(time) => {
+ let msec = time
+ .duration_since(UNIX_EPOCH)
+ .map(|t| t.as_secs_f64() * 1000f64)
+ .unwrap_or_else(|err| err.duration().as_secs_f64() * -1000f64);
+ serde_json::Number::from_f64(msec)
+ .map(Value::Number)
+ .unwrap_or(Value::Null)
+ }
+ Err(_) => Value::Null,
+ }
+}
+
+#[inline(always)]
+fn get_stat_json(metadata: std::fs::Metadata) -> Value {
+ // Unix stat member (number types only). 0 if not on unix.
+ macro_rules! usm {
+ ($member:ident) => {{
+ #[cfg(unix)]
+ {
+ metadata.$member()
+ }
+ #[cfg(not(unix))]
+ {
+ 0
+ }
+ }};
+ }
+
+ #[cfg(unix)]
+ use std::os::unix::fs::MetadataExt;
+ let json_val = json!({
+ "isFile": metadata.is_file(),
+ "isDirectory": metadata.is_dir(),
+ "isSymlink": metadata.file_type().is_symlink(),
+ "size": metadata.len(),
+ // In milliseconds, like JavaScript. Available on both Unix or Windows.
+ "mtime": to_msec(metadata.modified()),
+ "atime": to_msec(metadata.accessed()),
+ "birthtime": to_msec(metadata.created()),
+ // Following are only valid under Unix.
+ "dev": usm!(dev),
+ "ino": usm!(ino),
+ "mode": usm!(mode),
+ "nlink": usm!(nlink),
+ "uid": usm!(uid),
+ "gid": usm!(gid),
+ "rdev": usm!(rdev),
+ // TODO(kevinkassimo): *time_nsec requires BigInt.
+ // Probably should be treated as String if we need to add them.
+ "blksize": usm!(blksize),
+ "blocks": usm!(blocks),
+ });
+ json_val
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct StatArgs {
+ path: String,
+ lstat: bool,
+}
+
+fn op_stat_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: StatArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let lstat = args.lstat;
+ state.borrow::<Permissions>().check_read(&path)?;
+ debug!("op_stat_sync {} {}", path.display(), lstat);
+ let metadata = if lstat {
+ std::fs::symlink_metadata(&path)?
+ } else {
+ std::fs::metadata(&path)?
+ };
+ Ok(get_stat_json(metadata))
+}
+
+async fn op_stat_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: StatArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let lstat = args.lstat;
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_read(&path)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_stat_async {} {}", path.display(), lstat);
+ let metadata = if lstat {
+ std::fs::symlink_metadata(&path)?
+ } else {
+ std::fs::metadata(&path)?
+ };
+ Ok(get_stat_json(metadata))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct RealpathArgs {
+ path: String,
+}
+
+fn op_realpath_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: RealpathArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&path)?;
+ if path.is_relative() {
+ permissions.check_read_blind(&current_dir()?, "CWD")?;
+ }
+
+ debug!("op_realpath_sync {}", path.display());
+ // corresponds to the realpath on Unix and
+ // CreateFile and GetFinalPathNameByHandle on Windows
+ let realpath = canonicalize_path(&path)?;
+ let realpath_str = into_string(realpath.into_os_string())?;
+ Ok(json!(realpath_str))
+}
+
+async fn op_realpath_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: RealpathArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+
+ {
+ let state = state.borrow();
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&path)?;
+ if path.is_relative() {
+ permissions.check_read_blind(&current_dir()?, "CWD")?;
+ }
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_realpath_async {}", path.display());
+ // corresponds to the realpath on Unix and
+ // CreateFile and GetFinalPathNameByHandle on Windows
+ let realpath = canonicalize_path(&path)?;
+ let realpath_str = into_string(realpath.into_os_string())?;
+ Ok(json!(realpath_str))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ReadDirArgs {
+ path: String,
+}
+
+fn op_read_dir_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ReadDirArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+
+ state.borrow::<Permissions>().check_read(&path)?;
+
+ debug!("op_read_dir_sync {}", path.display());
+ let entries: Vec<_> = std::fs::read_dir(path)?
+ .filter_map(|entry| {
+ let entry = entry.unwrap();
+ let file_type = entry.file_type().unwrap();
+ // Not all filenames can be encoded as UTF-8. Skip those for now.
+ if let Ok(name) = into_string(entry.file_name()) {
+ Some(json!({
+ "name": name,
+ "isFile": file_type.is_file(),
+ "isDirectory": file_type.is_dir(),
+ "isSymlink": file_type.is_symlink()
+ }))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(json!({ "entries": entries }))
+}
+
+async fn op_read_dir_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: ReadDirArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_read(&path)?;
+ }
+ tokio::task::spawn_blocking(move || {
+ debug!("op_read_dir_async {}", path.display());
+ let entries: Vec<_> = std::fs::read_dir(path)?
+ .filter_map(|entry| {
+ let entry = entry.unwrap();
+ let file_type = entry.file_type().unwrap();
+ // Not all filenames can be encoded as UTF-8. Skip those for now.
+ if let Ok(name) = into_string(entry.file_name()) {
+ Some(json!({
+ "name": name,
+ "isFile": file_type.is_file(),
+ "isDirectory": file_type.is_dir(),
+ "isSymlink": file_type.is_symlink()
+ }))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(json!({ "entries": entries }))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct RenameArgs {
+ oldpath: String,
+ newpath: String,
+}
+
+fn op_rename_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: RenameArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&oldpath)?;
+ permissions.check_write(&oldpath)?;
+ permissions.check_write(&newpath)?;
+ debug!("op_rename_sync {} {}", oldpath.display(), newpath.display());
+ std::fs::rename(&oldpath, &newpath)?;
+ Ok(json!({}))
+}
+
+async fn op_rename_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: RenameArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+ {
+ let state = state.borrow();
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&oldpath)?;
+ permissions.check_write(&oldpath)?;
+ permissions.check_write(&newpath)?;
+ }
+ tokio::task::spawn_blocking(move || {
+ debug!(
+ "op_rename_async {} {}",
+ oldpath.display(),
+ newpath.display()
+ );
+ std::fs::rename(&oldpath, &newpath)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct LinkArgs {
+ oldpath: String,
+ newpath: String,
+}
+
+fn op_link_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.link");
+ let args: LinkArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&oldpath)?;
+ permissions.check_write(&newpath)?;
+
+ debug!("op_link_sync {} {}", oldpath.display(), newpath.display());
+ std::fs::hard_link(&oldpath, &newpath)?;
+ Ok(json!({}))
+}
+
+async fn op_link_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ super::check_unstable2(&state, "Deno.link");
+
+ let args: LinkArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+
+ {
+ let state = state.borrow();
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_read(&oldpath)?;
+ permissions.check_write(&newpath)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_link_async {} {}", oldpath.display(), newpath.display());
+ std::fs::hard_link(&oldpath, &newpath)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SymlinkArgs {
+ oldpath: String,
+ newpath: String,
+ #[cfg(not(unix))]
+ options: Option<SymlinkOptions>,
+}
+
+#[cfg(not(unix))]
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SymlinkOptions {
+ _type: String,
+}
+
+fn op_symlink_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.symlink");
+ let args: SymlinkArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+
+ state.borrow::<Permissions>().check_write(&newpath)?;
+
+ debug!(
+ "op_symlink_sync {} {}",
+ oldpath.display(),
+ newpath.display()
+ );
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::symlink;
+ symlink(&oldpath, &newpath)?;
+ Ok(json!({}))
+ }
+ #[cfg(not(unix))]
+ {
+ use std::os::windows::fs::{symlink_dir, symlink_file};
+
+ match args.options {
+ Some(options) => match options._type.as_ref() {
+ "file" => symlink_file(&oldpath, &newpath)?,
+ "dir" => symlink_dir(&oldpath, &newpath)?,
+ _ => return Err(type_error("unsupported type")),
+ },
+ None => {
+ let old_meta = std::fs::metadata(&oldpath);
+ match old_meta {
+ Ok(metadata) => {
+ if metadata.is_file() {
+ symlink_file(&oldpath, &newpath)?
+ } else if metadata.is_dir() {
+ symlink_dir(&oldpath, &newpath)?
+ }
+ }
+ Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
+ }
+ }
+ };
+ Ok(json!({}))
+ }
+}
+
+async fn op_symlink_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ super::check_unstable2(&state, "Deno.symlink");
+
+ let args: SymlinkArgs = serde_json::from_value(args)?;
+ let oldpath = PathBuf::from(&args.oldpath);
+ let newpath = PathBuf::from(&args.newpath);
+
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&newpath)?;
+ }
+
+ tokio::task::spawn_blocking(move || {
+ debug!("op_symlink_async {} {}", oldpath.display(), newpath.display());
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::symlink;
+ symlink(&oldpath, &newpath)?;
+ Ok(json!({}))
+ }
+ #[cfg(not(unix))]
+ {
+ use std::os::windows::fs::{symlink_dir, symlink_file};
+
+ match args.options {
+ Some(options) => match options._type.as_ref() {
+ "file" => symlink_file(&oldpath, &newpath)?,
+ "dir" => symlink_dir(&oldpath, &newpath)?,
+ _ => return Err(type_error("unsupported type")),
+ },
+ None => {
+ let old_meta = std::fs::metadata(&oldpath);
+ match old_meta {
+ Ok(metadata) => {
+ if metadata.is_file() {
+ symlink_file(&oldpath, &newpath)?
+ } else if metadata.is_dir() {
+ symlink_dir(&oldpath, &newpath)?
+ }
+ }
+ Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
+ }
+ }
+ };
+ Ok(json!({}))
+ }
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ReadLinkArgs {
+ path: String,
+}
+
+fn op_read_link_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ReadLinkArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+
+ state.borrow::<Permissions>().check_read(&path)?;
+
+ debug!("op_read_link_value {}", path.display());
+ let target = std::fs::read_link(&path)?.into_os_string();
+ let targetstr = into_string(target)?;
+ Ok(json!(targetstr))
+}
+
+async fn op_read_link_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: ReadLinkArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_read(&path)?;
+ }
+ tokio::task::spawn_blocking(move || {
+ debug!("op_read_link_async {}", path.display());
+ let target = std::fs::read_link(&path)?.into_os_string();
+ let targetstr = into_string(target)?;
+ Ok(json!(targetstr))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FtruncateArgs {
+ rid: i32,
+ len: i32,
+}
+
+fn op_ftruncate_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.ftruncate");
+ let args: FtruncateArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let len = args.len as u64;
+ std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => std_file.set_len(len).map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot truncate this type of resource")),
+ })?;
+ Ok(json!({}))
+}
+
+async fn op_ftruncate_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ super::check_unstable2(&state, "Deno.ftruncate");
+ let args: FtruncateArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let len = args.len as u64;
+ std_file_resource(&mut state.borrow_mut(), rid, |r| match r {
+ Ok(std_file) => std_file.set_len(len).map_err(AnyError::from),
+ Err(_) => Err(type_error("cannot truncate this type of resource")),
+ })?;
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct TruncateArgs {
+ path: String,
+ len: u64,
+}
+
+fn op_truncate_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: TruncateArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let len = args.len;
+
+ state.borrow::<Permissions>().check_write(&path)?;
+
+ debug!("op_truncate_sync {} {}", path.display(), len);
+ let f = std::fs::OpenOptions::new().write(true).open(&path)?;
+ f.set_len(len)?;
+ Ok(json!({}))
+}
+
+async fn op_truncate_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: TruncateArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let len = args.len;
+ {
+ let state = state.borrow();
+ state.borrow::<Permissions>().check_write(&path)?;
+ }
+ tokio::task::spawn_blocking(move || {
+ debug!("op_truncate_async {} {}", path.display(), len);
+ let f = std::fs::OpenOptions::new().write(true).open(&path)?;
+ f.set_len(len)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+fn make_temp(
+ dir: Option<&Path>,
+ prefix: Option<&str>,
+ suffix: Option<&str>,
+ is_dir: bool,
+) -> std::io::Result<PathBuf> {
+ let prefix_ = prefix.unwrap_or("");
+ let suffix_ = suffix.unwrap_or("");
+ let mut buf: PathBuf = match dir {
+ Some(ref p) => p.to_path_buf(),
+ None => temp_dir(),
+ }
+ .join("_");
+ let mut rng = thread_rng();
+ loop {
+ let unique = rng.gen::<u32>();
+ buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_));
+ let r = if is_dir {
+ #[allow(unused_mut)]
+ let mut builder = std::fs::DirBuilder::new();
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::DirBuilderExt;
+ builder.mode(0o700);
+ }
+ builder.create(buf.as_path())
+ } else {
+ let mut open_options = std::fs::OpenOptions::new();
+ open_options.write(true).create_new(true);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::OpenOptionsExt;
+ open_options.mode(0o600);
+ }
+ open_options.open(buf.as_path())?;
+ Ok(())
+ };
+ match r {
+ Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
+ Ok(_) => return Ok(buf),
+ Err(e) => return Err(e),
+ }
+ }
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct MakeTempArgs {
+ dir: Option<String>,
+ prefix: Option<String>,
+ suffix: Option<String>,
+}
+
+fn op_make_temp_dir_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: MakeTempArgs = serde_json::from_value(args)?;
+
+ let dir = args.dir.map(|s| PathBuf::from(&s));
+ let prefix = args.prefix.map(String::from);
+ let suffix = args.suffix.map(String::from);
+
+ state
+ .borrow::<Permissions>()
+ .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?;
+
+ // TODO(piscisaureus): use byte vector for paths, not a string.
+ // See https://github.com/denoland/deno/issues/627.
+ // We can't assume that paths are always valid utf8 strings.
+ let path = make_temp(
+ // Converting Option<String> to Option<&str>
+ dir.as_deref(),
+ prefix.as_deref(),
+ suffix.as_deref(),
+ true,
+ )?;
+ let path_str = into_string(path.into_os_string())?;
+
+ Ok(json!(path_str))
+}
+
+async fn op_make_temp_dir_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: MakeTempArgs = serde_json::from_value(args)?;
+
+ let dir = args.dir.map(|s| PathBuf::from(&s));
+ let prefix = args.prefix.map(String::from);
+ let suffix = args.suffix.map(String::from);
+ {
+ let state = state.borrow();
+ state
+ .borrow::<Permissions>()
+ .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?;
+ }
+ tokio::task::spawn_blocking(move || {
+ // TODO(piscisaureus): use byte vector for paths, not a string.
+ // See https://github.com/denoland/deno/issues/627.
+ // We can't assume that paths are always valid utf8 strings.
+ let path = make_temp(
+ // Converting Option<String> to Option<&str>
+ dir.as_deref(),
+ prefix.as_deref(),
+ suffix.as_deref(),
+ true,
+ )?;
+ let path_str = into_string(path.into_os_string())?;
+
+ Ok(json!(path_str))
+ })
+ .await
+ .unwrap()
+}
+
+fn op_make_temp_file_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: MakeTempArgs = serde_json::from_value(args)?;
+
+ let dir = args.dir.map(|s| PathBuf::from(&s));
+ let prefix = args.prefix.map(String::from);
+ let suffix = args.suffix.map(String::from);
+
+ state
+ .borrow::<Permissions>()
+ .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?;
+
+ // TODO(piscisaureus): use byte vector for paths, not a string.
+ // See https://github.com/denoland/deno/issues/627.
+ // We can't assume that paths are always valid utf8 strings.
+ let path = make_temp(
+ // Converting Option<String> to Option<&str>
+ dir.as_deref(),
+ prefix.as_deref(),
+ suffix.as_deref(),
+ false,
+ )?;
+ let path_str = into_string(path.into_os_string())?;
+
+ Ok(json!(path_str))
+}
+
+async fn op_make_temp_file_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: MakeTempArgs = serde_json::from_value(args)?;
+
+ let dir = args.dir.map(|s| PathBuf::from(&s));
+ let prefix = args.prefix.map(String::from);
+ let suffix = args.suffix.map(String::from);
+ {
+ let state = state.borrow();
+ state
+ .borrow::<Permissions>()
+ .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?;
+ }
+ tokio::task::spawn_blocking(move || {
+ // TODO(piscisaureus): use byte vector for paths, not a string.
+ // See https://github.com/denoland/deno/issues/627.
+ // We can't assume that paths are always valid utf8 strings.
+ let path = make_temp(
+ // Converting Option<String> to Option<&str>
+ dir.as_deref(),
+ prefix.as_deref(),
+ suffix.as_deref(),
+ false,
+ )?;
+ let path_str = into_string(path.into_os_string())?;
+
+ Ok(json!(path_str))
+ })
+ .await
+ .unwrap()
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FutimeArgs {
+ rid: i32,
+ atime: (i64, u32),
+ mtime: (i64, u32),
+}
+
+fn op_futime_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.futimeSync");
+ let args: FutimeArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1);
+ let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
+
+ std_file_resource(state, rid, |r| match r {
+ Ok(std_file) => {
+ filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
+ .map_err(AnyError::from)
+ }
+ Err(_) => Err(type_error(
+ "cannot futime on this type of resource".to_string(),
+ )),
+ })?;
+
+ Ok(json!({}))
+}
+
+async fn op_futime_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let mut state = state.borrow_mut();
+ super::check_unstable(&state, "Deno.futime");
+ let args: FutimeArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1);
+ let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
+ // TODO Not actually async! https://github.com/denoland/deno/issues/7400
+ std_file_resource(&mut state, rid, |r| match r {
+ Ok(std_file) => {
+ filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
+ .map_err(AnyError::from)
+ }
+ Err(_) => Err(type_error(
+ "cannot futime on this type of resource".to_string(),
+ )),
+ })?;
+
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct UtimeArgs {
+ path: String,
+ atime: (i64, u32),
+ mtime: (i64, u32),
+}
+
+fn op_utime_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.utime");
+
+ let args: UtimeArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1);
+ let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
+
+ state.borrow::<Permissions>().check_write(&path)?;
+ filetime::set_file_times(path, atime, mtime)?;
+ Ok(json!({}))
+}
+
+async fn op_utime_async(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let state = state.borrow();
+ super::check_unstable(&state, "Deno.utime");
+
+ let args: UtimeArgs = serde_json::from_value(args)?;
+ let path = PathBuf::from(&args.path);
+ let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1);
+ let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
+
+ state.borrow::<Permissions>().check_write(&path)?;
+
+ tokio::task::spawn_blocking(move || {
+ filetime::set_file_times(path, atime, mtime)?;
+ Ok(json!({}))
+ })
+ .await
+ .unwrap()
+}
+
+fn op_cwd(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let path = current_dir()?;
+ state
+ .borrow::<Permissions>()
+ .check_read_blind(&path, "CWD")?;
+ let path_str = into_string(path.into_os_string())?;
+ Ok(json!(path_str))
+}
diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs
new file mode 100644
index 000000000..4832c915c
--- /dev/null
+++ b/runtime/ops/fs_events.rs
@@ -0,0 +1,133 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::permissions::Permissions;
+use deno_core::error::bad_resource_id;
+use deno_core::error::AnyError;
+use deno_core::futures::future::poll_fn;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use notify::event::Event as NotifyEvent;
+use notify::Error as NotifyError;
+use notify::EventKind;
+use notify::RecommendedWatcher;
+use notify::RecursiveMode;
+use notify::Watcher;
+use serde::Deserialize;
+use serde::Serialize;
+use std::cell::RefCell;
+use std::convert::From;
+use std::path::PathBuf;
+use std::rc::Rc;
+use tokio::sync::mpsc;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_fs_events_open", op_fs_events_open);
+ super::reg_json_async(rt, "op_fs_events_poll", op_fs_events_poll);
+}
+
+struct FsEventsResource {
+ #[allow(unused)]
+ watcher: RecommendedWatcher,
+ receiver: mpsc::Receiver<Result<FsEvent, AnyError>>,
+}
+
+/// Represents a file system event.
+///
+/// We do not use the event directly from the notify crate. We flatten
+/// the structure into this simpler structure. We want to only make it more
+/// complex as needed.
+///
+/// Feel free to expand this struct as long as you can add tests to demonstrate
+/// the complexity.
+#[derive(Serialize, Debug)]
+struct FsEvent {
+ kind: String,
+ paths: Vec<PathBuf>,
+}
+
+impl From<NotifyEvent> for FsEvent {
+ fn from(e: NotifyEvent) -> Self {
+ let kind = match e.kind {
+ EventKind::Any => "any",
+ EventKind::Access(_) => "access",
+ EventKind::Create(_) => "create",
+ EventKind::Modify(_) => "modify",
+ EventKind::Remove(_) => "remove",
+ EventKind::Other => todo!(), // What's this for? Leaving it out for now.
+ }
+ .to_string();
+ FsEvent {
+ kind,
+ paths: e.paths,
+ }
+ }
+}
+
+fn op_fs_events_open(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ #[derive(Deserialize)]
+ struct OpenArgs {
+ recursive: bool,
+ paths: Vec<String>,
+ }
+ let args: OpenArgs = serde_json::from_value(args)?;
+ let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16);
+ let sender = std::sync::Mutex::new(sender);
+ let mut watcher: RecommendedWatcher =
+ Watcher::new_immediate(move |res: Result<NotifyEvent, NotifyError>| {
+ let res2 = res.map(FsEvent::from).map_err(AnyError::from);
+ let mut sender = sender.lock().unwrap();
+ // Ignore result, if send failed it means that watcher was already closed,
+ // but not all messages have been flushed.
+ let _ = sender.try_send(res2);
+ })?;
+ let recursive_mode = if args.recursive {
+ RecursiveMode::Recursive
+ } else {
+ RecursiveMode::NonRecursive
+ };
+ for path in &args.paths {
+ state
+ .borrow::<Permissions>()
+ .check_read(&PathBuf::from(path))?;
+ watcher.watch(path, recursive_mode)?;
+ }
+ let resource = FsEventsResource { watcher, receiver };
+ let rid = state.resource_table.add("fsEvents", Box::new(resource));
+ Ok(json!(rid))
+}
+
+async fn op_fs_events_poll(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ #[derive(Deserialize)]
+ struct PollArgs {
+ rid: u32,
+ }
+ let PollArgs { rid } = serde_json::from_value(args)?;
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let watcher = state
+ .resource_table
+ .get_mut::<FsEventsResource>(rid)
+ .ok_or_else(bad_resource_id)?;
+ watcher
+ .receiver
+ .poll_recv(cx)
+ .map(|maybe_result| match maybe_result {
+ Some(Ok(value)) => Ok(json!({ "value": value, "done": false })),
+ Some(Err(err)) => Err(err),
+ None => Ok(json!({ "done": true })),
+ })
+ })
+ .await
+}
diff --git a/runtime/ops/io.rs b/runtime/ops/io.rs
new file mode 100644
index 000000000..0f8af905a
--- /dev/null
+++ b/runtime/ops/io.rs
@@ -0,0 +1,473 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::dispatch_minimal::minimal_op;
+use super::dispatch_minimal::MinimalOp;
+use crate::metrics::metrics_op;
+use deno_core::error::bad_resource_id;
+use deno_core::error::resource_unavailable;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::futures;
+use deno_core::futures::future::poll_fn;
+use deno_core::futures::future::FutureExt;
+use deno_core::futures::ready;
+use deno_core::BufVec;
+use deno_core::JsRuntime;
+use deno_core::OpState;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::task::Context;
+use std::task::Poll;
+use tokio::io::{AsyncRead, AsyncWrite};
+use tokio::net::TcpStream;
+use tokio_rustls::client::TlsStream as ClientTlsStream;
+use tokio_rustls::server::TlsStream as ServerTlsStream;
+
+#[cfg(not(windows))]
+use std::os::unix::io::FromRawFd;
+
+#[cfg(windows)]
+use std::os::windows::io::FromRawHandle;
+
+lazy_static! {
+ /// Due to portability issues on Windows handle to stdout is created from raw
+ /// file descriptor. The caveat of that approach is fact that when this
+ /// handle is dropped underlying file descriptor is closed - that is highly
+ /// not desirable in case of stdout. That's why we store this global handle
+ /// that is then cloned when obtaining stdio for process. In turn when
+ /// resource table is dropped storing reference to that handle, the handle
+ /// itself won't be closed (so Deno.core.print) will still work.
+ // TODO(ry) It should be possible to close stdout.
+ static ref STDIN_HANDLE: Option<std::fs::File> = {
+ #[cfg(not(windows))]
+ let stdin = unsafe { Some(std::fs::File::from_raw_fd(0)) };
+ #[cfg(windows)]
+ let stdin = unsafe {
+ let handle = winapi::um::processenv::GetStdHandle(
+ winapi::um::winbase::STD_INPUT_HANDLE,
+ );
+ if handle.is_null() {
+ return None;
+ }
+ Some(std::fs::File::from_raw_handle(handle))
+ };
+ stdin
+ };
+ static ref STDOUT_HANDLE: Option<std::fs::File> = {
+ #[cfg(not(windows))]
+ let stdout = unsafe { Some(std::fs::File::from_raw_fd(1)) };
+ #[cfg(windows)]
+ let stdout = unsafe {
+ let handle = winapi::um::processenv::GetStdHandle(
+ winapi::um::winbase::STD_OUTPUT_HANDLE,
+ );
+ if handle.is_null() {
+ return None;
+ }
+ Some(std::fs::File::from_raw_handle(handle))
+ };
+ stdout
+ };
+ static ref STDERR_HANDLE: Option<std::fs::File> = {
+ #[cfg(not(windows))]
+ let stderr = unsafe { Some(std::fs::File::from_raw_fd(2)) };
+ #[cfg(windows)]
+ let stderr = unsafe {
+ let handle = winapi::um::processenv::GetStdHandle(
+ winapi::um::winbase::STD_ERROR_HANDLE,
+ );
+ if handle.is_null() {
+ return None;
+ }
+ Some(std::fs::File::from_raw_handle(handle))
+ };
+ stderr
+ };
+}
+
+pub fn init(rt: &mut JsRuntime) {
+ rt.register_op("op_read", metrics_op(minimal_op(op_read)));
+ rt.register_op("op_write", metrics_op(minimal_op(op_write)));
+}
+
+pub fn get_stdio() -> (
+ Option<StreamResourceHolder>,
+ Option<StreamResourceHolder>,
+ Option<StreamResourceHolder>,
+) {
+ let stdin = get_stdio_stream(&STDIN_HANDLE);
+ let stdout = get_stdio_stream(&STDOUT_HANDLE);
+ let stderr = get_stdio_stream(&STDERR_HANDLE);
+
+ (stdin, stdout, stderr)
+}
+
+fn get_stdio_stream(
+ handle: &Option<std::fs::File>,
+) -> Option<StreamResourceHolder> {
+ match handle {
+ None => None,
+ Some(file_handle) => match file_handle.try_clone() {
+ Ok(clone) => Some(StreamResourceHolder::new(StreamResource::FsFile(
+ Some((tokio::fs::File::from_std(clone), FileMetadata::default())),
+ ))),
+ Err(_e) => None,
+ },
+ }
+}
+
+fn no_buffer_specified() -> AnyError {
+ type_error("no buffer specified")
+}
+
+#[cfg(unix)]
+use nix::sys::termios;
+
+#[derive(Default)]
+pub struct TTYMetadata {
+ #[cfg(unix)]
+ pub mode: Option<termios::Termios>,
+}
+
+#[derive(Default)]
+pub struct FileMetadata {
+ pub tty: TTYMetadata,
+}
+
+pub struct StreamResourceHolder {
+ pub resource: StreamResource,
+ waker: HashMap<usize, futures::task::AtomicWaker>,
+ waker_counter: AtomicUsize,
+}
+
+impl StreamResourceHolder {
+ pub fn new(resource: StreamResource) -> StreamResourceHolder {
+ StreamResourceHolder {
+ resource,
+ // Atleast one task is expecter for the resource
+ waker: HashMap::with_capacity(1),
+ // Tracks wakers Ids
+ waker_counter: AtomicUsize::new(0),
+ }
+ }
+}
+
+impl Drop for StreamResourceHolder {
+ fn drop(&mut self) {
+ self.wake_tasks();
+ }
+}
+
+impl StreamResourceHolder {
+ pub fn track_task(&mut self, cx: &Context) -> Result<usize, AnyError> {
+ let waker = futures::task::AtomicWaker::new();
+ waker.register(cx.waker());
+ // Its OK if it overflows
+ let task_waker_id = self.waker_counter.fetch_add(1, Ordering::Relaxed);
+ self.waker.insert(task_waker_id, waker);
+ Ok(task_waker_id)
+ }
+
+ pub fn wake_tasks(&mut self) {
+ for waker in self.waker.values() {
+ waker.wake();
+ }
+ }
+
+ pub fn untrack_task(&mut self, task_waker_id: usize) {
+ self.waker.remove(&task_waker_id);
+ }
+}
+
+pub enum StreamResource {
+ FsFile(Option<(tokio::fs::File, FileMetadata)>),
+ TcpStream(Option<tokio::net::TcpStream>),
+ #[cfg(not(windows))]
+ UnixStream(tokio::net::UnixStream),
+ ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
+ ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
+ ChildStdin(tokio::process::ChildStdin),
+ ChildStdout(tokio::process::ChildStdout),
+ ChildStderr(tokio::process::ChildStderr),
+}
+
+trait UnpinAsyncRead: AsyncRead + Unpin {}
+trait UnpinAsyncWrite: AsyncWrite + Unpin {}
+
+impl<T: AsyncRead + Unpin> UnpinAsyncRead for T {}
+impl<T: AsyncWrite + Unpin> UnpinAsyncWrite for T {}
+
+/// `DenoAsyncRead` is the same as the `tokio_io::AsyncRead` trait
+/// but uses an `AnyError` error instead of `std::io:Error`
+pub trait DenoAsyncRead {
+ fn poll_read(
+ &mut self,
+ cx: &mut Context,
+ buf: &mut [u8],
+ ) -> Poll<Result<usize, AnyError>>;
+}
+
+impl DenoAsyncRead for StreamResource {
+ fn poll_read(
+ &mut self,
+ cx: &mut Context,
+ buf: &mut [u8],
+ ) -> Poll<Result<usize, AnyError>> {
+ use StreamResource::*;
+ let f: &mut dyn UnpinAsyncRead = match self {
+ FsFile(Some((f, _))) => f,
+ FsFile(None) => return Poll::Ready(Err(resource_unavailable())),
+ TcpStream(Some(f)) => f,
+ #[cfg(not(windows))]
+ UnixStream(f) => f,
+ ClientTlsStream(f) => f,
+ ServerTlsStream(f) => f,
+ ChildStdout(f) => f,
+ ChildStderr(f) => f,
+ _ => return Err(bad_resource_id()).into(),
+ };
+ let v = ready!(Pin::new(f).poll_read(cx, buf))?;
+ Ok(v).into()
+ }
+}
+
+pub fn op_read(
+ state: Rc<RefCell<OpState>>,
+ is_sync: bool,
+ rid: i32,
+ mut zero_copy: BufVec,
+) -> MinimalOp {
+ debug!("read rid={}", rid);
+ match zero_copy.len() {
+ 0 => return MinimalOp::Sync(Err(no_buffer_specified())),
+ 1 => {}
+ _ => panic!("Invalid number of arguments"),
+ }
+
+ if is_sync {
+ MinimalOp::Sync({
+ // First we look up the rid in the resource table.
+ std_file_resource(&mut state.borrow_mut(), rid as u32, move |r| match r {
+ Ok(std_file) => {
+ use std::io::Read;
+ std_file
+ .read(&mut zero_copy[0])
+ .map(|n: usize| n as i32)
+ .map_err(AnyError::from)
+ }
+ Err(_) => Err(type_error("sync read not allowed on this resource")),
+ })
+ })
+ } else {
+ let mut zero_copy = zero_copy[0].clone();
+ MinimalOp::Async(
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let resource_holder = state
+ .resource_table
+ .get_mut::<StreamResourceHolder>(rid as u32)
+ .ok_or_else(bad_resource_id)?;
+
+ let mut task_tracker_id: Option<usize> = None;
+ let nread = match resource_holder.resource.poll_read(cx, &mut zero_copy)
+ {
+ Poll::Ready(t) => {
+ if let Some(id) = task_tracker_id {
+ resource_holder.untrack_task(id);
+ }
+ t
+ }
+ Poll::Pending => {
+ task_tracker_id.replace(resource_holder.track_task(cx)?);
+ return Poll::Pending;
+ }
+ }?;
+ Poll::Ready(Ok(nread as i32))
+ })
+ .boxed_local(),
+ )
+ }
+}
+
+/// `DenoAsyncWrite` is the same as the `tokio_io::AsyncWrite` trait
+/// but uses an `AnyError` error instead of `std::io:Error`
+pub trait DenoAsyncWrite {
+ fn poll_write(
+ &mut self,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, AnyError>>;
+
+ fn poll_close(&mut self, cx: &mut Context) -> Poll<Result<(), AnyError>>;
+
+ fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), AnyError>>;
+}
+
+impl DenoAsyncWrite for StreamResource {
+ fn poll_write(
+ &mut self,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, AnyError>> {
+ use StreamResource::*;
+ let f: &mut dyn UnpinAsyncWrite = match self {
+ FsFile(Some((f, _))) => f,
+ FsFile(None) => return Poll::Pending,
+ TcpStream(Some(f)) => f,
+ #[cfg(not(windows))]
+ UnixStream(f) => f,
+ ClientTlsStream(f) => f,
+ ServerTlsStream(f) => f,
+ ChildStdin(f) => f,
+ _ => return Err(bad_resource_id()).into(),
+ };
+
+ let v = ready!(Pin::new(f).poll_write(cx, buf))?;
+ Ok(v).into()
+ }
+
+ fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), AnyError>> {
+ use StreamResource::*;
+ let f: &mut dyn UnpinAsyncWrite = match self {
+ FsFile(Some((f, _))) => f,
+ FsFile(None) => return Poll::Pending,
+ TcpStream(Some(f)) => f,
+ #[cfg(not(windows))]
+ UnixStream(f) => f,
+ ClientTlsStream(f) => f,
+ ServerTlsStream(f) => f,
+ ChildStdin(f) => f,
+ _ => return Err(bad_resource_id()).into(),
+ };
+
+ ready!(Pin::new(f).poll_flush(cx))?;
+ Ok(()).into()
+ }
+
+ fn poll_close(&mut self, _cx: &mut Context) -> Poll<Result<(), AnyError>> {
+ unimplemented!()
+ }
+}
+
+pub fn op_write(
+ state: Rc<RefCell<OpState>>,
+ is_sync: bool,
+ rid: i32,
+ zero_copy: BufVec,
+) -> MinimalOp {
+ debug!("write rid={}", rid);
+ match zero_copy.len() {
+ 0 => return MinimalOp::Sync(Err(no_buffer_specified())),
+ 1 => {}
+ _ => panic!("Invalid number of arguments"),
+ }
+
+ if is_sync {
+ MinimalOp::Sync({
+ // First we look up the rid in the resource table.
+ std_file_resource(&mut state.borrow_mut(), rid as u32, move |r| match r {
+ Ok(std_file) => {
+ use std::io::Write;
+ std_file
+ .write(&zero_copy[0])
+ .map(|nwritten: usize| nwritten as i32)
+ .map_err(AnyError::from)
+ }
+ Err(_) => Err(type_error("sync read not allowed on this resource")),
+ })
+ })
+ } else {
+ let zero_copy = zero_copy[0].clone();
+ MinimalOp::Async(
+ async move {
+ let nwritten = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let resource_holder = state
+ .resource_table
+ .get_mut::<StreamResourceHolder>(rid as u32)
+ .ok_or_else(bad_resource_id)?;
+ resource_holder.resource.poll_write(cx, &zero_copy)
+ })
+ .await?;
+
+ // TODO(bartlomieju): this step was added during upgrade to Tokio 0.2
+ // and the reasons for the need to explicitly flush are not fully known.
+ // Figure out why it's needed and preferably remove it.
+ // https://github.com/denoland/deno/issues/3565
+ poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let resource_holder = state
+ .resource_table
+ .get_mut::<StreamResourceHolder>(rid as u32)
+ .ok_or_else(bad_resource_id)?;
+ resource_holder.resource.poll_flush(cx)
+ })
+ .await?;
+
+ Ok(nwritten as i32)
+ }
+ .boxed_local(),
+ )
+ }
+}
+
+/// Helper function for operating on a std::fs::File stored in the resource table.
+///
+/// We store file system file resources as tokio::fs::File, so this is a little
+/// utility function that gets a std::fs:File when you need to do blocking
+/// operations.
+///
+/// Returns ErrorKind::Busy if the resource is being used by another op.
+pub fn std_file_resource<F, T>(
+ state: &mut OpState,
+ rid: u32,
+ mut f: F,
+) -> Result<T, AnyError>
+where
+ F: FnMut(
+ Result<&mut std::fs::File, &mut StreamResource>,
+ ) -> Result<T, AnyError>,
+{
+ // First we look up the rid in the resource table.
+ let mut r = state.resource_table.get_mut::<StreamResourceHolder>(rid);
+ if let Some(ref mut resource_holder) = r {
+ // Sync write only works for FsFile. It doesn't make sense to do this
+ // for non-blocking sockets. So we error out if not FsFile.
+ match &mut resource_holder.resource {
+ StreamResource::FsFile(option_file_metadata) => {
+ // The object in the resource table is a tokio::fs::File - but in
+ // order to do a blocking write on it, we must turn it into a
+ // std::fs::File. Hopefully this code compiles down to nothing.
+ if let Some((tokio_file, metadata)) = option_file_metadata.take() {
+ match tokio_file.try_into_std() {
+ Ok(mut std_file) => {
+ let result = f(Ok(&mut std_file));
+ // Turn the std_file handle back into a tokio file, put it back
+ // in the resource table.
+ let tokio_file = tokio::fs::File::from_std(std_file);
+ resource_holder.resource =
+ StreamResource::FsFile(Some((tokio_file, metadata)));
+ // return the result.
+ result
+ }
+ Err(tokio_file) => {
+ // This function will return an error containing the file if
+ // some operation is in-flight.
+ resource_holder.resource =
+ StreamResource::FsFile(Some((tokio_file, metadata)));
+ Err(resource_unavailable())
+ }
+ }
+ } else {
+ Err(resource_unavailable())
+ }
+ }
+ _ => f(Err(&mut resource_holder.resource)),
+ }
+ } else {
+ Err(bad_resource_id())
+ }
+}
diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs
new file mode 100644
index 000000000..a27122657
--- /dev/null
+++ b/runtime/ops/mod.rs
@@ -0,0 +1,89 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+mod dispatch_minimal;
+pub use dispatch_minimal::MinimalOp;
+
+pub mod crypto;
+pub mod fetch;
+pub mod fs;
+pub mod fs_events;
+pub mod io;
+pub mod net;
+#[cfg(unix)]
+mod net_unix;
+pub mod os;
+pub mod permissions;
+pub mod plugin;
+pub mod process;
+pub mod runtime;
+pub mod signal;
+pub mod timers;
+pub mod tls;
+pub mod tty;
+pub mod web_worker;
+pub mod websocket;
+pub mod worker_host;
+
+use crate::metrics::metrics_op;
+use deno_core::error::AnyError;
+use deno_core::json_op_async;
+use deno_core::json_op_sync;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::JsRuntime;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use std::cell::RefCell;
+use std::future::Future;
+use std::rc::Rc;
+
+pub fn reg_json_async<F, R>(rt: &mut JsRuntime, name: &'static str, op_fn: F)
+where
+ F: Fn(Rc<RefCell<OpState>>, Value, BufVec) -> R + 'static,
+ R: Future<Output = Result<Value, AnyError>> + 'static,
+{
+ rt.register_op(name, metrics_op(json_op_async(op_fn)));
+}
+
+pub fn reg_json_sync<F>(rt: &mut JsRuntime, name: &'static str, op_fn: F)
+where
+ F: Fn(&mut OpState, Value, &mut [ZeroCopyBuf]) -> Result<Value, AnyError>
+ + 'static,
+{
+ rt.register_op(name, metrics_op(json_op_sync(op_fn)));
+}
+
+/// `UnstableChecker` is a struct so it can be placed inside `GothamState`;
+/// using type alias for a bool could work, but there's a high chance
+/// that there might be another type alias pointing to a bool, which
+/// would override previously used alias.
+pub struct UnstableChecker {
+ pub unstable: bool,
+}
+
+impl UnstableChecker {
+ /// Quits the process if the --unstable flag was not provided.
+ ///
+ /// This is intentionally a non-recoverable check so that people cannot probe
+ /// for unstable APIs from stable programs.
+ // NOTE(bartlomieju): keep in sync with `cli/program_state.rs`
+ pub fn check_unstable(&self, api_name: &str) {
+ if !self.unstable {
+ eprintln!(
+ "Unstable API '{}'. The --unstable flag must be provided.",
+ api_name
+ );
+ std::process::exit(70);
+ }
+ }
+}
+/// Helper for checking unstable features. Used for sync ops.
+pub fn check_unstable(state: &OpState, api_name: &str) {
+ state.borrow::<UnstableChecker>().check_unstable(api_name)
+}
+
+/// Helper for checking unstable features. Used for async ops.
+pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
+ let state = state.borrow();
+ state.borrow::<UnstableChecker>().check_unstable(api_name)
+}
diff --git a/runtime/ops/net.rs b/runtime/ops/net.rs
new file mode 100644
index 000000000..98ff83fc0
--- /dev/null
+++ b/runtime/ops/net.rs
@@ -0,0 +1,566 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::ops::io::StreamResource;
+use crate::ops::io::StreamResourceHolder;
+use crate::permissions::Permissions;
+use crate::resolve_addr::resolve_addr;
+use deno_core::error::bad_resource;
+use deno_core::error::bad_resource_id;
+use deno_core::error::custom_error;
+use deno_core::error::generic_error;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::futures;
+use deno_core::futures::future::poll_fn;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::net::Shutdown;
+use std::net::SocketAddr;
+use std::rc::Rc;
+use std::task::Context;
+use std::task::Poll;
+use tokio::net::TcpListener;
+use tokio::net::TcpStream;
+use tokio::net::UdpSocket;
+
+#[cfg(unix)]
+use super::net_unix;
+#[cfg(unix)]
+use std::path::Path;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_async(rt, "op_accept", op_accept);
+ super::reg_json_async(rt, "op_connect", op_connect);
+ super::reg_json_sync(rt, "op_shutdown", op_shutdown);
+ super::reg_json_sync(rt, "op_listen", op_listen);
+ super::reg_json_async(rt, "op_datagram_receive", op_datagram_receive);
+ super::reg_json_async(rt, "op_datagram_send", op_datagram_send);
+}
+
+#[derive(Deserialize)]
+pub(crate) struct AcceptArgs {
+ pub rid: i32,
+ pub transport: String,
+}
+
+async fn accept_tcp(
+ state: Rc<RefCell<OpState>>,
+ args: AcceptArgs,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let rid = args.rid as u32;
+
+ let accept_fut = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let listener_resource = state
+ .resource_table
+ .get_mut::<TcpListenerResource>(rid)
+ .ok_or_else(|| bad_resource("Listener has been closed"))?;
+ let listener = &mut listener_resource.listener;
+ match listener.poll_accept(cx).map_err(AnyError::from) {
+ Poll::Ready(Ok((stream, addr))) => {
+ listener_resource.untrack_task();
+ Poll::Ready(Ok((stream, addr)))
+ }
+ Poll::Pending => {
+ listener_resource.track_task(cx)?;
+ Poll::Pending
+ }
+ Poll::Ready(Err(e)) => {
+ listener_resource.untrack_task();
+ Poll::Ready(Err(e))
+ }
+ }
+ });
+ let (tcp_stream, _socket_addr) = accept_fut.await?;
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+
+ let mut state = state.borrow_mut();
+ let rid = state.resource_table.add(
+ "tcpStream",
+ Box::new(StreamResourceHolder::new(StreamResource::TcpStream(Some(
+ tcp_stream,
+ )))),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": "tcp",
+ },
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": "tcp",
+ }
+ }))
+}
+
+async fn op_accept(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let args: AcceptArgs = serde_json::from_value(args)?;
+ match args.transport.as_str() {
+ "tcp" => accept_tcp(state, args, bufs).await,
+ #[cfg(unix)]
+ "unix" => net_unix::accept_unix(state, args, bufs).await,
+ _ => Err(generic_error(format!(
+ "Unsupported transport protocol {}",
+ args.transport
+ ))),
+ }
+}
+
+#[derive(Deserialize)]
+pub(crate) struct ReceiveArgs {
+ pub rid: i32,
+ pub transport: String,
+}
+
+async fn receive_udp(
+ state: Rc<RefCell<OpState>>,
+ args: ReceiveArgs,
+ zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ assert_eq!(zero_copy.len(), 1, "Invalid number of arguments");
+ let mut zero_copy = zero_copy[0].clone();
+
+ let rid = args.rid as u32;
+
+ let receive_fut = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get_mut::<UdpSocketResource>(rid)
+ .ok_or_else(|| bad_resource("Socket has been closed"))?;
+ let socket = &mut resource.socket;
+ socket
+ .poll_recv_from(cx, &mut zero_copy)
+ .map_err(AnyError::from)
+ });
+ let (size, remote_addr) = receive_fut.await?;
+ Ok(json!({
+ "size": size,
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": "udp",
+ }
+ }))
+}
+
+async fn op_datagram_receive(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ assert_eq!(zero_copy.len(), 1, "Invalid number of arguments");
+
+ let args: ReceiveArgs = serde_json::from_value(args)?;
+ match args.transport.as_str() {
+ "udp" => receive_udp(state, args, zero_copy).await,
+ #[cfg(unix)]
+ "unixpacket" => net_unix::receive_unix_packet(state, args, zero_copy).await,
+ _ => Err(generic_error(format!(
+ "Unsupported transport protocol {}",
+ args.transport
+ ))),
+ }
+}
+
+#[derive(Deserialize)]
+struct SendArgs {
+ rid: i32,
+ transport: String,
+ #[serde(flatten)]
+ transport_args: ArgsEnum,
+}
+
+async fn op_datagram_send(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ assert_eq!(zero_copy.len(), 1, "Invalid number of arguments");
+ let zero_copy = zero_copy[0].clone();
+
+ match serde_json::from_value(args)? {
+ SendArgs {
+ rid,
+ transport,
+ transport_args: ArgsEnum::Ip(args),
+ } if transport == "udp" => {
+ {
+ let s = state.borrow();
+ s.borrow::<Permissions>()
+ .check_net(&args.hostname, args.port)?;
+ }
+ let addr = resolve_addr(&args.hostname, args.port)?;
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get_mut::<UdpSocketResource>(rid as u32)
+ .ok_or_else(|| bad_resource("Socket has been closed"))?;
+ resource
+ .socket
+ .poll_send_to(cx, &zero_copy, &addr)
+ .map_ok(|byte_length| json!(byte_length))
+ .map_err(AnyError::from)
+ })
+ .await
+ }
+ #[cfg(unix)]
+ SendArgs {
+ rid,
+ transport,
+ transport_args: ArgsEnum::Unix(args),
+ } if transport == "unixpacket" => {
+ let address_path = Path::new(&args.path);
+ {
+ let s = state.borrow();
+ s.borrow::<Permissions>().check_write(&address_path)?;
+ }
+ let mut state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get_mut::<net_unix::UnixDatagramResource>(rid as u32)
+ .ok_or_else(|| {
+ custom_error("NotConnected", "Socket has been closed")
+ })?;
+ let socket = &mut resource.socket;
+ let byte_length = socket
+ .send_to(&zero_copy, &resource.local_addr.as_pathname().unwrap())
+ .await?;
+
+ Ok(json!(byte_length))
+ }
+ _ => Err(type_error("Wrong argument format!")),
+ }
+}
+
+#[derive(Deserialize)]
+struct ConnectArgs {
+ transport: String,
+ #[serde(flatten)]
+ transport_args: ArgsEnum,
+}
+
+async fn op_connect(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ match serde_json::from_value(args)? {
+ ConnectArgs {
+ transport,
+ transport_args: ArgsEnum::Ip(args),
+ } if transport == "tcp" => {
+ {
+ let state_ = state.borrow();
+ state_
+ .borrow::<Permissions>()
+ .check_net(&args.hostname, args.port)?;
+ }
+ let addr = resolve_addr(&args.hostname, args.port)?;
+ let tcp_stream = TcpStream::connect(&addr).await?;
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+
+ let mut state_ = state.borrow_mut();
+ let rid = state_.resource_table.add(
+ "tcpStream",
+ Box::new(StreamResourceHolder::new(StreamResource::TcpStream(Some(
+ tcp_stream,
+ )))),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": transport,
+ },
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": transport,
+ }
+ }))
+ }
+ #[cfg(unix)]
+ ConnectArgs {
+ transport,
+ transport_args: ArgsEnum::Unix(args),
+ } if transport == "unix" => {
+ let address_path = Path::new(&args.path);
+ super::check_unstable2(&state, "Deno.connect");
+ {
+ let state_ = state.borrow();
+ state_.borrow::<Permissions>().check_read(&address_path)?;
+ state_.borrow::<Permissions>().check_write(&address_path)?;
+ }
+ let path = args.path;
+ let unix_stream = net_unix::UnixStream::connect(Path::new(&path)).await?;
+ let local_addr = unix_stream.local_addr()?;
+ let remote_addr = unix_stream.peer_addr()?;
+
+ let mut state_ = state.borrow_mut();
+ let rid = state_.resource_table.add(
+ "unixStream",
+ Box::new(StreamResourceHolder::new(StreamResource::UnixStream(
+ unix_stream,
+ ))),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "path": local_addr.as_pathname(),
+ "transport": transport,
+ },
+ "remoteAddr": {
+ "path": remote_addr.as_pathname(),
+ "transport": transport,
+ }
+ }))
+ }
+ _ => Err(type_error("Wrong argument format!")),
+ }
+}
+
+#[derive(Deserialize)]
+struct ShutdownArgs {
+ rid: i32,
+ how: i32,
+}
+
+fn op_shutdown(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.shutdown");
+
+ let args: ShutdownArgs = serde_json::from_value(args)?;
+
+ let rid = args.rid as u32;
+ let how = args.how;
+
+ let shutdown_mode = match how {
+ 0 => Shutdown::Read,
+ 1 => Shutdown::Write,
+ _ => unimplemented!(),
+ };
+
+ let resource_holder = state
+ .resource_table
+ .get_mut::<StreamResourceHolder>(rid)
+ .ok_or_else(bad_resource_id)?;
+ match resource_holder.resource {
+ StreamResource::TcpStream(Some(ref mut stream)) => {
+ TcpStream::shutdown(stream, shutdown_mode)?;
+ }
+ #[cfg(unix)]
+ StreamResource::UnixStream(ref mut stream) => {
+ net_unix::UnixStream::shutdown(stream, shutdown_mode)?;
+ }
+ _ => return Err(bad_resource_id()),
+ }
+
+ Ok(json!({}))
+}
+
+#[allow(dead_code)]
+struct TcpListenerResource {
+ listener: TcpListener,
+ waker: Option<futures::task::AtomicWaker>,
+ local_addr: SocketAddr,
+}
+
+impl Drop for TcpListenerResource {
+ fn drop(&mut self) {
+ self.wake_task();
+ }
+}
+
+impl TcpListenerResource {
+ /// Track the current task so future awaiting for connection
+ /// can be notified when listener is closed.
+ ///
+ /// Throws an error if another task is already tracked.
+ pub fn track_task(&mut self, cx: &Context) -> Result<(), AnyError> {
+ // Currently, we only allow tracking a single accept task for a listener.
+ // This might be changed in the future with multiple workers.
+ // Caveat: TcpListener by itself also only tracks an accept task at a time.
+ // See https://github.com/tokio-rs/tokio/issues/846#issuecomment-454208883
+ if self.waker.is_some() {
+ return Err(custom_error("Busy", "Another accept task is ongoing"));
+ }
+
+ let waker = futures::task::AtomicWaker::new();
+ waker.register(cx.waker());
+ self.waker.replace(waker);
+ Ok(())
+ }
+
+ /// Notifies a task when listener is closed so accept future can resolve.
+ pub fn wake_task(&mut self) {
+ if let Some(waker) = self.waker.as_ref() {
+ waker.wake();
+ }
+ }
+
+ /// Stop tracking a task.
+ /// Happens when the task is done and thus no further tracking is needed.
+ pub fn untrack_task(&mut self) {
+ if self.waker.is_some() {
+ self.waker.take();
+ }
+ }
+}
+
+struct UdpSocketResource {
+ socket: UdpSocket,
+}
+
+#[derive(Deserialize)]
+struct IpListenArgs {
+ hostname: String,
+ port: u16,
+}
+
+#[derive(Deserialize)]
+#[serde(untagged)]
+enum ArgsEnum {
+ Ip(IpListenArgs),
+ #[cfg(unix)]
+ Unix(net_unix::UnixListenArgs),
+}
+
+#[derive(Deserialize)]
+struct ListenArgs {
+ transport: String,
+ #[serde(flatten)]
+ transport_args: ArgsEnum,
+}
+
+fn listen_tcp(
+ state: &mut OpState,
+ addr: SocketAddr,
+) -> Result<(u32, SocketAddr), AnyError> {
+ let std_listener = std::net::TcpListener::bind(&addr)?;
+ let listener = TcpListener::from_std(std_listener)?;
+ let local_addr = listener.local_addr()?;
+ let listener_resource = TcpListenerResource {
+ listener,
+ waker: None,
+ local_addr,
+ };
+ let rid = state
+ .resource_table
+ .add("tcpListener", Box::new(listener_resource));
+
+ Ok((rid, local_addr))
+}
+
+fn listen_udp(
+ state: &mut OpState,
+ addr: SocketAddr,
+) -> Result<(u32, SocketAddr), AnyError> {
+ let std_socket = std::net::UdpSocket::bind(&addr)?;
+ let socket = UdpSocket::from_std(std_socket)?;
+ let local_addr = socket.local_addr()?;
+ let socket_resource = UdpSocketResource { socket };
+ let rid = state
+ .resource_table
+ .add("udpSocket", Box::new(socket_resource));
+
+ Ok((rid, local_addr))
+}
+
+fn op_listen(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let permissions = state.borrow::<Permissions>();
+ match serde_json::from_value(args)? {
+ ListenArgs {
+ transport,
+ transport_args: ArgsEnum::Ip(args),
+ } => {
+ {
+ if transport == "udp" {
+ super::check_unstable(state, "Deno.listenDatagram");
+ }
+ permissions.check_net(&args.hostname, args.port)?;
+ }
+ let addr = resolve_addr(&args.hostname, args.port)?;
+ let (rid, local_addr) = if transport == "tcp" {
+ listen_tcp(state, addr)?
+ } else {
+ listen_udp(state, addr)?
+ };
+ debug!(
+ "New listener {} {}:{}",
+ rid,
+ local_addr.ip().to_string(),
+ local_addr.port()
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": transport,
+ },
+ }))
+ }
+ #[cfg(unix)]
+ ListenArgs {
+ transport,
+ transport_args: ArgsEnum::Unix(args),
+ } if transport == "unix" || transport == "unixpacket" => {
+ let address_path = Path::new(&args.path);
+ {
+ if transport == "unix" {
+ super::check_unstable(state, "Deno.listen");
+ }
+ if transport == "unixpacket" {
+ super::check_unstable(state, "Deno.listenDatagram");
+ }
+ permissions.check_read(&address_path)?;
+ permissions.check_write(&address_path)?;
+ }
+ let (rid, local_addr) = if transport == "unix" {
+ net_unix::listen_unix(state, &address_path)?
+ } else {
+ net_unix::listen_unix_packet(state, &address_path)?
+ };
+ debug!(
+ "New listener {} {}",
+ rid,
+ local_addr.as_pathname().unwrap().display(),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "path": local_addr.as_pathname(),
+ "transport": transport,
+ },
+ }))
+ }
+ #[cfg(unix)]
+ _ => Err(type_error("Wrong argument format!")),
+ }
+}
diff --git a/runtime/ops/net_unix.rs b/runtime/ops/net_unix.rs
new file mode 100644
index 000000000..4c416a5a4
--- /dev/null
+++ b/runtime/ops/net_unix.rs
@@ -0,0 +1,151 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::ops::io::StreamResource;
+use crate::ops::io::StreamResourceHolder;
+use crate::ops::net::AcceptArgs;
+use crate::ops::net::ReceiveArgs;
+use deno_core::error::bad_resource;
+use deno_core::error::AnyError;
+use deno_core::futures::future::poll_fn;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::fs::remove_file;
+use std::os::unix;
+use std::path::Path;
+use std::rc::Rc;
+use std::task::Poll;
+use tokio::net::UnixDatagram;
+use tokio::net::UnixListener;
+pub use tokio::net::UnixStream;
+
+struct UnixListenerResource {
+ listener: UnixListener,
+}
+
+pub struct UnixDatagramResource {
+ pub socket: UnixDatagram,
+ pub local_addr: unix::net::SocketAddr,
+}
+
+#[derive(Deserialize)]
+pub struct UnixListenArgs {
+ pub path: String,
+}
+
+pub(crate) async fn accept_unix(
+ state: Rc<RefCell<OpState>>,
+ args: AcceptArgs,
+ _bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let rid = args.rid as u32;
+
+ let accept_fut = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let listener_resource = state
+ .resource_table
+ .get_mut::<UnixListenerResource>(rid)
+ .ok_or_else(|| bad_resource("Listener has been closed"))?;
+ let listener = &mut listener_resource.listener;
+ use deno_core::futures::StreamExt;
+ match listener.poll_next_unpin(cx) {
+ Poll::Ready(Some(stream)) => {
+ //listener_resource.untrack_task();
+ Poll::Ready(stream)
+ }
+ Poll::Ready(None) => todo!(),
+ Poll::Pending => {
+ //listener_resource.track_task(cx)?;
+ Poll::Pending
+ }
+ }
+ .map_err(AnyError::from)
+ });
+ let unix_stream = accept_fut.await?;
+
+ let local_addr = unix_stream.local_addr()?;
+ let remote_addr = unix_stream.peer_addr()?;
+ let mut state = state.borrow_mut();
+ let rid = state.resource_table.add(
+ "unixStream",
+ Box::new(StreamResourceHolder::new(StreamResource::UnixStream(
+ unix_stream,
+ ))),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "path": local_addr.as_pathname(),
+ "transport": "unix",
+ },
+ "remoteAddr": {
+ "path": remote_addr.as_pathname(),
+ "transport": "unix",
+ }
+ }))
+}
+
+pub(crate) async fn receive_unix_packet(
+ state: Rc<RefCell<OpState>>,
+ args: ReceiveArgs,
+ bufs: BufVec,
+) -> Result<Value, AnyError> {
+ assert_eq!(bufs.len(), 1, "Invalid number of arguments");
+
+ let rid = args.rid as u32;
+ let mut buf = bufs.into_iter().next().unwrap();
+
+ let mut state = state.borrow_mut();
+ let resource = state
+ .resource_table
+ .get_mut::<UnixDatagramResource>(rid)
+ .ok_or_else(|| bad_resource("Socket has been closed"))?;
+ let (size, remote_addr) = resource.socket.recv_from(&mut buf).await?;
+ Ok(json!({
+ "size": size,
+ "remoteAddr": {
+ "path": remote_addr.as_pathname(),
+ "transport": "unixpacket",
+ }
+ }))
+}
+
+pub fn listen_unix(
+ state: &mut OpState,
+ addr: &Path,
+) -> Result<(u32, unix::net::SocketAddr), AnyError> {
+ if addr.exists() {
+ remove_file(&addr).unwrap();
+ }
+ let listener = UnixListener::bind(&addr)?;
+ let local_addr = listener.local_addr()?;
+ let listener_resource = UnixListenerResource { listener };
+ let rid = state
+ .resource_table
+ .add("unixListener", Box::new(listener_resource));
+
+ Ok((rid, local_addr))
+}
+
+pub fn listen_unix_packet(
+ state: &mut OpState,
+ addr: &Path,
+) -> Result<(u32, unix::net::SocketAddr), AnyError> {
+ if addr.exists() {
+ remove_file(&addr).unwrap();
+ }
+ let socket = UnixDatagram::bind(&addr)?;
+ let local_addr = socket.local_addr()?;
+ let datagram_resource = UnixDatagramResource {
+ socket,
+ local_addr: local_addr.clone(),
+ };
+ let rid = state
+ .resource_table
+ .add("unixDatagram", Box::new(datagram_resource));
+
+ Ok((rid, local_addr))
+}
diff --git a/runtime/ops/os.rs b/runtime/ops/os.rs
new file mode 100644
index 000000000..6fd404a23
--- /dev/null
+++ b/runtime/ops/os.rs
@@ -0,0 +1,192 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::permissions::Permissions;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::url::Url;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::env;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_exit", op_exit);
+ super::reg_json_sync(rt, "op_env", op_env);
+ super::reg_json_sync(rt, "op_exec_path", op_exec_path);
+ super::reg_json_sync(rt, "op_set_env", op_set_env);
+ super::reg_json_sync(rt, "op_get_env", op_get_env);
+ super::reg_json_sync(rt, "op_delete_env", op_delete_env);
+ super::reg_json_sync(rt, "op_hostname", op_hostname);
+ super::reg_json_sync(rt, "op_loadavg", op_loadavg);
+ super::reg_json_sync(rt, "op_os_release", op_os_release);
+ super::reg_json_sync(rt, "op_system_memory_info", op_system_memory_info);
+ super::reg_json_sync(rt, "op_system_cpu_info", op_system_cpu_info);
+}
+
+fn op_exec_path(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let current_exe = env::current_exe().unwrap();
+ state
+ .borrow::<Permissions>()
+ .check_read_blind(&current_exe, "exec_path")?;
+ // Now apply URL parser to current exe to get fully resolved path, otherwise
+ // we might get `./` and `../` bits in `exec_path`
+ let exe_url = Url::from_file_path(current_exe).unwrap();
+ let path = exe_url.to_file_path().unwrap();
+ Ok(json!(path))
+}
+
+#[derive(Deserialize)]
+struct SetEnv {
+ key: String,
+ value: String,
+}
+
+fn op_set_env(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: SetEnv = serde_json::from_value(args)?;
+ state.borrow::<Permissions>().check_env()?;
+ env::set_var(args.key, args.value);
+ Ok(json!({}))
+}
+
+fn op_env(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ state.borrow::<Permissions>().check_env()?;
+ let v = env::vars().collect::<HashMap<String, String>>();
+ Ok(json!(v))
+}
+
+#[derive(Deserialize)]
+struct GetEnv {
+ key: String,
+}
+
+fn op_get_env(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: GetEnv = serde_json::from_value(args)?;
+ state.borrow::<Permissions>().check_env()?;
+ let r = match env::var(args.key) {
+ Err(env::VarError::NotPresent) => json!([]),
+ v => json!([v?]),
+ };
+ Ok(r)
+}
+
+#[derive(Deserialize)]
+struct DeleteEnv {
+ key: String,
+}
+
+fn op_delete_env(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: DeleteEnv = serde_json::from_value(args)?;
+ state.borrow::<Permissions>().check_env()?;
+ env::remove_var(args.key);
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+struct Exit {
+ code: i32,
+}
+
+fn op_exit(
+ _state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: Exit = serde_json::from_value(args)?;
+ std::process::exit(args.code)
+}
+
+fn op_loadavg(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.loadavg");
+ state.borrow::<Permissions>().check_env()?;
+ match sys_info::loadavg() {
+ Ok(loadavg) => Ok(json!([loadavg.one, loadavg.five, loadavg.fifteen])),
+ Err(_) => Ok(json!([0f64, 0f64, 0f64])),
+ }
+}
+
+fn op_hostname(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.hostname");
+ state.borrow::<Permissions>().check_env()?;
+ let hostname = sys_info::hostname().unwrap_or_else(|_| "".to_string());
+ Ok(json!(hostname))
+}
+
+fn op_os_release(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.osRelease");
+ state.borrow::<Permissions>().check_env()?;
+ let release = sys_info::os_release().unwrap_or_else(|_| "".to_string());
+ Ok(json!(release))
+}
+
+fn op_system_memory_info(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.systemMemoryInfo");
+ state.borrow::<Permissions>().check_env()?;
+ match sys_info::mem_info() {
+ Ok(info) => Ok(json!({
+ "total": info.total,
+ "free": info.free,
+ "available": info.avail,
+ "buffers": info.buffers,
+ "cached": info.cached,
+ "swapTotal": info.swap_total,
+ "swapFree": info.swap_free
+ })),
+ Err(_) => Ok(json!({})),
+ }
+}
+
+fn op_system_cpu_info(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.systemCpuInfo");
+ state.borrow::<Permissions>().check_env()?;
+
+ let cores = sys_info::cpu_num().ok();
+ let speed = sys_info::cpu_speed().ok();
+
+ Ok(json!({
+ "cores": cores,
+ "speed": speed
+ }))
+}
diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs
new file mode 100644
index 000000000..7474c0e37
--- /dev/null
+++ b/runtime/ops/permissions.rs
@@ -0,0 +1,103 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::permissions::Permissions;
+use deno_core::error::custom_error;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::path::Path;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_query_permission", op_query_permission);
+ super::reg_json_sync(rt, "op_revoke_permission", op_revoke_permission);
+ super::reg_json_sync(rt, "op_request_permission", op_request_permission);
+}
+
+#[derive(Deserialize)]
+struct PermissionArgs {
+ name: String,
+ url: Option<String>,
+ path: Option<String>,
+}
+
+pub fn op_query_permission(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: PermissionArgs = serde_json::from_value(args)?;
+ let permissions = state.borrow::<Permissions>();
+ let path = args.path.as_deref();
+ let perm = match args.name.as_ref() {
+ "read" => permissions.query_read(&path.as_deref().map(Path::new)),
+ "write" => permissions.query_write(&path.as_deref().map(Path::new)),
+ "net" => permissions.query_net_url(&args.url.as_deref())?,
+ "env" => permissions.query_env(),
+ "run" => permissions.query_run(),
+ "plugin" => permissions.query_plugin(),
+ "hrtime" => permissions.query_hrtime(),
+ n => {
+ return Err(custom_error(
+ "ReferenceError",
+ format!("No such permission name: {}", n),
+ ))
+ }
+ };
+ Ok(json!({ "state": perm.to_string() }))
+}
+
+pub fn op_revoke_permission(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: PermissionArgs = serde_json::from_value(args)?;
+ let permissions = state.borrow_mut::<Permissions>();
+ let path = args.path.as_deref();
+ let perm = match args.name.as_ref() {
+ "read" => permissions.revoke_read(&path.as_deref().map(Path::new)),
+ "write" => permissions.revoke_write(&path.as_deref().map(Path::new)),
+ "net" => permissions.revoke_net(&args.url.as_deref())?,
+ "env" => permissions.revoke_env(),
+ "run" => permissions.revoke_run(),
+ "plugin" => permissions.revoke_plugin(),
+ "hrtime" => permissions.revoke_hrtime(),
+ n => {
+ return Err(custom_error(
+ "ReferenceError",
+ format!("No such permission name: {}", n),
+ ))
+ }
+ };
+ Ok(json!({ "state": perm.to_string() }))
+}
+
+pub fn op_request_permission(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: PermissionArgs = serde_json::from_value(args)?;
+ let permissions = state.borrow_mut::<Permissions>();
+ let path = args.path.as_deref();
+ let perm = match args.name.as_ref() {
+ "read" => permissions.request_read(&path.as_deref().map(Path::new)),
+ "write" => permissions.request_write(&path.as_deref().map(Path::new)),
+ "net" => permissions.request_net(&args.url.as_deref())?,
+ "env" => permissions.request_env(),
+ "run" => permissions.request_run(),
+ "plugin" => permissions.request_plugin(),
+ "hrtime" => permissions.request_hrtime(),
+ n => {
+ return Err(custom_error(
+ "ReferenceError",
+ format!("No such permission name: {}", n),
+ ))
+ }
+ };
+ Ok(json!({ "state": perm.to_string() }))
+}
diff --git a/runtime/ops/plugin.rs b/runtime/ops/plugin.rs
new file mode 100644
index 000000000..1f3669b6f
--- /dev/null
+++ b/runtime/ops/plugin.rs
@@ -0,0 +1,156 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::metrics::metrics_op;
+use crate::permissions::Permissions;
+use deno_core::error::AnyError;
+use deno_core::futures::prelude::*;
+use deno_core::plugin_api;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::JsRuntime;
+use deno_core::Op;
+use deno_core::OpAsyncFuture;
+use deno_core::OpId;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use dlopen::symbor::Library;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::path::PathBuf;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::task::Context;
+use std::task::Poll;
+
+pub fn init(rt: &mut JsRuntime) {
+ super::reg_json_sync(rt, "op_open_plugin", op_open_plugin);
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct OpenPluginArgs {
+ filename: String,
+}
+
+pub fn op_open_plugin(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: OpenPluginArgs = serde_json::from_value(args)?;
+ let filename = PathBuf::from(&args.filename);
+
+ super::check_unstable(state, "Deno.openPlugin");
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_plugin(&filename)?;
+
+ debug!("Loading Plugin: {:#?}", filename);
+ let plugin_lib = Library::open(filename).map(Rc::new)?;
+ let plugin_resource = PluginResource::new(&plugin_lib);
+
+ let rid;
+ let deno_plugin_init;
+ {
+ rid = state
+ .resource_table
+ .add("plugin", Box::new(plugin_resource));
+ deno_plugin_init = *unsafe {
+ state
+ .resource_table
+ .get::<PluginResource>(rid)
+ .unwrap()
+ .lib
+ .symbol::<plugin_api::InitFn>("deno_plugin_init")
+ .unwrap()
+ };
+ }
+
+ let mut interface = PluginInterface::new(state, &plugin_lib);
+ deno_plugin_init(&mut interface);
+
+ Ok(json!(rid))
+}
+
+struct PluginResource {
+ lib: Rc<Library>,
+}
+
+impl PluginResource {
+ fn new(lib: &Rc<Library>) -> Self {
+ Self { lib: lib.clone() }
+ }
+}
+
+struct PluginInterface<'a> {
+ state: &'a mut OpState,
+ plugin_lib: &'a Rc<Library>,
+}
+
+impl<'a> PluginInterface<'a> {
+ fn new(state: &'a mut OpState, plugin_lib: &'a Rc<Library>) -> Self {
+ Self { state, plugin_lib }
+ }
+}
+
+impl<'a> plugin_api::Interface for PluginInterface<'a> {
+ /// Does the same as `core::Isolate::register_op()`, but additionally makes
+ /// the registered op dispatcher, as well as the op futures created by it,
+ /// keep reference to the plugin `Library` object, so that the plugin doesn't
+ /// get unloaded before all its op registrations and the futures created by
+ /// them are dropped.
+ fn register_op(
+ &mut self,
+ name: &str,
+ dispatch_op_fn: plugin_api::DispatchOpFn,
+ ) -> OpId {
+ let plugin_lib = self.plugin_lib.clone();
+ let plugin_op_fn = move |state_rc: Rc<RefCell<OpState>>,
+ mut zero_copy: BufVec| {
+ let mut state = state_rc.borrow_mut();
+ let mut interface = PluginInterface::new(&mut state, &plugin_lib);
+ let op = dispatch_op_fn(&mut interface, &mut zero_copy);
+ match op {
+ sync_op @ Op::Sync(..) => sync_op,
+ Op::Async(fut) => Op::Async(PluginOpAsyncFuture::new(&plugin_lib, fut)),
+ Op::AsyncUnref(fut) => {
+ Op::AsyncUnref(PluginOpAsyncFuture::new(&plugin_lib, fut))
+ }
+ _ => unreachable!(),
+ }
+ };
+ self
+ .state
+ .op_table
+ .register_op(name, metrics_op(Box::new(plugin_op_fn)))
+ }
+}
+
+struct PluginOpAsyncFuture {
+ fut: Option<OpAsyncFuture>,
+ _plugin_lib: Rc<Library>,
+}
+
+impl PluginOpAsyncFuture {
+ fn new(plugin_lib: &Rc<Library>, fut: OpAsyncFuture) -> Pin<Box<Self>> {
+ let wrapped_fut = Self {
+ fut: Some(fut),
+ _plugin_lib: plugin_lib.clone(),
+ };
+ Box::pin(wrapped_fut)
+ }
+}
+
+impl Future for PluginOpAsyncFuture {
+ type Output = <OpAsyncFuture as Future>::Output;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
+ self.fut.as_mut().unwrap().poll_unpin(ctx)
+ }
+}
+
+impl Drop for PluginOpAsyncFuture {
+ fn drop(&mut self) {
+ self.fut.take();
+ }
+}
diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs
new file mode 100644
index 000000000..67b3d0761
--- /dev/null
+++ b/runtime/ops/process.rs
@@ -0,0 +1,290 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::io::{std_file_resource, StreamResource, StreamResourceHolder};
+use crate::permissions::Permissions;
+use deno_core::error::bad_resource_id;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::futures::future::poll_fn;
+use deno_core::futures::future::FutureExt;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::rc::Rc;
+use tokio::process::Command;
+
+#[cfg(unix)]
+use std::os::unix::process::ExitStatusExt;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_run", op_run);
+ super::reg_json_async(rt, "op_run_status", op_run_status);
+ super::reg_json_sync(rt, "op_kill", op_kill);
+}
+
+fn clone_file(
+ state: &mut OpState,
+ rid: u32,
+) -> Result<std::fs::File, AnyError> {
+ std_file_resource(state, rid, move |r| match r {
+ Ok(std_file) => std_file.try_clone().map_err(AnyError::from),
+ Err(_) => Err(bad_resource_id()),
+ })
+}
+
+fn subprocess_stdio_map(s: &str) -> Result<std::process::Stdio, AnyError> {
+ match s {
+ "inherit" => Ok(std::process::Stdio::inherit()),
+ "piped" => Ok(std::process::Stdio::piped()),
+ "null" => Ok(std::process::Stdio::null()),
+ _ => Err(type_error("Invalid resource for stdio")),
+ }
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct RunArgs {
+ cmd: Vec<String>,
+ cwd: Option<String>,
+ env: Vec<(String, String)>,
+ stdin: String,
+ stdout: String,
+ stderr: String,
+ stdin_rid: u32,
+ stdout_rid: u32,
+ stderr_rid: u32,
+}
+
+struct ChildResource {
+ child: tokio::process::Child,
+}
+
+fn op_run(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let run_args: RunArgs = serde_json::from_value(args)?;
+ state.borrow::<Permissions>().check_run()?;
+
+ let args = run_args.cmd;
+ let env = run_args.env;
+ let cwd = run_args.cwd;
+
+ let mut c = Command::new(args.get(0).unwrap());
+ (1..args.len()).for_each(|i| {
+ let arg = args.get(i).unwrap();
+ c.arg(arg);
+ });
+ cwd.map(|d| c.current_dir(d));
+ for (key, value) in &env {
+ c.env(key, value);
+ }
+
+ // TODO: make this work with other resources, eg. sockets
+ if !run_args.stdin.is_empty() {
+ c.stdin(subprocess_stdio_map(run_args.stdin.as_ref())?);
+ } else {
+ let file = clone_file(state, run_args.stdin_rid)?;
+ c.stdin(file);
+ }
+
+ if !run_args.stdout.is_empty() {
+ c.stdout(subprocess_stdio_map(run_args.stdout.as_ref())?);
+ } else {
+ let file = clone_file(state, run_args.stdout_rid)?;
+ c.stdout(file);
+ }
+
+ if !run_args.stderr.is_empty() {
+ c.stderr(subprocess_stdio_map(run_args.stderr.as_ref())?);
+ } else {
+ let file = clone_file(state, run_args.stderr_rid)?;
+ c.stderr(file);
+ }
+
+ // We want to kill child when it's closed
+ c.kill_on_drop(true);
+
+ // Spawn the command.
+ let mut child = c.spawn()?;
+ let pid = child.id();
+
+ let stdin_rid = match child.stdin.take() {
+ Some(child_stdin) => {
+ let rid = state.resource_table.add(
+ "childStdin",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStdin(
+ child_stdin,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let stdout_rid = match child.stdout.take() {
+ Some(child_stdout) => {
+ let rid = state.resource_table.add(
+ "childStdout",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStdout(
+ child_stdout,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let stderr_rid = match child.stderr.take() {
+ Some(child_stderr) => {
+ let rid = state.resource_table.add(
+ "childStderr",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStderr(
+ child_stderr,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let child_resource = ChildResource { child };
+ let child_rid = state.resource_table.add("child", Box::new(child_resource));
+
+ Ok(json!({
+ "rid": child_rid,
+ "pid": pid,
+ "stdinRid": stdin_rid,
+ "stdoutRid": stdout_rid,
+ "stderrRid": stderr_rid,
+ }))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct RunStatusArgs {
+ rid: i32,
+}
+
+async fn op_run_status(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: RunStatusArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+
+ {
+ let s = state.borrow();
+ s.borrow::<Permissions>().check_run()?;
+ }
+
+ let run_status = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let child_resource = state
+ .resource_table
+ .get_mut::<ChildResource>(rid)
+ .ok_or_else(bad_resource_id)?;
+ let child = &mut child_resource.child;
+ child.poll_unpin(cx).map_err(AnyError::from)
+ })
+ .await?;
+
+ let code = run_status.code();
+
+ #[cfg(unix)]
+ let signal = run_status.signal();
+ #[cfg(not(unix))]
+ let signal = None;
+
+ code
+ .or(signal)
+ .expect("Should have either an exit code or a signal.");
+ let got_signal = signal.is_some();
+
+ Ok(json!({
+ "gotSignal": got_signal,
+ "exitCode": code.unwrap_or(-1),
+ "exitSignal": signal.unwrap_or(-1),
+ }))
+}
+
+#[cfg(not(unix))]
+const SIGINT: i32 = 2;
+#[cfg(not(unix))]
+const SIGKILL: i32 = 9;
+#[cfg(not(unix))]
+const SIGTERM: i32 = 15;
+
+#[cfg(not(unix))]
+use winapi::{
+ shared::minwindef::DWORD,
+ um::{
+ handleapi::CloseHandle,
+ processthreadsapi::{OpenProcess, TerminateProcess},
+ winnt::PROCESS_TERMINATE,
+ },
+};
+
+#[cfg(unix)]
+pub fn kill(pid: i32, signo: i32) -> Result<(), AnyError> {
+ use nix::sys::signal::{kill as unix_kill, Signal};
+ use nix::unistd::Pid;
+ use std::convert::TryFrom;
+ let sig = Signal::try_from(signo)?;
+ unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from)
+}
+
+#[cfg(not(unix))]
+pub fn kill(pid: i32, signal: i32) -> Result<(), AnyError> {
+ use std::io::Error;
+ match signal {
+ SIGINT | SIGKILL | SIGTERM => {
+ if pid <= 0 {
+ return Err(type_error("unsupported pid"));
+ }
+ unsafe {
+ let handle = OpenProcess(PROCESS_TERMINATE, 0, pid as DWORD);
+ if handle.is_null() {
+ return Err(Error::last_os_error().into());
+ }
+ if TerminateProcess(handle, 1) == 0 {
+ CloseHandle(handle);
+ return Err(Error::last_os_error().into());
+ }
+ if CloseHandle(handle) == 0 {
+ return Err(Error::last_os_error().into());
+ }
+ }
+ }
+ _ => {
+ return Err(type_error("unsupported signal"));
+ }
+ }
+ Ok(())
+}
+
+#[derive(Deserialize)]
+struct KillArgs {
+ pid: i32,
+ signo: i32,
+}
+
+fn op_kill(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.kill");
+ state.borrow::<Permissions>().check_run()?;
+
+ let args: KillArgs = serde_json::from_value(args)?;
+ kill(args.pid, args.signo)?;
+ Ok(json!({}))
+}
diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs
new file mode 100644
index 000000000..cb3b53d53
--- /dev/null
+++ b/runtime/ops/runtime.rs
@@ -0,0 +1,118 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::metrics::Metrics;
+use crate::permissions::Permissions;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::ModuleSpecifier;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+
+pub fn init(rt: &mut deno_core::JsRuntime, main_module: ModuleSpecifier) {
+ {
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ state.put::<ModuleSpecifier>(main_module);
+ }
+ super::reg_json_sync(rt, "op_main_module", op_main_module);
+ super::reg_json_sync(rt, "op_metrics", op_metrics);
+}
+
+fn op_main_module(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let main = state.borrow::<ModuleSpecifier>().to_string();
+ let main_url = ModuleSpecifier::resolve_url_or_path(&main)?;
+ if main_url.as_url().scheme() == "file" {
+ let main_path = std::env::current_dir().unwrap().join(main_url.to_string());
+ state
+ .borrow::<Permissions>()
+ .check_read_blind(&main_path, "main_module")?;
+ }
+ Ok(json!(&main))
+}
+
+fn op_metrics(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let m = state.borrow::<Metrics>();
+
+ Ok(json!({
+ "opsDispatched": m.ops_dispatched,
+ "opsDispatchedSync": m.ops_dispatched_sync,
+ "opsDispatchedAsync": m.ops_dispatched_async,
+ "opsDispatchedAsyncUnref": m.ops_dispatched_async_unref,
+ "opsCompleted": m.ops_completed,
+ "opsCompletedSync": m.ops_completed_sync,
+ "opsCompletedAsync": m.ops_completed_async,
+ "opsCompletedAsyncUnref": m.ops_completed_async_unref,
+ "bytesSentControl": m.bytes_sent_control,
+ "bytesSentData": m.bytes_sent_data,
+ "bytesReceived": m.bytes_received
+ }))
+}
+
+pub fn ppid() -> Value {
+ #[cfg(windows)]
+ {
+ // Adopted from rustup:
+ // https://github.com/rust-lang/rustup/blob/1.21.1/src/cli/self_update.rs#L1036
+ // Copyright Diggory Blake, the Mozilla Corporation, and rustup contributors.
+ // Licensed under either of
+ // - Apache License, Version 2.0
+ // - MIT license
+ use std::mem;
+ use winapi::shared::minwindef::DWORD;
+ use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
+ use winapi::um::processthreadsapi::GetCurrentProcessId;
+ use winapi::um::tlhelp32::{
+ CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32,
+ TH32CS_SNAPPROCESS,
+ };
+ unsafe {
+ // Take a snapshot of system processes, one of which is ours
+ // and contains our parent's pid
+ let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if snapshot == INVALID_HANDLE_VALUE {
+ return serde_json::to_value(-1).unwrap();
+ }
+
+ let mut entry: PROCESSENTRY32 = mem::zeroed();
+ entry.dwSize = mem::size_of::<PROCESSENTRY32>() as DWORD;
+
+ // Iterate over system processes looking for ours
+ let success = Process32First(snapshot, &mut entry);
+ if success == 0 {
+ CloseHandle(snapshot);
+ return serde_json::to_value(-1).unwrap();
+ }
+
+ let this_pid = GetCurrentProcessId();
+ while entry.th32ProcessID != this_pid {
+ let success = Process32Next(snapshot, &mut entry);
+ if success == 0 {
+ CloseHandle(snapshot);
+ return serde_json::to_value(-1).unwrap();
+ }
+ }
+ CloseHandle(snapshot);
+
+ // FIXME: Using the process ID exposes a race condition
+ // wherein the parent process already exited and the OS
+ // reassigned its ID.
+ let parent_id = entry.th32ParentProcessID;
+ serde_json::to_value(parent_id).unwrap()
+ }
+ }
+ #[cfg(not(windows))]
+ {
+ use std::os::unix::process::parent_id;
+ serde_json::to_value(parent_id()).unwrap()
+ }
+}
diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs
new file mode 100644
index 000000000..be6bc0a35
--- /dev/null
+++ b/runtime/ops/signal.rs
@@ -0,0 +1,142 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[cfg(unix)]
+use deno_core::error::bad_resource_id;
+#[cfg(unix)]
+use deno_core::futures::future::poll_fn;
+#[cfg(unix)]
+use deno_core::serde_json;
+#[cfg(unix)]
+use deno_core::serde_json::json;
+#[cfg(unix)]
+use serde::Deserialize;
+#[cfg(unix)]
+use std::task::Waker;
+#[cfg(unix)]
+use tokio::signal::unix::{signal, Signal, SignalKind};
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_signal_bind", op_signal_bind);
+ super::reg_json_sync(rt, "op_signal_unbind", op_signal_unbind);
+ super::reg_json_async(rt, "op_signal_poll", op_signal_poll);
+}
+
+#[cfg(unix)]
+/// The resource for signal stream.
+/// The second element is the waker of polling future.
+pub struct SignalStreamResource(pub Signal, pub Option<Waker>);
+
+#[cfg(unix)]
+#[derive(Deserialize)]
+struct BindSignalArgs {
+ signo: i32,
+}
+
+#[cfg(unix)]
+#[derive(Deserialize)]
+struct SignalArgs {
+ rid: i32,
+}
+
+#[cfg(unix)]
+fn op_signal_bind(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.signal");
+ let args: BindSignalArgs = serde_json::from_value(args)?;
+ let rid = state.resource_table.add(
+ "signal",
+ Box::new(SignalStreamResource(
+ signal(SignalKind::from_raw(args.signo)).expect(""),
+ None,
+ )),
+ );
+ Ok(json!({
+ "rid": rid,
+ }))
+}
+
+#[cfg(unix)]
+async fn op_signal_poll(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ super::check_unstable2(&state, "Deno.signal");
+ let args: SignalArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+
+ let future = poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ if let Some(mut signal) =
+ state.resource_table.get_mut::<SignalStreamResource>(rid)
+ {
+ signal.1 = Some(cx.waker().clone());
+ return signal.0.poll_recv(cx);
+ }
+ std::task::Poll::Ready(None)
+ });
+ let result = future.await;
+ Ok(json!({ "done": result.is_none() }))
+}
+
+#[cfg(unix)]
+pub fn op_signal_unbind(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.signal");
+ let args: SignalArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let resource = state.resource_table.get_mut::<SignalStreamResource>(rid);
+ if let Some(signal) = resource {
+ if let Some(waker) = &signal.1 {
+ // Wakes up the pending poll if exists.
+ // This prevents the poll future from getting stuck forever.
+ waker.clone().wake();
+ }
+ }
+ state
+ .resource_table
+ .close(rid)
+ .ok_or_else(bad_resource_id)?;
+ Ok(json!({}))
+}
+
+#[cfg(not(unix))]
+pub fn op_signal_bind(
+ _state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ unimplemented!();
+}
+
+#[cfg(not(unix))]
+fn op_signal_unbind(
+ _state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ unimplemented!();
+}
+
+#[cfg(not(unix))]
+async fn op_signal_poll(
+ _state: Rc<RefCell<OpState>>,
+ _args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ unimplemented!();
+}
diff --git a/runtime/ops/timers.rs b/runtime/ops/timers.rs
new file mode 100644
index 000000000..8037fd698
--- /dev/null
+++ b/runtime/ops/timers.rs
@@ -0,0 +1,193 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+//! This module helps deno implement timers.
+//!
+//! As an optimization, we want to avoid an expensive calls into rust for every
+//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is
+//! implemented that calls into Rust for only the smallest timeout. Thus we
+//! only need to be able to start, cancel and await a single timer (or Delay, as Tokio
+//! calls it) for an entire Isolate. This is what is implemented here.
+
+use super::dispatch_minimal::minimal_op;
+use super::dispatch_minimal::MinimalOp;
+use crate::metrics::metrics_op;
+use crate::permissions::Permissions;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::futures;
+use deno_core::futures::channel::oneshot;
+use deno_core::futures::FutureExt;
+use deno_core::futures::TryFutureExt;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::future::Future;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::thread::sleep;
+use std::time::Duration;
+use std::time::Instant;
+
+pub type StartTime = Instant;
+
+type TimerFuture = Pin<Box<dyn Future<Output = Result<(), ()>>>>;
+
+#[derive(Default)]
+pub struct GlobalTimer {
+ tx: Option<oneshot::Sender<()>>,
+ pub future: Option<TimerFuture>,
+}
+
+impl GlobalTimer {
+ pub fn cancel(&mut self) {
+ if let Some(tx) = self.tx.take() {
+ tx.send(()).ok();
+ }
+ }
+
+ pub fn new_timeout(&mut self, deadline: Instant) {
+ if self.tx.is_some() {
+ self.cancel();
+ }
+ assert!(self.tx.is_none());
+ self.future.take();
+
+ let (tx, rx) = oneshot::channel();
+ self.tx = Some(tx);
+
+ let delay = tokio::time::delay_until(deadline.into());
+ let rx = rx
+ .map_err(|err| panic!("Unexpected error in receiving channel {:?}", err));
+
+ let fut = futures::future::select(delay, rx)
+ .then(|_| futures::future::ok(()))
+ .boxed_local();
+ self.future = Some(fut);
+ }
+}
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ {
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ state.put::<GlobalTimer>(GlobalTimer::default());
+ state.put::<StartTime>(StartTime::now());
+ }
+ super::reg_json_sync(rt, "op_global_timer_stop", op_global_timer_stop);
+ super::reg_json_sync(rt, "op_global_timer_start", op_global_timer_start);
+ super::reg_json_async(rt, "op_global_timer", op_global_timer);
+ rt.register_op("op_now", metrics_op(minimal_op(op_now)));
+ super::reg_json_sync(rt, "op_sleep_sync", op_sleep_sync);
+}
+
+fn op_global_timer_stop(
+ state: &mut OpState,
+ _args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let global_timer = state.borrow_mut::<GlobalTimer>();
+ global_timer.cancel();
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+struct GlobalTimerArgs {
+ timeout: u64,
+}
+
+// Set up a timer that will be later awaited by JS promise.
+// It's a separate op, because canceling a timeout immediately
+// after setting it caused a race condition (because Tokio timeout)
+// might have been registered after next event loop tick.
+//
+// See https://github.com/denoland/deno/issues/7599 for more
+// details.
+fn op_global_timer_start(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: GlobalTimerArgs = serde_json::from_value(args)?;
+ let val = args.timeout;
+
+ let deadline = Instant::now() + Duration::from_millis(val);
+ let global_timer = state.borrow_mut::<GlobalTimer>();
+ global_timer.new_timeout(deadline);
+ Ok(json!({}))
+}
+
+async fn op_global_timer(
+ state: Rc<RefCell<OpState>>,
+ _args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let maybe_timer_fut = {
+ let mut s = state.borrow_mut();
+ let global_timer = s.borrow_mut::<GlobalTimer>();
+ global_timer.future.take()
+ };
+ if let Some(timer_fut) = maybe_timer_fut {
+ let _ = timer_fut.await;
+ }
+ Ok(json!({}))
+}
+
+// Returns a milliseconds and nanoseconds subsec
+// since the start time of the deno runtime.
+// If the High precision flag is not set, the
+// nanoseconds are rounded on 2ms.
+fn op_now(
+ state: Rc<RefCell<OpState>>,
+ // Arguments are discarded
+ _sync: bool,
+ _x: i32,
+ mut zero_copy: BufVec,
+) -> MinimalOp {
+ match zero_copy.len() {
+ 0 => return MinimalOp::Sync(Err(type_error("no buffer specified"))),
+ 1 => {}
+ _ => {
+ return MinimalOp::Sync(Err(type_error("Invalid number of arguments")))
+ }
+ }
+
+ let op_state = state.borrow();
+ let start_time = op_state.borrow::<StartTime>();
+ let seconds = start_time.elapsed().as_secs();
+ let mut subsec_nanos = start_time.elapsed().subsec_nanos() as f64;
+ let reduced_time_precision = 2_000_000.0; // 2ms in nanoseconds
+
+ // If the permission is not enabled
+ // Round the nano result on 2 milliseconds
+ // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision
+ if op_state.borrow::<Permissions>().check_hrtime().is_err() {
+ subsec_nanos -= subsec_nanos % reduced_time_precision;
+ }
+
+ let result = (seconds * 1_000) as f64 + (subsec_nanos / 1_000_000.0);
+
+ (&mut zero_copy[0]).copy_from_slice(&result.to_be_bytes());
+
+ MinimalOp::Sync(Ok(0))
+}
+
+#[derive(Deserialize)]
+struct SleepArgs {
+ millis: u64,
+}
+
+fn op_sleep_sync(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.sleepSync");
+ let args: SleepArgs = serde_json::from_value(args)?;
+ sleep(Duration::from_millis(args.millis));
+ Ok(json!({}))
+}
diff --git a/runtime/ops/tls.rs b/runtime/ops/tls.rs
new file mode 100644
index 000000000..37fd8f206
--- /dev/null
+++ b/runtime/ops/tls.rs
@@ -0,0 +1,431 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::io::{StreamResource, StreamResourceHolder};
+use crate::permissions::Permissions;
+use crate::resolve_addr::resolve_addr;
+use deno_core::error::bad_resource;
+use deno_core::error::bad_resource_id;
+use deno_core::error::custom_error;
+use deno_core::error::AnyError;
+use deno_core::futures;
+use deno_core::futures::future::poll_fn;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::convert::From;
+use std::fs::File;
+use std::io::BufReader;
+use std::net::SocketAddr;
+use std::path::Path;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::task::Context;
+use std::task::Poll;
+use tokio::net::TcpListener;
+use tokio::net::TcpStream;
+use tokio_rustls::{rustls::ClientConfig, TlsConnector};
+use tokio_rustls::{
+ rustls::{
+ internal::pemfile::{certs, pkcs8_private_keys, rsa_private_keys},
+ Certificate, NoClientAuth, PrivateKey, ServerConfig,
+ },
+ TlsAcceptor,
+};
+use webpki::DNSNameRef;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_async(rt, "op_start_tls", op_start_tls);
+ super::reg_json_async(rt, "op_connect_tls", op_connect_tls);
+ super::reg_json_sync(rt, "op_listen_tls", op_listen_tls);
+ super::reg_json_async(rt, "op_accept_tls", op_accept_tls);
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ConnectTLSArgs {
+ transport: String,
+ hostname: String,
+ port: u16,
+ cert_file: Option<String>,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct StartTLSArgs {
+ rid: u32,
+ cert_file: Option<String>,
+ hostname: String,
+}
+
+async fn op_start_tls(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: StartTLSArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let cert_file = args.cert_file.clone();
+
+ let mut domain = args.hostname;
+ if domain.is_empty() {
+ domain.push_str("localhost");
+ }
+ {
+ super::check_unstable2(&state, "Deno.startTls");
+ let s = state.borrow();
+ let permissions = s.borrow::<Permissions>();
+ permissions.check_net(&domain, 0)?;
+ if let Some(path) = cert_file.clone() {
+ permissions.check_read(Path::new(&path))?;
+ }
+ }
+ let mut resource_holder = {
+ let mut state_ = state.borrow_mut();
+ match state_.resource_table.remove::<StreamResourceHolder>(rid) {
+ Some(resource) => *resource,
+ None => return Err(bad_resource_id()),
+ }
+ };
+
+ if let StreamResource::TcpStream(ref mut tcp_stream) =
+ resource_holder.resource
+ {
+ let tcp_stream = tcp_stream.take().unwrap();
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+ let mut config = ClientConfig::new();
+ config
+ .root_store
+ .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+ if let Some(path) = cert_file {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ config.root_store.add_pem_file(reader).unwrap();
+ }
+
+ let tls_connector = TlsConnector::from(Arc::new(config));
+ let dnsname =
+ DNSNameRef::try_from_ascii_str(&domain).expect("Invalid DNS lookup");
+ let tls_stream = tls_connector.connect(dnsname, tcp_stream).await?;
+
+ let rid = {
+ let mut state_ = state.borrow_mut();
+ state_.resource_table.add(
+ "clientTlsStream",
+ Box::new(StreamResourceHolder::new(StreamResource::ClientTlsStream(
+ Box::new(tls_stream),
+ ))),
+ )
+ };
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": "tcp",
+ },
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": "tcp",
+ }
+ }))
+ } else {
+ Err(bad_resource_id())
+ }
+}
+
+async fn op_connect_tls(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: ConnectTLSArgs = serde_json::from_value(args)?;
+ let cert_file = args.cert_file.clone();
+ {
+ let s = state.borrow();
+ let permissions = s.borrow::<Permissions>();
+ permissions.check_net(&args.hostname, args.port)?;
+ if let Some(path) = cert_file.clone() {
+ permissions.check_read(Path::new(&path))?;
+ }
+ }
+ let mut domain = args.hostname.clone();
+ if domain.is_empty() {
+ domain.push_str("localhost");
+ }
+
+ let addr = resolve_addr(&args.hostname, args.port)?;
+ let tcp_stream = TcpStream::connect(&addr).await?;
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+ let mut config = ClientConfig::new();
+ config
+ .root_store
+ .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+ if let Some(path) = cert_file {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ config.root_store.add_pem_file(reader).unwrap();
+ }
+ let tls_connector = TlsConnector::from(Arc::new(config));
+ let dnsname =
+ DNSNameRef::try_from_ascii_str(&domain).expect("Invalid DNS lookup");
+ let tls_stream = tls_connector.connect(dnsname, tcp_stream).await?;
+ let rid = {
+ let mut state_ = state.borrow_mut();
+ state_.resource_table.add(
+ "clientTlsStream",
+ Box::new(StreamResourceHolder::new(StreamResource::ClientTlsStream(
+ Box::new(tls_stream),
+ ))),
+ )
+ };
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": args.transport,
+ },
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": args.transport,
+ }
+ }))
+}
+
+fn load_certs(path: &str) -> Result<Vec<Certificate>, AnyError> {
+ let cert_file = File::open(path)?;
+ let reader = &mut BufReader::new(cert_file);
+
+ let certs = certs(reader)
+ .map_err(|_| custom_error("InvalidData", "Unable to decode certificate"))?;
+
+ if certs.is_empty() {
+ let e = custom_error("InvalidData", "No certificates found in cert file");
+ return Err(e);
+ }
+
+ Ok(certs)
+}
+
+fn key_decode_err() -> AnyError {
+ custom_error("InvalidData", "Unable to decode key")
+}
+
+fn key_not_found_err() -> AnyError {
+ custom_error("InvalidData", "No keys found in key file")
+}
+
+/// Starts with -----BEGIN RSA PRIVATE KEY-----
+fn load_rsa_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ let keys = rsa_private_keys(reader).map_err(|_| key_decode_err())?;
+ Ok(keys)
+}
+
+/// Starts with -----BEGIN PRIVATE KEY-----
+fn load_pkcs8_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ let keys = pkcs8_private_keys(reader).map_err(|_| key_decode_err())?;
+ Ok(keys)
+}
+
+fn load_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
+ let path = path.to_string();
+ let mut keys = load_rsa_keys(&path)?;
+
+ if keys.is_empty() {
+ keys = load_pkcs8_keys(&path)?;
+ }
+
+ if keys.is_empty() {
+ return Err(key_not_found_err());
+ }
+
+ Ok(keys)
+}
+
+#[allow(dead_code)]
+pub struct TlsListenerResource {
+ listener: TcpListener,
+ tls_acceptor: TlsAcceptor,
+ waker: Option<futures::task::AtomicWaker>,
+ local_addr: SocketAddr,
+}
+
+impl Drop for TlsListenerResource {
+ fn drop(&mut self) {
+ self.wake_task();
+ }
+}
+
+impl TlsListenerResource {
+ /// Track the current task so future awaiting for connection
+ /// can be notified when listener is closed.
+ ///
+ /// Throws an error if another task is already tracked.
+ pub fn track_task(&mut self, cx: &Context) -> Result<(), AnyError> {
+ // Currently, we only allow tracking a single accept task for a listener.
+ // This might be changed in the future with multiple workers.
+ // Caveat: TcpListener by itself also only tracks an accept task at a time.
+ // See https://github.com/tokio-rs/tokio/issues/846#issuecomment-454208883
+ if self.waker.is_some() {
+ return Err(custom_error("Busy", "Another accept task is ongoing"));
+ }
+
+ let waker = futures::task::AtomicWaker::new();
+ waker.register(cx.waker());
+ self.waker.replace(waker);
+ Ok(())
+ }
+
+ /// Notifies a task when listener is closed so accept future can resolve.
+ pub fn wake_task(&mut self) {
+ if let Some(waker) = self.waker.as_ref() {
+ waker.wake();
+ }
+ }
+
+ /// Stop tracking a task.
+ /// Happens when the task is done and thus no further tracking is needed.
+ pub fn untrack_task(&mut self) {
+ self.waker.take();
+ }
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ListenTlsArgs {
+ transport: String,
+ hostname: String,
+ port: u16,
+ cert_file: String,
+ key_file: String,
+}
+
+fn op_listen_tls(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: ListenTlsArgs = serde_json::from_value(args)?;
+ assert_eq!(args.transport, "tcp");
+
+ let cert_file = args.cert_file;
+ let key_file = args.key_file;
+ {
+ let permissions = state.borrow::<Permissions>();
+ permissions.check_net(&args.hostname, args.port)?;
+ permissions.check_read(Path::new(&cert_file))?;
+ permissions.check_read(Path::new(&key_file))?;
+ }
+ let mut config = ServerConfig::new(NoClientAuth::new());
+ config
+ .set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0))
+ .expect("invalid key or certificate");
+ let tls_acceptor = TlsAcceptor::from(Arc::new(config));
+ let addr = resolve_addr(&args.hostname, args.port)?;
+ let std_listener = std::net::TcpListener::bind(&addr)?;
+ let listener = TcpListener::from_std(std_listener)?;
+ let local_addr = listener.local_addr()?;
+ let tls_listener_resource = TlsListenerResource {
+ listener,
+ tls_acceptor,
+ waker: None,
+ local_addr,
+ };
+
+ let rid = state
+ .resource_table
+ .add("tlsListener", Box::new(tls_listener_resource));
+
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": args.transport,
+ },
+ }))
+}
+
+#[derive(Deserialize)]
+struct AcceptTlsArgs {
+ rid: i32,
+}
+
+async fn op_accept_tls(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: AcceptTlsArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let accept_fut = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let listener_resource = state
+ .resource_table
+ .get_mut::<TlsListenerResource>(rid)
+ .ok_or_else(|| bad_resource("Listener has been closed"))?;
+ let listener = &mut listener_resource.listener;
+ match listener.poll_accept(cx).map_err(AnyError::from) {
+ Poll::Ready(Ok((stream, addr))) => {
+ listener_resource.untrack_task();
+ Poll::Ready(Ok((stream, addr)))
+ }
+ Poll::Pending => {
+ listener_resource.track_task(cx)?;
+ Poll::Pending
+ }
+ Poll::Ready(Err(e)) => {
+ listener_resource.untrack_task();
+ Poll::Ready(Err(e))
+ }
+ }
+ });
+ let (tcp_stream, _socket_addr) = accept_fut.await?;
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+ let tls_acceptor = {
+ let state_ = state.borrow();
+ let resource = state_
+ .resource_table
+ .get::<TlsListenerResource>(rid)
+ .ok_or_else(bad_resource_id)
+ .expect("Can't find tls listener");
+ resource.tls_acceptor.clone()
+ };
+ let tls_stream = tls_acceptor.accept(tcp_stream).await?;
+ let rid = {
+ let mut state_ = state.borrow_mut();
+ state_.resource_table.add(
+ "serverTlsStream",
+ Box::new(StreamResourceHolder::new(StreamResource::ServerTlsStream(
+ Box::new(tls_stream),
+ ))),
+ )
+ };
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "transport": "tcp",
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port()
+ },
+ "remoteAddr": {
+ "transport": "tcp",
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port()
+ }
+ }))
+}
diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs
new file mode 100644
index 000000000..ad66bcf1a
--- /dev/null
+++ b/runtime/ops/tty.rs
@@ -0,0 +1,334 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::io::std_file_resource;
+use super::io::StreamResource;
+use super::io::StreamResourceHolder;
+use deno_core::error::bad_resource_id;
+use deno_core::error::not_supported;
+use deno_core::error::resource_unavailable;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use serde::Serialize;
+use std::io::Error;
+
+#[cfg(unix)]
+use nix::sys::termios;
+
+#[cfg(windows)]
+use deno_core::error::custom_error;
+#[cfg(windows)]
+use winapi::shared::minwindef::DWORD;
+#[cfg(windows)]
+use winapi::um::wincon;
+#[cfg(windows)]
+const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT
+ | wincon::ENABLE_ECHO_INPUT
+ | wincon::ENABLE_PROCESSED_INPUT;
+
+#[cfg(windows)]
+fn get_windows_handle(
+ f: &std::fs::File,
+) -> Result<std::os::windows::io::RawHandle, AnyError> {
+ use std::os::windows::io::AsRawHandle;
+ use winapi::um::handleapi;
+
+ let handle = f.as_raw_handle();
+ if handle == handleapi::INVALID_HANDLE_VALUE {
+ return Err(Error::last_os_error().into());
+ } else if handle.is_null() {
+ return Err(custom_error("ReferenceError", "null handle"));
+ }
+ Ok(handle)
+}
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_json_sync(rt, "op_set_raw", op_set_raw);
+ super::reg_json_sync(rt, "op_isatty", op_isatty);
+ super::reg_json_sync(rt, "op_console_size", op_console_size);
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SetRawOptions {
+ cbreak: bool,
+}
+
+#[derive(Deserialize)]
+struct SetRawArgs {
+ rid: u32,
+ mode: bool,
+ options: SetRawOptions,
+}
+
+fn op_set_raw(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.setRaw");
+
+ let args: SetRawArgs = serde_json::from_value(args)?;
+ let rid = args.rid;
+ let is_raw = args.mode;
+ let cbreak = args.options.cbreak;
+
+ // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
+ // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs
+ // and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs
+ // Copyright (c) 2015 Katsu Kawakami & Rustyline authors. MIT license.
+ // Copyright (c) 2019 Timon. MIT license.
+ #[cfg(windows)]
+ {
+ use std::os::windows::io::AsRawHandle;
+ use winapi::shared::minwindef::FALSE;
+ use winapi::um::{consoleapi, handleapi};
+
+ let resource_holder =
+ state.resource_table.get_mut::<StreamResourceHolder>(rid);
+ if resource_holder.is_none() {
+ return Err(bad_resource_id());
+ }
+ if cbreak {
+ return Err(not_supported());
+ }
+ let resource_holder = resource_holder.unwrap();
+
+ // For now, only stdin.
+ let handle = match &mut resource_holder.resource {
+ StreamResource::FsFile(ref mut option_file_metadata) => {
+ if let Some((tokio_file, metadata)) = option_file_metadata.take() {
+ match tokio_file.try_into_std() {
+ Ok(std_file) => {
+ let raw_handle = std_file.as_raw_handle();
+ // Turn the std_file handle back into a tokio file, put it back
+ // in the resource table.
+ let tokio_file = tokio::fs::File::from_std(std_file);
+ resource_holder.resource =
+ StreamResource::FsFile(Some((tokio_file, metadata)));
+ // return the result.
+ raw_handle
+ }
+ Err(tokio_file) => {
+ // This function will return an error containing the file if
+ // some operation is in-flight.
+ resource_holder.resource =
+ StreamResource::FsFile(Some((tokio_file, metadata)));
+ return Err(resource_unavailable());
+ }
+ }
+ } else {
+ return Err(resource_unavailable());
+ }
+ }
+ _ => {
+ return Err(bad_resource_id());
+ }
+ };
+
+ if handle == handleapi::INVALID_HANDLE_VALUE {
+ return Err(Error::last_os_error().into());
+ } else if handle.is_null() {
+ return Err(custom_error("ReferenceError", "null handle"));
+ }
+ let mut original_mode: DWORD = 0;
+ if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) }
+ == FALSE
+ {
+ return Err(Error::last_os_error().into());
+ }
+ let new_mode = if is_raw {
+ original_mode & !RAW_MODE_MASK
+ } else {
+ original_mode | RAW_MODE_MASK
+ };
+ if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE {
+ return Err(Error::last_os_error().into());
+ }
+
+ Ok(json!({}))
+ }
+ #[cfg(unix)]
+ {
+ use std::os::unix::io::AsRawFd;
+
+ let resource_holder =
+ state.resource_table.get_mut::<StreamResourceHolder>(rid);
+ if resource_holder.is_none() {
+ return Err(bad_resource_id());
+ }
+
+ if is_raw {
+ let (raw_fd, maybe_tty_mode) =
+ match &mut resource_holder.unwrap().resource {
+ StreamResource::FsFile(Some((f, ref mut metadata))) => {
+ (f.as_raw_fd(), &mut metadata.tty.mode)
+ }
+ StreamResource::FsFile(None) => return Err(resource_unavailable()),
+ _ => {
+ return Err(not_supported());
+ }
+ };
+
+ if maybe_tty_mode.is_none() {
+ // Save original mode.
+ let original_mode = termios::tcgetattr(raw_fd)?;
+ maybe_tty_mode.replace(original_mode);
+ }
+
+ let mut raw = maybe_tty_mode.clone().unwrap();
+
+ raw.input_flags &= !(termios::InputFlags::BRKINT
+ | termios::InputFlags::ICRNL
+ | termios::InputFlags::INPCK
+ | termios::InputFlags::ISTRIP
+ | termios::InputFlags::IXON);
+
+ raw.control_flags |= termios::ControlFlags::CS8;
+
+ raw.local_flags &= !(termios::LocalFlags::ECHO
+ | termios::LocalFlags::ICANON
+ | termios::LocalFlags::IEXTEN);
+ if !cbreak {
+ raw.local_flags &= !(termios::LocalFlags::ISIG);
+ }
+ raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1;
+ raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0;
+ termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?;
+ Ok(json!({}))
+ } else {
+ // Try restore saved mode.
+ let (raw_fd, maybe_tty_mode) =
+ match &mut resource_holder.unwrap().resource {
+ StreamResource::FsFile(Some((f, ref mut metadata))) => {
+ (f.as_raw_fd(), &mut metadata.tty.mode)
+ }
+ StreamResource::FsFile(None) => {
+ return Err(resource_unavailable());
+ }
+ _ => {
+ return Err(bad_resource_id());
+ }
+ };
+
+ if let Some(mode) = maybe_tty_mode.take() {
+ termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?;
+ }
+
+ Ok(json!({}))
+ }
+ }
+}
+
+#[derive(Deserialize)]
+struct IsattyArgs {
+ rid: u32,
+}
+
+fn op_isatty(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: IsattyArgs = serde_json::from_value(args)?;
+ let rid = args.rid;
+
+ let isatty: bool = std_file_resource(state, rid as u32, move |r| match r {
+ Ok(std_file) => {
+ #[cfg(windows)]
+ {
+ use winapi::um::consoleapi;
+
+ let handle = get_windows_handle(&std_file)?;
+ let mut test_mode: DWORD = 0;
+ // If I cannot get mode out of console, it is not a console.
+ Ok(unsafe { consoleapi::GetConsoleMode(handle, &mut test_mode) != 0 })
+ }
+ #[cfg(unix)]
+ {
+ use std::os::unix::io::AsRawFd;
+ let raw_fd = std_file.as_raw_fd();
+ Ok(unsafe { libc::isatty(raw_fd as libc::c_int) == 1 })
+ }
+ }
+ Err(StreamResource::FsFile(_)) => unreachable!(),
+ _ => Ok(false),
+ })?;
+ Ok(json!(isatty))
+}
+
+#[derive(Deserialize)]
+struct ConsoleSizeArgs {
+ rid: u32,
+}
+
+#[derive(Serialize)]
+struct ConsoleSize {
+ columns: u32,
+ rows: u32,
+}
+
+fn op_console_size(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ super::check_unstable(state, "Deno.consoleSize");
+
+ let args: ConsoleSizeArgs = serde_json::from_value(args)?;
+ let rid = args.rid;
+
+ let size = std_file_resource(state, rid as u32, move |r| match r {
+ Ok(std_file) => {
+ #[cfg(windows)]
+ {
+ use std::os::windows::io::AsRawHandle;
+ let handle = std_file.as_raw_handle();
+
+ unsafe {
+ let mut bufinfo: winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO =
+ std::mem::zeroed();
+
+ if winapi::um::wincon::GetConsoleScreenBufferInfo(
+ handle,
+ &mut bufinfo,
+ ) == 0
+ {
+ return Err(Error::last_os_error().into());
+ }
+
+ Ok(ConsoleSize {
+ columns: bufinfo.dwSize.X as u32,
+ rows: bufinfo.dwSize.Y as u32,
+ })
+ }
+ }
+
+ #[cfg(unix)]
+ {
+ use std::os::unix::io::AsRawFd;
+
+ let fd = std_file.as_raw_fd();
+ unsafe {
+ let mut size: libc::winsize = std::mem::zeroed();
+ if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) != 0 {
+ return Err(Error::last_os_error().into());
+ }
+
+ // TODO (caspervonb) return a tuple instead
+ Ok(ConsoleSize {
+ columns: size.ws_col as u32,
+ rows: size.ws_row as u32,
+ })
+ }
+ }
+ }
+ Err(_) => Err(bad_resource_id()),
+ })?;
+
+ Ok(json!(size))
+}
diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs
new file mode 100644
index 000000000..d88330a04
--- /dev/null
+++ b/runtime/ops/web_worker.rs
@@ -0,0 +1,37 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::web_worker::WebWorkerHandle;
+use crate::web_worker::WorkerEvent;
+use deno_core::futures::channel::mpsc;
+use deno_core::serde_json::json;
+
+pub fn init(
+ rt: &mut deno_core::JsRuntime,
+ sender: mpsc::Sender<WorkerEvent>,
+ handle: WebWorkerHandle,
+) {
+ // Post message to host as guest worker.
+ let sender_ = sender.clone();
+ super::reg_json_sync(
+ rt,
+ "op_worker_post_message",
+ move |_state, _args, bufs| {
+ assert_eq!(bufs.len(), 1, "Invalid number of arguments");
+ let msg_buf: Box<[u8]> = (*bufs[0]).into();
+ sender_
+ .clone()
+ .try_send(WorkerEvent::Message(msg_buf))
+ .expect("Failed to post message to host");
+ Ok(json!({}))
+ },
+ );
+
+ // Notify host that guest worker closes.
+ super::reg_json_sync(rt, "op_worker_close", move |_state, _args, _bufs| {
+ // Notify parent that we're finished
+ sender.clone().close_channel();
+ // Terminate execution of current worker
+ handle.terminate();
+ Ok(json!({}))
+ });
+}
diff --git a/runtime/ops/websocket.rs b/runtime/ops/websocket.rs
new file mode 100644
index 000000000..a8c591a33
--- /dev/null
+++ b/runtime/ops/websocket.rs
@@ -0,0 +1,326 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::permissions::Permissions;
+use core::task::Poll;
+use deno_core::error::bad_resource_id;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::futures::future::poll_fn;
+use deno_core::futures::StreamExt;
+use deno_core::futures::{ready, SinkExt};
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::url;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::{serde_json, ZeroCopyBuf};
+use http::{Method, Request, Uri};
+use serde::Deserialize;
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::fs::File;
+use std::io::BufReader;
+use std::rc::Rc;
+use std::sync::Arc;
+use tokio::net::TcpStream;
+use tokio_rustls::{rustls::ClientConfig, TlsConnector};
+use tokio_tungstenite::stream::Stream as StreamSwitcher;
+use tokio_tungstenite::tungstenite::Error as TungsteniteError;
+use tokio_tungstenite::tungstenite::{
+ handshake::client::Response, protocol::frame::coding::CloseCode,
+ protocol::CloseFrame, Message,
+};
+use tokio_tungstenite::{client_async, WebSocketStream};
+use webpki::DNSNameRef;
+
+#[derive(Clone)]
+struct WsCaFile(String);
+#[derive(Clone)]
+struct WsUserAgent(String);
+
+pub fn init(
+ rt: &mut deno_core::JsRuntime,
+ maybe_ca_file: Option<&str>,
+ user_agent: String,
+) {
+ {
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ if let Some(ca_file) = maybe_ca_file {
+ state.put::<WsCaFile>(WsCaFile(ca_file.to_string()));
+ }
+ state.put::<WsUserAgent>(WsUserAgent(user_agent));
+ }
+ super::reg_json_sync(rt, "op_ws_check_permission", op_ws_check_permission);
+ super::reg_json_async(rt, "op_ws_create", op_ws_create);
+ super::reg_json_async(rt, "op_ws_send", op_ws_send);
+ super::reg_json_async(rt, "op_ws_close", op_ws_close);
+ super::reg_json_async(rt, "op_ws_next_event", op_ws_next_event);
+}
+
+type MaybeTlsStream =
+ StreamSwitcher<TcpStream, tokio_rustls::client::TlsStream<TcpStream>>;
+
+type WsStream = WebSocketStream<MaybeTlsStream>;
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CheckPermissionArgs {
+ url: String,
+}
+
+// This op is needed because creating a WS instance in JavaScript is a sync
+// operation and should throw error when permissions are not fullfiled,
+// but actual op that connects WS is async.
+pub fn op_ws_check_permission(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: CheckPermissionArgs = serde_json::from_value(args)?;
+
+ state
+ .borrow::<Permissions>()
+ .check_net_url(&url::Url::parse(&args.url)?)?;
+
+ Ok(json!({}))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CreateArgs {
+ url: String,
+ protocols: String,
+}
+
+pub async fn op_ws_create(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let args: CreateArgs = serde_json::from_value(args)?;
+
+ {
+ let s = state.borrow();
+ s.borrow::<Permissions>()
+ .check_net_url(&url::Url::parse(&args.url)?)
+ .expect(
+ "Permission check should have been done in op_ws_check_permission",
+ );
+ }
+
+ let maybe_ca_file = state.borrow().try_borrow::<WsCaFile>().cloned();
+ let user_agent = state.borrow().borrow::<WsUserAgent>().0.clone();
+ let uri: Uri = args.url.parse()?;
+ let mut request = Request::builder().method(Method::GET).uri(&uri);
+
+ request = request.header("User-Agent", user_agent);
+
+ if !args.protocols.is_empty() {
+ request = request.header("Sec-WebSocket-Protocol", args.protocols);
+ }
+
+ let request = request.body(())?;
+ let domain = &uri.host().unwrap().to_string();
+ let port = &uri.port_u16().unwrap_or(match uri.scheme_str() {
+ Some("wss") => 443,
+ Some("ws") => 80,
+ _ => unreachable!(),
+ });
+ let addr = format!("{}:{}", domain, port);
+ let try_socket = TcpStream::connect(addr).await;
+ let tcp_socket = match try_socket.map_err(TungsteniteError::Io) {
+ Ok(socket) => socket,
+ Err(_) => return Ok(json!({"success": false})),
+ };
+
+ let socket: MaybeTlsStream = match uri.scheme_str() {
+ Some("ws") => StreamSwitcher::Plain(tcp_socket),
+ Some("wss") => {
+ let mut config = ClientConfig::new();
+ config
+ .root_store
+ .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+
+ if let Some(ws_ca_file) = maybe_ca_file {
+ let key_file = File::open(ws_ca_file.0)?;
+ let reader = &mut BufReader::new(key_file);
+ config.root_store.add_pem_file(reader).unwrap();
+ }
+
+ let tls_connector = TlsConnector::from(Arc::new(config));
+ let dnsname =
+ DNSNameRef::try_from_ascii_str(&domain).expect("Invalid DNS lookup");
+ let tls_socket = tls_connector.connect(dnsname, tcp_socket).await?;
+ StreamSwitcher::Tls(tls_socket)
+ }
+ _ => unreachable!(),
+ };
+
+ let (stream, response): (WsStream, Response) =
+ client_async(request, socket).await.map_err(|err| {
+ type_error(format!(
+ "failed to connect to WebSocket: {}",
+ err.to_string()
+ ))
+ })?;
+
+ let mut state = state.borrow_mut();
+ let rid = state
+ .resource_table
+ .add("webSocketStream", Box::new(stream));
+
+ let protocol = match response.headers().get("Sec-WebSocket-Protocol") {
+ Some(header) => header.to_str().unwrap(),
+ None => "",
+ };
+ let extensions = response
+ .headers()
+ .get_all("Sec-WebSocket-Extensions")
+ .iter()
+ .map(|header| header.to_str().unwrap())
+ .collect::<String>();
+ Ok(json!({
+ "success": true,
+ "rid": rid,
+ "protocol": protocol,
+ "extensions": extensions
+ }))
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SendArgs {
+ rid: u32,
+ text: Option<String>,
+}
+
+pub async fn op_ws_send(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let args: SendArgs = serde_json::from_value(args)?;
+
+ let mut maybe_msg = Some(match args.text {
+ Some(text) => Message::Text(text),
+ None => Message::Binary(bufs[0].to_vec()),
+ });
+ let rid = args.rid;
+
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let stream = state
+ .resource_table
+ .get_mut::<WsStream>(rid)
+ .ok_or_else(bad_resource_id)?;
+
+ // TODO(ry) Handle errors below instead of unwrap.
+ // Need to map `TungsteniteError` to `AnyError`.
+ ready!(stream.poll_ready_unpin(cx)).unwrap();
+ if let Some(msg) = maybe_msg.take() {
+ stream.start_send_unpin(msg).unwrap();
+ }
+ ready!(stream.poll_flush_unpin(cx)).unwrap();
+
+ Poll::Ready(Ok(json!({})))
+ })
+ .await
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CloseArgs {
+ rid: u32,
+ code: Option<u16>,
+ reason: Option<String>,
+}
+
+pub async fn op_ws_close(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let args: CloseArgs = serde_json::from_value(args)?;
+ let rid = args.rid;
+ let mut maybe_msg = Some(Message::Close(args.code.map(|c| CloseFrame {
+ code: CloseCode::from(c),
+ reason: match args.reason {
+ Some(reason) => Cow::from(reason),
+ None => Default::default(),
+ },
+ })));
+
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let stream = state
+ .resource_table
+ .get_mut::<WsStream>(rid)
+ .ok_or_else(bad_resource_id)?;
+
+ // TODO(ry) Handle errors below instead of unwrap.
+ // Need to map `TungsteniteError` to `AnyError`.
+ ready!(stream.poll_ready_unpin(cx)).unwrap();
+ if let Some(msg) = maybe_msg.take() {
+ stream.start_send_unpin(msg).unwrap();
+ }
+ ready!(stream.poll_flush_unpin(cx)).unwrap();
+ ready!(stream.poll_close_unpin(cx)).unwrap();
+
+ Poll::Ready(Ok(json!({})))
+ })
+ .await
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct NextEventArgs {
+ rid: u32,
+}
+
+pub async fn op_ws_next_event(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _bufs: BufVec,
+) -> Result<Value, AnyError> {
+ let args: NextEventArgs = serde_json::from_value(args)?;
+ poll_fn(move |cx| {
+ let mut state = state.borrow_mut();
+ let stream = state
+ .resource_table
+ .get_mut::<WsStream>(args.rid)
+ .ok_or_else(bad_resource_id)?;
+ stream
+ .poll_next_unpin(cx)
+ .map(|val| {
+ match val {
+ Some(Ok(Message::Text(text))) => json!({
+ "type": "string",
+ "data": text
+ }),
+ Some(Ok(Message::Binary(data))) => {
+ // TODO(ry): don't use json to send binary data.
+ json!({
+ "type": "binary",
+ "data": data
+ })
+ }
+ Some(Ok(Message::Close(Some(frame)))) => json!({
+ "type": "close",
+ "code": u16::from(frame.code),
+ "reason": frame.reason.as_ref()
+ }),
+ Some(Ok(Message::Close(None))) => json!({ "type": "close" }),
+ Some(Ok(Message::Ping(_))) => json!({"type": "ping"}),
+ Some(Ok(Message::Pong(_))) => json!({"type": "pong"}),
+ Some(Err(_)) => json!({"type": "error"}),
+ None => {
+ state.resource_table.close(args.rid).unwrap();
+ json!({"type": "closed"})
+ }
+ }
+ })
+ .map(Ok)
+ })
+ .await
+}
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
new file mode 100644
index 000000000..871e4b9fe
--- /dev/null
+++ b/runtime/ops/worker_host.rs
@@ -0,0 +1,318 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::permissions::Permissions;
+use crate::web_worker::run_web_worker;
+use crate::web_worker::WebWorker;
+use crate::web_worker::WebWorkerHandle;
+use crate::web_worker::WorkerEvent;
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use deno_core::error::JsError;
+use deno_core::futures::channel::mpsc;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::ModuleSpecifier;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::From;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::thread::JoinHandle;
+
+pub struct CreateWebWorkerArgs {
+ pub name: String,
+ pub worker_id: u32,
+ pub permissions: Permissions,
+ pub main_module: ModuleSpecifier,
+ pub use_deno_namespace: bool,
+}
+
+pub type CreateWebWorkerCb =
+ dyn Fn(CreateWebWorkerArgs) -> WebWorker + Sync + Send;
+
+/// A holder for callback that is used to create a new
+/// WebWorker. It's a struct instead of a type alias
+/// because `GothamState` used in `OpState` overrides
+/// value if type alises have the same underlying type
+#[derive(Clone)]
+pub struct CreateWebWorkerCbHolder(Arc<CreateWebWorkerCb>);
+
+#[derive(Deserialize)]
+struct HostUnhandledErrorArgs {
+ message: String,
+}
+
+pub fn init(
+ rt: &mut deno_core::JsRuntime,
+ sender: Option<mpsc::Sender<WorkerEvent>>,
+ create_web_worker_cb: Arc<CreateWebWorkerCb>,
+) {
+ {
+ let op_state = rt.op_state();
+ let mut state = op_state.borrow_mut();
+ state.put::<WorkersTable>(WorkersTable::default());
+ state.put::<WorkerId>(WorkerId::default());
+
+ let create_module_loader = CreateWebWorkerCbHolder(create_web_worker_cb);
+ state.put::<CreateWebWorkerCbHolder>(create_module_loader);
+ }
+ super::reg_json_sync(rt, "op_create_worker", op_create_worker);
+ super::reg_json_sync(
+ rt,
+ "op_host_terminate_worker",
+ op_host_terminate_worker,
+ );
+ super::reg_json_sync(rt, "op_host_post_message", op_host_post_message);
+ super::reg_json_async(rt, "op_host_get_message", op_host_get_message);
+ super::reg_json_sync(
+ rt,
+ "op_host_unhandled_error",
+ move |_state, args, _zero_copy| {
+ if let Some(mut sender) = sender.clone() {
+ let args: HostUnhandledErrorArgs = serde_json::from_value(args)?;
+ sender
+ .try_send(WorkerEvent::Error(generic_error(args.message)))
+ .expect("Failed to propagate error event to parent worker");
+ Ok(json!(true))
+ } else {
+ Err(generic_error("Cannot be called from main worker."))
+ }
+ },
+ );
+}
+
+pub struct WorkerThread {
+ join_handle: JoinHandle<Result<(), AnyError>>,
+ worker_handle: WebWorkerHandle,
+}
+
+pub type WorkersTable = HashMap<u32, WorkerThread>;
+pub type WorkerId = u32;
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CreateWorkerArgs {
+ name: Option<String>,
+ specifier: String,
+ has_source_code: bool,
+ source_code: String,
+ use_deno_namespace: bool,
+}
+
+/// Create worker as the host
+fn op_create_worker(
+ state: &mut OpState,
+ args: Value,
+ _data: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: CreateWorkerArgs = serde_json::from_value(args)?;
+
+ let specifier = args.specifier.clone();
+ let maybe_source_code = if args.has_source_code {
+ Some(args.source_code.clone())
+ } else {
+ None
+ };
+ let args_name = args.name;
+ let use_deno_namespace = args.use_deno_namespace;
+ if use_deno_namespace {
+ super::check_unstable(state, "Worker.deno");
+ }
+ let permissions = state.borrow::<Permissions>().clone();
+ let worker_id = state.take::<WorkerId>();
+ let create_module_loader = state.take::<CreateWebWorkerCbHolder>();
+ state.put::<CreateWebWorkerCbHolder>(create_module_loader.clone());
+ state.put::<WorkerId>(worker_id + 1);
+
+ let module_specifier = ModuleSpecifier::resolve_url(&specifier)?;
+ let worker_name = args_name.unwrap_or_else(|| "".to_string());
+
+ let (handle_sender, handle_receiver) =
+ std::sync::mpsc::sync_channel::<Result<WebWorkerHandle, AnyError>>(1);
+
+ // Setup new thread
+ let thread_builder =
+ std::thread::Builder::new().name(format!("deno-worker-{}", worker_id));
+
+ // Spawn it
+ let join_handle = thread_builder.spawn(move || {
+ // Any error inside this block is terminal:
+ // - JS worker is useless - meaning it throws an exception and can't do anything else,
+ // all action done upon it should be noops
+ // - newly spawned thread exits
+
+ let worker = (create_module_loader.0)(CreateWebWorkerArgs {
+ name: worker_name,
+ worker_id,
+ permissions,
+ main_module: module_specifier.clone(),
+ use_deno_namespace,
+ });
+
+ // Send thread safe handle to newly created worker to host thread
+ handle_sender.send(Ok(worker.thread_safe_handle())).unwrap();
+ drop(handle_sender);
+
+ // At this point the only method of communication with host
+ // is using `worker.internal_channels`.
+ //
+ // Host can already push messages and interact with worker.
+ run_web_worker(worker, module_specifier, maybe_source_code)
+ })?;
+
+ let worker_handle = handle_receiver.recv().unwrap()?;
+
+ let worker_thread = WorkerThread {
+ join_handle,
+ worker_handle,
+ };
+
+ // At this point all interactions with worker happen using thread
+ // safe handler returned from previous function calls
+ state
+ .borrow_mut::<WorkersTable>()
+ .insert(worker_id, worker_thread);
+
+ Ok(json!({ "id": worker_id }))
+}
+
+#[derive(Deserialize)]
+struct WorkerArgs {
+ id: i32,
+}
+
+fn op_host_terminate_worker(
+ state: &mut OpState,
+ args: Value,
+ _data: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let args: WorkerArgs = serde_json::from_value(args)?;
+ let id = args.id as u32;
+ let worker_thread = state
+ .borrow_mut::<WorkersTable>()
+ .remove(&id)
+ .expect("No worker handle found");
+ worker_thread.worker_handle.terminate();
+ worker_thread
+ .join_handle
+ .join()
+ .expect("Panic in worker thread")
+ .expect("Panic in worker event loop");
+ Ok(json!({}))
+}
+
+fn serialize_worker_event(event: WorkerEvent) -> Value {
+ match event {
+ WorkerEvent::Message(buf) => json!({ "type": "msg", "data": buf }),
+ WorkerEvent::TerminalError(error) => match error.downcast::<JsError>() {
+ Ok(js_error) => json!({
+ "type": "terminalError",
+ "error": {
+ "message": js_error.message,
+ "fileName": js_error.script_resource_name,
+ "lineNumber": js_error.line_number,
+ "columnNumber": js_error.start_column,
+ }
+ }),
+ Err(error) => json!({
+ "type": "terminalError",
+ "error": {
+ "message": error.to_string(),
+ }
+ }),
+ },
+ WorkerEvent::Error(error) => match error.downcast::<JsError>() {
+ Ok(js_error) => json!({
+ "type": "error",
+ "error": {
+ "message": js_error.message,
+ "fileName": js_error.script_resource_name,
+ "lineNumber": js_error.line_number,
+ "columnNumber": js_error.start_column,
+ }
+ }),
+ Err(error) => json!({
+ "type": "error",
+ "error": {
+ "message": error.to_string(),
+ }
+ }),
+ },
+ }
+}
+
+/// Try to remove worker from workers table - NOTE: `Worker.terminate()`
+/// might have been called already meaning that we won't find worker in
+/// table - in that case ignore.
+fn try_remove_and_close(state: Rc<RefCell<OpState>>, id: u32) {
+ let mut s = state.borrow_mut();
+ let workers = s.borrow_mut::<WorkersTable>();
+ if let Some(mut worker_thread) = workers.remove(&id) {
+ worker_thread.worker_handle.sender.close_channel();
+ worker_thread
+ .join_handle
+ .join()
+ .expect("Worker thread panicked")
+ .expect("Panic in worker event loop");
+ }
+}
+
+/// Get message from guest worker as host
+async fn op_host_get_message(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: WorkerArgs = serde_json::from_value(args)?;
+ let id = args.id as u32;
+
+ let worker_handle = {
+ let s = state.borrow();
+ let workers_table = s.borrow::<WorkersTable>();
+ let maybe_handle = workers_table.get(&id);
+ if let Some(handle) = maybe_handle {
+ handle.worker_handle.clone()
+ } else {
+ // If handle was not found it means worker has already shutdown
+ return Ok(json!({ "type": "close" }));
+ }
+ };
+
+ let maybe_event = worker_handle.get_event().await?;
+ if let Some(event) = maybe_event {
+ // Terminal error means that worker should be removed from worker table.
+ if let WorkerEvent::TerminalError(_) = &event {
+ try_remove_and_close(state, id);
+ }
+ return Ok(serialize_worker_event(event));
+ }
+
+ // If there was no event from worker it means it has already been closed.
+ try_remove_and_close(state, id);
+ Ok(json!({ "type": "close" }))
+}
+
+/// Post message to guest worker as host
+fn op_host_post_message(
+ state: &mut OpState,
+ args: Value,
+ data: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ assert_eq!(data.len(), 1, "Invalid number of arguments");
+ let args: WorkerArgs = serde_json::from_value(args)?;
+ let id = args.id as u32;
+ let msg = Vec::from(&*data[0]).into_boxed_slice();
+
+ debug!("post message to worker {}", id);
+ let worker_thread = state
+ .borrow::<WorkersTable>()
+ .get(&id)
+ .expect("No worker handle found");
+ worker_thread.worker_handle.post_message(msg)?;
+ Ok(json!({}))
+}
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
new file mode 100644
index 000000000..88f9c7179
--- /dev/null
+++ b/runtime/permissions.rs
@@ -0,0 +1,1108 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::colors;
+use crate::fs_util::resolve_from_cwd;
+use deno_core::error::custom_error;
+use deno_core::error::uri_error;
+use deno_core::error::AnyError;
+use deno_core::url;
+use deno_core::ModuleSpecifier;
+use serde::Deserialize;
+use std::collections::HashSet;
+use std::env::current_dir;
+use std::fmt;
+use std::hash::Hash;
+#[cfg(not(test))]
+use std::io;
+use std::path::{Path, PathBuf};
+#[cfg(test)]
+use std::sync::atomic::AtomicBool;
+#[cfg(test)]
+use std::sync::atomic::Ordering;
+#[cfg(test)]
+use std::sync::Mutex;
+
+const PERMISSION_EMOJI: &str = "⚠️";
+
+/// Tri-state value for storing permission state
+#[derive(PartialEq, Debug, Clone, Copy, Deserialize)]
+pub enum PermissionState {
+ Granted = 0,
+ Prompt = 1,
+ Denied = 2,
+}
+
+impl PermissionState {
+ /// Check the permission state.
+ fn check(self, msg: &str, flag_name: &str) -> Result<(), AnyError> {
+ if self == PermissionState::Granted {
+ log_perm_access(msg);
+ return Ok(());
+ }
+ let message = format!("{}, run again with the {} flag", msg, flag_name);
+ Err(custom_error("PermissionDenied", message))
+ }
+}
+
+impl fmt::Display for PermissionState {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ PermissionState::Granted => f.pad("granted"),
+ PermissionState::Prompt => f.pad("prompt"),
+ PermissionState::Denied => f.pad("denied"),
+ }
+ }
+}
+
+impl Default for PermissionState {
+ fn default() -> Self {
+ PermissionState::Prompt
+ }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
+pub struct UnaryPermission<T: Eq + Hash> {
+ pub global_state: PermissionState,
+ pub granted_list: HashSet<T>,
+ pub denied_list: HashSet<T>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
+pub struct Permissions {
+ pub read: UnaryPermission<PathBuf>,
+ pub write: UnaryPermission<PathBuf>,
+ pub net: UnaryPermission<String>,
+ pub env: PermissionState,
+ pub run: PermissionState,
+ pub plugin: PermissionState,
+ pub hrtime: PermissionState,
+}
+
+fn resolve_fs_allowlist(allowlist: &[PathBuf]) -> HashSet<PathBuf> {
+ allowlist
+ .iter()
+ .map(|raw_path| resolve_from_cwd(Path::new(&raw_path)).unwrap())
+ .collect()
+}
+
+#[derive(Clone, Debug, PartialEq, Default)]
+pub struct PermissionsOptions {
+ pub allow_env: bool,
+ pub allow_hrtime: bool,
+ pub allow_net: bool,
+ pub allow_plugin: bool,
+ pub allow_read: bool,
+ pub allow_run: bool,
+ pub allow_write: bool,
+ pub net_allowlist: Vec<String>,
+ pub read_allowlist: Vec<PathBuf>,
+ pub write_allowlist: Vec<PathBuf>,
+}
+
+impl Permissions {
+ pub fn from_options(opts: &PermissionsOptions) -> Self {
+ fn state_from_flag_bool(flag: bool) -> PermissionState {
+ if flag {
+ PermissionState::Granted
+ } else {
+ PermissionState::Prompt
+ }
+ }
+ Self {
+ read: UnaryPermission::<PathBuf> {
+ global_state: state_from_flag_bool(opts.allow_read),
+ granted_list: resolve_fs_allowlist(&opts.read_allowlist),
+ ..Default::default()
+ },
+ write: UnaryPermission::<PathBuf> {
+ global_state: state_from_flag_bool(opts.allow_write),
+ granted_list: resolve_fs_allowlist(&opts.write_allowlist),
+ ..Default::default()
+ },
+ net: UnaryPermission::<String> {
+ global_state: state_from_flag_bool(opts.allow_net),
+ granted_list: opts.net_allowlist.iter().cloned().collect(),
+ ..Default::default()
+ },
+ env: state_from_flag_bool(opts.allow_env),
+ run: state_from_flag_bool(opts.allow_run),
+ plugin: state_from_flag_bool(opts.allow_plugin),
+ hrtime: state_from_flag_bool(opts.allow_hrtime),
+ }
+ }
+
+ /// Arbitrary helper. Resolves the path from CWD, and also gets a path that
+ /// can be displayed without leaking the CWD when not allowed.
+ fn resolved_and_display_path(&self, path: &Path) -> (PathBuf, PathBuf) {
+ let resolved_path = resolve_from_cwd(path).unwrap();
+ let display_path = if path.is_absolute() {
+ path.to_path_buf()
+ } else {
+ match self
+ .query_read(&Some(&current_dir().unwrap()))
+ .check("", "")
+ {
+ Ok(_) => resolved_path.clone(),
+ Err(_) => path.to_path_buf(),
+ }
+ };
+ (resolved_path, display_path)
+ }
+
+ pub fn allow_all() -> Self {
+ Self {
+ read: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ env: PermissionState::Granted,
+ run: PermissionState::Granted,
+ plugin: PermissionState::Granted,
+ hrtime: PermissionState::Granted,
+ }
+ }
+
+ pub fn query_read(&self, path: &Option<&Path>) -> PermissionState {
+ let path = path.map(|p| resolve_from_cwd(p).unwrap());
+ if self.read.global_state == PermissionState::Denied
+ && match path.as_ref() {
+ None => true,
+ Some(path) => check_path_blocklist(path, &self.read.denied_list),
+ }
+ {
+ return PermissionState::Denied;
+ }
+ if self.read.global_state == PermissionState::Granted
+ || match path.as_ref() {
+ None => false,
+ Some(path) => check_path_allowlist(path, &self.read.granted_list),
+ }
+ {
+ return PermissionState::Granted;
+ }
+ PermissionState::Prompt
+ }
+
+ pub fn query_write(&self, path: &Option<&Path>) -> PermissionState {
+ let path = path.map(|p| resolve_from_cwd(p).unwrap());
+ if self.write.global_state == PermissionState::Denied
+ && match path.as_ref() {
+ None => true,
+ Some(path) => check_path_blocklist(path, &self.write.denied_list),
+ }
+ {
+ return PermissionState::Denied;
+ }
+ if self.write.global_state == PermissionState::Granted
+ || match path.as_ref() {
+ None => false,
+ Some(path) => check_path_allowlist(path, &self.write.granted_list),
+ }
+ {
+ return PermissionState::Granted;
+ }
+ PermissionState::Prompt
+ }
+
+ pub fn query_net(&self, host: &str, port: Option<u16>) -> PermissionState {
+ if self.net.global_state == PermissionState::Denied
+ || check_host_and_port_list(host, port, &self.net.denied_list)
+ {
+ return PermissionState::Denied;
+ }
+ if self.net.global_state == PermissionState::Granted
+ || check_host_and_port_list(host, port, &self.net.granted_list)
+ {
+ return PermissionState::Granted;
+ }
+ PermissionState::Prompt
+ }
+
+ pub fn query_net_url(
+ &self,
+ url: &Option<&str>,
+ ) -> Result<PermissionState, AnyError> {
+ if url.is_none() {
+ return Ok(self.net.global_state);
+ }
+ let url: &str = url.unwrap();
+ // If url is invalid, then throw a TypeError.
+ let parsed = url::Url::parse(url)?;
+ // The url may be parsed correctly but still lack a host, i.e. "localhost:235" or "mailto:someone@somewhere.com" or "file:/1.txt"
+ // Note that host:port combos are parsed as scheme:path
+ if parsed.host().is_none() {
+ return Err(custom_error(
+ "URIError",
+ "invalid urlormat: <scheme>://<host>[:port][/subpath]",
+ ));
+ }
+ Ok(self.query_net(
+ &parsed.host().unwrap().to_string(),
+ parsed.port_or_known_default(),
+ ))
+ }
+
+ pub fn query_env(&self) -> PermissionState {
+ self.env
+ }
+
+ pub fn query_run(&self) -> PermissionState {
+ self.run
+ }
+
+ pub fn query_plugin(&self) -> PermissionState {
+ self.plugin
+ }
+
+ pub fn query_hrtime(&self) -> PermissionState {
+ self.hrtime
+ }
+
+ pub fn request_read(&mut self, path: &Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let (resolved_path, display_path) = self.resolved_and_display_path(path);
+ let state = self.query_read(&Some(&resolved_path));
+ if state == PermissionState::Prompt {
+ if permission_prompt(&format!(
+ "Deno requests read access to \"{}\"",
+ display_path.display()
+ )) {
+ self
+ .read
+ .granted_list
+ .retain(|path| !path.starts_with(&resolved_path));
+ self.read.granted_list.insert(resolved_path);
+ return PermissionState::Granted;
+ } else {
+ self
+ .read
+ .denied_list
+ .retain(|path| !resolved_path.starts_with(path));
+ self.read.denied_list.insert(resolved_path);
+ self.read.global_state = PermissionState::Denied;
+ return PermissionState::Denied;
+ }
+ }
+ state
+ } else {
+ let state = self.query_read(&None);
+ if state == PermissionState::Prompt {
+ if permission_prompt("Deno requests read access") {
+ self.read.granted_list.clear();
+ self.read.global_state = PermissionState::Granted;
+ return PermissionState::Granted;
+ } else {
+ self.read.global_state = PermissionState::Denied;
+ return PermissionState::Denied;
+ }
+ }
+ state
+ }
+ }
+
+ pub fn request_write(&mut self, path: &Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let (resolved_path, display_path) = self.resolved_and_display_path(path);
+ let state = self.query_write(&Some(&resolved_path));
+ if state == PermissionState::Prompt {
+ if permission_prompt(&format!(
+ "Deno requests write access to \"{}\"",
+ display_path.display()
+ )) {
+ self
+ .write
+ .granted_list
+ .retain(|path| !path.starts_with(&resolved_path));
+ self.write.granted_list.insert(resolved_path);
+ return PermissionState::Granted;
+ } else {
+ self
+ .write
+ .denied_list
+ .retain(|path| !resolved_path.starts_with(path));
+ self.write.denied_list.insert(resolved_path);
+ self.write.global_state = PermissionState::Denied;
+ return PermissionState::Denied;
+ }
+ }
+ state
+ } else {
+ let state = self.query_write(&None);
+ if state == PermissionState::Prompt {
+ if permission_prompt("Deno requests write access") {
+ self.write.granted_list.clear();
+ self.write.global_state = PermissionState::Granted;
+ return PermissionState::Granted;
+ } else {
+ self.write.global_state = PermissionState::Denied;
+ return PermissionState::Denied;
+ }
+ }
+ state
+ }
+ }
+
+ pub fn request_net(
+ &mut self,
+ url: &Option<&str>,
+ ) -> Result<PermissionState, AnyError> {
+ if let Some(url) = url {
+ let state = self.query_net_url(&Some(url))?;
+ if state == PermissionState::Prompt {
+ if permission_prompt(&format!(
+ "Deno requests network access to \"{}\"",
+ url
+ )) {
+ self.net.granted_list.insert(url.to_string());
+ return Ok(PermissionState::Granted);
+ } else {
+ self.net.denied_list.insert(url.to_string());
+ self.net.global_state = PermissionState::Denied;
+ return Ok(PermissionState::Denied);
+ }
+ }
+ Ok(state)
+ } else {
+ let state = self.query_net_url(&None)?;
+ if state == PermissionState::Prompt {
+ if permission_prompt("Deno requests network access") {
+ self.net.granted_list.clear();
+ self.net.global_state = PermissionState::Granted;
+ return Ok(PermissionState::Granted);
+ } else {
+ self.net.global_state = PermissionState::Denied;
+ return Ok(PermissionState::Denied);
+ }
+ }
+ Ok(state)
+ }
+ }
+
+ pub fn request_env(&mut self) -> PermissionState {
+ if self.env == PermissionState::Prompt {
+ if permission_prompt("Deno requests access to environment variables") {
+ self.env = PermissionState::Granted;
+ } else {
+ self.env = PermissionState::Denied;
+ }
+ }
+ self.env
+ }
+
+ pub fn request_run(&mut self) -> PermissionState {
+ if self.run == PermissionState::Prompt {
+ if permission_prompt("Deno requests to access to run a subprocess") {
+ self.run = PermissionState::Granted;
+ } else {
+ self.run = PermissionState::Denied;
+ }
+ }
+ self.run
+ }
+
+ pub fn request_plugin(&mut self) -> PermissionState {
+ if self.plugin == PermissionState::Prompt {
+ if permission_prompt("Deno requests to open plugins") {
+ self.plugin = PermissionState::Granted;
+ } else {
+ self.plugin = PermissionState::Denied;
+ }
+ }
+ self.plugin
+ }
+
+ pub fn request_hrtime(&mut self) -> PermissionState {
+ if self.hrtime == PermissionState::Prompt {
+ if permission_prompt("Deno requests access to high precision time") {
+ self.hrtime = PermissionState::Granted;
+ } else {
+ self.hrtime = PermissionState::Denied;
+ }
+ }
+ self.hrtime
+ }
+
+ pub fn revoke_read(&mut self, path: &Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let path = resolve_from_cwd(path).unwrap();
+ self
+ .read
+ .granted_list
+ .retain(|path_| !path_.starts_with(&path));
+ } else {
+ self.read.granted_list.clear();
+ if self.read.global_state == PermissionState::Granted {
+ self.read.global_state = PermissionState::Prompt;
+ }
+ }
+ self.query_read(path)
+ }
+
+ pub fn revoke_write(&mut self, path: &Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let path = resolve_from_cwd(path).unwrap();
+ self
+ .write
+ .granted_list
+ .retain(|path_| !path_.starts_with(&path));
+ } else {
+ self.write.granted_list.clear();
+ if self.write.global_state == PermissionState::Granted {
+ self.write.global_state = PermissionState::Prompt;
+ }
+ }
+ self.query_write(path)
+ }
+
+ pub fn revoke_net(
+ &mut self,
+ url: &Option<&str>,
+ ) -> Result<PermissionState, AnyError> {
+ if let Some(url) = url {
+ self.net.granted_list.remove(*url);
+ } else {
+ self.net.granted_list.clear();
+ if self.net.global_state == PermissionState::Granted {
+ self.net.global_state = PermissionState::Prompt;
+ }
+ }
+ self.query_net_url(url)
+ }
+
+ pub fn revoke_env(&mut self) -> PermissionState {
+ if self.env == PermissionState::Granted {
+ self.env = PermissionState::Prompt;
+ }
+ self.env
+ }
+
+ pub fn revoke_run(&mut self) -> PermissionState {
+ if self.run == PermissionState::Granted {
+ self.run = PermissionState::Prompt;
+ }
+ self.run
+ }
+
+ pub fn revoke_plugin(&mut self) -> PermissionState {
+ if self.plugin == PermissionState::Granted {
+ self.plugin = PermissionState::Prompt;
+ }
+ self.plugin
+ }
+
+ pub fn revoke_hrtime(&mut self) -> PermissionState {
+ if self.hrtime == PermissionState::Granted {
+ self.hrtime = PermissionState::Prompt;
+ }
+ self.hrtime
+ }
+
+ pub fn check_read(&self, path: &Path) -> Result<(), AnyError> {
+ let (resolved_path, display_path) = self.resolved_and_display_path(path);
+ self.query_read(&Some(&resolved_path)).check(
+ &format!("read access to \"{}\"", display_path.display()),
+ "--allow-read",
+ )
+ }
+
+ /// As `check_read()`, but permission error messages will anonymize the path
+ /// by replacing it with the given `display`.
+ pub fn check_read_blind(
+ &self,
+ path: &Path,
+ display: &str,
+ ) -> Result<(), AnyError> {
+ let resolved_path = resolve_from_cwd(path).unwrap();
+ self
+ .query_read(&Some(&resolved_path))
+ .check(&format!("read access to <{}>", display), "--allow-read")
+ }
+
+ pub fn check_write(&self, path: &Path) -> Result<(), AnyError> {
+ let (resolved_path, display_path) = self.resolved_and_display_path(path);
+ self.query_write(&Some(&resolved_path)).check(
+ &format!("write access to \"{}\"", display_path.display()),
+ "--allow-write",
+ )
+ }
+
+ pub fn check_net(&self, hostname: &str, port: u16) -> Result<(), AnyError> {
+ self.query_net(hostname, Some(port)).check(
+ &format!("network access to \"{}:{}\"", hostname, port),
+ "--allow-net",
+ )
+ }
+
+ pub fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> {
+ let host = url.host_str().ok_or_else(|| uri_error("missing host"))?;
+ self
+ .query_net(host, url.port_or_known_default())
+ .check(&format!("network access to \"{}\"", url), "--allow-net")
+ }
+
+ /// A helper function that determines if the module specifier is a local or
+ /// remote, and performs a read or net check for the specifier.
+ pub fn check_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Result<(), AnyError> {
+ let url = specifier.as_url();
+ if url.scheme() == "file" {
+ let path = url.to_file_path().unwrap();
+ self.check_read(&path)
+ } else {
+ self.check_net_url(url)
+ }
+ }
+
+ pub fn check_env(&self) -> Result<(), AnyError> {
+ self
+ .env
+ .check("access to environment variables", "--allow-env")
+ }
+
+ pub fn check_run(&self) -> Result<(), AnyError> {
+ self.run.check("access to run a subprocess", "--allow-run")
+ }
+
+ pub fn check_plugin(&self, path: &Path) -> Result<(), AnyError> {
+ let (_, display_path) = self.resolved_and_display_path(path);
+ self.plugin.check(
+ &format!("access to open a plugin: {}", display_path.display()),
+ "--allow-plugin",
+ )
+ }
+
+ pub fn check_hrtime(&self) -> Result<(), AnyError> {
+ self
+ .hrtime
+ .check("access to high precision time", "--allow-hrtime")
+ }
+}
+
+impl deno_fetch::FetchPermissions for Permissions {
+ fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> {
+ Permissions::check_net_url(self, url)
+ }
+
+ fn check_read(&self, p: &PathBuf) -> Result<(), AnyError> {
+ Permissions::check_read(self, p)
+ }
+}
+
+/// Shows the permission prompt and returns the answer according to the user input.
+/// This loops until the user gives the proper input.
+#[cfg(not(test))]
+fn permission_prompt(message: &str) -> bool {
+ if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
+ return false;
+ };
+ let msg = format!(
+ "️{} {}. Grant? [g/d (g = grant, d = deny)] ",
+ PERMISSION_EMOJI, message
+ );
+ // print to stderr so that if deno is > to a file this is still displayed.
+ eprint!("{}", colors::bold(&msg));
+ loop {
+ let mut input = String::new();
+ let stdin = io::stdin();
+ let result = stdin.read_line(&mut input);
+ if result.is_err() {
+ return false;
+ };
+ let ch = input.chars().next().unwrap();
+ match ch.to_ascii_lowercase() {
+ 'g' => return true,
+ 'd' => return false,
+ _ => {
+ // If we don't get a recognized option try again.
+ let msg_again =
+ format!("Unrecognized option '{}' [g/d (g = grant, d = deny)] ", ch);
+ eprint!("{}", colors::bold(&msg_again));
+ }
+ };
+ }
+}
+
+#[cfg(test)]
+lazy_static! {
+ /// Lock this when you use `set_prompt_result` in a test case.
+ static ref PERMISSION_PROMPT_GUARD: Mutex<()> = Mutex::new(());
+}
+
+#[cfg(test)]
+static STUB_PROMPT_VALUE: AtomicBool = AtomicBool::new(true);
+
+#[cfg(test)]
+fn set_prompt_result(value: bool) {
+ STUB_PROMPT_VALUE.store(value, Ordering::SeqCst);
+}
+
+// When testing, permission prompt returns the value of STUB_PROMPT_VALUE
+// which we set from the test functions.
+#[cfg(test)]
+fn permission_prompt(_message: &str) -> bool {
+ STUB_PROMPT_VALUE.load(Ordering::SeqCst)
+}
+
+fn log_perm_access(message: &str) {
+ debug!(
+ "{}",
+ colors::bold(&format!("{}️ Granted {}", PERMISSION_EMOJI, message))
+ );
+}
+
+fn check_path_allowlist(path: &Path, allowlist: &HashSet<PathBuf>) -> bool {
+ for path_ in allowlist {
+ if path.starts_with(path_) {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_path_blocklist(path: &Path, blocklist: &HashSet<PathBuf>) -> bool {
+ for path_ in blocklist {
+ if path_.starts_with(path) {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_host_and_port_list(
+ host: &str,
+ port: Option<u16>,
+ allowlist: &HashSet<String>,
+) -> bool {
+ allowlist.contains(host)
+ || (port.is_some()
+ && allowlist.contains(&format!("{}:{}", host, port.unwrap())))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use deno_core::serde_json;
+
+ // Creates vector of strings, Vec<String>
+ macro_rules! svec {
+ ($($x:expr),*) => (vec![$($x.to_string()),*]);
+ }
+
+ #[test]
+ fn check_paths() {
+ let allowlist = vec![
+ PathBuf::from("/a/specific/dir/name"),
+ PathBuf::from("/a/specific"),
+ PathBuf::from("/b/c"),
+ ];
+
+ let perms = Permissions::from_options(&PermissionsOptions {
+ read_allowlist: allowlist.clone(),
+ write_allowlist: allowlist,
+ ..Default::default()
+ });
+
+ // Inside of /a/specific and /a/specific/dir/name
+ assert!(perms.check_read(Path::new("/a/specific/dir/name")).is_ok());
+ assert!(perms.check_write(Path::new("/a/specific/dir/name")).is_ok());
+
+ // Inside of /a/specific but outside of /a/specific/dir/name
+ assert!(perms.check_read(Path::new("/a/specific/dir")).is_ok());
+ assert!(perms.check_write(Path::new("/a/specific/dir")).is_ok());
+
+ // Inside of /a/specific and /a/specific/dir/name
+ assert!(perms
+ .check_read(Path::new("/a/specific/dir/name/inner"))
+ .is_ok());
+ assert!(perms
+ .check_write(Path::new("/a/specific/dir/name/inner"))
+ .is_ok());
+
+ // Inside of /a/specific but outside of /a/specific/dir/name
+ assert!(perms.check_read(Path::new("/a/specific/other/dir")).is_ok());
+ assert!(perms
+ .check_write(Path::new("/a/specific/other/dir"))
+ .is_ok());
+
+ // Exact match with /b/c
+ assert!(perms.check_read(Path::new("/b/c")).is_ok());
+ assert!(perms.check_write(Path::new("/b/c")).is_ok());
+
+ // Sub path within /b/c
+ assert!(perms.check_read(Path::new("/b/c/sub/path")).is_ok());
+ assert!(perms.check_write(Path::new("/b/c/sub/path")).is_ok());
+
+ // Sub path within /b/c, needs normalizing
+ assert!(perms
+ .check_read(Path::new("/b/c/sub/path/../path/."))
+ .is_ok());
+ assert!(perms
+ .check_write(Path::new("/b/c/sub/path/../path/."))
+ .is_ok());
+
+ // Inside of /b but outside of /b/c
+ assert!(perms.check_read(Path::new("/b/e")).is_err());
+ assert!(perms.check_write(Path::new("/b/e")).is_err());
+
+ // Inside of /a but outside of /a/specific
+ assert!(perms.check_read(Path::new("/a/b")).is_err());
+ assert!(perms.check_write(Path::new("/a/b")).is_err());
+ }
+
+ #[test]
+ fn test_check_net() {
+ let perms = Permissions::from_options(&PermissionsOptions {
+ net_allowlist: svec![
+ "localhost",
+ "deno.land",
+ "github.com:3000",
+ "127.0.0.1",
+ "172.16.0.2:8000",
+ "www.github.com:443"
+ ],
+ ..Default::default()
+ });
+
+ let domain_tests = vec![
+ ("localhost", 1234, true),
+ ("deno.land", 0, true),
+ ("deno.land", 3000, true),
+ ("deno.lands", 0, false),
+ ("deno.lands", 3000, false),
+ ("github.com", 3000, true),
+ ("github.com", 0, false),
+ ("github.com", 2000, false),
+ ("github.net", 3000, false),
+ ("127.0.0.1", 0, true),
+ ("127.0.0.1", 3000, true),
+ ("127.0.0.2", 0, false),
+ ("127.0.0.2", 3000, false),
+ ("172.16.0.2", 8000, true),
+ ("172.16.0.2", 0, false),
+ ("172.16.0.2", 6000, false),
+ ("172.16.0.1", 8000, false),
+ // Just some random hosts that should err
+ ("somedomain", 0, false),
+ ("192.168.0.1", 0, false),
+ ];
+
+ let url_tests = vec![
+ // Any protocol + port for localhost should be ok, since we don't specify
+ ("http://localhost", true),
+ ("https://localhost", true),
+ ("https://localhost:4443", true),
+ ("tcp://localhost:5000", true),
+ ("udp://localhost:6000", true),
+ // Correct domain + any port and protocol should be ok incorrect shouldn't
+ ("https://deno.land/std/example/welcome.ts", true),
+ ("https://deno.land:3000/std/example/welcome.ts", true),
+ ("https://deno.lands/std/example/welcome.ts", false),
+ ("https://deno.lands:3000/std/example/welcome.ts", false),
+ // Correct domain + port should be ok all other combinations should err
+ ("https://github.com:3000/denoland/deno", true),
+ ("https://github.com/denoland/deno", false),
+ ("https://github.com:2000/denoland/deno", false),
+ ("https://github.net:3000/denoland/deno", false),
+ // Correct ipv4 address + any port should be ok others should err
+ ("tcp://127.0.0.1", true),
+ ("https://127.0.0.1", true),
+ ("tcp://127.0.0.1:3000", true),
+ ("https://127.0.0.1:3000", true),
+ ("tcp://127.0.0.2", false),
+ ("https://127.0.0.2", false),
+ ("tcp://127.0.0.2:3000", false),
+ ("https://127.0.0.2:3000", false),
+ // Correct address + port should be ok all other combinations should err
+ ("tcp://172.16.0.2:8000", true),
+ ("https://172.16.0.2:8000", true),
+ ("tcp://172.16.0.2", false),
+ ("https://172.16.0.2", false),
+ ("tcp://172.16.0.2:6000", false),
+ ("https://172.16.0.2:6000", false),
+ ("tcp://172.16.0.1:8000", false),
+ ("https://172.16.0.1:8000", false),
+ // Testing issue #6531 (Network permissions check doesn't account for well-known default ports) so we dont regress
+ ("https://www.github.com:443/robots.txt", true),
+ ];
+
+ for (url_str, is_ok) in url_tests.iter() {
+ let u = url::Url::parse(url_str).unwrap();
+ assert_eq!(*is_ok, perms.check_net_url(&u).is_ok());
+ }
+
+ for (host, port, is_ok) in domain_tests.iter() {
+ assert_eq!(*is_ok, perms.check_net(host, *port).is_ok());
+ }
+ }
+
+ #[test]
+ fn check_specifiers() {
+ let read_allowlist = if cfg!(target_os = "windows") {
+ vec![PathBuf::from("C:\\a")]
+ } else {
+ vec![PathBuf::from("/a")]
+ };
+ let perms = Permissions::from_options(&PermissionsOptions {
+ read_allowlist,
+ net_allowlist: svec!["localhost"],
+ ..Default::default()
+ });
+
+ let mut fixtures = vec![
+ (
+ ModuleSpecifier::resolve_url_or_path("http://localhost:4545/mod.ts")
+ .unwrap(),
+ true,
+ ),
+ (
+ ModuleSpecifier::resolve_url_or_path("http://deno.land/x/mod.ts")
+ .unwrap(),
+ false,
+ ),
+ ];
+
+ if cfg!(target_os = "windows") {
+ fixtures.push((
+ ModuleSpecifier::resolve_url_or_path("file:///C:/a/mod.ts").unwrap(),
+ true,
+ ));
+ fixtures.push((
+ ModuleSpecifier::resolve_url_or_path("file:///C:/b/mod.ts").unwrap(),
+ false,
+ ));
+ } else {
+ fixtures.push((
+ ModuleSpecifier::resolve_url_or_path("file:///a/mod.ts").unwrap(),
+ true,
+ ));
+ fixtures.push((
+ ModuleSpecifier::resolve_url_or_path("file:///b/mod.ts").unwrap(),
+ false,
+ ));
+ }
+
+ for (specifier, expected) in fixtures {
+ assert_eq!(perms.check_specifier(&specifier).is_ok(), expected);
+ }
+ }
+
+ #[test]
+ fn test_deserialize_perms() {
+ let json_perms = r#"
+ {
+ "read": {
+ "global_state": "Granted",
+ "granted_list": [],
+ "denied_list": []
+ },
+ "write": {
+ "global_state": "Granted",
+ "granted_list": [],
+ "denied_list": []
+ },
+ "net": {
+ "global_state": "Granted",
+ "granted_list": [],
+ "denied_list": []
+ },
+ "env": "Granted",
+ "run": "Granted",
+ "plugin": "Granted",
+ "hrtime": "Granted"
+ }
+ "#;
+ let perms0 = Permissions {
+ read: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ env: PermissionState::Granted,
+ run: PermissionState::Granted,
+ hrtime: PermissionState::Granted,
+ plugin: PermissionState::Granted,
+ };
+ let deserialized_perms: Permissions =
+ serde_json::from_str(json_perms).unwrap();
+ assert_eq!(perms0, deserialized_perms);
+ }
+
+ #[test]
+ fn test_query() {
+ let perms1 = Permissions {
+ read: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Granted,
+ ..Default::default()
+ },
+ env: PermissionState::Granted,
+ run: PermissionState::Granted,
+ plugin: PermissionState::Granted,
+ hrtime: PermissionState::Granted,
+ };
+ let perms2 = Permissions {
+ read: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ granted_list: resolve_fs_allowlist(&[PathBuf::from("/foo")]),
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ granted_list: resolve_fs_allowlist(&[PathBuf::from("/foo")]),
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ granted_list: ["127.0.0.1:8000".to_string()].iter().cloned().collect(),
+ ..Default::default()
+ },
+ env: PermissionState::Prompt,
+ run: PermissionState::Prompt,
+ plugin: PermissionState::Prompt,
+ hrtime: PermissionState::Prompt,
+ };
+ #[rustfmt::skip]
+ {
+ assert_eq!(perms1.query_read(&None), PermissionState::Granted);
+ assert_eq!(perms1.query_read(&Some(&Path::new("/foo"))), PermissionState::Granted);
+ assert_eq!(perms2.query_read(&None), PermissionState::Prompt);
+ assert_eq!(perms2.query_read(&Some(&Path::new("/foo"))), PermissionState::Granted);
+ assert_eq!(perms2.query_read(&Some(&Path::new("/foo/bar"))), PermissionState::Granted);
+ assert_eq!(perms1.query_write(&None), PermissionState::Granted);
+ assert_eq!(perms1.query_write(&Some(&Path::new("/foo"))), PermissionState::Granted);
+ assert_eq!(perms2.query_write(&None), PermissionState::Prompt);
+ assert_eq!(perms2.query_write(&Some(&Path::new("/foo"))), PermissionState::Granted);
+ assert_eq!(perms2.query_write(&Some(&Path::new("/foo/bar"))), PermissionState::Granted);
+ assert_eq!(perms1.query_net_url(&None).unwrap(), PermissionState::Granted);
+ assert_eq!(perms1.query_net_url(&Some("http://127.0.0.1:8000")).unwrap(), PermissionState::Granted);
+ assert_eq!(perms2.query_net_url(&None).unwrap(), PermissionState::Prompt);
+ assert_eq!(perms2.query_net_url(&Some("http://127.0.0.1:8000")).unwrap(), PermissionState::Granted);
+ assert_eq!(perms1.query_env(), PermissionState::Granted);
+ assert_eq!(perms2.query_env(), PermissionState::Prompt);
+ assert_eq!(perms1.query_run(), PermissionState::Granted);
+ assert_eq!(perms2.query_run(), PermissionState::Prompt);
+ assert_eq!(perms1.query_plugin(), PermissionState::Granted);
+ assert_eq!(perms2.query_plugin(), PermissionState::Prompt);
+ assert_eq!(perms1.query_hrtime(), PermissionState::Granted);
+ assert_eq!(perms2.query_hrtime(), PermissionState::Prompt);
+ };
+ }
+
+ #[test]
+ fn test_request() {
+ let mut perms = Permissions {
+ read: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ ..Default::default()
+ },
+ env: PermissionState::Prompt,
+ run: PermissionState::Prompt,
+ plugin: PermissionState::Prompt,
+ hrtime: PermissionState::Prompt,
+ };
+ #[rustfmt::skip]
+ {
+ let _guard = PERMISSION_PROMPT_GUARD.lock().unwrap();
+ set_prompt_result(true);
+ assert_eq!(perms.request_read(&Some(&Path::new("/foo"))), PermissionState::Granted);
+ assert_eq!(perms.query_read(&None), PermissionState::Prompt);
+ set_prompt_result(false);
+ assert_eq!(perms.request_read(&Some(&Path::new("/foo/bar"))), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_write(&Some(&Path::new("/foo"))), PermissionState::Denied);
+ assert_eq!(perms.query_write(&Some(&Path::new("/foo/bar"))), PermissionState::Prompt);
+ set_prompt_result(true);
+ assert_eq!(perms.request_write(&None), PermissionState::Denied);
+ set_prompt_result(true);
+ assert_eq!(perms.request_net(&None).unwrap(), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_net(&Some("http://127.0.0.1:8000")).unwrap(), PermissionState::Granted);
+ set_prompt_result(true);
+ assert_eq!(perms.request_env(), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_env(), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_run(), PermissionState::Denied);
+ set_prompt_result(true);
+ assert_eq!(perms.request_run(), PermissionState::Denied);
+ set_prompt_result(true);
+ assert_eq!(perms.request_plugin(), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_plugin(), PermissionState::Granted);
+ set_prompt_result(false);
+ assert_eq!(perms.request_hrtime(), PermissionState::Denied);
+ set_prompt_result(true);
+ assert_eq!(perms.request_hrtime(), PermissionState::Denied);
+ };
+ }
+
+ #[test]
+ fn test_revoke() {
+ let mut perms = Permissions {
+ read: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ granted_list: resolve_fs_allowlist(&[PathBuf::from("/foo")]),
+ ..Default::default()
+ },
+ write: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ granted_list: resolve_fs_allowlist(&[PathBuf::from("/foo")]),
+ ..Default::default()
+ },
+ net: UnaryPermission {
+ global_state: PermissionState::Denied,
+ ..Default::default()
+ },
+ env: PermissionState::Granted,
+ run: PermissionState::Granted,
+ plugin: PermissionState::Prompt,
+ hrtime: PermissionState::Denied,
+ };
+ #[rustfmt::skip]
+ {
+ assert_eq!(perms.revoke_read(&Some(&Path::new("/foo/bar"))), PermissionState::Granted);
+ assert_eq!(perms.revoke_read(&Some(&Path::new("/foo"))), PermissionState::Prompt);
+ assert_eq!(perms.query_read(&Some(&Path::new("/foo/bar"))), PermissionState::Prompt);
+ assert_eq!(perms.revoke_write(&Some(&Path::new("/foo/bar"))), PermissionState::Granted);
+ assert_eq!(perms.revoke_write(&None), PermissionState::Prompt);
+ assert_eq!(perms.query_write(&Some(&Path::new("/foo/bar"))), PermissionState::Prompt);
+ assert_eq!(perms.revoke_net(&None).unwrap(), PermissionState::Denied);
+ assert_eq!(perms.revoke_env(), PermissionState::Prompt);
+ assert_eq!(perms.revoke_run(), PermissionState::Prompt);
+ assert_eq!(perms.revoke_plugin(), PermissionState::Prompt);
+ assert_eq!(perms.revoke_hrtime(), PermissionState::Denied);
+ };
+ }
+}
diff --git a/runtime/resolve_addr.rs b/runtime/resolve_addr.rs
new file mode 100644
index 000000000..c3dc52f8f
--- /dev/null
+++ b/runtime/resolve_addr.rs
@@ -0,0 +1,72 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use std::net::SocketAddr;
+use std::net::ToSocketAddrs;
+
+/// Resolve network address. Returns a future.
+pub fn resolve_addr(hostname: &str, port: u16) -> Result<SocketAddr, AnyError> {
+ // Default to localhost if given just the port. Example: ":80"
+ let addr: &str = if !hostname.is_empty() {
+ &hostname
+ } else {
+ "0.0.0.0"
+ };
+
+ // If this looks like an ipv6 IP address. Example: "[2001:db8::1]"
+ // Then we remove the brackets.
+ let addr = if addr.starts_with('[') && addr.ends_with(']') {
+ let l = addr.len() - 1;
+ addr.get(1..l).unwrap()
+ } else {
+ addr
+ };
+ let addr_port_pair = (addr, port);
+ let mut iter = addr_port_pair.to_socket_addrs()?;
+ Ok(iter.next().unwrap())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::net::Ipv4Addr;
+ use std::net::Ipv6Addr;
+ use std::net::SocketAddrV4;
+ use std::net::SocketAddrV6;
+
+ #[test]
+ fn resolve_addr1() {
+ let expected =
+ SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80));
+ let actual = resolve_addr("127.0.0.1", 80).unwrap();
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn resolve_addr2() {
+ let expected =
+ SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 80));
+ let actual = resolve_addr("", 80).unwrap();
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn resolve_addr3() {
+ let expected =
+ SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25));
+ let actual = resolve_addr("192.0.2.1", 25).unwrap();
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn resolve_addr_ipv6() {
+ let expected = SocketAddr::V6(SocketAddrV6::new(
+ Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
+ 8080,
+ 0,
+ 0,
+ ));
+ let actual = resolve_addr("[2001:db8::1]", 8080).unwrap();
+ assert_eq!(actual, expected);
+ }
+}
diff --git a/runtime/rt/00_bootstrap_namespace.js b/runtime/rt/00_bootstrap_namespace.js
new file mode 100644
index 000000000..514cbe3f0
--- /dev/null
+++ b/runtime/rt/00_bootstrap_namespace.js
@@ -0,0 +1,9 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// The only purpose of this file is to set up "globalThis.__bootstrap" namespace,
+// that is used by scripts in this directory to reference exports between
+// the files.
+
+// This namespace is removed during runtime bootstrapping process.
+
+globalThis.__bootstrap = globalThis.__bootstrap || {};
diff --git a/runtime/rt/01_build.js b/runtime/rt/01_build.js
new file mode 100644
index 000000000..7c1dc817e
--- /dev/null
+++ b/runtime/rt/01_build.js
@@ -0,0 +1,26 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const build = {
+ target: "unknown",
+ arch: "unknown",
+ os: "unknown",
+ vendor: "unknown",
+ env: undefined,
+ };
+
+ function setBuildInfo(target) {
+ const [arch, vendor, os, env] = target.split("-", 4);
+ build.target = target;
+ build.arch = arch;
+ build.vendor = vendor;
+ build.os = os;
+ build.env = env;
+ Object.freeze(build);
+ }
+
+ window.__bootstrap.build = {
+ build,
+ setBuildInfo,
+ };
+})(this);
diff --git a/runtime/rt/01_colors.js b/runtime/rt/01_colors.js
new file mode 100644
index 000000000..39e4a7a18
--- /dev/null
+++ b/runtime/rt/01_colors.js
@@ -0,0 +1,92 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ function code(open, close) {
+ return {
+ open: `\x1b[${open}m`,
+ close: `\x1b[${close}m`,
+ regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
+ };
+ }
+
+ function run(str, code) {
+ return `${code.open}${str.replace(code.regexp, code.open)}${code.close}`;
+ }
+
+ function bold(str) {
+ return run(str, code(1, 22));
+ }
+
+ function italic(str) {
+ return run(str, code(3, 23));
+ }
+
+ function yellow(str) {
+ return run(str, code(33, 39));
+ }
+
+ function cyan(str) {
+ return run(str, code(36, 39));
+ }
+
+ function red(str) {
+ return run(str, code(31, 39));
+ }
+
+ function green(str) {
+ return run(str, code(32, 39));
+ }
+
+ function bgRed(str) {
+ return run(str, code(41, 49));
+ }
+
+ function white(str) {
+ return run(str, code(37, 39));
+ }
+
+ function gray(str) {
+ return run(str, code(90, 39));
+ }
+
+ function magenta(str) {
+ return run(str, code(35, 39));
+ }
+
+ function dim(str) {
+ return run(str, code(2, 22));
+ }
+
+ // https://github.com/chalk/ansi-regex/blob/2b56fb0c7a07108e5b54241e8faec160d393aedb/index.js
+ const ANSI_PATTERN = new RegExp(
+ [
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
+ ].join("|"),
+ "g",
+ );
+
+ function stripColor(string) {
+ return string.replace(ANSI_PATTERN, "");
+ }
+
+ function maybeColor(fn) {
+ return !(globalThis.Deno?.noColor ?? false) ? fn : (s) => s;
+ }
+
+ window.__bootstrap.colors = {
+ bold,
+ italic,
+ yellow,
+ cyan,
+ red,
+ green,
+ bgRed,
+ white,
+ gray,
+ magenta,
+ dim,
+ stripColor,
+ maybeColor,
+ };
+})(this);
diff --git a/runtime/rt/01_errors.js b/runtime/rt/01_errors.js
new file mode 100644
index 000000000..d5933069b
--- /dev/null
+++ b/runtime/rt/01_errors.js
@@ -0,0 +1,162 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ class NotFound extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotFound";
+ }
+ }
+
+ class PermissionDenied extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "PermissionDenied";
+ }
+ }
+
+ class ConnectionRefused extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionRefused";
+ }
+ }
+
+ class ConnectionReset extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionReset";
+ }
+ }
+
+ class ConnectionAborted extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionAborted";
+ }
+ }
+
+ class NotConnected extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotConnected";
+ }
+ }
+
+ class AddrInUse extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AddrInUse";
+ }
+ }
+
+ class AddrNotAvailable extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AddrNotAvailable";
+ }
+ }
+
+ class BrokenPipe extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "BrokenPipe";
+ }
+ }
+
+ class AlreadyExists extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AlreadyExists";
+ }
+ }
+
+ class InvalidData extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "InvalidData";
+ }
+ }
+
+ class TimedOut extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "TimedOut";
+ }
+ }
+
+ class Interrupted extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Interrupted";
+ }
+ }
+
+ class WriteZero extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "WriteZero";
+ }
+ }
+
+ class UnexpectedEof extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "UnexpectedEof";
+ }
+ }
+
+ class BadResource extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "BadResource";
+ }
+ }
+
+ class Http extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Http";
+ }
+ }
+
+ class Busy extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Busy";
+ }
+ }
+
+ class NotSupported extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotSupported";
+ }
+ }
+
+ const errors = {
+ NotFound,
+ PermissionDenied,
+ ConnectionRefused,
+ ConnectionReset,
+ ConnectionAborted,
+ NotConnected,
+ AddrInUse,
+ AddrNotAvailable,
+ BrokenPipe,
+ AlreadyExists,
+ InvalidData,
+ TimedOut,
+ Interrupted,
+ WriteZero,
+ UnexpectedEof,
+ BadResource,
+ Http,
+ Busy,
+ NotSupported,
+ };
+
+ window.__bootstrap.errors = {
+ errors,
+ };
+})(this);
diff --git a/runtime/rt/01_internals.js b/runtime/rt/01_internals.js
new file mode 100644
index 000000000..eee9eeaf7
--- /dev/null
+++ b/runtime/rt/01_internals.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const internalSymbol = Symbol("Deno.internal");
+
+ // The object where all the internal fields for testing will be living.
+ const internalObject = {};
+
+ // Register a field to internalObject for test access,
+ // through Deno[Deno.internal][name].
+ function exposeForTest(name, value) {
+ Object.defineProperty(internalObject, name, {
+ value,
+ enumerable: false,
+ });
+ }
+
+ window.__bootstrap.internals = {
+ internalSymbol,
+ internalObject,
+ exposeForTest,
+ };
+})(this);
diff --git a/runtime/rt/01_version.js b/runtime/rt/01_version.js
new file mode 100644
index 000000000..325e1156f
--- /dev/null
+++ b/runtime/rt/01_version.js
@@ -0,0 +1,26 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const version = {
+ deno: "",
+ v8: "",
+ typescript: "",
+ };
+
+ function setVersions(
+ denoVersion,
+ v8Version,
+ tsVersion,
+ ) {
+ version.deno = denoVersion;
+ version.v8 = v8Version;
+ version.typescript = tsVersion;
+
+ Object.freeze(version);
+ }
+
+ window.__bootstrap.version = {
+ version,
+ setVersions,
+ };
+})(this);
diff --git a/runtime/rt/01_web_util.js b/runtime/rt/01_web_util.js
new file mode 100644
index 000000000..a9573a71d
--- /dev/null
+++ b/runtime/rt/01_web_util.js
@@ -0,0 +1,160 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const illegalConstructorKey = Symbol("illegalConstructorKey");
+
+ function requiredArguments(
+ name,
+ length,
+ required,
+ ) {
+ if (length < required) {
+ const errMsg = `${name} requires at least ${required} argument${
+ required === 1 ? "" : "s"
+ }, but only ${length} present`;
+ throw new TypeError(errMsg);
+ }
+ }
+
+ const objectCloneMemo = new WeakMap();
+
+ function cloneArrayBuffer(
+ srcBuffer,
+ srcByteOffset,
+ srcLength,
+ _cloneConstructor,
+ ) {
+ // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
+ return srcBuffer.slice(
+ srcByteOffset,
+ srcByteOffset + srcLength,
+ );
+ }
+
+ /** Clone a value in a similar way to structured cloning. It is similar to a
+ * StructureDeserialize(StructuredSerialize(...)). */
+ function cloneValue(value) {
+ switch (typeof value) {
+ case "number":
+ case "string":
+ case "boolean":
+ case "undefined":
+ case "bigint":
+ return value;
+ case "object": {
+ if (objectCloneMemo.has(value)) {
+ return objectCloneMemo.get(value);
+ }
+ if (value === null) {
+ return value;
+ }
+ if (value instanceof Date) {
+ return new Date(value.valueOf());
+ }
+ if (value instanceof RegExp) {
+ return new RegExp(value);
+ }
+ if (value instanceof SharedArrayBuffer) {
+ return value;
+ }
+ if (value instanceof ArrayBuffer) {
+ const cloned = cloneArrayBuffer(
+ value,
+ 0,
+ value.byteLength,
+ ArrayBuffer,
+ );
+ objectCloneMemo.set(value, cloned);
+ return cloned;
+ }
+ if (ArrayBuffer.isView(value)) {
+ const clonedBuffer = cloneValue(value.buffer);
+ // Use DataViewConstructor type purely for type-checking, can be a
+ // DataView or TypedArray. They use the same constructor signature,
+ // only DataView has a length in bytes and TypedArrays use a length in
+ // terms of elements, so we adjust for that.
+ let length;
+ if (value instanceof DataView) {
+ length = value.byteLength;
+ } else {
+ length = value.length;
+ }
+ return new (value.constructor)(
+ clonedBuffer,
+ value.byteOffset,
+ length,
+ );
+ }
+ if (value instanceof Map) {
+ const clonedMap = new Map();
+ objectCloneMemo.set(value, clonedMap);
+ value.forEach((v, k) => {
+ clonedMap.set(cloneValue(k), cloneValue(v));
+ });
+ return clonedMap;
+ }
+ if (value instanceof Set) {
+ // assumes that cloneValue still takes only one argument
+ const clonedSet = new Set([...value].map(cloneValue));
+ objectCloneMemo.set(value, clonedSet);
+ return clonedSet;
+ }
+
+ // default for objects
+ const clonedObj = {};
+ objectCloneMemo.set(value, clonedObj);
+ const sourceKeys = Object.getOwnPropertyNames(value);
+ for (const key of sourceKeys) {
+ clonedObj[key] = cloneValue(value[key]);
+ }
+ Reflect.setPrototypeOf(clonedObj, Reflect.getPrototypeOf(value));
+ return clonedObj;
+ }
+ case "symbol":
+ case "function":
+ default:
+ throw new DOMException("Uncloneable value in stream", "DataCloneError");
+ }
+ }
+
+ 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;
+ }
+ function defineEventHandler(emitter, name, defaultValue = undefined) {
+ // HTML specification section 8.1.5.1
+ Object.defineProperty(emitter, `on${name}`, {
+ get() {
+ return this[handlerSymbol]?.get(name)?.handler ?? defaultValue;
+ },
+ 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,
+ });
+ }
+ window.__bootstrap.webUtil = {
+ illegalConstructorKey,
+ requiredArguments,
+ defineEventHandler,
+ cloneValue,
+ };
+})(this);
diff --git a/runtime/rt/02_console.js b/runtime/rt/02_console.js
new file mode 100644
index 000000000..971837bd6
--- /dev/null
+++ b/runtime/rt/02_console.js
@@ -0,0 +1,1732 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const exposeForTest = window.__bootstrap.internals.exposeForTest;
+ const colors = window.__bootstrap.colors;
+
+ function isInvalidDate(x) {
+ return isNaN(x.getTime());
+ }
+
+ function hasOwnProperty(obj, v) {
+ if (obj == null) {
+ return false;
+ }
+ return Object.prototype.hasOwnProperty.call(obj, v);
+ }
+
+ // Copyright Joyent, Inc. and other Node contributors. MIT license.
+ // Forked from Node's lib/internal/cli_table.js
+
+ function isTypedArray(x) {
+ return ArrayBuffer.isView(x) && !(x instanceof DataView);
+ }
+
+ const tableChars = {
+ middleMiddle: "─",
+ rowMiddle: "┼",
+ topRight: "┐",
+ topLeft: "┌",
+ leftMiddle: "├",
+ topMiddle: "┬",
+ bottomRight: "┘",
+ bottomLeft: "└",
+ bottomMiddle: "┴",
+ rightMiddle: "┤",
+ left: "│ ",
+ right: " │",
+ middle: " │ ",
+ };
+
+ function isFullWidthCodePoint(code) {
+ // Code points are partially derived from:
+ // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
+ return (
+ code >= 0x1100 &&
+ (code <= 0x115f || // Hangul Jamo
+ code === 0x2329 || // LEFT-POINTING ANGLE BRACKET
+ code === 0x232a || // RIGHT-POINTING ANGLE BRACKET
+ // CJK Radicals Supplement .. Enclosed CJK Letters and Months
+ (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
+ // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
+ (code >= 0x3250 && code <= 0x4dbf) ||
+ // CJK Unified Ideographs .. Yi Radicals
+ (code >= 0x4e00 && code <= 0xa4c6) ||
+ // Hangul Jamo Extended-A
+ (code >= 0xa960 && code <= 0xa97c) ||
+ // Hangul Syllables
+ (code >= 0xac00 && code <= 0xd7a3) ||
+ // CJK Compatibility Ideographs
+ (code >= 0xf900 && code <= 0xfaff) ||
+ // Vertical Forms
+ (code >= 0xfe10 && code <= 0xfe19) ||
+ // CJK Compatibility Forms .. Small Form Variants
+ (code >= 0xfe30 && code <= 0xfe6b) ||
+ // Halfwidth and Fullwidth Forms
+ (code >= 0xff01 && code <= 0xff60) ||
+ (code >= 0xffe0 && code <= 0xffe6) ||
+ // Kana Supplement
+ (code >= 0x1b000 && code <= 0x1b001) ||
+ // Enclosed Ideographic Supplement
+ (code >= 0x1f200 && code <= 0x1f251) ||
+ // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff
+ // Emoticons 0x1f600 - 0x1f64f
+ (code >= 0x1f300 && code <= 0x1f64f) ||
+ // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
+ (code >= 0x20000 && code <= 0x3fffd))
+ );
+ }
+
+ function getStringWidth(str) {
+ str = colors.stripColor(str).normalize("NFC");
+ let width = 0;
+
+ for (const ch of str) {
+ width += isFullWidthCodePoint(ch.codePointAt(0)) ? 2 : 1;
+ }
+
+ return width;
+ }
+
+ function renderRow(row, columnWidths) {
+ let out = tableChars.left;
+ for (let i = 0; i < row.length; i++) {
+ const cell = row[i];
+ const len = getStringWidth(cell);
+ const needed = (columnWidths[i] - len) / 2;
+ // round(needed) + ceil(needed) will always add up to the amount
+ // of spaces we need while also left justifying the output.
+ out += `${" ".repeat(needed)}${cell}${" ".repeat(Math.ceil(needed))}`;
+ if (i !== row.length - 1) {
+ out += tableChars.middle;
+ }
+ }
+ out += tableChars.right;
+ return out;
+ }
+
+ function cliTable(head, columns) {
+ const rows = [];
+ const columnWidths = head.map((h) => getStringWidth(h));
+ const longestColumn = columns.reduce(
+ (n, a) => Math.max(n, a.length),
+ 0,
+ );
+
+ for (let i = 0; i < head.length; i++) {
+ const column = columns[i];
+ for (let j = 0; j < longestColumn; j++) {
+ if (rows[j] === undefined) {
+ rows[j] = [];
+ }
+ const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : "");
+ const width = columnWidths[i] || 0;
+ const counted = getStringWidth(value);
+ columnWidths[i] = Math.max(width, counted);
+ }
+ }
+
+ const divider = columnWidths.map((i) =>
+ tableChars.middleMiddle.repeat(i + 2)
+ );
+
+ let result = `${tableChars.topLeft}${divider.join(tableChars.topMiddle)}` +
+ `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` +
+ `${tableChars.leftMiddle}${divider.join(tableChars.rowMiddle)}` +
+ `${tableChars.rightMiddle}\n`;
+
+ for (const row of rows) {
+ result += `${renderRow(row, columnWidths)}\n`;
+ }
+
+ result +=
+ `${tableChars.bottomLeft}${divider.join(tableChars.bottomMiddle)}` +
+ tableChars.bottomRight;
+
+ return result;
+ }
+ /* End of forked part */
+
+ const DEFAULT_INSPECT_OPTIONS = {
+ depth: 4,
+ indentLevel: 0,
+ sorted: false,
+ trailingComma: false,
+ compact: true,
+ iterableLimit: 100,
+ showProxy: false,
+ colors: false,
+ getters: false,
+ };
+
+ const DEFAULT_INDENT = " "; // Default indent string
+
+ const LINE_BREAKING_LENGTH = 80;
+ const MIN_GROUP_LENGTH = 6;
+ const STR_ABBREVIATE_SIZE = 100;
+
+ const PROMISE_STRING_BASE_LENGTH = 12;
+
+ class CSI {
+ static kClear = "\x1b[1;1H";
+ static kClearScreenDown = "\x1b[0J";
+ }
+
+ function getClassInstanceName(instance) {
+ if (typeof instance != "object") {
+ return "";
+ }
+ const constructor = instance?.constructor;
+ if (typeof constructor == "function") {
+ return constructor.name ?? "";
+ }
+ return "";
+ }
+
+ function maybeColor(fn, inspectOptions) {
+ return inspectOptions.colors ? fn : (s) => s;
+ }
+
+ function inspectFunction(value, _ctx) {
+ if (customInspect in value && typeof value[customInspect] === "function") {
+ try {
+ return String(value[customInspect]());
+ } catch {
+ // pass
+ }
+ }
+ // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction
+ let cstrName = Object.getPrototypeOf(value)?.constructor?.name;
+ if (!cstrName) {
+ // If prototype is removed or broken,
+ // use generic 'Function' instead.
+ cstrName = "Function";
+ }
+ if (value.name && value.name !== "anonymous") {
+ // from MDN spec
+ return `[${cstrName}: ${value.name}]`;
+ }
+ return `[${cstrName}]`;
+ }
+
+ function inspectIterable(
+ value,
+ ctx,
+ level,
+ options,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ if (level >= inspectOptions.depth) {
+ return cyan(`[${options.typeName}]`);
+ }
+ ctx.add(value);
+
+ const entries = [];
+
+ const iter = value.entries();
+ let entriesLength = 0;
+ const next = () => {
+ return iter.next();
+ };
+ for (const el of iter) {
+ if (entriesLength < inspectOptions.iterableLimit) {
+ entries.push(
+ options.entryHandler(
+ el,
+ ctx,
+ level + 1,
+ inspectOptions,
+ next.bind(iter),
+ ),
+ );
+ }
+ entriesLength++;
+ }
+ ctx.delete(value);
+
+ if (options.sort) {
+ entries.sort();
+ }
+
+ if (entriesLength > inspectOptions.iterableLimit) {
+ const nmore = entriesLength - inspectOptions.iterableLimit;
+ entries.push(`... ${nmore} more items`);
+ }
+
+ const iPrefix = `${options.displayName ? options.displayName + " " : ""}`;
+
+ const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
+ const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
+ const closingIndentation = `${inspectOptions.trailingComma ? "," : ""}\n${
+ DEFAULT_INDENT.repeat(level)
+ }`;
+
+ let iContent;
+ if (options.group && entries.length > MIN_GROUP_LENGTH) {
+ const groups = groupEntries(entries, level, value);
+ iContent = `${initIndentation}${
+ groups.join(entryIndentation)
+ }${closingIndentation}`;
+ } else {
+ iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
+ if (
+ colors.stripColor(iContent).length > LINE_BREAKING_LENGTH ||
+ !inspectOptions.compact
+ ) {
+ iContent = `${initIndentation}${
+ entries.join(entryIndentation)
+ }${closingIndentation}`;
+ }
+ }
+
+ return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`;
+ }
+
+ // Ported from Node.js
+ // Copyright Node.js contributors. All rights reserved.
+ function groupEntries(
+ entries,
+ level,
+ value,
+ iterableLimit = 100,
+ ) {
+ let totalLength = 0;
+ let maxLength = 0;
+ let entriesLength = entries.length;
+ if (iterableLimit < entriesLength) {
+ // This makes sure the "... n more items" part is not taken into account.
+ entriesLength--;
+ }
+ const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
+ const dataLen = new Array(entriesLength);
+ // Calculate the total length of all output entries and the individual max
+ // entries length of all output entries.
+ // IN PROGRESS: Colors are being taken into account.
+ for (let i = 0; i < entriesLength; i++) {
+ // Taking colors into account: removing the ANSI color
+ // codes from the string before measuring its length
+ const len = colors.stripColor(entries[i]).length;
+ dataLen[i] = len;
+ totalLength += len + separatorSpace;
+ if (maxLength < len) maxLength = len;
+ }
+ // Add two to `maxLength` as we add a single whitespace character plus a comma
+ // in-between two entries.
+ const actualMax = maxLength + separatorSpace;
+ // Check if at least three entries fit next to each other and prevent grouping
+ // of arrays that contains entries of very different length (i.e., if a single
+ // entry is longer than 1/5 of all other entries combined). Otherwise the
+ // space in-between small entries would be enormous.
+ if (
+ actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH &&
+ (totalLength / actualMax > 5 || maxLength <= 6)
+ ) {
+ const approxCharHeights = 2.5;
+ const averageBias = Math.sqrt(actualMax - totalLength / entries.length);
+ const biasedMax = Math.max(actualMax - 3 - averageBias, 1);
+ // Dynamically check how many columns seem possible.
+ const columns = Math.min(
+ // Ideally a square should be drawn. We expect a character to be about 2.5
+ // times as high as wide. This is the area formula to calculate a square
+ // which contains n rectangles of size `actualMax * approxCharHeights`.
+ // Divide that by `actualMax` to receive the correct number of columns.
+ // The added bias increases the columns for short entries.
+ Math.round(
+ Math.sqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax,
+ ),
+ // Do not exceed the breakLength.
+ Math.floor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax),
+ // Limit the columns to a maximum of fifteen.
+ 15,
+ );
+ // Return with the original output if no grouping should happen.
+ if (columns <= 1) {
+ return entries;
+ }
+ const tmp = [];
+ const maxLineLength = [];
+ for (let i = 0; i < columns; i++) {
+ let lineMaxLength = 0;
+ for (let j = i; j < entries.length; j += columns) {
+ if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j];
+ }
+ lineMaxLength += separatorSpace;
+ maxLineLength[i] = lineMaxLength;
+ }
+ let order = "padStart";
+ if (value !== undefined) {
+ for (let i = 0; i < entries.length; i++) {
+ if (
+ typeof value[i] !== "number" &&
+ typeof value[i] !== "bigint"
+ ) {
+ order = "padEnd";
+ break;
+ }
+ }
+ }
+ // Each iteration creates a single line of grouped entries.
+ for (let i = 0; i < entriesLength; i += columns) {
+ // The last lines may contain less entries than columns.
+ const max = Math.min(i + columns, entriesLength);
+ let str = "";
+ let j = i;
+ for (; j < max - 1; j++) {
+ const lengthOfColorCodes = entries[j].length - dataLen[j];
+ const padding = maxLineLength[j - i] + lengthOfColorCodes;
+ str += `${entries[j]}, `[order](padding, " ");
+ }
+ if (order === "padStart") {
+ const lengthOfColorCodes = entries[j].length - dataLen[j];
+ const padding = maxLineLength[j - i] +
+ lengthOfColorCodes -
+ separatorSpace;
+ str += entries[j].padStart(padding, " ");
+ } else {
+ str += entries[j];
+ }
+ tmp.push(str);
+ }
+ if (iterableLimit < entries.length) {
+ tmp.push(entries[entriesLength]);
+ }
+ entries = tmp;
+ }
+ return entries;
+ }
+
+ function inspectValue(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const proxyDetails = core.getProxyDetails(value);
+ if (proxyDetails != null) {
+ return inspectOptions.showProxy
+ ? inspectProxy(proxyDetails, ctx, level, inspectOptions)
+ : inspectValue(proxyDetails[0], ctx, level, inspectOptions);
+ }
+
+ const green = maybeColor(colors.green, inspectOptions);
+ const yellow = maybeColor(colors.yellow, inspectOptions);
+ const dim = maybeColor(colors.dim, inspectOptions);
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ const bold = maybeColor(colors.bold, inspectOptions);
+ const red = maybeColor(colors.red, inspectOptions);
+
+ switch (typeof value) {
+ case "string":
+ return green(quoteString(value));
+ case "number": // Numbers are yellow
+ // Special handling of -0
+ return yellow(Object.is(value, -0) ? "-0" : `${value}`);
+ case "boolean": // booleans are yellow
+ return yellow(String(value));
+ case "undefined": // undefined is dim
+ return dim(String(value));
+ case "symbol": // Symbols are green
+ return green(maybeQuoteSymbol(value));
+ case "bigint": // Bigints are yellow
+ return yellow(`${value}n`);
+ case "function": // Function string is cyan
+ return cyan(inspectFunction(value, ctx));
+ case "object": // null is bold
+ if (value === null) {
+ return bold("null");
+ }
+
+ if (ctx.has(value)) {
+ // Circular string is cyan
+ return cyan("[Circular]");
+ }
+
+ return inspectObject(value, ctx, level, inspectOptions);
+ default:
+ // Not implemented is red
+ return red("[Not Implemented]");
+ }
+ }
+
+ // We can match Node's quoting behavior exactly by swapping the double quote and
+ // single quote in this array. That would give preference to single quotes.
+ // However, we prefer double quotes as the default.
+ const QUOTES = ['"', "'", "`"];
+
+ /** Surround the string in quotes.
+ *
+ * The quote symbol is chosen by taking the first of the `QUOTES` array which
+ * does not occur in the string. If they all occur, settle with `QUOTES[0]`.
+ *
+ * Insert a backslash before any occurrence of the chosen quote symbol and
+ * before any backslash.
+ */
+ function quoteString(string) {
+ const quote = QUOTES.find((c) => !string.includes(c)) ?? QUOTES[0];
+ const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g");
+ string = string.replace(escapePattern, "\\");
+ string = replaceEscapeSequences(string);
+ return `${quote}${string}${quote}`;
+ }
+
+ // Replace escape sequences that can modify output.
+ function replaceEscapeSequences(string) {
+ return string
+ .replace(/[\b]/g, "\\b")
+ .replace(/\f/g, "\\f")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/\t/g, "\\t")
+ .replace(/\v/g, "\\v")
+ .replace(
+ // deno-lint-ignore no-control-regex
+ /[\x00-\x1f\x7f-\x9f]/g,
+ (c) => "\\x" + c.charCodeAt(0).toString(16).padStart(2, "0"),
+ );
+ }
+
+ // Surround a string with quotes when it is required (e.g the string not a valid identifier).
+ function maybeQuoteString(string) {
+ if (/^[a-zA-Z_][a-zA-Z_0-9]*$/.test(string)) {
+ return replaceEscapeSequences(string);
+ }
+
+ return quoteString(string);
+ }
+
+ // Surround a symbol's description in quotes when it is required (e.g the description has non printable characters).
+ function maybeQuoteSymbol(symbol) {
+ if (symbol.description === undefined) {
+ return symbol.toString();
+ }
+
+ if (/^[a-zA-Z_][a-zA-Z_.0-9]*$/.test(symbol.description)) {
+ return symbol.toString();
+ }
+
+ return `Symbol(${quoteString(symbol.description)})`;
+ }
+
+ // Print strings when they are inside of arrays or objects with quotes
+ function inspectValueWithQuotes(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const green = maybeColor(colors.green, inspectOptions);
+ switch (typeof value) {
+ case "string": {
+ const trunc = value.length > STR_ABBREVIATE_SIZE
+ ? value.slice(0, STR_ABBREVIATE_SIZE) + "..."
+ : value;
+ return green(quoteString(trunc)); // Quoted strings are green
+ }
+ default:
+ return inspectValue(value, ctx, level, inspectOptions);
+ }
+ }
+
+ function inspectArray(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const dim = maybeColor(colors.dim, inspectOptions);
+ const options = {
+ typeName: "Array",
+ displayName: "",
+ delims: ["[", "]"],
+ entryHandler: (entry, ctx, level, inspectOptions, next) => {
+ const [index, val] = entry;
+ let i = index;
+ if (!value.hasOwnProperty(i)) {
+ i++;
+ while (!value.hasOwnProperty(i) && i < value.length) {
+ next();
+ i++;
+ }
+ const emptyItems = i - index;
+ const ending = emptyItems > 1 ? "s" : "";
+ return dim(`<${emptyItems} empty item${ending}>`);
+ } else {
+ return inspectValueWithQuotes(val, ctx, level, inspectOptions);
+ }
+ },
+ group: inspectOptions.compact,
+ sort: false,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectTypedArray(
+ typedArrayName,
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const valueLength = value.length;
+ const options = {
+ typeName: typedArrayName,
+ displayName: `${typedArrayName}(${valueLength})`,
+ delims: ["[", "]"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const val = entry[1];
+ return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
+ },
+ group: inspectOptions.compact,
+ sort: false,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectSet(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const options = {
+ typeName: "Set",
+ displayName: "Set",
+ delims: ["{", "}"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const val = entry[1];
+ return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
+ },
+ group: false,
+ sort: inspectOptions.sorted,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectMap(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const options = {
+ typeName: "Map",
+ displayName: "Map",
+ delims: ["{", "}"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const [key, val] = entry;
+ return `${
+ inspectValueWithQuotes(
+ key,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ } => ${inspectValueWithQuotes(val, ctx, level + 1, inspectOptions)}`;
+ },
+ group: false,
+ sort: inspectOptions.sorted,
+ };
+ return inspectIterable(
+ value,
+ ctx,
+ level,
+ options,
+ inspectOptions,
+ );
+ }
+
+ function inspectWeakSet(inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
+ }
+
+ function inspectWeakMap(inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
+ }
+
+ function inspectDate(value, inspectOptions) {
+ // without quotes, ISO format, in magenta like before
+ const magenta = maybeColor(colors.magenta, inspectOptions);
+ return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString());
+ }
+
+ function inspectRegExp(value, inspectOptions) {
+ const red = maybeColor(colors.red, inspectOptions);
+ return red(value.toString()); // RegExps are red
+ }
+
+ function inspectStringObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan
+ }
+
+ function inspectBooleanObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan
+ }
+
+ function inspectNumberObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan
+ }
+
+ const PromiseState = {
+ Pending: 0,
+ Fulfilled: 1,
+ Rejected: 2,
+ };
+
+ function inspectPromise(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ const red = maybeColor(colors.red, inspectOptions);
+
+ const [state, result] = core.getPromiseDetails(value);
+
+ if (state === PromiseState.Pending) {
+ return `Promise { ${cyan("<pending>")} }`;
+ }
+
+ const prefix = state === PromiseState.Fulfilled
+ ? ""
+ : `${red("<rejected>")} `;
+
+ const str = `${prefix}${
+ inspectValueWithQuotes(
+ result,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ }`;
+
+ if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
+ return `Promise {\n${DEFAULT_INDENT.repeat(level + 1)}${str}\n}`;
+ }
+
+ return `Promise { ${str} }`;
+ }
+
+ function inspectProxy(
+ targetAndHandler,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ return `Proxy ${
+ inspectArray(targetAndHandler, ctx, level, inspectOptions)
+ }`;
+ }
+
+ function inspectRawObject(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+
+ if (level >= inspectOptions.depth) {
+ return cyan("[Object]"); // wrappers are in cyan
+ }
+ ctx.add(value);
+
+ let baseString;
+
+ let shouldShowDisplayName = false;
+ let displayName = value[
+ Symbol.toStringTag
+ ];
+ if (!displayName) {
+ displayName = getClassInstanceName(value);
+ }
+ if (
+ displayName && displayName !== "Object" && displayName !== "anonymous"
+ ) {
+ shouldShowDisplayName = true;
+ }
+
+ const entries = [];
+ const stringKeys = Object.keys(value);
+ const symbolKeys = Object.getOwnPropertySymbols(value);
+ if (inspectOptions.sorted) {
+ stringKeys.sort();
+ symbolKeys.sort((s1, s2) =>
+ (s1.description ?? "").localeCompare(s2.description ?? "")
+ );
+ }
+
+ const red = maybeColor(colors.red, inspectOptions);
+
+ for (const key of stringKeys) {
+ if (inspectOptions.getters) {
+ let propertyValue;
+ let error = null;
+ try {
+ propertyValue = value[key];
+ } catch (error_) {
+ error = error_;
+ }
+ const inspectedValue = error == null
+ ? inspectValueWithQuotes(
+ propertyValue,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ : red(`[Thrown ${error.name}: ${error.message}]`);
+ entries.push(`${maybeQuoteString(key)}: ${inspectedValue}`);
+ } else {
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
+ if (descriptor.get !== undefined && descriptor.set !== undefined) {
+ entries.push(`${maybeQuoteString(key)}: [Getter/Setter]`);
+ } else if (descriptor.get !== undefined) {
+ entries.push(`${maybeQuoteString(key)}: [Getter]`);
+ } else {
+ entries.push(
+ `${maybeQuoteString(key)}: ${
+ inspectValueWithQuotes(value[key], ctx, level + 1, inspectOptions)
+ }`,
+ );
+ }
+ }
+ }
+
+ for (const key of symbolKeys) {
+ if (inspectOptions.getters) {
+ let propertyValue;
+ let error;
+ try {
+ propertyValue = value[key];
+ } catch (error_) {
+ error = error_;
+ }
+ const inspectedValue = error == null
+ ? inspectValueWithQuotes(
+ propertyValue,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ : red(`Thrown ${error.name}: ${error.message}`);
+ entries.push(`[${maybeQuoteSymbol(key)}]: ${inspectedValue}`);
+ } else {
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
+ if (descriptor.get !== undefined && descriptor.set !== undefined) {
+ entries.push(`[${maybeQuoteSymbol(key)}]: [Getter/Setter]`);
+ } else if (descriptor.get !== undefined) {
+ entries.push(`[${maybeQuoteSymbol(key)}]: [Getter]`);
+ } else {
+ entries.push(
+ `[${maybeQuoteSymbol(key)}]: ${
+ inspectValueWithQuotes(value[key], ctx, level + 1, inspectOptions)
+ }`,
+ );
+ }
+ }
+ }
+
+ // Making sure color codes are ignored when calculating the total length
+ const totalLength = entries.length + level +
+ colors.stripColor(entries.join("")).length;
+
+ ctx.delete(value);
+
+ if (entries.length === 0) {
+ baseString = "{}";
+ } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) {
+ const entryIndent = DEFAULT_INDENT.repeat(level + 1);
+ const closingIndent = DEFAULT_INDENT.repeat(level);
+ baseString = `{\n${entryIndent}${entries.join(`,\n${entryIndent}`)}${
+ inspectOptions.trailingComma ? "," : ""
+ }\n${closingIndent}}`;
+ } else {
+ baseString = `{ ${entries.join(", ")} }`;
+ }
+
+ if (shouldShowDisplayName) {
+ baseString = `${displayName} ${baseString}`;
+ }
+
+ return baseString;
+ }
+
+ function inspectObject(
+ value,
+ consoleContext,
+ level,
+ inspectOptions,
+ ) {
+ if (customInspect in value && typeof value[customInspect] === "function") {
+ try {
+ return String(value[customInspect]());
+ } catch {
+ // pass
+ }
+ }
+ // This non-unique symbol is used to support op_crates, ie.
+ // in op_crates/web we don't want to depend on unique "Deno.customInspect"
+ // symbol defined in the public API. Internal only, shouldn't be used
+ // by users.
+ const nonUniqueCustomInspect = Symbol.for("Deno.customInspect");
+ if (
+ nonUniqueCustomInspect in value &&
+ typeof value[nonUniqueCustomInspect] === "function"
+ ) {
+ try {
+ return String(value[nonUniqueCustomInspect]());
+ } catch {
+ // pass
+ }
+ }
+ if (value instanceof Error) {
+ return String(value.stack);
+ } else if (Array.isArray(value)) {
+ return inspectArray(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof Number) {
+ return inspectNumberObject(value, inspectOptions);
+ } else if (value instanceof Boolean) {
+ return inspectBooleanObject(value, inspectOptions);
+ } else if (value instanceof String) {
+ return inspectStringObject(value, inspectOptions);
+ } else if (value instanceof Promise) {
+ return inspectPromise(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof RegExp) {
+ return inspectRegExp(value, inspectOptions);
+ } else if (value instanceof Date) {
+ return inspectDate(value, inspectOptions);
+ } else if (value instanceof Set) {
+ return inspectSet(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof Map) {
+ return inspectMap(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof WeakSet) {
+ return inspectWeakSet(inspectOptions);
+ } else if (value instanceof WeakMap) {
+ return inspectWeakMap(inspectOptions);
+ } else if (isTypedArray(value)) {
+ return inspectTypedArray(
+ Object.getPrototypeOf(value).constructor.name,
+ value,
+ consoleContext,
+ level,
+ inspectOptions,
+ );
+ } else {
+ // Otherwise, default object formatting
+ return inspectRawObject(value, consoleContext, level, inspectOptions);
+ }
+ }
+
+ const colorKeywords = new Map([
+ ["black", "#000000"],
+ ["silver", "#c0c0c0"],
+ ["gray", "#808080"],
+ ["white", "#ffffff"],
+ ["maroon", "#800000"],
+ ["red", "#ff0000"],
+ ["purple", "#800080"],
+ ["fuchsia", "#ff00ff"],
+ ["green", "#008000"],
+ ["lime", "#00ff00"],
+ ["olive", "#808000"],
+ ["yellow", "#ffff00"],
+ ["navy", "#000080"],
+ ["blue", "#0000ff"],
+ ["teal", "#008080"],
+ ["aqua", "#00ffff"],
+ ["orange", "#ffa500"],
+ ["aliceblue", "#f0f8ff"],
+ ["antiquewhite", "#faebd7"],
+ ["aquamarine", "#7fffd4"],
+ ["azure", "#f0ffff"],
+ ["beige", "#f5f5dc"],
+ ["bisque", "#ffe4c4"],
+ ["blanchedalmond", "#ffebcd"],
+ ["blueviolet", "#8a2be2"],
+ ["brown", "#a52a2a"],
+ ["burlywood", "#deb887"],
+ ["cadetblue", "#5f9ea0"],
+ ["chartreuse", "#7fff00"],
+ ["chocolate", "#d2691e"],
+ ["coral", "#ff7f50"],
+ ["cornflowerblue", "#6495ed"],
+ ["cornsilk", "#fff8dc"],
+ ["crimson", "#dc143c"],
+ ["cyan", "#00ffff"],
+ ["darkblue", "#00008b"],
+ ["darkcyan", "#008b8b"],
+ ["darkgoldenrod", "#b8860b"],
+ ["darkgray", "#a9a9a9"],
+ ["darkgreen", "#006400"],
+ ["darkgrey", "#a9a9a9"],
+ ["darkkhaki", "#bdb76b"],
+ ["darkmagenta", "#8b008b"],
+ ["darkolivegreen", "#556b2f"],
+ ["darkorange", "#ff8c00"],
+ ["darkorchid", "#9932cc"],
+ ["darkred", "#8b0000"],
+ ["darksalmon", "#e9967a"],
+ ["darkseagreen", "#8fbc8f"],
+ ["darkslateblue", "#483d8b"],
+ ["darkslategray", "#2f4f4f"],
+ ["darkslategrey", "#2f4f4f"],
+ ["darkturquoise", "#00ced1"],
+ ["darkviolet", "#9400d3"],
+ ["deeppink", "#ff1493"],
+ ["deepskyblue", "#00bfff"],
+ ["dimgray", "#696969"],
+ ["dimgrey", "#696969"],
+ ["dodgerblue", "#1e90ff"],
+ ["firebrick", "#b22222"],
+ ["floralwhite", "#fffaf0"],
+ ["forestgreen", "#228b22"],
+ ["gainsboro", "#dcdcdc"],
+ ["ghostwhite", "#f8f8ff"],
+ ["gold", "#ffd700"],
+ ["goldenrod", "#daa520"],
+ ["greenyellow", "#adff2f"],
+ ["grey", "#808080"],
+ ["honeydew", "#f0fff0"],
+ ["hotpink", "#ff69b4"],
+ ["indianred", "#cd5c5c"],
+ ["indigo", "#4b0082"],
+ ["ivory", "#fffff0"],
+ ["khaki", "#f0e68c"],
+ ["lavender", "#e6e6fa"],
+ ["lavenderblush", "#fff0f5"],
+ ["lawngreen", "#7cfc00"],
+ ["lemonchiffon", "#fffacd"],
+ ["lightblue", "#add8e6"],
+ ["lightcoral", "#f08080"],
+ ["lightcyan", "#e0ffff"],
+ ["lightgoldenrodyellow", "#fafad2"],
+ ["lightgray", "#d3d3d3"],
+ ["lightgreen", "#90ee90"],
+ ["lightgrey", "#d3d3d3"],
+ ["lightpink", "#ffb6c1"],
+ ["lightsalmon", "#ffa07a"],
+ ["lightseagreen", "#20b2aa"],
+ ["lightskyblue", "#87cefa"],
+ ["lightslategray", "#778899"],
+ ["lightslategrey", "#778899"],
+ ["lightsteelblue", "#b0c4de"],
+ ["lightyellow", "#ffffe0"],
+ ["limegreen", "#32cd32"],
+ ["linen", "#faf0e6"],
+ ["magenta", "#ff00ff"],
+ ["mediumaquamarine", "#66cdaa"],
+ ["mediumblue", "#0000cd"],
+ ["mediumorchid", "#ba55d3"],
+ ["mediumpurple", "#9370db"],
+ ["mediumseagreen", "#3cb371"],
+ ["mediumslateblue", "#7b68ee"],
+ ["mediumspringgreen", "#00fa9a"],
+ ["mediumturquoise", "#48d1cc"],
+ ["mediumvioletred", "#c71585"],
+ ["midnightblue", "#191970"],
+ ["mintcream", "#f5fffa"],
+ ["mistyrose", "#ffe4e1"],
+ ["moccasin", "#ffe4b5"],
+ ["navajowhite", "#ffdead"],
+ ["oldlace", "#fdf5e6"],
+ ["olivedrab", "#6b8e23"],
+ ["orangered", "#ff4500"],
+ ["orchid", "#da70d6"],
+ ["palegoldenrod", "#eee8aa"],
+ ["palegreen", "#98fb98"],
+ ["paleturquoise", "#afeeee"],
+ ["palevioletred", "#db7093"],
+ ["papayawhip", "#ffefd5"],
+ ["peachpuff", "#ffdab9"],
+ ["peru", "#cd853f"],
+ ["pink", "#ffc0cb"],
+ ["plum", "#dda0dd"],
+ ["powderblue", "#b0e0e6"],
+ ["rosybrown", "#bc8f8f"],
+ ["royalblue", "#4169e1"],
+ ["saddlebrown", "#8b4513"],
+ ["salmon", "#fa8072"],
+ ["sandybrown", "#f4a460"],
+ ["seagreen", "#2e8b57"],
+ ["seashell", "#fff5ee"],
+ ["sienna", "#a0522d"],
+ ["skyblue", "#87ceeb"],
+ ["slateblue", "#6a5acd"],
+ ["slategray", "#708090"],
+ ["slategrey", "#708090"],
+ ["snow", "#fffafa"],
+ ["springgreen", "#00ff7f"],
+ ["steelblue", "#4682b4"],
+ ["tan", "#d2b48c"],
+ ["thistle", "#d8bfd8"],
+ ["tomato", "#ff6347"],
+ ["turquoise", "#40e0d0"],
+ ["violet", "#ee82ee"],
+ ["wheat", "#f5deb3"],
+ ["whitesmoke", "#f5f5f5"],
+ ["yellowgreen", "#9acd32"],
+ ["rebeccapurple", "#663399"],
+ ]);
+
+ function parseCssColor(colorString) {
+ if (colorKeywords.has(colorString)) {
+ colorString = colorKeywords.get(colorString);
+ }
+ // deno-fmt-ignore
+ const hashMatch = colorString.match(/^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/);
+ if (hashMatch != null) {
+ return [
+ Number(`0x${hashMatch[1]}`),
+ Number(`0x${hashMatch[2]}`),
+ Number(`0x${hashMatch[3]}`),
+ ];
+ }
+ // deno-fmt-ignore
+ const smallHashMatch = colorString.match(/^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/);
+ if (smallHashMatch != null) {
+ return [
+ Number(`0x${smallHashMatch[1]}0`),
+ Number(`0x${smallHashMatch[2]}0`),
+ Number(`0x${smallHashMatch[3]}0`),
+ ];
+ }
+ // deno-fmt-ignore
+ const rgbMatch = colorString.match(/^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/);
+ if (rgbMatch != null) {
+ return [
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[1])))),
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[2])))),
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[3])))),
+ ];
+ }
+ // deno-fmt-ignore
+ const hslMatch = colorString.match(/^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/);
+ if (hslMatch != null) {
+ // https://www.rapidtables.com/convert/color/hsl-to-rgb.html
+ let h = Number(hslMatch[1]) % 360;
+ if (h < 0) {
+ h += 360;
+ }
+ const s = Math.max(0, Math.min(100, Number(hslMatch[2]))) / 100;
+ const l = Math.max(0, Math.min(100, Number(hslMatch[3]))) / 100;
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+ const m = l - c / 2;
+ let r_;
+ let g_;
+ let b_;
+ if (h < 60) {
+ [r_, g_, b_] = [c, x, 0];
+ } else if (h < 120) {
+ [r_, g_, b_] = [x, c, 0];
+ } else if (h < 180) {
+ [r_, g_, b_] = [0, c, x];
+ } else if (h < 240) {
+ [r_, g_, b_] = [0, x, c];
+ } else if (h < 300) {
+ [r_, g_, b_] = [x, 0, c];
+ } else {
+ [r_, g_, b_] = [c, 0, x];
+ }
+ return [
+ Math.round((r_ + m) * 255),
+ Math.round((g_ + m) * 255),
+ Math.round((b_ + m) * 255),
+ ];
+ }
+ return null;
+ }
+
+ function getDefaultCss() {
+ return {
+ backgroundColor: null,
+ color: null,
+ fontWeight: null,
+ fontStyle: null,
+ textDecorationColor: null,
+ textDecorationLine: [],
+ };
+ }
+
+ function parseCss(cssString) {
+ const css = getDefaultCss();
+
+ const rawEntries = [];
+ let inValue = false;
+ let currentKey = null;
+ let parenthesesDepth = 0;
+ let currentPart = "";
+ for (let i = 0; i < cssString.length; i++) {
+ const c = cssString[i];
+ if (c == "(") {
+ parenthesesDepth++;
+ } else if (parenthesesDepth > 0) {
+ if (c == ")") {
+ parenthesesDepth--;
+ }
+ } else if (inValue) {
+ if (c == ";") {
+ const value = currentPart.trim();
+ if (value != "") {
+ rawEntries.push([currentKey, value]);
+ }
+ currentKey = null;
+ currentPart = "";
+ inValue = false;
+ continue;
+ }
+ } else if (c == ":") {
+ currentKey = currentPart.trim();
+ currentPart = "";
+ inValue = true;
+ continue;
+ }
+ currentPart += c;
+ }
+ if (inValue && parenthesesDepth == 0) {
+ const value = currentPart.trim();
+ if (value != "") {
+ rawEntries.push([currentKey, value]);
+ }
+ currentKey = null;
+ currentPart = "";
+ }
+
+ for (const [key, value] of rawEntries) {
+ if (key == "background-color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.backgroundColor = color;
+ }
+ } else if (key == "color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.color = color;
+ }
+ } else if (key == "font-weight") {
+ if (value == "bold") {
+ css.fontWeight = value;
+ }
+ } else if (key == "font-style") {
+ if (["italic", "oblique", "oblique 14deg"].includes(value)) {
+ css.fontStyle = "italic";
+ }
+ } else if (key == "text-decoration-line") {
+ css.textDecorationLine = [];
+ for (const lineType of value.split(/\s+/g)) {
+ if (["line-through", "overline", "underline"].includes(lineType)) {
+ css.textDecorationLine.push(lineType);
+ }
+ }
+ } else if (key == "text-decoration-color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.textDecorationColor = color;
+ }
+ } else if (key == "text-decoration") {
+ css.textDecorationColor = null;
+ css.textDecorationLine = [];
+ for (const arg of value.split(/\s+/g)) {
+ const maybeColor = parseCssColor(arg);
+ if (maybeColor != null) {
+ css.textDecorationColor = maybeColor;
+ } else if (["line-through", "overline", "underline"].includes(arg)) {
+ css.textDecorationLine.push(arg);
+ }
+ }
+ }
+ }
+
+ return css;
+ }
+
+ function colorEquals(color1, color2) {
+ return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] &&
+ color1?.[2] == color2?.[2];
+ }
+
+ function cssToAnsi(css, prevCss = null) {
+ prevCss = prevCss ?? getDefaultCss();
+ let ansi = "";
+ if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) {
+ if (css.backgroundColor != null) {
+ const [r, g, b] = css.backgroundColor;
+ ansi += `\x1b[48;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[49m";
+ }
+ }
+ if (!colorEquals(css.color, prevCss.color)) {
+ if (css.color != null) {
+ const [r, g, b] = css.color;
+ ansi += `\x1b[38;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[39m";
+ }
+ }
+ if (css.fontWeight != prevCss.fontWeight) {
+ if (css.fontWeight == "bold") {
+ ansi += `\x1b[1m`;
+ } else {
+ ansi += "\x1b[22m";
+ }
+ }
+ if (css.fontStyle != prevCss.fontStyle) {
+ if (css.fontStyle == "italic") {
+ ansi += `\x1b[3m`;
+ } else {
+ ansi += "\x1b[23m";
+ }
+ }
+ if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) {
+ if (css.textDecorationColor != null) {
+ const [r, g, b] = css.textDecorationColor;
+ ansi += `\x1b[58;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[59m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("line-through") !=
+ prevCss.textDecorationLine.includes("line-through")
+ ) {
+ if (css.textDecorationLine.includes("line-through")) {
+ ansi += "\x1b[9m";
+ } else {
+ ansi += "\x1b[29m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("overline") !=
+ prevCss.textDecorationLine.includes("overline")
+ ) {
+ if (css.textDecorationLine.includes("overline")) {
+ ansi += "\x1b[53m";
+ } else {
+ ansi += "\x1b[55m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("underline") !=
+ prevCss.textDecorationLine.includes("underline")
+ ) {
+ if (css.textDecorationLine.includes("underline")) {
+ ansi += "\x1b[4m";
+ } else {
+ ansi += "\x1b[24m";
+ }
+ }
+ return ansi;
+ }
+
+ function inspectArgs(args, inspectOptions = {}) {
+ const noColor = globalThis.Deno?.noColor ?? true;
+ const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions };
+ const first = args[0];
+ let a = 0;
+ let string = "";
+
+ if (typeof first == "string" && args.length > 1) {
+ a++;
+ // Index of the first not-yet-appended character. Use this so we only
+ // have to append to `string` when a substitution occurs / at the end.
+ let appendedChars = 0;
+ let usedStyle = false;
+ let prevCss = null;
+ for (let i = 0; i < first.length - 1; i++) {
+ if (first[i] == "%") {
+ const char = first[++i];
+ if (a < args.length) {
+ let formattedArg = null;
+ if (char == "s") {
+ // Format as a string.
+ formattedArg = String(args[a++]);
+ } else if (["d", "i"].includes(char)) {
+ // Format as an integer.
+ const value = args[a++];
+ if (typeof value == "bigint") {
+ formattedArg = `${value}n`;
+ } else if (typeof value == "number") {
+ formattedArg = `${parseInt(String(value))}`;
+ } else {
+ formattedArg = "NaN";
+ }
+ } else if (char == "f") {
+ // Format as a floating point value.
+ const value = args[a++];
+ if (typeof value == "number") {
+ formattedArg = `${value}`;
+ } else {
+ formattedArg = "NaN";
+ }
+ } else if (["O", "o"].includes(char)) {
+ // Format as an object.
+ formattedArg = inspectValue(
+ args[a++],
+ new Set(),
+ 0,
+ rInspectOptions,
+ );
+ } else if (char == "c") {
+ const value = args[a++];
+ if (!noColor) {
+ const css = parseCss(value);
+ formattedArg = cssToAnsi(css, prevCss);
+ if (formattedArg != "") {
+ usedStyle = true;
+ prevCss = css;
+ }
+ } else {
+ formattedArg = "";
+ }
+ }
+
+ if (formattedArg != null) {
+ string += first.slice(appendedChars, i - 1) + formattedArg;
+ appendedChars = i + 1;
+ }
+ }
+ if (char == "%") {
+ string += first.slice(appendedChars, i - 1) + "%";
+ appendedChars = i + 1;
+ }
+ }
+ }
+ string += first.slice(appendedChars);
+ if (usedStyle) {
+ string += "\x1b[0m";
+ }
+ }
+
+ for (; a < args.length; a++) {
+ if (a > 0) {
+ string += " ";
+ }
+ if (typeof args[a] == "string") {
+ string += args[a];
+ } else {
+ // Use default maximum depth for null or undefined arguments.
+ string += inspectValue(args[a], new Set(), 0, rInspectOptions);
+ }
+ }
+
+ if (rInspectOptions.indentLevel > 0) {
+ const groupIndent = DEFAULT_INDENT.repeat(rInspectOptions.indentLevel);
+ string = groupIndent + string.replaceAll("\n", `\n${groupIndent}`);
+ }
+
+ return string;
+ }
+
+ const countMap = new Map();
+ const timerMap = new Map();
+ const isConsoleInstance = Symbol("isConsoleInstance");
+
+ function getConsoleInspectOptions() {
+ return {
+ ...DEFAULT_INSPECT_OPTIONS,
+ colors: !(globalThis.Deno?.noColor ?? false),
+ };
+ }
+
+ class Console {
+ #printFunc = null;
+ [isConsoleInstance] = false;
+
+ constructor(printFunc) {
+ this.#printFunc = printFunc;
+ this.indentLevel = 0;
+ this[isConsoleInstance] = true;
+
+ // ref https://console.spec.whatwg.org/#console-namespace
+ // For historical web-compatibility reasons, the namespace object for
+ // console must have as its [[Prototype]] an empty object, created as if
+ // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%.
+ const console = Object.create({});
+ Object.assign(console, this);
+ return console;
+ }
+
+ log = (...args) => {
+ this.#printFunc(
+ inspectArgs(args, {
+ ...getConsoleInspectOptions(),
+ indentLevel: this.indentLevel,
+ }) + "\n",
+ false,
+ );
+ };
+
+ debug = this.log;
+ info = this.log;
+
+ dir = (obj, options = {}) => {
+ this.#printFunc(
+ inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) +
+ "\n",
+ false,
+ );
+ };
+
+ dirxml = this.dir;
+
+ warn = (...args) => {
+ this.#printFunc(
+ inspectArgs(args, {
+ ...getConsoleInspectOptions(),
+ indentLevel: this.indentLevel,
+ }) + "\n",
+ true,
+ );
+ };
+
+ error = this.warn;
+
+ assert = (condition = false, ...args) => {
+ if (condition) {
+ return;
+ }
+
+ if (args.length === 0) {
+ this.error("Assertion failed");
+ return;
+ }
+
+ const [first, ...rest] = args;
+
+ if (typeof first === "string") {
+ this.error(`Assertion failed: ${first}`, ...rest);
+ return;
+ }
+
+ this.error(`Assertion failed:`, ...args);
+ };
+
+ count = (label = "default") => {
+ label = String(label);
+
+ if (countMap.has(label)) {
+ const current = countMap.get(label) || 0;
+ countMap.set(label, current + 1);
+ } else {
+ countMap.set(label, 1);
+ }
+
+ this.info(`${label}: ${countMap.get(label)}`);
+ };
+
+ countReset = (label = "default") => {
+ label = String(label);
+
+ if (countMap.has(label)) {
+ countMap.set(label, 0);
+ } else {
+ this.warn(`Count for '${label}' does not exist`);
+ }
+ };
+
+ table = (data, properties) => {
+ if (properties !== undefined && !Array.isArray(properties)) {
+ throw new Error(
+ "The 'properties' argument must be of type Array. " +
+ "Received type string",
+ );
+ }
+
+ if (data === null || typeof data !== "object") {
+ return this.log(data);
+ }
+
+ const objectValues = {};
+ const indexKeys = [];
+ const values = [];
+
+ const stringifyValue = (value) =>
+ inspectValueWithQuotes(value, new Set(), 0, {
+ ...DEFAULT_INSPECT_OPTIONS,
+ depth: 1,
+ });
+ const toTable = (header, body) => this.log(cliTable(header, body));
+ const createColumn = (value, shift) => [
+ ...(shift ? [...new Array(shift)].map(() => "") : []),
+ stringifyValue(value),
+ ];
+
+ let resultData;
+ const isSet = data instanceof Set;
+ const isMap = data instanceof Map;
+ const valuesKey = "Values";
+ const indexKey = isSet || isMap ? "(iter idx)" : "(idx)";
+
+ if (data instanceof Set) {
+ resultData = [...data];
+ } else if (data instanceof Map) {
+ let idx = 0;
+ resultData = {};
+
+ data.forEach((v, k) => {
+ resultData[idx] = { Key: k, Values: v };
+ idx++;
+ });
+ } else {
+ resultData = data;
+ }
+
+ let hasPrimitives = false;
+ Object.keys(resultData).forEach((k, idx) => {
+ const value = resultData[k];
+ const primitive = value === null ||
+ (typeof value !== "function" && typeof value !== "object");
+ if (properties === undefined && primitive) {
+ hasPrimitives = true;
+ values.push(stringifyValue(value));
+ } else {
+ const valueObj = value || {};
+ const keys = properties || Object.keys(valueObj);
+ for (const k of keys) {
+ if (primitive || !valueObj.hasOwnProperty(k)) {
+ if (objectValues[k]) {
+ // fill with blanks for idx to avoid misplacing from later values
+ objectValues[k].push("");
+ }
+ } else {
+ if (objectValues[k]) {
+ objectValues[k].push(stringifyValue(valueObj[k]));
+ } else {
+ objectValues[k] = createColumn(valueObj[k], idx);
+ }
+ }
+ }
+ values.push("");
+ }
+
+ indexKeys.push(k);
+ });
+
+ const headerKeys = Object.keys(objectValues);
+ const bodyValues = Object.values(objectValues);
+ const header = [
+ indexKey,
+ ...(properties ||
+ [...headerKeys, !isMap && hasPrimitives && valuesKey]),
+ ].filter(Boolean);
+ const body = [indexKeys, ...bodyValues, values];
+
+ toTable(header, body);
+ };
+
+ time = (label = "default") => {
+ label = String(label);
+
+ if (timerMap.has(label)) {
+ this.warn(`Timer '${label}' already exists`);
+ return;
+ }
+
+ timerMap.set(label, Date.now());
+ };
+
+ timeLog = (label = "default", ...args) => {
+ label = String(label);
+
+ if (!timerMap.has(label)) {
+ this.warn(`Timer '${label}' does not exists`);
+ return;
+ }
+
+ const startTime = timerMap.get(label);
+ const duration = Date.now() - startTime;
+
+ this.info(`${label}: ${duration}ms`, ...args);
+ };
+
+ timeEnd = (label = "default") => {
+ label = String(label);
+
+ if (!timerMap.has(label)) {
+ this.warn(`Timer '${label}' does not exists`);
+ return;
+ }
+
+ const startTime = timerMap.get(label);
+ timerMap.delete(label);
+ const duration = Date.now() - startTime;
+
+ this.info(`${label}: ${duration}ms`);
+ };
+
+ group = (...label) => {
+ if (label.length > 0) {
+ this.log(...label);
+ }
+ this.indentLevel += 2;
+ };
+
+ groupCollapsed = this.group;
+
+ groupEnd = () => {
+ if (this.indentLevel > 0) {
+ this.indentLevel -= 2;
+ }
+ };
+
+ clear = () => {
+ this.indentLevel = 0;
+ this.#printFunc(CSI.kClear, false);
+ this.#printFunc(CSI.kClearScreenDown, false);
+ };
+
+ trace = (...args) => {
+ const message = inspectArgs(
+ args,
+ { ...getConsoleInspectOptions(), indentLevel: 0 },
+ );
+ const err = {
+ name: "Trace",
+ message,
+ };
+ Error.captureStackTrace(err, this.trace);
+ this.error(err.stack);
+ };
+
+ static [Symbol.hasInstance](instance) {
+ return instance[isConsoleInstance];
+ }
+ }
+
+ const customInspect = Symbol("Deno.customInspect");
+
+ function inspect(
+ value,
+ inspectOptions = {},
+ ) {
+ return inspectValue(value, new Set(), 0, {
+ ...DEFAULT_INSPECT_OPTIONS,
+ ...inspectOptions,
+ // TODO(nayeemrmn): Indent level is not supported.
+ indentLevel: 0,
+ });
+ }
+
+ // Expose these fields to internalObject for tests.
+ exposeForTest("Console", Console);
+ exposeForTest("cssToAnsi", cssToAnsi);
+ exposeForTest("inspectArgs", inspectArgs);
+ exposeForTest("parseCss", parseCss);
+ exposeForTest("parseCssColor", parseCssColor);
+
+ window.__bootstrap.console = {
+ CSI,
+ inspectArgs,
+ Console,
+ customInspect,
+ inspect,
+ };
+})(this);
diff --git a/runtime/rt/06_util.js b/runtime/rt/06_util.js
new file mode 100644
index 000000000..f4804c519
--- /dev/null
+++ b/runtime/rt/06_util.js
@@ -0,0 +1,148 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { build } = window.__bootstrap.build;
+ const internals = window.__bootstrap.internals;
+ let logDebug = false;
+ let logSource = "JS";
+
+ function setLogDebug(debug, source) {
+ logDebug = debug;
+ if (source) {
+ logSource = source;
+ }
+ }
+
+ function log(...args) {
+ if (logDebug) {
+ // if we destructure `console` off `globalThis` too early, we don't bind to
+ // the right console, therefore we don't log anything out.
+ globalThis.console.log(`DEBUG ${logSource} -`, ...args);
+ }
+ }
+
+ class AssertionError extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AssertionError";
+ }
+ }
+
+ function assert(cond, msg = "Assertion failed.") {
+ if (!cond) {
+ throw new AssertionError(msg);
+ }
+ }
+
+ function createResolvable() {
+ let resolve;
+ let reject;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ promise.resolve = resolve;
+ promise.reject = reject;
+ return promise;
+ }
+
+ function immutableDefine(
+ o,
+ p,
+ value,
+ ) {
+ Object.defineProperty(o, p, {
+ value,
+ configurable: false,
+ writable: false,
+ });
+ }
+
+ // Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
+ function pathFromURLWin32(url) {
+ let path = decodeURIComponent(
+ url.pathname
+ .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")
+ .replace(/\//g, "\\")
+ .replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
+ );
+ if (url.hostname != "") {
+ // Note: The `URL` implementation guarantees that the drive letter and
+ // hostname are mutually exclusive. Otherwise it would not have been valid
+ // to append the hostname and path like this.
+ path = `\\\\${url.hostname}${path}`;
+ }
+ return path;
+ }
+
+ // Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
+ function pathFromURLPosix(url) {
+ if (url.hostname !== "") {
+ throw new TypeError(`Host must be empty.`);
+ }
+
+ return decodeURIComponent(
+ url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
+ );
+ }
+
+ function pathFromURL(pathOrUrl) {
+ if (pathOrUrl instanceof URL) {
+ if (pathOrUrl.protocol != "file:") {
+ throw new TypeError("Must be a file URL.");
+ }
+
+ return build.os == "windows"
+ ? pathFromURLWin32(pathOrUrl)
+ : pathFromURLPosix(pathOrUrl);
+ }
+ return pathOrUrl;
+ }
+
+ internals.exposeForTest("pathFromURL", pathFromURL);
+
+ function writable(value) {
+ return {
+ value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ };
+ }
+
+ function nonEnumerable(value) {
+ return {
+ value,
+ writable: true,
+ configurable: true,
+ };
+ }
+
+ function readOnly(value) {
+ return {
+ value,
+ enumerable: true,
+ };
+ }
+
+ function getterOnly(getter) {
+ return {
+ get: getter,
+ enumerable: true,
+ };
+ }
+
+ window.__bootstrap.util = {
+ log,
+ setLogDebug,
+ createResolvable,
+ assert,
+ AssertionError,
+ immutableDefine,
+ pathFromURL,
+ writable,
+ nonEnumerable,
+ readOnly,
+ getterOnly,
+ };
+})(this);
diff --git a/runtime/rt/10_dispatch_minimal.js b/runtime/rt/10_dispatch_minimal.js
new file mode 100644
index 000000000..dceb23e5f
--- /dev/null
+++ b/runtime/rt/10_dispatch_minimal.js
@@ -0,0 +1,114 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const util = window.__bootstrap.util;
+
+ // Using an object without a prototype because `Map` was causing GC problems.
+ const promiseTableMin = Object.create(null);
+
+ const decoder = new TextDecoder();
+
+ // Note it's important that promiseId starts at 1 instead of 0, because sync
+ // messages are indicated with promiseId 0. If we ever add wrap around logic for
+ // overflows, this should be taken into account.
+ let _nextPromiseId = 1;
+
+ function nextPromiseId() {
+ return _nextPromiseId++;
+ }
+
+ function recordFromBufMinimal(ui8) {
+ const headerLen = 12;
+ const header = ui8.subarray(0, headerLen);
+ const buf32 = new Int32Array(
+ header.buffer,
+ header.byteOffset,
+ header.byteLength / 4,
+ );
+ const promiseId = buf32[0];
+ const arg = buf32[1];
+ const result = buf32[2];
+ let err;
+
+ if (arg < 0) {
+ err = {
+ className: decoder.decode(ui8.subarray(headerLen, headerLen + result)),
+ message: decoder.decode(ui8.subarray(headerLen + result)),
+ };
+ } else if (ui8.length != 12) {
+ throw new TypeError("Malformed response message");
+ }
+
+ return {
+ promiseId,
+ arg,
+ result,
+ err,
+ };
+ }
+
+ function unwrapResponse(res) {
+ if (res.err != null) {
+ const ErrorClass = core.getErrorClass(res.err.className);
+ if (!ErrorClass) {
+ throw new Error(
+ `Unregistered error class: "${res.err.className}"\n ${res.err.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
+ );
+ }
+ throw new ErrorClass(res.err.message);
+ }
+ return res.result;
+ }
+
+ const scratch32 = new Int32Array(3);
+ const scratchBytes = new Uint8Array(
+ scratch32.buffer,
+ scratch32.byteOffset,
+ scratch32.byteLength,
+ );
+ util.assert(scratchBytes.byteLength === scratch32.length * 4);
+
+ function asyncMsgFromRust(ui8) {
+ const record = recordFromBufMinimal(ui8);
+ const { promiseId } = record;
+ const promise = promiseTableMin[promiseId];
+ delete promiseTableMin[promiseId];
+ util.assert(promise);
+ promise.resolve(record);
+ }
+
+ async function sendAsync(opName, arg, zeroCopy) {
+ const promiseId = nextPromiseId(); // AKA cmdId
+ scratch32[0] = promiseId;
+ scratch32[1] = arg;
+ scratch32[2] = 0; // result
+ const promise = util.createResolvable();
+ const buf = core.dispatchByName(opName, scratchBytes, zeroCopy);
+ if (buf != null) {
+ const record = recordFromBufMinimal(buf);
+ // Sync result.
+ promise.resolve(record);
+ } else {
+ // Async result.
+ promiseTableMin[promiseId] = promise;
+ }
+
+ const res = await promise;
+ return unwrapResponse(res);
+ }
+
+ function sendSync(opName, arg, zeroCopy) {
+ scratch32[0] = 0; // promiseId 0 indicates sync
+ scratch32[1] = arg;
+ const res = core.dispatchByName(opName, scratchBytes, zeroCopy);
+ const resRecord = recordFromBufMinimal(res);
+ return unwrapResponse(resRecord);
+ }
+
+ window.__bootstrap.dispatchMinimal = {
+ asyncMsgFromRust,
+ sendSync,
+ sendAsync,
+ };
+})(this);
diff --git a/runtime/rt/11_timers.js b/runtime/rt/11_timers.js
new file mode 100644
index 000000000..5a59844a3
--- /dev/null
+++ b/runtime/rt/11_timers.js
@@ -0,0 +1,557 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const assert = window.__bootstrap.util.assert;
+ const core = window.Deno.core;
+ const { sendSync } = window.__bootstrap.dispatchMinimal;
+
+ function opStopGlobalTimer() {
+ core.jsonOpSync("op_global_timer_stop");
+ }
+
+ function opStartGlobalTimer(timeout) {
+ return core.jsonOpSync("op_global_timer_start", { timeout });
+ }
+
+ async function opWaitGlobalTimer() {
+ await core.jsonOpAsync("op_global_timer");
+ }
+
+ const nowBytes = new Uint8Array(8);
+ function opNow() {
+ sendSync("op_now", 0, nowBytes);
+ return new DataView(nowBytes.buffer).getFloat64();
+ }
+
+ function sleepSync(millis = 0) {
+ return core.jsonOpSync("op_sleep_sync", { millis });
+ }
+
+ // Derived from https://github.com/vadimg/js_bintrees. MIT Licensed.
+
+ class RBNode {
+ constructor(data) {
+ this.data = data;
+ this.left = null;
+ this.right = null;
+ this.red = true;
+ }
+
+ getChild(dir) {
+ return dir ? this.right : this.left;
+ }
+
+ setChild(dir, val) {
+ if (dir) {
+ this.right = val;
+ } else {
+ this.left = val;
+ }
+ }
+ }
+
+ class RBTree {
+ #comparator = null;
+ #root = null;
+
+ constructor(comparator) {
+ this.#comparator = comparator;
+ this.#root = null;
+ }
+
+ /** Returns `null` if tree is empty. */
+ min() {
+ let res = this.#root;
+ if (res === null) {
+ return null;
+ }
+ while (res.left !== null) {
+ res = res.left;
+ }
+ return res.data;
+ }
+
+ /** Returns node `data` if found, `null` otherwise. */
+ find(data) {
+ let res = this.#root;
+ while (res !== null) {
+ const c = this.#comparator(data, res.data);
+ if (c === 0) {
+ return res.data;
+ } else {
+ res = res.getChild(c > 0);
+ }
+ }
+ return null;
+ }
+
+ /** returns `true` if inserted, `false` if duplicate. */
+ insert(data) {
+ let ret = false;
+
+ if (this.#root === null) {
+ // empty tree
+ this.#root = new RBNode(data);
+ ret = true;
+ } else {
+ const head = new RBNode(null); // fake tree root
+
+ let dir = 0;
+ let last = 0;
+
+ // setup
+ let gp = null; // grandparent
+ let ggp = head; // grand-grand-parent
+ let p = null; // parent
+ let node = this.#root;
+ ggp.right = this.#root;
+
+ // search down
+ while (true) {
+ if (node === null) {
+ // insert new node at the bottom
+ node = new RBNode(data);
+ p.setChild(dir, node);
+ ret = true;
+ } else if (isRed(node.left) && isRed(node.right)) {
+ // color flip
+ node.red = true;
+ node.left.red = false;
+ node.right.red = false;
+ }
+
+ // fix red violation
+ if (isRed(node) && isRed(p)) {
+ const dir2 = ggp.right === gp;
+
+ assert(gp);
+ if (node === p.getChild(last)) {
+ ggp.setChild(dir2, singleRotate(gp, !last));
+ } else {
+ ggp.setChild(dir2, doubleRotate(gp, !last));
+ }
+ }
+
+ const cmp = this.#comparator(node.data, data);
+
+ // stop if found
+ if (cmp === 0) {
+ break;
+ }
+
+ last = dir;
+ dir = Number(cmp < 0); // Fix type
+
+ // update helpers
+ if (gp !== null) {
+ ggp = gp;
+ }
+ gp = p;
+ p = node;
+ node = node.getChild(dir);
+ }
+
+ // update root
+ this.#root = head.right;
+ }
+
+ // make root black
+ this.#root.red = false;
+
+ return ret;
+ }
+
+ /** Returns `true` if removed, `false` if not found. */
+ remove(data) {
+ if (this.#root === null) {
+ return false;
+ }
+
+ const head = new RBNode(null); // fake tree root
+ let node = head;
+ node.right = this.#root;
+ let p = null; // parent
+ let gp = null; // grand parent
+ let found = null; // found item
+ let dir = 1;
+
+ while (node.getChild(dir) !== null) {
+ const last = dir;
+
+ // update helpers
+ gp = p;
+ p = node;
+ node = node.getChild(dir);
+
+ const cmp = this.#comparator(data, node.data);
+
+ dir = cmp > 0;
+
+ // save found node
+ if (cmp === 0) {
+ found = node;
+ }
+
+ // push the red node down
+ if (!isRed(node) && !isRed(node.getChild(dir))) {
+ if (isRed(node.getChild(!dir))) {
+ const sr = singleRotate(node, dir);
+ p.setChild(last, sr);
+ p = sr;
+ } else if (!isRed(node.getChild(!dir))) {
+ const sibling = p.getChild(!last);
+ if (sibling !== null) {
+ if (
+ !isRed(sibling.getChild(!last)) &&
+ !isRed(sibling.getChild(last))
+ ) {
+ // color flip
+ p.red = false;
+ sibling.red = true;
+ node.red = true;
+ } else {
+ assert(gp);
+ const dir2 = gp.right === p;
+
+ if (isRed(sibling.getChild(last))) {
+ gp.setChild(dir2, doubleRotate(p, last));
+ } else if (isRed(sibling.getChild(!last))) {
+ gp.setChild(dir2, singleRotate(p, last));
+ }
+
+ // ensure correct coloring
+ const gpc = gp.getChild(dir2);
+ assert(gpc);
+ gpc.red = true;
+ node.red = true;
+ assert(gpc.left);
+ gpc.left.red = false;
+ assert(gpc.right);
+ gpc.right.red = false;
+ }
+ }
+ }
+ }
+ }
+
+ // replace and remove if found
+ if (found !== null) {
+ found.data = node.data;
+ assert(p);
+ p.setChild(p.right === node, node.getChild(node.left === null));
+ }
+
+ // update root and make it black
+ this.#root = head.right;
+ if (this.#root !== null) {
+ this.#root.red = false;
+ }
+
+ return found !== null;
+ }
+ }
+
+ function isRed(node) {
+ return node !== null && node.red;
+ }
+
+ function singleRotate(root, dir) {
+ const save = root.getChild(!dir);
+ assert(save);
+
+ root.setChild(!dir, save.getChild(dir));
+ save.setChild(dir, root);
+
+ root.red = true;
+ save.red = false;
+
+ return save;
+ }
+
+ function doubleRotate(root, dir) {
+ root.setChild(!dir, singleRotate(root.getChild(!dir), !dir));
+ return singleRotate(root, dir);
+ }
+
+ const { console } = globalThis;
+ const OriginalDate = Date;
+
+ // Timeout values > TIMEOUT_MAX are set to 1.
+ const TIMEOUT_MAX = 2 ** 31 - 1;
+
+ let globalTimeoutDue = null;
+
+ let nextTimerId = 1;
+ const idMap = new Map();
+ const dueTree = new RBTree((a, b) => a.due - b.due);
+
+ function clearGlobalTimeout() {
+ globalTimeoutDue = null;
+ opStopGlobalTimer();
+ }
+
+ let pendingEvents = 0;
+ const pendingFireTimers = [];
+
+ /** Process and run a single ready timer macrotask.
+ * This function should be registered through Deno.core.setMacrotaskCallback.
+ * Returns true when all ready macrotasks have been processed, false if more
+ * ready ones are available. The Isolate future would rely on the return value
+ * to repeatedly invoke this function until depletion. Multiple invocations
+ * of this function one at a time ensures newly ready microtasks are processed
+ * before next macrotask timer callback is invoked. */
+ function handleTimerMacrotask() {
+ if (pendingFireTimers.length > 0) {
+ fire(pendingFireTimers.shift());
+ return pendingFireTimers.length === 0;
+ }
+ return true;
+ }
+
+ async function setGlobalTimeout(due, now) {
+ // Since JS and Rust don't use the same clock, pass the time to rust as a
+ // relative time value. On the Rust side we'll turn that into an absolute
+ // value again.
+ const timeout = due - now;
+ assert(timeout >= 0);
+ // Send message to the backend.
+ globalTimeoutDue = due;
+ pendingEvents++;
+ // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout`
+ // is synchronous. That means that timer is cancelled, but this promise is still pending
+ // until next turn of event loop. This leads to "leaking of async ops" in tests;
+ // because `clearTimeout/clearInterval` might be the last statement in test function
+ // `opSanitizer` will immediately complain that there is pending op going on, unless
+ // some timeout/defer is put in place to allow promise resolution.
+ // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but
+ // I'm not if that's possible.
+ opStartGlobalTimer(timeout);
+ await opWaitGlobalTimer();
+ pendingEvents--;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ prepareReadyTimers();
+ }
+
+ function prepareReadyTimers() {
+ const now = OriginalDate.now();
+ // Bail out if we're not expecting the global timer to fire.
+ if (globalTimeoutDue === null || pendingEvents > 0) {
+ return;
+ }
+ // After firing the timers that are due now, this will hold the first timer
+ // list that hasn't fired yet.
+ let nextDueNode;
+ while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) {
+ dueTree.remove(nextDueNode);
+ // Fire all the timers in the list.
+ for (const timer of nextDueNode.timers) {
+ // With the list dropped, the timer is no longer scheduled.
+ timer.scheduled = false;
+ // Place the callback to pending timers to fire.
+ pendingFireTimers.push(timer);
+ }
+ }
+ setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now);
+ }
+
+ function setOrClearGlobalTimeout(due, now) {
+ if (due == null) {
+ clearGlobalTimeout();
+ } else {
+ setGlobalTimeout(due, now);
+ }
+ }
+
+ function schedule(timer, now) {
+ assert(!timer.scheduled);
+ assert(now <= timer.due);
+ // Find or create the list of timers that will fire at point-in-time `due`.
+ const maybeNewDueNode = { due: timer.due, timers: [] };
+ let dueNode = dueTree.find(maybeNewDueNode);
+ if (dueNode === null) {
+ dueTree.insert(maybeNewDueNode);
+ dueNode = maybeNewDueNode;
+ }
+ // Append the newly scheduled timer to the list and mark it as scheduled.
+ dueNode.timers.push(timer);
+ timer.scheduled = true;
+ // If the new timer is scheduled to fire before any timer that existed before,
+ // update the global timeout to reflect this.
+ if (globalTimeoutDue === null || globalTimeoutDue > timer.due) {
+ setOrClearGlobalTimeout(timer.due, now);
+ }
+ }
+
+ function unschedule(timer) {
+ // Check if our timer is pending scheduling or pending firing.
+ // If either is true, they are not in tree, and their idMap entry
+ // will be deleted soon. Remove it from queue.
+ let index = -1;
+ if ((index = pendingFireTimers.indexOf(timer)) >= 0) {
+ pendingFireTimers.splice(index);
+ return;
+ }
+ // If timer is not in the 2 pending queues and is unscheduled,
+ // it is not in the tree.
+ if (!timer.scheduled) {
+ return;
+ }
+ const searchKey = { due: timer.due, timers: [] };
+ // Find the list of timers that will fire at point-in-time `due`.
+ const list = dueTree.find(searchKey).timers;
+ if (list.length === 1) {
+ // Time timer is the only one in the list. Remove the entire list.
+ assert(list[0] === timer);
+ dueTree.remove(searchKey);
+ // If the unscheduled timer was 'next up', find when the next timer that
+ // still exists is due, and update the global alarm accordingly.
+ if (timer.due === globalTimeoutDue) {
+ const nextDueNode = dueTree.min();
+ setOrClearGlobalTimeout(
+ nextDueNode && nextDueNode.due,
+ OriginalDate.now(),
+ );
+ }
+ } else {
+ // Multiple timers that are due at the same point in time.
+ // Remove this timer from the list.
+ const index = list.indexOf(timer);
+ assert(index > -1);
+ list.splice(index, 1);
+ }
+ }
+
+ function fire(timer) {
+ // If the timer isn't found in the ID map, that means it has been cancelled
+ // between the timer firing and the promise callback (this function).
+ if (!idMap.has(timer.id)) {
+ return;
+ }
+ // Reschedule the timer if it is a repeating one, otherwise drop it.
+ if (!timer.repeat) {
+ // One-shot timer: remove the timer from this id-to-timer map.
+ idMap.delete(timer.id);
+ } else {
+ // Interval timer: compute when timer was supposed to fire next.
+ // However make sure to never schedule the next interval in the past.
+ const now = OriginalDate.now();
+ timer.due = Math.max(now, timer.due + timer.delay);
+ schedule(timer, now);
+ }
+ // Call the user callback. Intermediate assignment is to avoid leaking `this`
+ // to it, while also keeping the stack trace neat when it shows up in there.
+ const callback = timer.callback;
+ callback();
+ }
+
+ function checkThis(thisArg) {
+ if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
+ throw new TypeError("Illegal invocation");
+ }
+ }
+
+ function checkBigInt(n) {
+ if (typeof n === "bigint") {
+ throw new TypeError("Cannot convert a BigInt value to a number");
+ }
+ }
+
+ function setTimer(
+ cb,
+ delay,
+ args,
+ repeat,
+ ) {
+ // Bind `args` to the callback and bind `this` to globalThis(global).
+ const callback = cb.bind(globalThis, ...args);
+ // In the browser, the delay value must be coercible to an integer between 0
+ // and INT32_MAX. Any other value will cause the timer to fire immediately.
+ // We emulate this behavior.
+ const now = OriginalDate.now();
+ if (delay > TIMEOUT_MAX) {
+ console.warn(
+ `${delay} does not fit into` +
+ " a 32-bit signed integer." +
+ "\nTimeout duration was set to 1.",
+ );
+ delay = 1;
+ }
+ delay = Math.max(0, delay | 0);
+
+ // Create a new, unscheduled timer object.
+ const timer = {
+ id: nextTimerId++,
+ callback,
+ args,
+ delay,
+ due: now + delay,
+ repeat,
+ scheduled: false,
+ };
+ // Register the timer's existence in the id-to-timer map.
+ idMap.set(timer.id, timer);
+ // Schedule the timer in the due table.
+ schedule(timer, now);
+ return timer.id;
+ }
+
+ function setTimeout(
+ cb,
+ delay = 0,
+ ...args
+ ) {
+ checkBigInt(delay);
+ checkThis(this);
+ return setTimer(cb, delay, args, false);
+ }
+
+ function setInterval(
+ cb,
+ delay = 0,
+ ...args
+ ) {
+ checkBigInt(delay);
+ checkThis(this);
+ return setTimer(cb, delay, args, true);
+ }
+
+ function clearTimer(id) {
+ id = Number(id);
+ const timer = idMap.get(id);
+ if (timer === undefined) {
+ // Timer doesn't exist any more or never existed. This is not an error.
+ return;
+ }
+ // Unschedule the timer if it is currently scheduled, and forget about it.
+ unschedule(timer);
+ idMap.delete(timer.id);
+ }
+
+ function clearTimeout(id = 0) {
+ checkBigInt(id);
+ if (id === 0) {
+ return;
+ }
+ clearTimer(id);
+ }
+
+ function clearInterval(id = 0) {
+ checkBigInt(id);
+ if (id === 0) {
+ return;
+ }
+ clearTimer(id);
+ }
+
+ window.__bootstrap.timers = {
+ clearInterval,
+ setInterval,
+ clearTimeout,
+ setTimeout,
+ handleTimerMacrotask,
+ opStopGlobalTimer,
+ opStartGlobalTimer,
+ opNow,
+ sleepSync,
+ };
+})(this);
diff --git a/runtime/rt/11_workers.js b/runtime/rt/11_workers.js
new file mode 100644
index 000000000..62210dfae
--- /dev/null
+++ b/runtime/rt/11_workers.js
@@ -0,0 +1,206 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { Window } = window.__bootstrap.globalInterfaces;
+ const { log } = window.__bootstrap.util;
+ const { defineEventHandler } = window.__bootstrap.webUtil;
+
+ function createWorker(
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ useDenoNamespace,
+ name,
+ ) {
+ return core.jsonOpSync("op_create_worker", {
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ name,
+ useDenoNamespace,
+ });
+ }
+
+ function hostTerminateWorker(id) {
+ core.jsonOpSync("op_host_terminate_worker", { id });
+ }
+
+ function hostPostMessage(id, data) {
+ core.jsonOpSync("op_host_post_message", { id }, data);
+ }
+
+ function hostGetMessage(id) {
+ return core.jsonOpAsync("op_host_get_message", { id });
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ function encodeMessage(data) {
+ const dataJson = JSON.stringify(data);
+ return encoder.encode(dataJson);
+ }
+
+ function decodeMessage(dataIntArray) {
+ const dataJson = decoder.decode(dataIntArray);
+ return JSON.parse(dataJson);
+ }
+
+ class Worker extends EventTarget {
+ #id = 0;
+ #name = "";
+ #terminated = false;
+
+ constructor(specifier, options) {
+ super();
+ const { type = "classic", name = "unknown" } = options ?? {};
+
+ if (type !== "module") {
+ throw new Error(
+ 'Not yet implemented: only "module" type workers are supported',
+ );
+ }
+
+ this.#name = name;
+ const hasSourceCode = false;
+ const sourceCode = decoder.decode(new Uint8Array());
+
+ const useDenoNamespace = options ? !!options.deno : false;
+
+ const { id } = createWorker(
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ useDenoNamespace,
+ options?.name,
+ );
+ this.#id = id;
+ this.#poll();
+ }
+
+ #handleMessage = (msgData) => {
+ let data;
+ try {
+ data = decodeMessage(new Uint8Array(msgData));
+ } catch (e) {
+ const msgErrorEvent = new MessageEvent("messageerror", {
+ cancelable: false,
+ data,
+ });
+ return;
+ }
+
+ const msgEvent = new MessageEvent("message", {
+ cancelable: false,
+ data,
+ });
+
+ this.dispatchEvent(msgEvent);
+ };
+
+ #handleError = (e) => {
+ const event = new ErrorEvent("error", {
+ cancelable: true,
+ message: e.message,
+ lineno: e.lineNumber ? e.lineNumber + 1 : undefined,
+ colno: e.columnNumber ? e.columnNumber + 1 : undefined,
+ filename: e.fileName,
+ error: null,
+ });
+
+ let handled = false;
+
+ this.dispatchEvent(event);
+ if (event.defaultPrevented) {
+ handled = true;
+ }
+
+ return handled;
+ };
+
+ #poll = async () => {
+ while (!this.#terminated) {
+ const event = await hostGetMessage(this.#id);
+
+ // If terminate was called then we ignore all messages
+ if (this.#terminated) {
+ return;
+ }
+
+ const type = event.type;
+
+ if (type === "terminalError") {
+ this.#terminated = true;
+ if (!this.#handleError(event.error)) {
+ if (globalThis instanceof Window) {
+ throw new Error("Unhandled error event reached main worker.");
+ } else {
+ core.jsonOpSync(
+ "op_host_unhandled_error",
+ { message: event.error.message },
+ );
+ }
+ }
+ continue;
+ }
+
+ if (type === "msg") {
+ this.#handleMessage(event.data);
+ continue;
+ }
+
+ if (type === "error") {
+ if (!this.#handleError(event.error)) {
+ if (globalThis instanceof Window) {
+ throw new Error("Unhandled error event reached main worker.");
+ } else {
+ core.jsonOpSync(
+ "op_host_unhandled_error",
+ { message: event.error.message },
+ );
+ }
+ }
+ continue;
+ }
+
+ if (type === "close") {
+ log(`Host got "close" message from worker: ${this.#name}`);
+ this.#terminated = true;
+ return;
+ }
+
+ throw new Error(`Unknown worker event: "${type}"`);
+ }
+ };
+
+ postMessage(message, transferOrOptions) {
+ if (transferOrOptions) {
+ throw new Error(
+ "Not yet implemented: `transfer` and `options` are not supported.",
+ );
+ }
+
+ if (this.#terminated) {
+ return;
+ }
+
+ hostPostMessage(this.#id, encodeMessage(message));
+ }
+
+ terminate() {
+ if (!this.#terminated) {
+ this.#terminated = true;
+ hostTerminateWorker(this.#id);
+ }
+ }
+ }
+
+ defineEventHandler(Worker.prototype, "error");
+ defineEventHandler(Worker.prototype, "message");
+ defineEventHandler(Worker.prototype, "messageerror");
+
+ window.__bootstrap.worker = {
+ Worker,
+ };
+})(this);
diff --git a/runtime/rt/12_io.js b/runtime/rt/12_io.js
new file mode 100644
index 000000000..006d51cdd
--- /dev/null
+++ b/runtime/rt/12_io.js
@@ -0,0 +1,135 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// Interfaces 100% copied from Go.
+// Documentation liberally lifted from them too.
+// Thank you! We love Go! <3
+
+((window) => {
+ const DEFAULT_BUFFER_SIZE = 32 * 1024;
+ const { sendSync, sendAsync } = window.__bootstrap.dispatchMinimal;
+ // Seek whence values.
+ // https://golang.org/pkg/io/#pkg-constants
+ const SeekMode = {
+ 0: "Start",
+ 1: "Current",
+ 2: "End",
+
+ Start: 0,
+ Current: 1,
+ End: 2,
+ };
+
+ async function copy(
+ src,
+ dst,
+ options,
+ ) {
+ let n = 0;
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ let gotEOF = false;
+ while (gotEOF === false) {
+ const result = await src.read(b);
+ if (result === null) {
+ gotEOF = true;
+ } else {
+ let nwritten = 0;
+ while (nwritten < result) {
+ nwritten += await dst.write(b.subarray(nwritten, result));
+ }
+ n += nwritten;
+ }
+ }
+ return n;
+ }
+
+ async function* iter(
+ r,
+ options,
+ ) {
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ while (true) {
+ const result = await r.read(b);
+ if (result === null) {
+ break;
+ }
+
+ yield b.subarray(0, result);
+ }
+ }
+
+ function* iterSync(
+ r,
+ options,
+ ) {
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ while (true) {
+ const result = r.readSync(b);
+ if (result === null) {
+ break;
+ }
+
+ yield b.subarray(0, result);
+ }
+ }
+
+ function readSync(rid, buffer) {
+ if (buffer.length === 0) {
+ return 0;
+ }
+
+ const nread = sendSync("op_read", rid, buffer);
+ if (nread < 0) {
+ throw new Error("read error");
+ }
+
+ return nread === 0 ? null : nread;
+ }
+
+ async function read(
+ rid,
+ buffer,
+ ) {
+ if (buffer.length === 0) {
+ return 0;
+ }
+
+ const nread = await sendAsync("op_read", rid, buffer);
+ if (nread < 0) {
+ throw new Error("read error");
+ }
+
+ return nread === 0 ? null : nread;
+ }
+
+ function writeSync(rid, data) {
+ const result = sendSync("op_write", rid, data);
+ if (result < 0) {
+ throw new Error("write error");
+ }
+
+ return result;
+ }
+
+ async function write(rid, data) {
+ const result = await sendAsync("op_write", rid, data);
+ if (result < 0) {
+ throw new Error("write error");
+ }
+
+ return result;
+ }
+
+ window.__bootstrap.io = {
+ iterSync,
+ iter,
+ copy,
+ SeekMode,
+ read,
+ readSync,
+ write,
+ writeSync,
+ };
+})(this);
diff --git a/runtime/rt/13_buffer.js b/runtime/rt/13_buffer.js
new file mode 100644
index 000000000..e06e2138b
--- /dev/null
+++ b/runtime/rt/13_buffer.js
@@ -0,0 +1,241 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This code has been ported almost directly from Go's src/bytes/buffer.go
+// Copyright 2009 The Go Authors. All rights reserved. BSD license.
+// https://github.com/golang/go/blob/master/LICENSE
+
+((window) => {
+ const { assert } = window.__bootstrap.util;
+
+ // MIN_READ is the minimum ArrayBuffer size passed to a read call by
+ // buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond
+ // what is required to hold the contents of r, readFrom() will not grow the
+ // underlying buffer.
+ const MIN_READ = 32 * 1024;
+ const MAX_SIZE = 2 ** 32 - 2;
+
+ // `off` is the offset into `dst` where it will at which to begin writing values
+ // from `src`.
+ // Returns the number of bytes copied.
+ function copyBytes(src, dst, off = 0) {
+ const r = dst.byteLength - off;
+ if (src.byteLength > r) {
+ src = src.subarray(0, r);
+ }
+ dst.set(src, off);
+ return src.byteLength;
+ }
+
+ class Buffer {
+ #buf = null; // contents are the bytes buf[off : len(buf)]
+ #off = 0; // read at buf[off], write at buf[buf.byteLength]
+
+ constructor(ab) {
+ if (ab == null) {
+ this.#buf = new Uint8Array(0);
+ return;
+ }
+
+ this.#buf = new Uint8Array(ab);
+ }
+
+ bytes(options = { copy: true }) {
+ if (options.copy === false) return this.#buf.subarray(this.#off);
+ return this.#buf.slice(this.#off);
+ }
+
+ empty() {
+ return this.#buf.byteLength <= this.#off;
+ }
+
+ get length() {
+ return this.#buf.byteLength - this.#off;
+ }
+
+ get capacity() {
+ return this.#buf.buffer.byteLength;
+ }
+
+ truncate(n) {
+ if (n === 0) {
+ this.reset();
+ return;
+ }
+ if (n < 0 || n > this.length) {
+ throw Error("bytes.Buffer: truncation out of range");
+ }
+ this.#reslice(this.#off + n);
+ }
+
+ reset() {
+ this.#reslice(0);
+ this.#off = 0;
+ }
+
+ #tryGrowByReslice = (n) => {
+ const l = this.#buf.byteLength;
+ if (n <= this.capacity - l) {
+ this.#reslice(l + n);
+ return l;
+ }
+ return -1;
+ };
+
+ #reslice = (len) => {
+ assert(len <= this.#buf.buffer.byteLength);
+ this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
+ };
+
+ readSync(p) {
+ if (this.empty()) {
+ // Buffer is empty, reset to recover space.
+ this.reset();
+ if (p.byteLength === 0) {
+ // this edge case is tested in 'bufferReadEmptyAtEOF' test
+ return 0;
+ }
+ return null;
+ }
+ const nread = copyBytes(this.#buf.subarray(this.#off), p);
+ this.#off += nread;
+ return nread;
+ }
+
+ read(p) {
+ const rr = this.readSync(p);
+ return Promise.resolve(rr);
+ }
+
+ writeSync(p) {
+ const m = this.#grow(p.byteLength);
+ return copyBytes(p, this.#buf, m);
+ }
+
+ write(p) {
+ const n = this.writeSync(p);
+ return Promise.resolve(n);
+ }
+
+ #grow = (n) => {
+ const m = this.length;
+ // If buffer is empty, reset to recover space.
+ if (m === 0 && this.#off !== 0) {
+ this.reset();
+ }
+ // Fast: Try to grow by means of a reslice.
+ const i = this.#tryGrowByReslice(n);
+ if (i >= 0) {
+ return i;
+ }
+ const c = this.capacity;
+ if (n <= Math.floor(c / 2) - m) {
+ // We can slide things down instead of allocating a new
+ // ArrayBuffer. We only need m+n <= c to slide, but
+ // we instead let capacity get twice as large so we
+ // don't spend all our time copying.
+ copyBytes(this.#buf.subarray(this.#off), this.#buf);
+ } else if (c + n > MAX_SIZE) {
+ throw new Error("The buffer cannot be grown beyond the maximum size.");
+ } else {
+ // Not enough space anywhere, we need to allocate.
+ const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE));
+ copyBytes(this.#buf.subarray(this.#off), buf);
+ this.#buf = buf;
+ }
+ // Restore this.#off and len(this.#buf).
+ this.#off = 0;
+ this.#reslice(Math.min(m + n, MAX_SIZE));
+ return m;
+ };
+
+ grow(n) {
+ if (n < 0) {
+ throw Error("Buffer.grow: negative count");
+ }
+ const m = this.#grow(n);
+ this.#reslice(m);
+ }
+
+ async readFrom(r) {
+ let n = 0;
+ const tmp = new Uint8Array(MIN_READ);
+ while (true) {
+ const shouldGrow = this.capacity - this.length < MIN_READ;
+ // read into tmp buffer if there's not enough room
+ // otherwise read directly into the internal buffer
+ const buf = shouldGrow
+ ? tmp
+ : new Uint8Array(this.#buf.buffer, this.length);
+
+ const nread = await r.read(buf);
+ if (nread === null) {
+ return n;
+ }
+
+ // write will grow if needed
+ if (shouldGrow) this.writeSync(buf.subarray(0, nread));
+ else this.#reslice(this.length + nread);
+
+ n += nread;
+ }
+ }
+
+ readFromSync(r) {
+ let n = 0;
+ const tmp = new Uint8Array(MIN_READ);
+ while (true) {
+ const shouldGrow = this.capacity - this.length < MIN_READ;
+ // read into tmp buffer if there's not enough room
+ // otherwise read directly into the internal buffer
+ const buf = shouldGrow
+ ? tmp
+ : new Uint8Array(this.#buf.buffer, this.length);
+
+ const nread = r.readSync(buf);
+ if (nread === null) {
+ return n;
+ }
+
+ // write will grow if needed
+ if (shouldGrow) this.writeSync(buf.subarray(0, nread));
+ else this.#reslice(this.length + nread);
+
+ n += nread;
+ }
+ }
+ }
+
+ async function readAll(r) {
+ const buf = new Buffer();
+ await buf.readFrom(r);
+ return buf.bytes();
+ }
+
+ function readAllSync(r) {
+ const buf = new Buffer();
+ buf.readFromSync(r);
+ return buf.bytes();
+ }
+
+ async function writeAll(w, arr) {
+ let nwritten = 0;
+ while (nwritten < arr.length) {
+ nwritten += await w.write(arr.subarray(nwritten));
+ }
+ }
+
+ function writeAllSync(w, arr) {
+ let nwritten = 0;
+ while (nwritten < arr.length) {
+ nwritten += w.writeSync(arr.subarray(nwritten));
+ }
+ }
+
+ window.__bootstrap.buffer = {
+ writeAll,
+ writeAllSync,
+ readAll,
+ readAllSync,
+ Buffer,
+ };
+})(this);
diff --git a/runtime/rt/27_websocket.js b/runtime/rt/27_websocket.js
new file mode 100644
index 000000000..60428c24d
--- /dev/null
+++ b/runtime/rt/27_websocket.js
@@ -0,0 +1,316 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { requiredArguments, defineEventHandler } = window.__bootstrap.webUtil;
+ const CONNECTING = 0;
+ const OPEN = 1;
+ const CLOSING = 2;
+ const CLOSED = 3;
+
+ class WebSocket extends EventTarget {
+ #readyState = CONNECTING;
+
+ constructor(url, protocols = []) {
+ super();
+ requiredArguments("WebSocket", arguments.length, 1);
+
+ const wsURL = new URL(url);
+
+ if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") {
+ throw new DOMException(
+ "Only ws & wss schemes are allowed in a WebSocket URL.",
+ "SyntaxError",
+ );
+ }
+
+ if (wsURL.hash !== "" || wsURL.href.endsWith("#")) {
+ throw new DOMException(
+ "Fragments are not allowed in a WebSocket URL.",
+ "SyntaxError",
+ );
+ }
+
+ this.#url = wsURL.href;
+
+ core.jsonOpSync("op_ws_check_permission", {
+ url: this.#url,
+ });
+
+ if (protocols && typeof protocols === "string") {
+ protocols = [protocols];
+ }
+
+ if (
+ protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x))
+ ) {
+ throw new DOMException(
+ "Can't supply multiple times the same protocol.",
+ "SyntaxError",
+ );
+ }
+
+ core.jsonOpAsync("op_ws_create", {
+ url: wsURL.href,
+ protocols: protocols.join(", "),
+ }).then((create) => {
+ if (create.success) {
+ this.#rid = create.rid;
+ this.#extensions = create.extensions;
+ this.#protocol = create.protocol;
+
+ if (this.#readyState === CLOSING) {
+ core.jsonOpAsync("op_ws_close", {
+ rid: this.#rid,
+ }).then(() => {
+ this.#readyState = CLOSED;
+
+ const errEvent = new ErrorEvent("error");
+ errEvent.target = this;
+ this.dispatchEvent(errEvent);
+
+ const event = new CloseEvent("close");
+ event.target = this;
+ this.dispatchEvent(event);
+ core.close(this.#rid);
+ });
+ } else {
+ this.#readyState = OPEN;
+ const event = new Event("open");
+ event.target = this;
+ this.dispatchEvent(event);
+
+ this.#eventLoop();
+ }
+ } else {
+ this.#readyState = CLOSED;
+
+ const errEvent = new ErrorEvent("error");
+ errEvent.target = this;
+ this.dispatchEvent(errEvent);
+
+ const closeEvent = new CloseEvent("close");
+ closeEvent.target = this;
+ this.dispatchEvent(closeEvent);
+ }
+ }).catch((err) => {
+ this.#readyState = CLOSED;
+
+ const errorEv = new ErrorEvent(
+ "error",
+ { error: err, message: err.toString() },
+ );
+ errorEv.target = this;
+ this.dispatchEvent(errorEv);
+
+ const closeEv = new CloseEvent("close");
+ closeEv.target = this;
+ this.dispatchEvent(closeEv);
+ });
+ }
+
+ get CONNECTING() {
+ return CONNECTING;
+ }
+ get OPEN() {
+ return OPEN;
+ }
+ get CLOSING() {
+ return CLOSING;
+ }
+ get CLOSED() {
+ return CLOSED;
+ }
+
+ get readyState() {
+ return this.#readyState;
+ }
+
+ #extensions = "";
+ #protocol = "";
+ #url = "";
+ #rid;
+
+ get extensions() {
+ return this.#extensions;
+ }
+ get protocol() {
+ return this.#protocol;
+ }
+
+ #binaryType = "blob";
+ get binaryType() {
+ return this.#binaryType;
+ }
+ set binaryType(value) {
+ if (value === "blob" || value === "arraybuffer") {
+ this.#binaryType = value;
+ }
+ }
+ #bufferedAmount = 0;
+ get bufferedAmount() {
+ return this.#bufferedAmount;
+ }
+
+ get url() {
+ return this.#url;
+ }
+
+ send(data) {
+ requiredArguments("WebSocket.send", arguments.length, 1);
+
+ if (this.#readyState != OPEN) {
+ throw Error("readyState not OPEN");
+ }
+
+ const sendTypedArray = (ta) => {
+ this.#bufferedAmount += ta.size;
+ core.jsonOpAsync("op_ws_send", {
+ rid: this.#rid,
+ }, ta).then(() => {
+ this.#bufferedAmount -= ta.size;
+ });
+ };
+
+ if (data instanceof Blob) {
+ data.slice().arrayBuffer().then((ab) =>
+ sendTypedArray(new DataView(ab))
+ );
+ } else if (
+ data instanceof Int8Array || data instanceof Int16Array ||
+ data instanceof Int32Array || data instanceof Uint8Array ||
+ data instanceof Uint16Array || data instanceof Uint32Array ||
+ data instanceof Uint8ClampedArray || data instanceof Float32Array ||
+ data instanceof Float64Array || data instanceof DataView
+ ) {
+ sendTypedArray(data);
+ } else if (data instanceof ArrayBuffer) {
+ sendTypedArray(new DataView(data));
+ } else {
+ const string = String(data);
+ const encoder = new TextEncoder();
+ const d = encoder.encode(string);
+ this.#bufferedAmount += d.size;
+ core.jsonOpAsync("op_ws_send", {
+ rid: this.#rid,
+ text: string,
+ }).then(() => {
+ this.#bufferedAmount -= d.size;
+ });
+ }
+ }
+
+ close(code, reason) {
+ if (code && (code !== 1000 && !(3000 <= code > 5000))) {
+ throw new DOMException(
+ "The close code must be either 1000 or in the range of 3000 to 4999.",
+ "NotSupportedError",
+ );
+ }
+
+ const encoder = new TextEncoder();
+ if (reason && encoder.encode(reason).byteLength > 123) {
+ throw new DOMException(
+ "The close reason may not be longer than 123 bytes.",
+ "SyntaxError",
+ );
+ }
+
+ if (this.#readyState === CONNECTING) {
+ this.#readyState = CLOSING;
+ } else if (this.#readyState === OPEN) {
+ this.#readyState = CLOSING;
+
+ core.jsonOpAsync("op_ws_close", {
+ rid: this.#rid,
+ code,
+ reason,
+ }).then(() => {
+ this.#readyState = CLOSED;
+ const event = new CloseEvent("close", {
+ wasClean: true,
+ code,
+ reason,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+ core.close(this.#rid);
+ });
+ }
+ }
+
+ async #eventLoop() {
+ if (this.#readyState === OPEN) {
+ const message = await core.jsonOpAsync(
+ "op_ws_next_event",
+ { rid: this.#rid },
+ );
+ if (message.type === "string" || message.type === "binary") {
+ let data;
+
+ if (message.type === "string") {
+ data = message.data;
+ } else {
+ if (this.binaryType === "blob") {
+ data = new Blob([new Uint8Array(message.data)]);
+ } else {
+ data = new Uint8Array(message.data).buffer;
+ }
+ }
+
+ const event = new MessageEvent("message", {
+ data,
+ origin: this.#url,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+
+ this.#eventLoop();
+ } else if (message.type === "close") {
+ this.#readyState = CLOSED;
+ const event = new CloseEvent("close", {
+ wasClean: true,
+ code: message.code,
+ reason: message.reason,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+ } else if (message.type === "error") {
+ this.#readyState = CLOSED;
+
+ const errorEv = new ErrorEvent("error");
+ errorEv.target = this;
+ this.dispatchEvent(errorEv);
+
+ this.#readyState = CLOSED;
+ const closeEv = new CloseEvent("close");
+ closeEv.target = this;
+ this.dispatchEvent(closeEv);
+ }
+ }
+ }
+ }
+
+ Object.defineProperties(WebSocket, {
+ CONNECTING: {
+ value: 0,
+ },
+ OPEN: {
+ value: 1,
+ },
+ CLOSING: {
+ value: 2,
+ },
+ CLOSED: {
+ value: 3,
+ },
+ });
+
+ defineEventHandler(WebSocket.prototype, "message");
+ defineEventHandler(WebSocket.prototype, "error");
+ defineEventHandler(WebSocket.prototype, "close");
+ defineEventHandler(WebSocket.prototype, "open");
+ window.__bootstrap.webSocket = {
+ WebSocket,
+ };
+})(this);
diff --git a/runtime/rt/30_files.js b/runtime/rt/30_files.js
new file mode 100644
index 000000000..679b184fd
--- /dev/null
+++ b/runtime/rt/30_files.js
@@ -0,0 +1,209 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { read, readSync, write, writeSync } = window.__bootstrap.io;
+ const { pathFromURL } = window.__bootstrap.util;
+
+ function seekSync(
+ rid,
+ offset,
+ whence,
+ ) {
+ return core.jsonOpSync("op_seek_sync", { rid, offset, whence });
+ }
+
+ function seek(
+ rid,
+ offset,
+ whence,
+ ) {
+ return core.jsonOpAsync("op_seek_async", { rid, offset, whence });
+ }
+
+ function openSync(
+ path,
+ options = { read: true },
+ ) {
+ checkOpenOptions(options);
+ const mode = options?.mode;
+ const rid = core.jsonOpSync(
+ "op_open_sync",
+ { path: pathFromURL(path), options, mode },
+ );
+
+ return new File(rid);
+ }
+
+ async function open(
+ path,
+ options = { read: true },
+ ) {
+ checkOpenOptions(options);
+ const mode = options?.mode;
+ const rid = await core.jsonOpAsync(
+ "op_open_async",
+ { path: pathFromURL(path), options, mode },
+ );
+
+ return new File(rid);
+ }
+
+ function createSync(path) {
+ return openSync(path, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+ }
+
+ function create(path) {
+ return open(path, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+ }
+
+ class File {
+ #rid = 0;
+
+ constructor(rid) {
+ this.#rid = rid;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ readSync(p) {
+ return readSync(this.rid, p);
+ }
+
+ seek(offset, whence) {
+ return seek(this.rid, offset, whence);
+ }
+
+ seekSync(offset, whence) {
+ return seekSync(this.rid, offset, whence);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stdin {
+ constructor() {
+ }
+
+ get rid() {
+ return 0;
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ readSync(p) {
+ return readSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stdout {
+ constructor() {
+ }
+
+ get rid() {
+ return 1;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stderr {
+ constructor() {
+ }
+
+ get rid() {
+ return 2;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ const stdin = new Stdin();
+ const stdout = new Stdout();
+ const stderr = new Stderr();
+
+ function checkOpenOptions(options) {
+ if (Object.values(options).filter((val) => val === true).length === 0) {
+ throw new Error("OpenOptions requires at least one option to be true");
+ }
+
+ if (options.truncate && !options.write) {
+ throw new Error("'truncate' option requires 'write' option");
+ }
+
+ const createOrCreateNewWithoutWriteOrAppend =
+ (options.create || options.createNew) &&
+ !(options.write || options.append);
+
+ if (createOrCreateNewWithoutWriteOrAppend) {
+ throw new Error(
+ "'create' or 'createNew' options require 'write' or 'append' option",
+ );
+ }
+ }
+
+ window.__bootstrap.files = {
+ stdin,
+ stdout,
+ stderr,
+ File,
+ create,
+ createSync,
+ open,
+ openSync,
+ seek,
+ seekSync,
+ };
+})(this);
diff --git a/runtime/rt/30_fs.js b/runtime/rt/30_fs.js
new file mode 100644
index 000000000..33fab01e4
--- /dev/null
+++ b/runtime/rt/30_fs.js
@@ -0,0 +1,425 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { pathFromURL } = window.__bootstrap.util;
+ const build = window.__bootstrap.build.build;
+
+ function chmodSync(path, mode) {
+ core.jsonOpSync("op_chmod_sync", { path: pathFromURL(path), mode });
+ }
+
+ async function chmod(path, mode) {
+ await core.jsonOpAsync("op_chmod_async", { path: pathFromURL(path), mode });
+ }
+
+ function chownSync(
+ path,
+ uid,
+ gid,
+ ) {
+ core.jsonOpSync("op_chown_sync", { path: pathFromURL(path), uid, gid });
+ }
+
+ async function chown(
+ path,
+ uid,
+ gid,
+ ) {
+ await core.jsonOpAsync(
+ "op_chown_async",
+ { path: pathFromURL(path), uid, gid },
+ );
+ }
+
+ function copyFileSync(
+ fromPath,
+ toPath,
+ ) {
+ core.jsonOpSync("op_copy_file_sync", {
+ from: pathFromURL(fromPath),
+ to: pathFromURL(toPath),
+ });
+ }
+
+ async function copyFile(
+ fromPath,
+ toPath,
+ ) {
+ await core.jsonOpAsync("op_copy_file_async", {
+ from: pathFromURL(fromPath),
+ to: pathFromURL(toPath),
+ });
+ }
+
+ function cwd() {
+ return core.jsonOpSync("op_cwd");
+ }
+
+ function chdir(directory) {
+ core.jsonOpSync("op_chdir", { directory });
+ }
+
+ function makeTempDirSync(options = {}) {
+ return core.jsonOpSync("op_make_temp_dir_sync", options);
+ }
+
+ function makeTempDir(options = {}) {
+ return core.jsonOpAsync("op_make_temp_dir_async", options);
+ }
+
+ function makeTempFileSync(options = {}) {
+ return core.jsonOpSync("op_make_temp_file_sync", options);
+ }
+
+ function makeTempFile(options = {}) {
+ return core.jsonOpAsync("op_make_temp_file_async", options);
+ }
+
+ function mkdirArgs(path, options) {
+ const args = { path: pathFromURL(path), recursive: false };
+ if (options != null) {
+ if (typeof options.recursive == "boolean") {
+ args.recursive = options.recursive;
+ }
+ if (options.mode) {
+ args.mode = options.mode;
+ }
+ }
+ return args;
+ }
+
+ function mkdirSync(path, options) {
+ core.jsonOpSync("op_mkdir_sync", mkdirArgs(path, options));
+ }
+
+ async function mkdir(
+ path,
+ options,
+ ) {
+ await core.jsonOpAsync("op_mkdir_async", mkdirArgs(path, options));
+ }
+
+ function res(response) {
+ return response.entries;
+ }
+
+ function readDirSync(path) {
+ return res(
+ core.jsonOpSync("op_read_dir_sync", { path: pathFromURL(path) }),
+ )[
+ Symbol.iterator
+ ]();
+ }
+
+ function readDir(path) {
+ const array = core.jsonOpAsync(
+ "op_read_dir_async",
+ { path: pathFromURL(path) },
+ )
+ .then(
+ res,
+ );
+ return {
+ async *[Symbol.asyncIterator]() {
+ yield* await array;
+ },
+ };
+ }
+
+ function readLinkSync(path) {
+ return core.jsonOpSync("op_read_link_sync", { path: pathFromURL(path) });
+ }
+
+ function readLink(path) {
+ return core.jsonOpAsync("op_read_link_async", { path: pathFromURL(path) });
+ }
+
+ function realPathSync(path) {
+ return core.jsonOpSync("op_realpath_sync", { path });
+ }
+
+ function realPath(path) {
+ return core.jsonOpAsync("op_realpath_async", { path });
+ }
+
+ function removeSync(
+ path,
+ options = {},
+ ) {
+ core.jsonOpSync("op_remove_sync", {
+ path: pathFromURL(path),
+ recursive: !!options.recursive,
+ });
+ }
+
+ async function remove(
+ path,
+ options = {},
+ ) {
+ await core.jsonOpAsync("op_remove_async", {
+ path: pathFromURL(path),
+ recursive: !!options.recursive,
+ });
+ }
+
+ function renameSync(oldpath, newpath) {
+ core.jsonOpSync("op_rename_sync", { oldpath, newpath });
+ }
+
+ async function rename(oldpath, newpath) {
+ await core.jsonOpAsync("op_rename_async", { oldpath, newpath });
+ }
+
+ function parseFileInfo(response) {
+ const unix = build.os === "darwin" || build.os === "linux";
+ return {
+ isFile: response.isFile,
+ isDirectory: response.isDirectory,
+ isSymlink: response.isSymlink,
+ size: response.size,
+ mtime: response.mtime != null ? new Date(response.mtime) : null,
+ atime: response.atime != null ? new Date(response.atime) : null,
+ birthtime: response.birthtime != null
+ ? new Date(response.birthtime)
+ : null,
+ // Only non-null if on Unix
+ dev: unix ? response.dev : null,
+ ino: unix ? response.ino : null,
+ mode: unix ? response.mode : null,
+ nlink: unix ? response.nlink : null,
+ uid: unix ? response.uid : null,
+ gid: unix ? response.gid : null,
+ rdev: unix ? response.rdev : null,
+ blksize: unix ? response.blksize : null,
+ blocks: unix ? response.blocks : null,
+ };
+ }
+
+ function fstatSync(rid) {
+ return parseFileInfo(core.jsonOpSync("op_fstat_sync", { rid }));
+ }
+
+ async function fstat(rid) {
+ return parseFileInfo(await core.jsonOpAsync("op_fstat_async", { rid }));
+ }
+
+ async function lstat(path) {
+ const res = await core.jsonOpAsync("op_stat_async", {
+ path: pathFromURL(path),
+ lstat: true,
+ });
+ return parseFileInfo(res);
+ }
+
+ function lstatSync(path) {
+ const res = core.jsonOpSync("op_stat_sync", {
+ path: pathFromURL(path),
+ lstat: true,
+ });
+ return parseFileInfo(res);
+ }
+
+ async function stat(path) {
+ const res = await core.jsonOpAsync("op_stat_async", {
+ path: pathFromURL(path),
+ lstat: false,
+ });
+ return parseFileInfo(res);
+ }
+
+ function statSync(path) {
+ const res = core.jsonOpSync("op_stat_sync", {
+ path: pathFromURL(path),
+ lstat: false,
+ });
+ return parseFileInfo(res);
+ }
+
+ function coerceLen(len) {
+ if (len == null || len < 0) {
+ return 0;
+ }
+
+ return len;
+ }
+
+ function ftruncateSync(rid, len) {
+ core.jsonOpSync("op_ftruncate_sync", { rid, len: coerceLen(len) });
+ }
+
+ async function ftruncate(rid, len) {
+ await core.jsonOpAsync("op_ftruncate_async", { rid, len: coerceLen(len) });
+ }
+
+ function truncateSync(path, len) {
+ core.jsonOpSync("op_truncate_sync", { path, len: coerceLen(len) });
+ }
+
+ async function truncate(path, len) {
+ await core.jsonOpAsync("op_truncate_async", { path, len: coerceLen(len) });
+ }
+
+ function umask(mask) {
+ return core.jsonOpSync("op_umask", { mask });
+ }
+
+ function linkSync(oldpath, newpath) {
+ core.jsonOpSync("op_link_sync", { oldpath, newpath });
+ }
+
+ async function link(oldpath, newpath) {
+ await core.jsonOpAsync("op_link_async", { oldpath, newpath });
+ }
+
+ function toUnixTimeFromEpoch(value) {
+ if (value instanceof Date) {
+ const time = value.valueOf();
+ const seconds = Math.trunc(time / 1e3);
+ const nanoseconds = Math.trunc(time - (seconds * 1e3)) * 1e6;
+
+ return [
+ seconds,
+ nanoseconds,
+ ];
+ }
+
+ const seconds = value;
+ const nanoseconds = 0;
+
+ return [
+ seconds,
+ nanoseconds,
+ ];
+ }
+
+ function futimeSync(
+ rid,
+ atime,
+ mtime,
+ ) {
+ core.jsonOpSync("op_futime_sync", {
+ rid,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ async function futime(
+ rid,
+ atime,
+ mtime,
+ ) {
+ await core.jsonOpAsync("op_futime_async", {
+ rid,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ function utimeSync(
+ path,
+ atime,
+ mtime,
+ ) {
+ core.jsonOpSync("op_utime_sync", {
+ path,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ async function utime(
+ path,
+ atime,
+ mtime,
+ ) {
+ await core.jsonOpAsync("op_utime_async", {
+ path,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ function symlinkSync(
+ oldpath,
+ newpath,
+ options,
+ ) {
+ core.jsonOpSync("op_symlink_sync", { oldpath, newpath, options });
+ }
+
+ async function symlink(
+ oldpath,
+ newpath,
+ options,
+ ) {
+ await core.jsonOpAsync("op_symlink_async", { oldpath, newpath, options });
+ }
+
+ function fdatasyncSync(rid) {
+ core.jsonOpSync("op_fdatasync_sync", { rid });
+ }
+
+ async function fdatasync(rid) {
+ await core.jsonOpAsync("op_fdatasync_async", { rid });
+ }
+
+ function fsyncSync(rid) {
+ core.jsonOpSync("op_fsync_sync", { rid });
+ }
+
+ async function fsync(rid) {
+ await core.jsonOpAsync("op_fsync_async", { rid });
+ }
+
+ window.__bootstrap.fs = {
+ cwd,
+ chdir,
+ chmodSync,
+ chmod,
+ chown,
+ chownSync,
+ copyFile,
+ copyFileSync,
+ makeTempFile,
+ makeTempDir,
+ makeTempFileSync,
+ makeTempDirSync,
+ mkdir,
+ mkdirSync,
+ readDir,
+ readDirSync,
+ readLinkSync,
+ readLink,
+ realPathSync,
+ realPath,
+ remove,
+ removeSync,
+ renameSync,
+ rename,
+ fstatSync,
+ fstat,
+ lstat,
+ lstatSync,
+ stat,
+ statSync,
+ ftruncate,
+ ftruncateSync,
+ truncate,
+ truncateSync,
+ umask,
+ link,
+ linkSync,
+ futime,
+ futimeSync,
+ utime,
+ utimeSync,
+ symlink,
+ symlinkSync,
+ fdatasync,
+ fdatasyncSync,
+ fsync,
+ fsyncSync,
+ };
+})(this);
diff --git a/runtime/rt/30_metrics.js b/runtime/rt/30_metrics.js
new file mode 100644
index 000000000..d44a629cb
--- /dev/null
+++ b/runtime/rt/30_metrics.js
@@ -0,0 +1,13 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function metrics() {
+ return core.jsonOpSync("op_metrics");
+ }
+
+ window.__bootstrap.metrics = {
+ metrics,
+ };
+})(this);
diff --git a/runtime/rt/30_net.js b/runtime/rt/30_net.js
new file mode 100644
index 000000000..9a71f0693
--- /dev/null
+++ b/runtime/rt/30_net.js
@@ -0,0 +1,245 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { errors } = window.__bootstrap.errors;
+ const { read, write } = window.__bootstrap.io;
+
+ const ShutdownMode = {
+ // See http://man7.org/linux/man-pages/man2/shutdown.2.html
+ // Corresponding to SHUT_RD, SHUT_WR, SHUT_RDWR
+ 0: "Read",
+ 1: "Write",
+ 2: "ReadWrite",
+ Read: 0,
+ Write: 1,
+ ReadWrite: 2, // unused
+ };
+
+ function shutdown(rid, how) {
+ core.jsonOpSync("op_shutdown", { rid, how });
+ return Promise.resolve();
+ }
+
+ function opAccept(
+ rid,
+ transport,
+ ) {
+ return core.jsonOpAsync("op_accept", { rid, transport });
+ }
+
+ function opListen(args) {
+ return core.jsonOpSync("op_listen", args);
+ }
+
+ function opConnect(args) {
+ return core.jsonOpAsync("op_connect", args);
+ }
+
+ function opReceive(
+ rid,
+ transport,
+ zeroCopy,
+ ) {
+ return core.jsonOpAsync(
+ "op_datagram_receive",
+ { rid, transport },
+ zeroCopy,
+ );
+ }
+
+ function opSend(args, zeroCopy) {
+ return core.jsonOpAsync("op_datagram_send", args, zeroCopy);
+ }
+
+ class Conn {
+ #rid = 0;
+ #remoteAddr = null;
+ #localAddr = null;
+ constructor(
+ rid,
+ remoteAddr,
+ localAddr,
+ ) {
+ this.#rid = rid;
+ this.#remoteAddr = remoteAddr;
+ this.#localAddr = localAddr;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get remoteAddr() {
+ return this.#remoteAddr;
+ }
+
+ get localAddr() {
+ return this.#localAddr;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ // TODO(lucacasonato): make this unavailable in stable
+ closeWrite() {
+ shutdown(this.rid, ShutdownMode.Write);
+ }
+ }
+
+ class Listener {
+ #rid = 0;
+ #addr = null;
+
+ constructor(rid, addr) {
+ this.#rid = rid;
+ this.#addr = addr;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get addr() {
+ return this.#addr;
+ }
+
+ async accept() {
+ const res = await opAccept(this.rid, this.addr.transport);
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ async next() {
+ let conn;
+ try {
+ conn = await this.accept();
+ } catch (error) {
+ if (error instanceof errors.BadResource) {
+ return { value: undefined, done: true };
+ }
+ throw error;
+ }
+ return { value: conn, done: false };
+ }
+
+ return(value) {
+ this.close();
+ return Promise.resolve({ value, done: true });
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+ }
+
+ class Datagram {
+ #rid = 0;
+ #addr = null;
+
+ constructor(
+ rid,
+ addr,
+ bufSize = 1024,
+ ) {
+ this.#rid = rid;
+ this.#addr = addr;
+ this.bufSize = bufSize;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get addr() {
+ return this.#addr;
+ }
+
+ async receive(p) {
+ const buf = p || new Uint8Array(this.bufSize);
+ const { size, remoteAddr } = await opReceive(
+ this.rid,
+ this.addr.transport,
+ buf,
+ );
+ const sub = buf.subarray(0, size);
+ return [sub, remoteAddr];
+ }
+
+ send(p, addr) {
+ const remote = { hostname: "127.0.0.1", ...addr };
+
+ const args = { ...remote, rid: this.rid };
+ return opSend(args, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ async *[Symbol.asyncIterator]() {
+ while (true) {
+ try {
+ yield await this.receive();
+ } catch (err) {
+ if (err instanceof errors.BadResource) {
+ break;
+ }
+ throw err;
+ }
+ }
+ }
+ }
+
+ function listen({ hostname, ...options }) {
+ const res = opListen({
+ transport: "tcp",
+ hostname: typeof hostname === "undefined" ? "0.0.0.0" : hostname,
+ ...options,
+ });
+
+ return new Listener(res.rid, res.localAddr);
+ }
+
+ async function connect(
+ options,
+ ) {
+ let res;
+
+ if (options.transport === "unix") {
+ res = await opConnect(options);
+ } else {
+ res = await opConnect({
+ transport: "tcp",
+ hostname: "127.0.0.1",
+ ...options,
+ });
+ }
+
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ window.__bootstrap.net = {
+ connect,
+ Conn,
+ opConnect,
+ listen,
+ opListen,
+ Listener,
+ shutdown,
+ ShutdownMode,
+ Datagram,
+ };
+})(this);
diff --git a/runtime/rt/30_os.js b/runtime/rt/30_os.js
new file mode 100644
index 000000000..ebc4e8916
--- /dev/null
+++ b/runtime/rt/30_os.js
@@ -0,0 +1,66 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function loadavg() {
+ return core.jsonOpSync("op_loadavg");
+ }
+
+ function hostname() {
+ return core.jsonOpSync("op_hostname");
+ }
+
+ function osRelease() {
+ return core.jsonOpSync("op_os_release");
+ }
+
+ function systemMemoryInfo() {
+ return core.jsonOpSync("op_system_memory_info");
+ }
+
+ function systemCpuInfo() {
+ return core.jsonOpSync("op_system_cpu_info");
+ }
+
+ function exit(code = 0) {
+ core.jsonOpSync("op_exit", { code });
+ throw new Error("Code not reachable");
+ }
+
+ function setEnv(key, value) {
+ core.jsonOpSync("op_set_env", { key, value });
+ }
+
+ function getEnv(key) {
+ return core.jsonOpSync("op_get_env", { key })[0];
+ }
+
+ function deleteEnv(key) {
+ core.jsonOpSync("op_delete_env", { key });
+ }
+
+ const env = {
+ get: getEnv,
+ toObject() {
+ return core.jsonOpSync("op_env");
+ },
+ set: setEnv,
+ delete: deleteEnv,
+ };
+
+ function execPath() {
+ return core.jsonOpSync("op_exec_path");
+ }
+
+ window.__bootstrap.os = {
+ env,
+ execPath,
+ exit,
+ osRelease,
+ systemMemoryInfo,
+ systemCpuInfo,
+ hostname,
+ loadavg,
+ };
+})(this);
diff --git a/runtime/rt/40_compiler_api.js b/runtime/rt/40_compiler_api.js
new file mode 100644
index 000000000..ea963b67b
--- /dev/null
+++ b/runtime/rt/40_compiler_api.js
@@ -0,0 +1,97 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This file contains the runtime APIs which will dispatch work to the internal
+// compiler within Deno.
+((window) => {
+ const core = window.Deno.core;
+ const util = window.__bootstrap.util;
+
+ function opCompile(request) {
+ return core.jsonOpAsync("op_compile", request);
+ }
+
+ function opTranspile(
+ request,
+ ) {
+ return core.jsonOpAsync("op_transpile", request);
+ }
+
+ function checkRelative(specifier) {
+ return specifier.match(/^([\.\/\\]|https?:\/{2}|file:\/{2})/)
+ ? specifier
+ : `./${specifier}`;
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ function transpileOnly(
+ sources,
+ options = {},
+ ) {
+ util.log("Deno.transpileOnly", { sources: Object.keys(sources), options });
+ const payload = {
+ sources,
+ options: JSON.stringify(options),
+ };
+ return opTranspile(payload);
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ async function compile(
+ rootName,
+ sources,
+ options = {},
+ ) {
+ const payload = {
+ rootName: sources ? rootName : checkRelative(rootName),
+ sources,
+ options: JSON.stringify(options),
+ bundle: false,
+ };
+ util.log("Deno.compile", {
+ rootName: payload.rootName,
+ sources: !!sources,
+ options,
+ });
+ /** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
+ const result = await opCompile(payload);
+ util.assert(result.emittedFiles);
+ const maybeDiagnostics = result.diagnostics.length === 0
+ ? undefined
+ : result.diagnostics;
+
+ return [maybeDiagnostics, result.emittedFiles];
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ async function bundle(
+ rootName,
+ sources,
+ options = {},
+ ) {
+ const payload = {
+ rootName: sources ? rootName : checkRelative(rootName),
+ sources,
+ options: JSON.stringify(options),
+ bundle: true,
+ };
+ util.log("Deno.bundle", {
+ rootName: payload.rootName,
+ sources: !!sources,
+ options,
+ });
+ /** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
+ const result = await opCompile(payload);
+ const output = result.emittedFiles["deno:///bundle.js"];
+ util.assert(output);
+ const maybeDiagnostics = result.diagnostics.length === 0
+ ? undefined
+ : result.diagnostics;
+ return [maybeDiagnostics, output];
+ }
+
+ window.__bootstrap.compilerApi = {
+ bundle,
+ compile,
+ transpileOnly,
+ };
+})(this);
diff --git a/runtime/rt/40_diagnostics.js b/runtime/rt/40_diagnostics.js
new file mode 100644
index 000000000..2b7457853
--- /dev/null
+++ b/runtime/rt/40_diagnostics.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// Diagnostic provides an abstraction for advice/errors received from a
+// compiler, which is strongly influenced by the format of TypeScript
+// diagnostics.
+
+((window) => {
+ const DiagnosticCategory = {
+ 0: "Warning",
+ 1: "Error",
+ 2: "Suggestion",
+ 3: "Message",
+
+ Warning: 0,
+ Error: 1,
+ Suggestion: 2,
+ Message: 3,
+ };
+
+ window.__bootstrap.diagnostics = {
+ DiagnosticCategory,
+ };
+})(this);
diff --git a/runtime/rt/40_error_stack.js b/runtime/rt/40_error_stack.js
new file mode 100644
index 000000000..da2ee51f3
--- /dev/null
+++ b/runtime/rt/40_error_stack.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function opFormatDiagnostics(diagnostics) {
+ return core.jsonOpSync("op_format_diagnostic", diagnostics);
+ }
+
+ function opApplySourceMap(location) {
+ const res = core.jsonOpSync("op_apply_source_map", location);
+ return {
+ fileName: res.fileName,
+ lineNumber: res.lineNumber,
+ columnNumber: res.columnNumber,
+ };
+ }
+
+ window.__bootstrap.errorStack = {
+ opApplySourceMap,
+ opFormatDiagnostics,
+ };
+})(this);
diff --git a/runtime/rt/40_fs_events.js b/runtime/rt/40_fs_events.js
new file mode 100644
index 000000000..a36adecba
--- /dev/null
+++ b/runtime/rt/40_fs_events.js
@@ -0,0 +1,52 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { errors } = window.__bootstrap.errors;
+
+ class FsWatcher {
+ #rid = 0;
+
+ constructor(paths, options) {
+ const { recursive } = options;
+ this.#rid = core.jsonOpSync("op_fs_events_open", { recursive, paths });
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ async next() {
+ try {
+ return await core.jsonOpAsync("op_fs_events_poll", {
+ rid: this.rid,
+ });
+ } catch (error) {
+ if (error instanceof errors.BadResource) {
+ return { value: undefined, done: true };
+ }
+ throw error;
+ }
+ }
+
+ return(value) {
+ core.close(this.rid);
+ return Promise.resolve({ value, done: true });
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+ }
+
+ function watchFs(
+ paths,
+ options = { recursive: true },
+ ) {
+ return new FsWatcher(Array.isArray(paths) ? paths : [paths], options);
+ }
+
+ window.__bootstrap.fsEvents = {
+ watchFs,
+ };
+})(this);
diff --git a/runtime/rt/40_net_unstable.js b/runtime/rt/40_net_unstable.js
new file mode 100644
index 000000000..fcc899a30
--- /dev/null
+++ b/runtime/rt/40_net_unstable.js
@@ -0,0 +1,48 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const net = window.__bootstrap.net;
+
+ function listen(options) {
+ if (options.transport === "unix") {
+ const res = net.opListen(options);
+ return new net.Listener(res.rid, res.localAddr);
+ } else {
+ return net.listen(options);
+ }
+ }
+
+ function listenDatagram(
+ options,
+ ) {
+ let res;
+ if (options.transport === "unixpacket") {
+ res = net.opListen(options);
+ } else {
+ res = net.opListen({
+ transport: "udp",
+ hostname: "127.0.0.1",
+ ...options,
+ });
+ }
+
+ return new net.Datagram(res.rid, res.localAddr);
+ }
+
+ async function connect(
+ options,
+ ) {
+ if (options.transport === "unix") {
+ const res = await net.opConnect(options);
+ return new net.Conn(res.rid, res.remoteAddr, res.localAddr);
+ } else {
+ return net.connect(options);
+ }
+ }
+
+ window.__bootstrap.netUnstable = {
+ connect,
+ listenDatagram,
+ listen,
+ };
+})(this);
diff --git a/runtime/rt/40_performance.js b/runtime/rt/40_performance.js
new file mode 100644
index 000000000..0a63dc704
--- /dev/null
+++ b/runtime/rt/40_performance.js
@@ -0,0 +1,341 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { opNow } = window.__bootstrap.timers;
+ const { cloneValue, illegalConstructorKey } = window.__bootstrap.webUtil;
+
+ const customInspect = Symbol.for("Deno.customInspect");
+ let performanceEntries = [];
+
+ function findMostRecent(
+ name,
+ type,
+ ) {
+ return performanceEntries
+ .slice()
+ .reverse()
+ .find((entry) => entry.name === name && entry.entryType === type);
+ }
+
+ function convertMarkToTimestamp(mark) {
+ if (typeof mark === "string") {
+ const entry = findMostRecent(mark, "mark");
+ if (!entry) {
+ throw new SyntaxError(`Cannot find mark: "${mark}".`);
+ }
+ return entry.startTime;
+ }
+ if (mark < 0) {
+ throw new TypeError("Mark cannot be negative.");
+ }
+ return mark;
+ }
+
+ function filterByNameType(
+ name,
+ type,
+ ) {
+ return performanceEntries.filter(
+ (entry) =>
+ (name ? entry.name === name : true) &&
+ (type ? entry.entryType === type : true),
+ );
+ }
+
+ function now() {
+ return opNow();
+ }
+
+ class PerformanceEntry {
+ #name = "";
+ #entryType = "";
+ #startTime = 0;
+ #duration = 0;
+
+ get name() {
+ return this.#name;
+ }
+
+ get entryType() {
+ return this.#entryType;
+ }
+
+ get startTime() {
+ return this.#startTime;
+ }
+
+ get duration() {
+ return this.#duration;
+ }
+
+ constructor(
+ name = null,
+ entryType = null,
+ startTime = null,
+ duration = null,
+ key = null,
+ ) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ this.#name = name;
+ this.#entryType = entryType;
+ this.#startTime = startTime;
+ this.#duration = duration;
+ }
+
+ toJSON() {
+ return {
+ name: this.#name,
+ entryType: this.#entryType,
+ startTime: this.#startTime,
+ duration: this.#duration,
+ };
+ }
+
+ [customInspect]() {
+ return `${this.constructor.name} { name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class PerformanceMark extends PerformanceEntry {
+ #detail = null;
+
+ get detail() {
+ return this.#detail;
+ }
+
+ get entryType() {
+ return "mark";
+ }
+
+ constructor(
+ name,
+ { detail = null, startTime = now() } = {},
+ ) {
+ super(name, "mark", startTime, 0, illegalConstructorKey);
+ if (startTime < 0) {
+ throw new TypeError("startTime cannot be negative");
+ }
+ this.#detail = cloneValue(detail);
+ }
+
+ toJSON() {
+ return {
+ name: this.name,
+ entryType: this.entryType,
+ startTime: this.startTime,
+ duration: this.duration,
+ detail: this.detail,
+ };
+ }
+
+ [customInspect]() {
+ return this.detail
+ ? `${this.constructor.name} {\n detail: ${
+ JSON.stringify(this.detail, null, 2)
+ },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
+ : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class PerformanceMeasure extends PerformanceEntry {
+ #detail = null;
+
+ get detail() {
+ return this.#detail;
+ }
+
+ get entryType() {
+ return "measure";
+ }
+
+ constructor(
+ name,
+ startTime,
+ duration,
+ detail = null,
+ key,
+ ) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ super(name, "measure", startTime, duration, illegalConstructorKey);
+ this.#detail = cloneValue(detail);
+ }
+
+ toJSON() {
+ return {
+ name: this.name,
+ entryType: this.entryType,
+ startTime: this.startTime,
+ duration: this.duration,
+ detail: this.detail,
+ };
+ }
+
+ [customInspect]() {
+ return this.detail
+ ? `${this.constructor.name} {\n detail: ${
+ JSON.stringify(this.detail, null, 2)
+ },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
+ : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class Performance {
+ constructor(key = null) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ }
+
+ clearMarks(markName) {
+ if (markName == null) {
+ performanceEntries = performanceEntries.filter(
+ (entry) => entry.entryType !== "mark",
+ );
+ } else {
+ performanceEntries = performanceEntries.filter(
+ (entry) => !(entry.name === markName && entry.entryType === "mark"),
+ );
+ }
+ }
+
+ clearMeasures(measureName) {
+ if (measureName == null) {
+ performanceEntries = performanceEntries.filter(
+ (entry) => entry.entryType !== "measure",
+ );
+ } else {
+ performanceEntries = performanceEntries.filter(
+ (entry) =>
+ !(entry.name === measureName && entry.entryType === "measure"),
+ );
+ }
+ }
+
+ getEntries() {
+ return filterByNameType();
+ }
+
+ getEntriesByName(
+ name,
+ type,
+ ) {
+ return filterByNameType(name, type);
+ }
+
+ getEntriesByType(type) {
+ return filterByNameType(undefined, type);
+ }
+
+ mark(
+ markName,
+ options = {},
+ ) {
+ // 3.1.1.1 If the global object is a Window object and markName uses the
+ // same name as a read only attribute in the PerformanceTiming interface,
+ // throw a SyntaxError. - not implemented
+ const entry = new PerformanceMark(markName, options);
+ // 3.1.1.7 Queue entry - not implemented
+ performanceEntries.push(entry);
+ return entry;
+ }
+
+ measure(
+ measureName,
+ startOrMeasureOptions = {},
+ endMark,
+ ) {
+ if (
+ startOrMeasureOptions && typeof startOrMeasureOptions === "object" &&
+ Object.keys(startOrMeasureOptions).length > 0
+ ) {
+ if (endMark) {
+ throw new TypeError("Options cannot be passed with endMark.");
+ }
+ if (
+ !("start" in startOrMeasureOptions) &&
+ !("end" in startOrMeasureOptions)
+ ) {
+ throw new TypeError(
+ "A start or end mark must be supplied in options.",
+ );
+ }
+ if (
+ "start" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions &&
+ "end" in startOrMeasureOptions
+ ) {
+ throw new TypeError(
+ "Cannot specify start, end, and duration together in options.",
+ );
+ }
+ }
+ let endTime;
+ if (endMark) {
+ endTime = convertMarkToTimestamp(endMark);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "end" in startOrMeasureOptions
+ ) {
+ endTime = convertMarkToTimestamp(startOrMeasureOptions.end);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "start" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions
+ ) {
+ const start = convertMarkToTimestamp(startOrMeasureOptions.start);
+ const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
+ endTime = start + duration;
+ } else {
+ endTime = now();
+ }
+ let startTime;
+ if (
+ typeof startOrMeasureOptions === "object" &&
+ "start" in startOrMeasureOptions
+ ) {
+ startTime = convertMarkToTimestamp(startOrMeasureOptions.start);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "end" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions
+ ) {
+ const end = convertMarkToTimestamp(startOrMeasureOptions.end);
+ const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
+ startTime = end - duration;
+ } else if (typeof startOrMeasureOptions === "string") {
+ startTime = convertMarkToTimestamp(startOrMeasureOptions);
+ } else {
+ startTime = 0;
+ }
+ const entry = new PerformanceMeasure(
+ measureName,
+ startTime,
+ endTime - startTime,
+ typeof startOrMeasureOptions === "object"
+ ? startOrMeasureOptions.detail ?? null
+ : null,
+ illegalConstructorKey,
+ );
+ performanceEntries.push(entry);
+ return entry;
+ }
+
+ now() {
+ return now();
+ }
+ }
+
+ const performance = new Performance(illegalConstructorKey);
+
+ window.__bootstrap.performance = {
+ PerformanceEntry,
+ PerformanceMark,
+ PerformanceMeasure,
+ Performance,
+ performance,
+ };
+})(this);
diff --git a/runtime/rt/40_permissions.js b/runtime/rt/40_permissions.js
new file mode 100644
index 000000000..50d471b6a
--- /dev/null
+++ b/runtime/rt/40_permissions.js
@@ -0,0 +1,65 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { illegalConstructorKey } = window.__bootstrap.webUtil;
+
+ function opQuery(desc) {
+ return core.jsonOpSync("op_query_permission", desc).state;
+ }
+
+ function opRevoke(desc) {
+ return core.jsonOpSync("op_revoke_permission", desc).state;
+ }
+
+ function opRequest(desc) {
+ return core.jsonOpSync("op_request_permission", desc).state;
+ }
+
+ class PermissionStatus {
+ constructor(state = null, key = null) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ this.state = state;
+ }
+ // TODO(kt3k): implement onchange handler
+ }
+
+ class Permissions {
+ constructor(key) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ }
+
+ query(desc) {
+ const state = opQuery(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+
+ revoke(desc) {
+ const state = opRevoke(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+
+ request(desc) {
+ const state = opRequest(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+ }
+
+ const permissions = new Permissions(illegalConstructorKey);
+
+ window.__bootstrap.permissions = {
+ permissions,
+ Permissions,
+ PermissionStatus,
+ };
+})(this);
diff --git a/runtime/rt/40_plugins.js b/runtime/rt/40_plugins.js
new file mode 100644
index 000000000..f5aefd400
--- /dev/null
+++ b/runtime/rt/40_plugins.js
@@ -0,0 +1,13 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function openPlugin(filename) {
+ return core.jsonOpSync("op_open_plugin", { filename });
+ }
+
+ window.__bootstrap.plugins = {
+ openPlugin,
+ };
+})(this);
diff --git a/runtime/rt/40_process.js b/runtime/rt/40_process.js
new file mode 100644
index 000000000..b46a1aead
--- /dev/null
+++ b/runtime/rt/40_process.js
@@ -0,0 +1,122 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { File } = window.__bootstrap.files;
+ const { readAll } = window.__bootstrap.buffer;
+ const { assert, pathFromURL } = window.__bootstrap.util;
+
+ function opKill(pid, signo) {
+ core.jsonOpSync("op_kill", { pid, signo });
+ }
+
+ function opRunStatus(rid) {
+ return core.jsonOpAsync("op_run_status", { rid });
+ }
+
+ function opRun(request) {
+ assert(request.cmd.length > 0);
+ return core.jsonOpSync("op_run", request);
+ }
+
+ async function runStatus(rid) {
+ const res = await opRunStatus(rid);
+
+ if (res.gotSignal) {
+ const signal = res.exitSignal;
+ return { success: false, code: 128 + signal, signal };
+ } else if (res.exitCode != 0) {
+ return { success: false, code: res.exitCode };
+ } else {
+ return { success: true, code: 0 };
+ }
+ }
+
+ class Process {
+ constructor(res) {
+ this.rid = res.rid;
+ this.pid = res.pid;
+
+ if (res.stdinRid && res.stdinRid > 0) {
+ this.stdin = new File(res.stdinRid);
+ }
+
+ if (res.stdoutRid && res.stdoutRid > 0) {
+ this.stdout = new File(res.stdoutRid);
+ }
+
+ if (res.stderrRid && res.stderrRid > 0) {
+ this.stderr = new File(res.stderrRid);
+ }
+ }
+
+ status() {
+ return runStatus(this.rid);
+ }
+
+ async output() {
+ if (!this.stdout) {
+ throw new TypeError("stdout was not piped");
+ }
+ try {
+ return await readAll(this.stdout);
+ } finally {
+ this.stdout.close();
+ }
+ }
+
+ async stderrOutput() {
+ if (!this.stderr) {
+ throw new TypeError("stderr was not piped");
+ }
+ try {
+ return await readAll(this.stderr);
+ } finally {
+ this.stderr.close();
+ }
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ kill(signo) {
+ opKill(this.pid, signo);
+ }
+ }
+
+ function isRid(arg) {
+ return !isNaN(arg);
+ }
+
+ function run({
+ cmd,
+ cwd = undefined,
+ env = {},
+ stdout = "inherit",
+ stderr = "inherit",
+ stdin = "inherit",
+ }) {
+ if (cmd[0] != null) {
+ cmd[0] = pathFromURL(cmd[0]);
+ }
+ const res = opRun({
+ cmd: cmd.map(String),
+ cwd,
+ env: Object.entries(env),
+ stdin: isRid(stdin) ? "" : stdin,
+ stdout: isRid(stdout) ? "" : stdout,
+ stderr: isRid(stderr) ? "" : stderr,
+ stdinRid: isRid(stdin) ? stdin : 0,
+ stdoutRid: isRid(stdout) ? stdout : 0,
+ stderrRid: isRid(stderr) ? stderr : 0,
+ });
+ return new Process(res);
+ }
+
+ window.__bootstrap.process = {
+ run,
+ Process,
+ kill: opKill,
+ };
+})(this);
diff --git a/runtime/rt/40_read_file.js b/runtime/rt/40_read_file.js
new file mode 100644
index 000000000..9a36f335b
--- /dev/null
+++ b/runtime/rt/40_read_file.js
@@ -0,0 +1,43 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { open, openSync } = window.__bootstrap.files;
+ const { readAll, readAllSync } = window.__bootstrap.buffer;
+
+ function readFileSync(path) {
+ const file = openSync(path);
+ const contents = readAllSync(file);
+ file.close();
+ return contents;
+ }
+
+ async function readFile(path) {
+ const file = await open(path);
+ const contents = await readAll(file);
+ file.close();
+ return contents;
+ }
+
+ function readTextFileSync(path) {
+ const file = openSync(path);
+ const contents = readAllSync(file);
+ file.close();
+ const decoder = new TextDecoder();
+ return decoder.decode(contents);
+ }
+
+ async function readTextFile(path) {
+ const file = await open(path);
+ const contents = await readAll(file);
+ file.close();
+ const decoder = new TextDecoder();
+ return decoder.decode(contents);
+ }
+
+ window.__bootstrap.readFile = {
+ readFile,
+ readFileSync,
+ readTextFileSync,
+ readTextFile,
+ };
+})(this);
diff --git a/runtime/rt/40_signals.js b/runtime/rt/40_signals.js
new file mode 100644
index 000000000..739c963fd
--- /dev/null
+++ b/runtime/rt/40_signals.js
@@ -0,0 +1,256 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { build } = window.__bootstrap.build;
+
+ function bindSignal(signo) {
+ return core.jsonOpSync("op_signal_bind", { signo });
+ }
+
+ function pollSignal(rid) {
+ return core.jsonOpAsync("op_signal_poll", { rid });
+ }
+
+ function unbindSignal(rid) {
+ core.jsonOpSync("op_signal_unbind", { rid });
+ }
+
+ // From `kill -l`
+ const LinuxSignal = {
+ 1: "SIGHUP",
+ 2: "SIGINT",
+ 3: "SIGQUIT",
+ 4: "SIGILL",
+ 5: "SIGTRAP",
+ 6: "SIGABRT",
+ 7: "SIGBUS",
+ 8: "SIGFPE",
+ 9: "SIGKILL",
+ 10: "SIGUSR1",
+ 11: "SIGSEGV",
+ 12: "SIGUSR2",
+ 13: "SIGPIPE",
+ 14: "SIGALRM",
+ 15: "SIGTERM",
+ 16: "SIGSTKFLT",
+ 17: "SIGCHLD",
+ 18: "SIGCONT",
+ 19: "SIGSTOP",
+ 20: "SIGTSTP",
+ 21: "SIGTTIN",
+ 22: "SIGTTOU",
+ 23: "SIGURG",
+ 24: "SIGXCPU",
+ 25: "SIGXFSZ",
+ 26: "SIGVTALRM",
+ 27: "SIGPROF",
+ 28: "SIGWINCH",
+ 29: "SIGIO",
+ 30: "SIGPWR",
+ 31: "SIGSYS",
+ SIGHUP: 1,
+ SIGINT: 2,
+ SIGQUIT: 3,
+ SIGILL: 4,
+ SIGTRAP: 5,
+ SIGABRT: 6,
+ SIGBUS: 7,
+ SIGFPE: 8,
+ SIGKILL: 9,
+ SIGUSR1: 10,
+ SIGSEGV: 11,
+ SIGUSR2: 12,
+ SIGPIPE: 13,
+ SIGALRM: 14,
+ SIGTERM: 15,
+ SIGSTKFLT: 16,
+ SIGCHLD: 17,
+ SIGCONT: 18,
+ SIGSTOP: 19,
+ SIGTSTP: 20,
+ SIGTTIN: 21,
+ SIGTTOU: 22,
+ SIGURG: 23,
+ SIGXCPU: 24,
+ SIGXFSZ: 25,
+ SIGVTALRM: 26,
+ SIGPROF: 27,
+ SIGWINCH: 28,
+ SIGIO: 29,
+ SIGPWR: 30,
+ SIGSYS: 31,
+ };
+
+ // From `kill -l`
+ const MacOSSignal = {
+ 1: "SIGHUP",
+ 2: "SIGINT",
+ 3: "SIGQUIT",
+ 4: "SIGILL",
+ 5: "SIGTRAP",
+ 6: "SIGABRT",
+ 7: "SIGEMT",
+ 8: "SIGFPE",
+ 9: "SIGKILL",
+ 10: "SIGBUS",
+ 11: "SIGSEGV",
+ 12: "SIGSYS",
+ 13: "SIGPIPE",
+ 14: "SIGALRM",
+ 15: "SIGTERM",
+ 16: "SIGURG",
+ 17: "SIGSTOP",
+ 18: "SIGTSTP",
+ 19: "SIGCONT",
+ 20: "SIGCHLD",
+ 21: "SIGTTIN",
+ 22: "SIGTTOU",
+ 23: "SIGIO",
+ 24: "SIGXCPU",
+ 25: "SIGXFSZ",
+ 26: "SIGVTALRM",
+ 27: "SIGPROF",
+ 28: "SIGWINCH",
+ 29: "SIGINFO",
+ 30: "SIGUSR1",
+ 31: "SIGUSR2",
+ SIGHUP: 1,
+ SIGINT: 2,
+ SIGQUIT: 3,
+ SIGILL: 4,
+ SIGTRAP: 5,
+ SIGABRT: 6,
+ SIGEMT: 7,
+ SIGFPE: 8,
+ SIGKILL: 9,
+ SIGBUS: 10,
+ SIGSEGV: 11,
+ SIGSYS: 12,
+ SIGPIPE: 13,
+ SIGALRM: 14,
+ SIGTERM: 15,
+ SIGURG: 16,
+ SIGSTOP: 17,
+ SIGTSTP: 18,
+ SIGCONT: 19,
+ SIGCHLD: 20,
+ SIGTTIN: 21,
+ SIGTTOU: 22,
+ SIGIO: 23,
+ SIGXCPU: 24,
+ SIGXFSZ: 25,
+ SIGVTALRM: 26,
+ SIGPROF: 27,
+ SIGWINCH: 28,
+ SIGINFO: 29,
+ SIGUSR1: 30,
+ SIGUSR2: 31,
+ };
+
+ const Signal = {};
+
+ function setSignals() {
+ if (build.os === "darwin") {
+ Object.assign(Signal, MacOSSignal);
+ } else {
+ Object.assign(Signal, LinuxSignal);
+ }
+ }
+
+ function signal(signo) {
+ if (build.os === "windows") {
+ throw new Error("not implemented!");
+ }
+ return new SignalStream(signo);
+ }
+
+ const signals = {
+ alarm() {
+ return signal(Signal.SIGALRM);
+ },
+ child() {
+ return signal(Signal.SIGCHLD);
+ },
+ hungup() {
+ return signal(Signal.SIGHUP);
+ },
+ interrupt() {
+ return signal(Signal.SIGINT);
+ },
+ io() {
+ return signal(Signal.SIGIO);
+ },
+ pipe() {
+ return signal(Signal.SIGPIPE);
+ },
+ quit() {
+ return signal(Signal.SIGQUIT);
+ },
+ terminate() {
+ return signal(Signal.SIGTERM);
+ },
+ userDefined1() {
+ return signal(Signal.SIGUSR1);
+ },
+ userDefined2() {
+ return signal(Signal.SIGUSR2);
+ },
+ windowChange() {
+ return signal(Signal.SIGWINCH);
+ },
+ };
+
+ class SignalStream {
+ #disposed = false;
+ #pollingPromise = Promise.resolve(false);
+ #rid = 0;
+
+ constructor(signo) {
+ this.#rid = bindSignal(signo).rid;
+ this.#loop();
+ }
+
+ #pollSignal = async () => {
+ const res = await pollSignal(this.#rid);
+ return res.done;
+ };
+
+ #loop = async () => {
+ do {
+ this.#pollingPromise = this.#pollSignal();
+ } while (!(await this.#pollingPromise) && !this.#disposed);
+ };
+
+ then(
+ f,
+ g,
+ ) {
+ return this.#pollingPromise.then(() => {}).then(f, g);
+ }
+
+ async next() {
+ return { done: await this.#pollingPromise, value: undefined };
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+
+ dispose() {
+ if (this.#disposed) {
+ throw new Error("The stream has already been disposed.");
+ }
+ this.#disposed = true;
+ unbindSignal(this.#rid);
+ }
+ }
+
+ window.__bootstrap.signals = {
+ signal,
+ signals,
+ Signal,
+ SignalStream,
+ setSignals,
+ };
+})(this);
diff --git a/runtime/rt/40_testing.js b/runtime/rt/40_testing.js
new file mode 100644
index 000000000..082d17fe0
--- /dev/null
+++ b/runtime/rt/40_testing.js
@@ -0,0 +1,350 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const colors = window.__bootstrap.colors;
+ const { exit } = window.__bootstrap.os;
+ const { Console, inspectArgs } = window.__bootstrap.console;
+ const { stdout } = window.__bootstrap.files;
+ const { exposeForTest } = window.__bootstrap.internals;
+ const { metrics } = window.__bootstrap.metrics;
+ const { assert } = window.__bootstrap.util;
+
+ const disabledConsole = new Console(() => {});
+
+ function delay(ms) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, ms);
+ });
+ }
+
+ function formatDuration(time = 0) {
+ const gray = colors.maybeColor(colors.gray);
+ const italic = colors.maybeColor(colors.italic);
+ const timeStr = `(${time}ms)`;
+ return gray(italic(timeStr));
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not leak async "ops" - ie. number of async
+ // completed ops after the test is the same as number of dispatched
+ // ops. Note that "unref" ops are ignored since in nature that are
+ // optional.
+ function assertOps(fn) {
+ return async function asyncOpSanitizer() {
+ const pre = metrics();
+ await fn();
+ // Defer until next event loop turn - that way timeouts and intervals
+ // cleared can actually be removed from resource table, otherwise
+ // false positives may occur (https://github.com/denoland/deno/issues/4591)
+ await delay(0);
+ const post = metrics();
+ // We're checking diff because one might spawn HTTP server in the background
+ // that will be a pending async op before test starts.
+ const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync;
+ const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync;
+ assert(
+ dispatchedDiff === completedDiff,
+ `Test case is leaking async ops.
+Before:
+ - dispatched: ${pre.opsDispatchedAsync}
+ - completed: ${pre.opsCompletedAsync}
+After:
+ - dispatched: ${post.opsDispatchedAsync}
+ - completed: ${post.opsCompletedAsync}
+
+Make sure to await all promises returned from Deno APIs before
+finishing test case.`,
+ );
+ };
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not "leak" resources - ie. resource table after
+ // the test has exactly the same contents as before the test.
+ function assertResources(
+ fn,
+ ) {
+ return async function resourceSanitizer() {
+ const pre = core.resources();
+ await fn();
+ const post = core.resources();
+
+ const preStr = JSON.stringify(pre, null, 2);
+ const postStr = JSON.stringify(post, null, 2);
+ const msg = `Test case is leaking resources.
+Before: ${preStr}
+After: ${postStr}
+
+Make sure to close all open resource handles returned from Deno APIs before
+finishing test case.`;
+ assert(preStr === postStr, msg);
+ };
+ }
+
+ const TEST_REGISTRY = [];
+
+ // Main test function provided by Deno, as you can see it merely
+ // creates a new object with "name" and "fn" fields.
+ function test(
+ t,
+ fn,
+ ) {
+ let testDef;
+ const defaults = {
+ ignore: false,
+ only: false,
+ sanitizeOps: true,
+ sanitizeResources: true,
+ };
+
+ if (typeof t === "string") {
+ if (!fn || typeof fn != "function") {
+ throw new TypeError("Missing test function");
+ }
+ if (!t) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDef = { fn: fn, name: t, ...defaults };
+ } else {
+ if (!t.fn) {
+ throw new TypeError("Missing test function");
+ }
+ if (!t.name) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDef = { ...defaults, ...t };
+ }
+
+ if (testDef.sanitizeOps) {
+ testDef.fn = assertOps(testDef.fn);
+ }
+
+ if (testDef.sanitizeResources) {
+ testDef.fn = assertResources(testDef.fn);
+ }
+
+ TEST_REGISTRY.push(testDef);
+ }
+
+ const encoder = new TextEncoder();
+
+ function log(msg, noNewLine = false) {
+ if (!noNewLine) {
+ msg += "\n";
+ }
+
+ // Using `stdout` here because it doesn't force new lines
+ // compared to `console.log`; `core.print` on the other hand
+ // is line-buffered and doesn't output message without newline
+ stdout.writeSync(encoder.encode(msg));
+ }
+
+ function reportToConsole(message) {
+ const green = colors.maybeColor(colors.green);
+ const red = colors.maybeColor(colors.red);
+ const yellow = colors.maybeColor(colors.yellow);
+ const redFailed = red("FAILED");
+ const greenOk = green("ok");
+ const yellowIgnored = yellow("ignored");
+ if (message.start != null) {
+ log(`running ${message.start.tests.length} tests`);
+ } else if (message.testStart != null) {
+ const { name } = message.testStart;
+
+ log(`test ${name} ... `, true);
+ return;
+ } else if (message.testEnd != null) {
+ switch (message.testEnd.status) {
+ case "passed":
+ log(`${greenOk} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ case "failed":
+ log(`${redFailed} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ case "ignored":
+ log(`${yellowIgnored} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ }
+ } else if (message.end != null) {
+ const failures = message.end.results.filter((m) => m.error != null);
+ if (failures.length > 0) {
+ log(`\nfailures:\n`);
+
+ for (const { name, error } of failures) {
+ log(name);
+ log(inspectArgs([error]));
+ log("");
+ }
+
+ log(`failures:\n`);
+
+ for (const { name } of failures) {
+ log(`\t${name}`);
+ }
+ }
+ log(
+ `\ntest result: ${message.end.failed ? redFailed : greenOk}. ` +
+ `${message.end.passed} passed; ${message.end.failed} failed; ` +
+ `${message.end.ignored} ignored; ${message.end.measured} measured; ` +
+ `${message.end.filtered} filtered out ` +
+ `${formatDuration(message.end.duration)}\n`,
+ );
+
+ if (message.end.usedOnly && message.end.failed == 0) {
+ log(`${redFailed} because the "only" option was used\n`);
+ }
+ }
+ }
+
+ exposeForTest("reportToConsole", reportToConsole);
+
+ // TODO: already implements AsyncGenerator<RunTestsMessage>, but add as "implements to class"
+ // TODO: implements PromiseLike<RunTestsEndResult>
+ class TestRunner {
+ #usedOnly = false;
+
+ constructor(
+ tests,
+ filterFn,
+ failFast,
+ ) {
+ this.stats = {
+ filtered: 0,
+ ignored: 0,
+ measured: 0,
+ passed: 0,
+ failed: 0,
+ };
+ this.filterFn = filterFn;
+ this.failFast = failFast;
+ const onlyTests = tests.filter(({ only }) => only);
+ this.#usedOnly = onlyTests.length > 0;
+ const unfilteredTests = this.#usedOnly ? onlyTests : tests;
+ this.testsToRun = unfilteredTests.filter(filterFn);
+ this.stats.filtered = unfilteredTests.length - this.testsToRun.length;
+ }
+
+ async *[Symbol.asyncIterator]() {
+ yield { start: { tests: this.testsToRun } };
+
+ const results = [];
+ const suiteStart = +new Date();
+ for (const test of this.testsToRun) {
+ const endMessage = {
+ name: test.name,
+ duration: 0,
+ };
+ yield { testStart: { ...test } };
+ if (test.ignore) {
+ endMessage.status = "ignored";
+ this.stats.ignored++;
+ } else {
+ const start = +new Date();
+ try {
+ await test.fn();
+ endMessage.status = "passed";
+ this.stats.passed++;
+ } catch (err) {
+ endMessage.status = "failed";
+ endMessage.error = err;
+ this.stats.failed++;
+ }
+ endMessage.duration = +new Date() - start;
+ }
+ results.push(endMessage);
+ yield { testEnd: endMessage };
+ if (this.failFast && endMessage.error != null) {
+ break;
+ }
+ }
+
+ const duration = +new Date() - suiteStart;
+
+ yield {
+ end: { ...this.stats, usedOnly: this.#usedOnly, duration, results },
+ };
+ }
+ }
+
+ function createFilterFn(
+ filter,
+ skip,
+ ) {
+ return (def) => {
+ let passes = true;
+
+ if (filter) {
+ if (filter instanceof RegExp) {
+ passes = passes && filter.test(def.name);
+ } else if (filter.startsWith("/") && filter.endsWith("/")) {
+ const filterAsRegex = new RegExp(filter.slice(1, filter.length - 1));
+ passes = passes && filterAsRegex.test(def.name);
+ } else {
+ passes = passes && def.name.includes(filter);
+ }
+ }
+
+ if (skip) {
+ if (skip instanceof RegExp) {
+ passes = passes && !skip.test(def.name);
+ } else {
+ passes = passes && !def.name.includes(skip);
+ }
+ }
+
+ return passes;
+ };
+ }
+
+ exposeForTest("createFilterFn", createFilterFn);
+
+ async function runTests({
+ exitOnFail = true,
+ failFast = false,
+ filter = undefined,
+ skip = undefined,
+ disableLog = false,
+ reportToConsole: reportToConsole_ = true,
+ onMessage = undefined,
+ } = {}) {
+ const filterFn = createFilterFn(filter, skip);
+ const testRunner = new TestRunner(TEST_REGISTRY, filterFn, failFast);
+
+ const originalConsole = globalThis.console;
+
+ if (disableLog) {
+ globalThis.console = disabledConsole;
+ }
+
+ let endMsg;
+
+ for await (const message of testRunner) {
+ if (onMessage != null) {
+ await onMessage(message);
+ }
+ if (reportToConsole_) {
+ reportToConsole(message);
+ }
+ if (message.end != null) {
+ endMsg = message.end;
+ }
+ }
+
+ if (disableLog) {
+ globalThis.console = originalConsole;
+ }
+
+ if ((endMsg.failed > 0 || endMsg?.usedOnly) && exitOnFail) {
+ exit(1);
+ }
+
+ return endMsg;
+ }
+
+ exposeForTest("runTests", runTests);
+
+ window.__bootstrap.testing = {
+ test,
+ };
+})(this);
diff --git a/runtime/rt/40_tls.js b/runtime/rt/40_tls.js
new file mode 100644
index 000000000..d66e0bd01
--- /dev/null
+++ b/runtime/rt/40_tls.js
@@ -0,0 +1,82 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { Listener, Conn } = window.__bootstrap.net;
+
+ function opConnectTls(
+ args,
+ ) {
+ return core.jsonOpAsync("op_connect_tls", args);
+ }
+
+ function opAcceptTLS(rid) {
+ return core.jsonOpAsync("op_accept_tls", { rid });
+ }
+
+ function opListenTls(args) {
+ return core.jsonOpSync("op_listen_tls", args);
+ }
+
+ function opStartTls(args) {
+ return core.jsonOpAsync("op_start_tls", args);
+ }
+
+ async function connectTls({
+ port,
+ hostname = "127.0.0.1",
+ transport = "tcp",
+ certFile = undefined,
+ }) {
+ const res = await opConnectTls({
+ port,
+ hostname,
+ transport,
+ certFile,
+ });
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ class TLSListener extends Listener {
+ async accept() {
+ const res = await opAcceptTLS(this.rid);
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+ }
+
+ function listenTls({
+ port,
+ certFile,
+ keyFile,
+ hostname = "0.0.0.0",
+ transport = "tcp",
+ }) {
+ const res = opListenTls({
+ port,
+ certFile,
+ keyFile,
+ hostname,
+ transport,
+ });
+ return new TLSListener(res.rid, res.localAddr);
+ }
+
+ async function startTls(
+ conn,
+ { hostname = "127.0.0.1", certFile } = {},
+ ) {
+ const res = await opStartTls({
+ rid: conn.rid,
+ hostname,
+ certFile,
+ });
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ window.__bootstrap.tls = {
+ startTls,
+ listenTls,
+ connectTls,
+ TLSListener,
+ };
+})(this);
diff --git a/runtime/rt/40_tty.js b/runtime/rt/40_tty.js
new file mode 100644
index 000000000..598d33237
--- /dev/null
+++ b/runtime/rt/40_tty.js
@@ -0,0 +1,28 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function consoleSize(rid) {
+ return core.jsonOpSync("op_console_size", { rid });
+ }
+
+ function isatty(rid) {
+ return core.jsonOpSync("op_isatty", { rid });
+ }
+
+ const DEFAULT_SET_RAW_OPTIONS = {
+ cbreak: false,
+ };
+
+ function setRaw(rid, mode, options = {}) {
+ const rOptions = { ...DEFAULT_SET_RAW_OPTIONS, ...options };
+ core.jsonOpSync("op_set_raw", { rid, mode, options: rOptions });
+ }
+
+ window.__bootstrap.tty = {
+ consoleSize,
+ isatty,
+ setRaw,
+ };
+})(this);
diff --git a/runtime/rt/40_write_file.js b/runtime/rt/40_write_file.js
new file mode 100644
index 000000000..7a9cb1f40
--- /dev/null
+++ b/runtime/rt/40_write_file.js
@@ -0,0 +1,92 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+((window) => {
+ const { stat, statSync, chmod, chmodSync } = window.__bootstrap.fs;
+ const { open, openSync } = window.__bootstrap.files;
+ const { writeAll, writeAllSync } = window.__bootstrap.buffer;
+ const { build } = window.__bootstrap.build;
+
+ function writeFileSync(
+ path,
+ data,
+ options = {},
+ ) {
+ if (options.create !== undefined) {
+ const create = !!options.create;
+ if (!create) {
+ // verify that file exists
+ statSync(path);
+ }
+ }
+
+ const openOptions = options.append
+ ? { write: true, create: true, append: true }
+ : { write: true, create: true, truncate: true };
+ const file = openSync(path, openOptions);
+
+ if (
+ options.mode !== undefined &&
+ options.mode !== null &&
+ build.os !== "windows"
+ ) {
+ chmodSync(path, options.mode);
+ }
+
+ writeAllSync(file, data);
+ file.close();
+ }
+
+ async function writeFile(
+ path,
+ data,
+ options = {},
+ ) {
+ if (options.create !== undefined) {
+ const create = !!options.create;
+ if (!create) {
+ // verify that file exists
+ await stat(path);
+ }
+ }
+
+ const openOptions = options.append
+ ? { write: true, create: true, append: true }
+ : { write: true, create: true, truncate: true };
+ const file = await open(path, openOptions);
+
+ if (
+ options.mode !== undefined &&
+ options.mode !== null &&
+ build.os !== "windows"
+ ) {
+ await chmod(path, options.mode);
+ }
+
+ await writeAll(file, data);
+ file.close();
+ }
+
+ function writeTextFileSync(
+ path,
+ data,
+ options = {},
+ ) {
+ const encoder = new TextEncoder();
+ return writeFileSync(path, encoder.encode(data), options);
+ }
+
+ function writeTextFile(
+ path,
+ data,
+ options = {},
+ ) {
+ const encoder = new TextEncoder();
+ return writeFile(path, encoder.encode(data), options);
+ }
+
+ window.__bootstrap.writeFile = {
+ writeTextFile,
+ writeTextFileSync,
+ writeFile,
+ writeFileSync,
+ };
+})(this);
diff --git a/runtime/rt/41_prompt.js b/runtime/rt/41_prompt.js
new file mode 100644
index 000000000..ec294668b
--- /dev/null
+++ b/runtime/rt/41_prompt.js
@@ -0,0 +1,80 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+((window) => {
+ const { stdin } = window.__bootstrap.files;
+ const { isatty } = window.__bootstrap.tty;
+ const LF = "\n".charCodeAt(0);
+ const CR = "\r".charCodeAt(0);
+ const decoder = new TextDecoder();
+ const core = window.Deno.core;
+
+ function alert(message = "Alert") {
+ if (!isatty(stdin.rid)) {
+ return;
+ }
+
+ core.print(`${message} [Enter] `, false);
+
+ readLineFromStdinSync();
+ }
+
+ function confirm(message = "Confirm") {
+ if (!isatty(stdin.rid)) {
+ return false;
+ }
+
+ core.print(`${message} [y/N] `, false);
+
+ const answer = readLineFromStdinSync();
+
+ return answer === "Y" || answer === "y";
+ }
+
+ function prompt(message = "Prompt", defaultValue) {
+ defaultValue ??= null;
+
+ if (!isatty(stdin.rid)) {
+ return null;
+ }
+
+ core.print(`${message} `, false);
+
+ if (defaultValue) {
+ core.print(`[${defaultValue}] `, false);
+ }
+
+ return readLineFromStdinSync() || defaultValue;
+ }
+
+ function readLineFromStdinSync() {
+ const c = new Uint8Array(1);
+ const buf = [];
+
+ while (true) {
+ const n = stdin.readSync(c);
+ if (n === null || n === 0) {
+ break;
+ }
+ if (c[0] === CR) {
+ const n = stdin.readSync(c);
+ if (c[0] === LF) {
+ break;
+ }
+ buf.push(CR);
+ if (n === null || n === 0) {
+ break;
+ }
+ }
+ if (c[0] === LF) {
+ break;
+ }
+ buf.push(c[0]);
+ }
+ return decoder.decode(new Uint8Array(buf));
+ }
+
+ window.__bootstrap.prompt = {
+ alert,
+ confirm,
+ prompt,
+ };
+})(this);
diff --git a/runtime/rt/90_deno_ns.js b/runtime/rt/90_deno_ns.js
new file mode 100644
index 000000000..9188788ec
--- /dev/null
+++ b/runtime/rt/90_deno_ns.js
@@ -0,0 +1,137 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const __bootstrap = window.__bootstrap;
+ __bootstrap.denoNs = {
+ test: __bootstrap.testing.test,
+ metrics: __bootstrap.metrics.metrics,
+ Process: __bootstrap.process.Process,
+ run: __bootstrap.process.run,
+ isatty: __bootstrap.tty.isatty,
+ writeFileSync: __bootstrap.writeFile.writeFileSync,
+ writeFile: __bootstrap.writeFile.writeFile,
+ writeTextFileSync: __bootstrap.writeFile.writeTextFileSync,
+ writeTextFile: __bootstrap.writeFile.writeTextFile,
+ readTextFile: __bootstrap.readFile.readTextFile,
+ readTextFileSync: __bootstrap.readFile.readTextFileSync,
+ readFile: __bootstrap.readFile.readFile,
+ readFileSync: __bootstrap.readFile.readFileSync,
+ watchFs: __bootstrap.fsEvents.watchFs,
+ chmodSync: __bootstrap.fs.chmodSync,
+ chmod: __bootstrap.fs.chmod,
+ chown: __bootstrap.fs.chown,
+ chownSync: __bootstrap.fs.chownSync,
+ copyFileSync: __bootstrap.fs.copyFileSync,
+ cwd: __bootstrap.fs.cwd,
+ makeTempDirSync: __bootstrap.fs.makeTempDirSync,
+ makeTempDir: __bootstrap.fs.makeTempDir,
+ makeTempFileSync: __bootstrap.fs.makeTempFileSync,
+ makeTempFile: __bootstrap.fs.makeTempFile,
+ mkdirSync: __bootstrap.fs.mkdirSync,
+ mkdir: __bootstrap.fs.mkdir,
+ chdir: __bootstrap.fs.chdir,
+ copyFile: __bootstrap.fs.copyFile,
+ readDirSync: __bootstrap.fs.readDirSync,
+ readDir: __bootstrap.fs.readDir,
+ readLinkSync: __bootstrap.fs.readLinkSync,
+ readLink: __bootstrap.fs.readLink,
+ realPathSync: __bootstrap.fs.realPathSync,
+ realPath: __bootstrap.fs.realPath,
+ removeSync: __bootstrap.fs.removeSync,
+ remove: __bootstrap.fs.remove,
+ renameSync: __bootstrap.fs.renameSync,
+ rename: __bootstrap.fs.rename,
+ version: __bootstrap.version.version,
+ build: __bootstrap.build.build,
+ statSync: __bootstrap.fs.statSync,
+ lstatSync: __bootstrap.fs.lstatSync,
+ stat: __bootstrap.fs.stat,
+ lstat: __bootstrap.fs.lstat,
+ truncateSync: __bootstrap.fs.truncateSync,
+ truncate: __bootstrap.fs.truncate,
+ errors: __bootstrap.errors.errors,
+ customInspect: __bootstrap.console.customInspect,
+ inspect: __bootstrap.console.inspect,
+ env: __bootstrap.os.env,
+ exit: __bootstrap.os.exit,
+ execPath: __bootstrap.os.execPath,
+ Buffer: __bootstrap.buffer.Buffer,
+ readAll: __bootstrap.buffer.readAll,
+ readAllSync: __bootstrap.buffer.readAllSync,
+ writeAll: __bootstrap.buffer.writeAll,
+ writeAllSync: __bootstrap.buffer.writeAllSync,
+ copy: __bootstrap.io.copy,
+ iter: __bootstrap.io.iter,
+ iterSync: __bootstrap.io.iterSync,
+ SeekMode: __bootstrap.io.SeekMode,
+ read: __bootstrap.io.read,
+ readSync: __bootstrap.io.readSync,
+ write: __bootstrap.io.write,
+ writeSync: __bootstrap.io.writeSync,
+ File: __bootstrap.files.File,
+ open: __bootstrap.files.open,
+ openSync: __bootstrap.files.openSync,
+ create: __bootstrap.files.create,
+ createSync: __bootstrap.files.createSync,
+ stdin: __bootstrap.files.stdin,
+ stdout: __bootstrap.files.stdout,
+ stderr: __bootstrap.files.stderr,
+ seek: __bootstrap.files.seek,
+ seekSync: __bootstrap.files.seekSync,
+ connect: __bootstrap.net.connect,
+ listen: __bootstrap.net.listen,
+ connectTls: __bootstrap.tls.connectTls,
+ listenTls: __bootstrap.tls.listenTls,
+ sleepSync: __bootstrap.timers.sleepSync,
+ fsyncSync: __bootstrap.fs.fsyncSync,
+ fsync: __bootstrap.fs.fsync,
+ fdatasyncSync: __bootstrap.fs.fdatasyncSync,
+ fdatasync: __bootstrap.fs.fdatasync,
+ };
+
+ __bootstrap.denoNsUnstable = {
+ signal: __bootstrap.signals.signal,
+ signals: __bootstrap.signals.signals,
+ Signal: __bootstrap.signals.Signal,
+ SignalStream: __bootstrap.signals.SignalStream,
+ transpileOnly: __bootstrap.compilerApi.transpileOnly,
+ compile: __bootstrap.compilerApi.compile,
+ bundle: __bootstrap.compilerApi.bundle,
+ permissions: __bootstrap.permissions.permissions,
+ Permissions: __bootstrap.permissions.Permissions,
+ PermissionStatus: __bootstrap.permissions.PermissionStatus,
+ openPlugin: __bootstrap.plugins.openPlugin,
+ kill: __bootstrap.process.kill,
+ setRaw: __bootstrap.tty.setRaw,
+ consoleSize: __bootstrap.tty.consoleSize,
+ DiagnosticCategory: __bootstrap.diagnostics.DiagnosticCategory,
+ loadavg: __bootstrap.os.loadavg,
+ hostname: __bootstrap.os.hostname,
+ osRelease: __bootstrap.os.osRelease,
+ systemMemoryInfo: __bootstrap.os.systemMemoryInfo,
+ systemCpuInfo: __bootstrap.os.systemCpuInfo,
+ applySourceMap: __bootstrap.errorStack.opApplySourceMap,
+ formatDiagnostics: __bootstrap.errorStack.opFormatDiagnostics,
+ shutdown: __bootstrap.net.shutdown,
+ ShutdownMode: __bootstrap.net.ShutdownMode,
+ listen: __bootstrap.netUnstable.listen,
+ connect: __bootstrap.netUnstable.connect,
+ listenDatagram: __bootstrap.netUnstable.listenDatagram,
+ startTls: __bootstrap.tls.startTls,
+ fstatSync: __bootstrap.fs.fstatSync,
+ fstat: __bootstrap.fs.fstat,
+ ftruncateSync: __bootstrap.fs.ftruncateSync,
+ ftruncate: __bootstrap.fs.ftruncate,
+ umask: __bootstrap.fs.umask,
+ link: __bootstrap.fs.link,
+ linkSync: __bootstrap.fs.linkSync,
+ futime: __bootstrap.fs.futime,
+ futimeSync: __bootstrap.fs.futimeSync,
+ utime: __bootstrap.fs.utime,
+ utimeSync: __bootstrap.fs.utimeSync,
+ symlink: __bootstrap.fs.symlink,
+ symlinkSync: __bootstrap.fs.symlinkSync,
+ HttpClient: __bootstrap.fetch.HttpClient,
+ createHttpClient: __bootstrap.fetch.createHttpClient,
+ };
+})(this);
diff --git a/runtime/rt/99_main.js b/runtime/rt/99_main.js
new file mode 100644
index 000000000..f38d51936
--- /dev/null
+++ b/runtime/rt/99_main.js
@@ -0,0 +1,395 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+// Removes the `__proto__` for security reasons. This intentionally makes
+// Deno non compliant with ECMA-262 Annex B.2.2.1
+//
+delete Object.prototype.__proto__;
+
+((window) => {
+ const core = Deno.core;
+ const util = window.__bootstrap.util;
+ const eventTarget = window.__bootstrap.eventTarget;
+ const globalInterfaces = window.__bootstrap.globalInterfaces;
+ const dispatchMinimal = window.__bootstrap.dispatchMinimal;
+ const build = window.__bootstrap.build;
+ const version = window.__bootstrap.version;
+ const errorStack = window.__bootstrap.errorStack;
+ const os = window.__bootstrap.os;
+ const timers = window.__bootstrap.timers;
+ const Console = window.__bootstrap.console.Console;
+ const worker = window.__bootstrap.worker;
+ const signals = window.__bootstrap.signals;
+ const { internalSymbol, internalObject } = window.__bootstrap.internals;
+ const performance = window.__bootstrap.performance;
+ const crypto = window.__bootstrap.crypto;
+ const url = window.__bootstrap.url;
+ const headers = window.__bootstrap.headers;
+ const streams = window.__bootstrap.streams;
+ const fileReader = window.__bootstrap.fileReader;
+ const webSocket = window.__bootstrap.webSocket;
+ const fetch = window.__bootstrap.fetch;
+ const prompt = window.__bootstrap.prompt;
+ const denoNs = window.__bootstrap.denoNs;
+ const denoNsUnstable = window.__bootstrap.denoNsUnstable;
+ const errors = window.__bootstrap.errors.errors;
+ const { defineEventHandler } = window.__bootstrap.webUtil;
+
+ let windowIsClosing = false;
+
+ function windowClose() {
+ if (!windowIsClosing) {
+ windowIsClosing = true;
+ // Push a macrotask to exit after a promise resolve.
+ // This is not perfect, but should be fine for first pass.
+ Promise.resolve().then(() =>
+ timers.setTimeout.call(
+ null,
+ () => {
+ // This should be fine, since only Window/MainWorker has .close()
+ os.exit(0);
+ },
+ 0,
+ )
+ );
+ }
+ }
+
+ const encoder = new TextEncoder();
+
+ function workerClose() {
+ if (isClosing) {
+ return;
+ }
+
+ isClosing = true;
+ opCloseWorker();
+ }
+
+ // TODO(bartlomieju): remove these functions
+ // Stuff for workers
+ const onmessage = () => {};
+ const onerror = () => {};
+
+ function postMessage(data) {
+ const dataJson = JSON.stringify(data);
+ const dataIntArray = encoder.encode(dataJson);
+ opPostMessage(dataIntArray);
+ }
+
+ let isClosing = false;
+ async function workerMessageRecvCallback(data) {
+ const msgEvent = new MessageEvent("message", {
+ cancelable: false,
+ data,
+ });
+
+ try {
+ if (globalThis["onmessage"]) {
+ const result = globalThis.onmessage(msgEvent);
+ if (result && "then" in result) {
+ await result;
+ }
+ }
+ globalThis.dispatchEvent(msgEvent);
+ } catch (e) {
+ let handled = false;
+
+ const errorEvent = new ErrorEvent("error", {
+ cancelable: true,
+ message: e.message,
+ lineno: e.lineNumber ? e.lineNumber + 1 : undefined,
+ colno: e.columnNumber ? e.columnNumber + 1 : undefined,
+ filename: e.fileName,
+ error: null,
+ });
+
+ if (globalThis["onerror"]) {
+ const ret = globalThis.onerror(
+ e.message,
+ e.fileName,
+ e.lineNumber,
+ e.columnNumber,
+ e,
+ );
+ handled = ret === true;
+ }
+
+ globalThis.dispatchEvent(errorEvent);
+ if (errorEvent.defaultPrevented) {
+ handled = true;
+ }
+
+ if (!handled) {
+ throw e;
+ }
+ }
+ }
+
+ function opPostMessage(data) {
+ core.jsonOpSync("op_worker_post_message", {}, data);
+ }
+
+ function opCloseWorker() {
+ core.jsonOpSync("op_worker_close");
+ }
+
+ function opMainModule() {
+ return core.jsonOpSync("op_main_module");
+ }
+
+ function runtimeStart(runtimeOptions, source) {
+ const opsMap = core.ops();
+ for (const [name, opId] of Object.entries(opsMap)) {
+ if (name === "op_write" || name === "op_read") {
+ core.setAsyncHandler(opId, dispatchMinimal.asyncMsgFromRust);
+ }
+ }
+
+ core.setMacrotaskCallback(timers.handleTimerMacrotask);
+ version.setVersions(
+ runtimeOptions.denoVersion,
+ runtimeOptions.v8Version,
+ runtimeOptions.tsVersion,
+ );
+ build.setBuildInfo(runtimeOptions.target);
+ util.setLogDebug(runtimeOptions.debugFlag, source);
+ // TODO(bartlomieju): a very crude way to disable
+ // source mapping of errors. This condition is true
+ // only for compiled standalone binaries.
+ let prepareStackTrace;
+ if (runtimeOptions.applySourceMaps) {
+ prepareStackTrace = core.createPrepareStackTrace(
+ errorStack.opApplySourceMap,
+ );
+ } else {
+ prepareStackTrace = core.createPrepareStackTrace();
+ }
+ Error.prepareStackTrace = prepareStackTrace;
+ }
+
+ function registerErrors() {
+ core.registerErrorClass("NotFound", errors.NotFound);
+ core.registerErrorClass("PermissionDenied", errors.PermissionDenied);
+ core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused);
+ core.registerErrorClass("ConnectionReset", errors.ConnectionReset);
+ core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted);
+ core.registerErrorClass("NotConnected", errors.NotConnected);
+ core.registerErrorClass("AddrInUse", errors.AddrInUse);
+ core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable);
+ core.registerErrorClass("BrokenPipe", errors.BrokenPipe);
+ core.registerErrorClass("AlreadyExists", errors.AlreadyExists);
+ core.registerErrorClass("InvalidData", errors.InvalidData);
+ core.registerErrorClass("TimedOut", errors.TimedOut);
+ core.registerErrorClass("Interrupted", errors.Interrupted);
+ core.registerErrorClass("WriteZero", errors.WriteZero);
+ core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof);
+ core.registerErrorClass("BadResource", errors.BadResource);
+ core.registerErrorClass("Http", errors.Http);
+ core.registerErrorClass("Busy", errors.Busy);
+ core.registerErrorClass("NotSupported", errors.NotSupported);
+ core.registerErrorClass("Error", Error);
+ core.registerErrorClass("RangeError", RangeError);
+ core.registerErrorClass("ReferenceError", ReferenceError);
+ core.registerErrorClass("SyntaxError", SyntaxError);
+ core.registerErrorClass("TypeError", TypeError);
+ core.registerErrorClass("URIError", URIError);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
+ const windowOrWorkerGlobalScope = {
+ Blob: util.nonEnumerable(fetch.Blob),
+ ByteLengthQueuingStrategy: util.nonEnumerable(
+ streams.ByteLengthQueuingStrategy,
+ ),
+ CloseEvent: util.nonEnumerable(CloseEvent),
+ CountQueuingStrategy: util.nonEnumerable(
+ streams.CountQueuingStrategy,
+ ),
+ CustomEvent: util.nonEnumerable(CustomEvent),
+ DOMException: util.nonEnumerable(DOMException),
+ ErrorEvent: util.nonEnumerable(ErrorEvent),
+ Event: util.nonEnumerable(Event),
+ EventTarget: util.nonEnumerable(EventTarget),
+ File: util.nonEnumerable(fetch.DomFile),
+ FileReader: util.nonEnumerable(fileReader.FileReader),
+ FormData: util.nonEnumerable(fetch.FormData),
+ Headers: util.nonEnumerable(headers.Headers),
+ MessageEvent: util.nonEnumerable(MessageEvent),
+ Performance: util.nonEnumerable(performance.Performance),
+ PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
+ PerformanceMark: util.nonEnumerable(performance.PerformanceMark),
+ PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure),
+ ProgressEvent: util.nonEnumerable(ProgressEvent),
+ ReadableStream: util.nonEnumerable(streams.ReadableStream),
+ Request: util.nonEnumerable(fetch.Request),
+ Response: util.nonEnumerable(fetch.Response),
+ TextDecoder: util.nonEnumerable(TextDecoder),
+ TextEncoder: util.nonEnumerable(TextEncoder),
+ TransformStream: util.nonEnumerable(streams.TransformStream),
+ URL: util.nonEnumerable(url.URL),
+ URLSearchParams: util.nonEnumerable(url.URLSearchParams),
+ WebSocket: util.nonEnumerable(webSocket.WebSocket),
+ Worker: util.nonEnumerable(worker.Worker),
+ WritableStream: util.nonEnumerable(streams.WritableStream),
+ atob: util.writable(atob),
+ btoa: util.writable(btoa),
+ clearInterval: util.writable(timers.clearInterval),
+ clearTimeout: util.writable(timers.clearTimeout),
+ console: util.writable(new Console(core.print)),
+ crypto: util.readOnly(crypto),
+ fetch: util.writable(fetch.fetch),
+ performance: util.writable(performance.performance),
+ setInterval: util.writable(timers.setInterval),
+ setTimeout: util.writable(timers.setTimeout),
+ };
+
+ const mainRuntimeGlobalProperties = {
+ Window: globalInterfaces.windowConstructorDescriptor,
+ window: util.readOnly(globalThis),
+ self: util.readOnly(globalThis),
+ // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope)
+ // it seems those two properties should be available to workers as well
+ onload: util.writable(null),
+ onunload: util.writable(null),
+ close: util.writable(windowClose),
+ closed: util.getterOnly(() => windowIsClosing),
+ alert: util.writable(prompt.alert),
+ confirm: util.writable(prompt.confirm),
+ prompt: util.writable(prompt.prompt),
+ };
+
+ const workerRuntimeGlobalProperties = {
+ WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor,
+ DedicatedWorkerGlobalScope:
+ globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor,
+ self: util.readOnly(globalThis),
+ onmessage: util.writable(onmessage),
+ onerror: util.writable(onerror),
+ // TODO: should be readonly?
+ close: util.nonEnumerable(workerClose),
+ postMessage: util.writable(postMessage),
+ workerMessageRecvCallback: util.nonEnumerable(workerMessageRecvCallback),
+ };
+
+ let hasBootstrapped = false;
+
+ function bootstrapMainRuntime(runtimeOptions) {
+ if (hasBootstrapped) {
+ throw new Error("Worker runtime already bootstrapped");
+ }
+ // Remove bootstrapping data from the global scope
+ delete globalThis.__bootstrap;
+ delete globalThis.bootstrap;
+ util.log("bootstrapMainRuntime");
+ hasBootstrapped = true;
+ Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
+ Object.defineProperties(globalThis, mainRuntimeGlobalProperties);
+ Object.setPrototypeOf(globalThis, Window.prototype);
+ eventTarget.setEventTargetData(globalThis);
+
+ defineEventHandler(window, "load", null);
+ defineEventHandler(window, "unload", null);
+
+ runtimeStart(runtimeOptions);
+ const { args, noColor, pid, ppid, unstableFlag } = runtimeOptions;
+
+ registerErrors();
+
+ const finalDenoNs = {
+ core,
+ internal: internalSymbol,
+ [internalSymbol]: internalObject,
+ resources: core.resources,
+ close: core.close,
+ ...denoNs,
+ };
+ Object.defineProperties(finalDenoNs, {
+ pid: util.readOnly(pid),
+ ppid: util.readOnly(ppid),
+ noColor: util.readOnly(noColor),
+ args: util.readOnly(Object.freeze(args)),
+ mainModule: util.getterOnly(opMainModule),
+ });
+
+ if (unstableFlag) {
+ Object.assign(finalDenoNs, denoNsUnstable);
+ }
+
+ // Setup `Deno` global - we're actually overriding already
+ // existing global `Deno` with `Deno` namespace from "./deno.ts".
+ util.immutableDefine(globalThis, "Deno", finalDenoNs);
+ Object.freeze(globalThis.Deno);
+ Object.freeze(globalThis.Deno.core);
+ Object.freeze(globalThis.Deno.core.sharedQueue);
+ signals.setSignals();
+
+ util.log("args", args);
+ }
+
+ function bootstrapWorkerRuntime(
+ runtimeOptions,
+ name,
+ useDenoNamespace,
+ internalName,
+ ) {
+ if (hasBootstrapped) {
+ throw new Error("Worker runtime already bootstrapped");
+ }
+ // Remove bootstrapping data from the global scope
+ delete globalThis.__bootstrap;
+ delete globalThis.bootstrap;
+ util.log("bootstrapWorkerRuntime");
+ hasBootstrapped = true;
+ Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
+ Object.defineProperties(globalThis, workerRuntimeGlobalProperties);
+ Object.defineProperties(globalThis, { name: util.readOnly(name) });
+ Object.setPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
+ eventTarget.setEventTargetData(globalThis);
+
+ runtimeStart(
+ runtimeOptions,
+ internalName ?? name,
+ );
+ const { unstableFlag, pid, noColor, args } = runtimeOptions;
+
+ registerErrors();
+
+ const finalDenoNs = {
+ core,
+ internal: internalSymbol,
+ [internalSymbol]: internalObject,
+ resources: core.resources,
+ close: core.close,
+ ...denoNs,
+ };
+ if (useDenoNamespace) {
+ if (unstableFlag) {
+ Object.assign(finalDenoNs, denoNsUnstable);
+ }
+ Object.defineProperties(finalDenoNs, {
+ pid: util.readOnly(pid),
+ noColor: util.readOnly(noColor),
+ args: util.readOnly(Object.freeze(args)),
+ });
+ // Setup `Deno` global - we're actually overriding already
+ // existing global `Deno` with `Deno` namespace from "./deno.ts".
+ util.immutableDefine(globalThis, "Deno", finalDenoNs);
+ Object.freeze(globalThis.Deno);
+ Object.freeze(globalThis.Deno.core);
+ Object.freeze(globalThis.Deno.core.sharedQueue);
+ signals.setSignals();
+ } else {
+ delete globalThis.Deno;
+ util.assert(globalThis.Deno === undefined);
+ }
+ }
+
+ Object.defineProperties(globalThis, {
+ bootstrap: {
+ value: {
+ mainRuntime: bootstrapMainRuntime,
+ workerRuntime: bootstrapWorkerRuntime,
+ },
+ configurable: true,
+ },
+ });
+})(this);
diff --git a/runtime/rt/README.md b/runtime/rt/README.md
new file mode 100644
index 000000000..b17fa22e5
--- /dev/null
+++ b/runtime/rt/README.md
@@ -0,0 +1,59 @@
+# Runtime JavaScript Code
+
+This directory contains Deno runtime code written in plain JavaScript.
+
+Each file is a plain, old **script**, not ES modules. The reason is that
+snapshotting ES modules is much harder, especially if one needs to manipulate
+global scope (like in case of Deno).
+
+Each file is prefixed with a number, telling in which order scripts should be
+loaded into V8 isolate. This is temporary solution and we're striving not to
+require specific order (though it's not 100% obvious if that's feasible).
+
+## Deno Web APIs
+
+This directory facilities Web APIs that are available in Deno.
+
+Please note, that some implementations might not be completely aligned with
+specification.
+
+Some Web APIs are using ops under the hood, eg. `console`, `performance`.
+
+## Implemented Web APIs
+
+- [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob): for
+ representing opaque binary data.
+- [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console): for
+ logging purposes.
+- [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent),
+ [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
+ and
+ [EventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventListener):
+ to work with DOM events.
+ - **Implementation notes:** There is no DOM hierarchy in Deno, so there is no
+ tree for Events to bubble/capture through.
+- [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch),
+ [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request),
+ [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response),
+ [Body](https://developer.mozilla.org/en-US/docs/Web/API/Body) and
+ [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers): modern
+ Promise-based HTTP Request API.
+- [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData): access
+ to a `multipart/form-data` serialization.
+- [Performance](https://developer.mozilla.org/en-US/docs/Web/API/Performance):
+ retrieving current time with a high precision.
+- [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout),
+ [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval),
+ [clearTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout):
+ scheduling callbacks in future and
+ [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval).
+- [Stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) for
+ creating, composing, and consuming streams of data.
+- [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) and
+ [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams):
+ to construct and parse URLSs.
+- [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker): executing
+ additional code in a separate thread.
+ - **Implementation notes:** Blob URLs are not supported, object ownership
+ cannot be transferred, posted data is serialized to JSON instead of
+ [structured cloning](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
diff --git a/runtime/tokio_util.rs b/runtime/tokio_util.rs
new file mode 100644
index 000000000..b25a2994f
--- /dev/null
+++ b/runtime/tokio_util.rs
@@ -0,0 +1,25 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+pub fn create_basic_runtime() -> tokio::runtime::Runtime {
+ tokio::runtime::Builder::new()
+ .basic_scheduler()
+ .enable_io()
+ .enable_time()
+ // This limits the number of threads for blocking operations (like for
+ // synchronous fs ops) or CPU bound tasks like when we run dprint in
+ // parallel for deno fmt.
+ // The default value is 512, which is an unhelpfully large thread pool. We
+ // don't ever want to have more than a couple dozen threads.
+ .max_threads(32)
+ .build()
+ .unwrap()
+}
+
+// TODO(ry) rename to run_local ?
+pub fn run_basic<F, R>(future: F) -> R
+where
+ F: std::future::Future<Output = R>,
+{
+ let mut rt = create_basic_runtime();
+ rt.block_on(future)
+}
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
new file mode 100644
index 000000000..db97e3604
--- /dev/null
+++ b/runtime/web_worker.rs
@@ -0,0 +1,595 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::colors;
+use crate::inspector::DenoInspector;
+use crate::inspector::InspectorServer;
+use crate::js;
+use crate::metrics::Metrics;
+use crate::ops;
+use crate::permissions::Permissions;
+use crate::tokio_util::create_basic_runtime;
+use deno_core::error::AnyError;
+use deno_core::futures::channel::mpsc;
+use deno_core::futures::future::poll_fn;
+use deno_core::futures::future::FutureExt;
+use deno_core::futures::stream::StreamExt;
+use deno_core::futures::task::AtomicWaker;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::url::Url;
+use deno_core::v8;
+use deno_core::GetErrorClassFn;
+use deno_core::JsErrorCreateFn;
+use deno_core::JsRuntime;
+use deno_core::ModuleLoader;
+use deno_core::ModuleSpecifier;
+use deno_core::RuntimeOptions;
+use std::env;
+use std::rc::Rc;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use std::task::Context;
+use std::task::Poll;
+use tokio::sync::Mutex as AsyncMutex;
+
+/// Events that are sent to host from child
+/// worker.
+pub enum WorkerEvent {
+ Message(Box<[u8]>),
+ Error(AnyError),
+ TerminalError(AnyError),
+}
+
+pub struct WorkerChannelsInternal {
+ pub sender: mpsc::Sender<WorkerEvent>,
+ pub receiver: mpsc::Receiver<Box<[u8]>>,
+}
+
+/// Wrapper for `WorkerHandle` that adds functionality
+/// for terminating workers.
+///
+/// This struct is used by host as well as worker itself.
+///
+/// Host uses it to communicate with worker and terminate it,
+/// while worker uses it only to finish execution on `self.close()`.
+#[derive(Clone)]
+pub struct WebWorkerHandle {
+ pub sender: mpsc::Sender<Box<[u8]>>,
+ pub receiver: Arc<AsyncMutex<mpsc::Receiver<WorkerEvent>>>,
+ terminate_tx: mpsc::Sender<()>,
+ terminated: Arc<AtomicBool>,
+ isolate_handle: v8::IsolateHandle,
+}
+
+impl WebWorkerHandle {
+ /// Post message to worker as a host.
+ pub fn post_message(&self, buf: Box<[u8]>) -> Result<(), AnyError> {
+ let mut sender = self.sender.clone();
+ sender.try_send(buf)?;
+ Ok(())
+ }
+
+ /// Get the event with lock.
+ /// Return error if more than one listener tries to get event
+ pub async fn get_event(&self) -> Result<Option<WorkerEvent>, AnyError> {
+ let mut receiver = self.receiver.try_lock()?;
+ Ok(receiver.next().await)
+ }
+
+ pub fn terminate(&self) {
+ // This function can be called multiple times by whomever holds
+ // the handle. However only a single "termination" should occur so
+ // we need a guard here.
+ let already_terminated = self.terminated.swap(true, Ordering::SeqCst);
+
+ if !already_terminated {
+ self.isolate_handle.terminate_execution();
+ let mut sender = self.terminate_tx.clone();
+ // This call should be infallible hence the `expect`.
+ // This might change in the future.
+ sender.try_send(()).expect("Failed to terminate");
+ }
+ }
+}
+
+fn create_channels(
+ isolate_handle: v8::IsolateHandle,
+ terminate_tx: mpsc::Sender<()>,
+) -> (WorkerChannelsInternal, WebWorkerHandle) {
+ let (in_tx, in_rx) = mpsc::channel::<Box<[u8]>>(1);
+ let (out_tx, out_rx) = mpsc::channel::<WorkerEvent>(1);
+ let internal_channels = WorkerChannelsInternal {
+ sender: out_tx,
+ receiver: in_rx,
+ };
+ let external_channels = WebWorkerHandle {
+ sender: in_tx,
+ receiver: Arc::new(AsyncMutex::new(out_rx)),
+ terminated: Arc::new(AtomicBool::new(false)),
+ terminate_tx,
+ isolate_handle,
+ };
+ (internal_channels, external_channels)
+}
+
+/// This struct is an implementation of `Worker` Web API
+///
+/// Each `WebWorker` is either a child of `MainWorker` or other
+/// `WebWorker`.
+pub struct WebWorker {
+ id: u32,
+ inspector: Option<Box<DenoInspector>>,
+ // Following fields are pub because they are accessed
+ // when creating a new WebWorker instance.
+ pub(crate) internal_channels: WorkerChannelsInternal,
+ pub js_runtime: JsRuntime,
+ pub name: String,
+ waker: AtomicWaker,
+ event_loop_idle: bool,
+ terminate_rx: mpsc::Receiver<()>,
+ handle: WebWorkerHandle,
+ pub use_deno_namespace: bool,
+}
+
+pub struct WebWorkerOptions {
+ /// Sets `Deno.args` in JS runtime.
+ pub args: Vec<String>,
+ pub debug_flag: bool,
+ pub unstable: bool,
+ pub ca_filepath: Option<String>,
+ pub user_agent: String,
+ pub seed: Option<u64>,
+ pub module_loader: Rc<dyn ModuleLoader>,
+ pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
+ pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
+ pub use_deno_namespace: bool,
+ pub attach_inspector: bool,
+ pub maybe_inspector_server: Option<Arc<InspectorServer>>,
+ pub apply_source_maps: bool,
+ /// Sets `Deno.version.deno` in JS runtime.
+ pub runtime_version: String,
+ /// Sets `Deno.version.typescript` in JS runtime.
+ pub ts_version: String,
+ /// Sets `Deno.noColor` in JS runtime.
+ pub no_color: bool,
+ pub get_error_class_fn: Option<GetErrorClassFn>,
+}
+
+impl WebWorker {
+ pub fn from_options(
+ name: String,
+ permissions: Permissions,
+ main_module: ModuleSpecifier,
+ worker_id: u32,
+ options: &WebWorkerOptions,
+ ) -> Self {
+ let mut js_runtime = JsRuntime::new(RuntimeOptions {
+ module_loader: Some(options.module_loader.clone()),
+ startup_snapshot: Some(js::deno_isolate_init()),
+ js_error_create_fn: options.js_error_create_fn.clone(),
+ get_error_class_fn: options.get_error_class_fn,
+ ..Default::default()
+ });
+
+ let inspector = if options.attach_inspector {
+ Some(DenoInspector::new(
+ &mut js_runtime,
+ options.maybe_inspector_server.clone(),
+ ))
+ } else {
+ None
+ };
+
+ let (terminate_tx, terminate_rx) = mpsc::channel::<()>(1);
+ let isolate_handle = js_runtime.v8_isolate().thread_safe_handle();
+ let (internal_channels, handle) =
+ create_channels(isolate_handle, terminate_tx);
+
+ let mut worker = Self {
+ id: worker_id,
+ inspector,
+ internal_channels,
+ js_runtime,
+ name,
+ waker: AtomicWaker::new(),
+ event_loop_idle: false,
+ terminate_rx,
+ handle,
+ use_deno_namespace: options.use_deno_namespace,
+ };
+
+ {
+ let handle = worker.thread_safe_handle();
+ let sender = worker.internal_channels.sender.clone();
+ let js_runtime = &mut worker.js_runtime;
+ // All ops registered in this function depend on these
+ {
+ let op_state = js_runtime.op_state();
+ let mut op_state = op_state.borrow_mut();
+ op_state.put::<Metrics>(Default::default());
+ op_state.put::<Permissions>(permissions);
+ op_state.put::<ops::UnstableChecker>(ops::UnstableChecker {
+ unstable: options.unstable,
+ });
+ }
+
+ ops::web_worker::init(js_runtime, sender.clone(), handle);
+ ops::runtime::init(js_runtime, main_module);
+ ops::fetch::init(
+ js_runtime,
+ options.user_agent.clone(),
+ options.ca_filepath.as_deref(),
+ );
+ ops::timers::init(js_runtime);
+ ops::worker_host::init(
+ js_runtime,
+ Some(sender),
+ options.create_web_worker_cb.clone(),
+ );
+ ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
+ ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
+ ops::reg_json_sync(
+ js_runtime,
+ "op_domain_to_ascii",
+ deno_web::op_domain_to_ascii,
+ );
+ ops::io::init(js_runtime);
+ ops::websocket::init(
+ js_runtime,
+ options.ca_filepath.as_deref(),
+ options.user_agent.clone(),
+ );
+
+ if options.use_deno_namespace {
+ ops::fs_events::init(js_runtime);
+ ops::fs::init(js_runtime);
+ ops::net::init(js_runtime);
+ ops::os::init(js_runtime);
+ ops::permissions::init(js_runtime);
+ ops::plugin::init(js_runtime);
+ ops::process::init(js_runtime);
+ ops::crypto::init(js_runtime, options.seed);
+ ops::signal::init(js_runtime);
+ ops::tls::init(js_runtime);
+ ops::tty::init(js_runtime);
+
+ let op_state = js_runtime.op_state();
+ let mut op_state = op_state.borrow_mut();
+ let (stdin, stdout, stderr) = ops::io::get_stdio();
+ if let Some(stream) = stdin {
+ op_state.resource_table.add("stdin", Box::new(stream));
+ }
+ if let Some(stream) = stdout {
+ op_state.resource_table.add("stdout", Box::new(stream));
+ }
+ if let Some(stream) = stderr {
+ op_state.resource_table.add("stderr", Box::new(stream));
+ }
+ }
+
+ worker
+ }
+ }
+
+ pub fn bootstrap(&mut self, options: &WebWorkerOptions) {
+ let runtime_options = json!({
+ "args": options.args,
+ "applySourceMaps": options.apply_source_maps,
+ "debugFlag": options.debug_flag,
+ "denoVersion": options.runtime_version,
+ "noColor": options.no_color,
+ "pid": std::process::id(),
+ "ppid": ops::runtime::ppid(),
+ "target": env!("TARGET"),
+ "tsVersion": options.ts_version,
+ "unstableFlag": options.unstable,
+ "v8Version": deno_core::v8_version(),
+ });
+
+ let runtime_options_str =
+ serde_json::to_string_pretty(&runtime_options).unwrap();
+
+ // Instead of using name for log we use `worker-${id}` because
+ // WebWorkers can have empty string as name.
+ let script = format!(
+ "bootstrap.workerRuntime({}, \"{}\", {}, \"worker-{}\")",
+ runtime_options_str, self.name, options.use_deno_namespace, self.id
+ );
+ self
+ .execute(&script)
+ .expect("Failed to execute worker bootstrap script");
+ }
+
+ /// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
+ pub fn execute(&mut self, js_source: &str) -> Result<(), AnyError> {
+ let path = env::current_dir().unwrap().join("__anonymous__");
+ let url = Url::from_file_path(path).unwrap();
+ self.js_runtime.execute(url.as_str(), js_source)
+ }
+
+ /// Loads, instantiates and executes specified JavaScript module.
+ pub async fn execute_module(
+ &mut self,
+ module_specifier: &ModuleSpecifier,
+ ) -> Result<(), AnyError> {
+ let id = self.js_runtime.load_module(module_specifier, None).await?;
+ self.js_runtime.mod_evaluate(id).await
+ }
+
+ /// Returns a way to communicate with the Worker from other threads.
+ pub fn thread_safe_handle(&self) -> WebWorkerHandle {
+ self.handle.clone()
+ }
+
+ pub fn has_been_terminated(&self) -> bool {
+ self.handle.terminated.load(Ordering::SeqCst)
+ }
+
+ pub fn poll_event_loop(
+ &mut self,
+ cx: &mut Context,
+ ) -> Poll<Result<(), AnyError>> {
+ if self.has_been_terminated() {
+ return Poll::Ready(Ok(()));
+ }
+
+ if !self.event_loop_idle {
+ let poll_result = {
+ // We always poll the inspector if it exists.
+ let _ = self.inspector.as_mut().map(|i| i.poll_unpin(cx));
+ self.waker.register(cx.waker());
+ self.js_runtime.poll_event_loop(cx)
+ };
+
+ if let Poll::Ready(r) = poll_result {
+ if self.has_been_terminated() {
+ return Poll::Ready(Ok(()));
+ }
+
+ if let Err(e) = r {
+ print_worker_error(e.to_string(), &self.name);
+ let mut sender = self.internal_channels.sender.clone();
+ sender
+ .try_send(WorkerEvent::Error(e))
+ .expect("Failed to post message to host");
+ }
+ self.event_loop_idle = true;
+ }
+ }
+
+ if let Poll::Ready(r) = self.terminate_rx.poll_next_unpin(cx) {
+ // terminate_rx should never be closed
+ assert!(r.is_some());
+ return Poll::Ready(Ok(()));
+ }
+
+ let maybe_msg_poll_result =
+ self.internal_channels.receiver.poll_next_unpin(cx);
+
+ if let Poll::Ready(maybe_msg) = maybe_msg_poll_result {
+ let msg =
+ maybe_msg.expect("Received `None` instead of message in worker");
+ let msg = String::from_utf8(msg.to_vec()).unwrap();
+ let script = format!("workerMessageRecvCallback({})", msg);
+
+ if let Err(e) = self.execute(&script) {
+ // If execution was terminated during message callback then
+ // just ignore it
+ if self.has_been_terminated() {
+ return Poll::Ready(Ok(()));
+ }
+
+ // Otherwise forward error to host
+ let mut sender = self.internal_channels.sender.clone();
+ sender
+ .try_send(WorkerEvent::Error(e))
+ .expect("Failed to post message to host");
+ }
+
+ // Let event loop be polled again
+ self.event_loop_idle = false;
+ self.waker.wake();
+ }
+
+ Poll::Pending
+ }
+
+ pub async fn run_event_loop(&mut self) -> Result<(), AnyError> {
+ poll_fn(|cx| self.poll_event_loop(cx)).await
+ }
+}
+
+impl Drop for WebWorker {
+ fn drop(&mut self) {
+ // The Isolate object must outlive the Inspector object, but this is
+ // currently not enforced by the type system.
+ self.inspector.take();
+ }
+}
+
+fn print_worker_error(error_str: String, name: &str) {
+ eprintln!(
+ "{}: Uncaught (in worker \"{}\") {}",
+ colors::red_bold("error"),
+ name,
+ error_str.trim_start_matches("Uncaught "),
+ );
+}
+
+/// This function should be called from a thread dedicated to this worker.
+// TODO(bartlomieju): check if order of actions is aligned to Worker spec
+pub fn run_web_worker(
+ mut worker: WebWorker,
+ specifier: ModuleSpecifier,
+ maybe_source_code: Option<String>,
+) -> Result<(), AnyError> {
+ let name = worker.name.to_string();
+
+ let mut rt = create_basic_runtime();
+
+ // TODO(bartlomieju): run following block using "select!"
+ // with terminate
+
+ // Execute provided source code immediately
+ let result = if let Some(source_code) = maybe_source_code {
+ worker.execute(&source_code)
+ } else {
+ // TODO(bartlomieju): add "type": "classic", ie. ability to load
+ // script instead of module
+ let load_future = worker.execute_module(&specifier).boxed_local();
+
+ rt.block_on(load_future)
+ };
+
+ let mut sender = worker.internal_channels.sender.clone();
+
+ // If sender is closed it means that worker has already been closed from
+ // within using "globalThis.close()"
+ if sender.is_closed() {
+ return Ok(());
+ }
+
+ if let Err(e) = result {
+ print_worker_error(e.to_string(), &name);
+ sender
+ .try_send(WorkerEvent::TerminalError(e))
+ .expect("Failed to post message to host");
+
+ // Failure to execute script is a terminal error, bye, bye.
+ return Ok(());
+ }
+
+ let result = rt.block_on(worker.run_event_loop());
+ debug!("Worker thread shuts down {}", &name);
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tokio_util;
+ use deno_core::serde_json::json;
+
+ fn create_test_web_worker() -> WebWorker {
+ let main_module =
+ ModuleSpecifier::resolve_url_or_path("./hello.js").unwrap();
+ let module_loader = Rc::new(deno_core::NoopModuleLoader);
+ let create_web_worker_cb = Arc::new(|_| unreachable!());
+
+ let options = WebWorkerOptions {
+ args: vec![],
+ apply_source_maps: false,
+ debug_flag: false,
+ unstable: false,
+ ca_filepath: None,
+ user_agent: "x".to_string(),
+ seed: None,
+ module_loader,
+ create_web_worker_cb,
+ js_error_create_fn: None,
+ use_deno_namespace: false,
+ attach_inspector: false,
+ maybe_inspector_server: None,
+ runtime_version: "x".to_string(),
+ ts_version: "x".to_string(),
+ no_color: true,
+ get_error_class_fn: None,
+ };
+
+ let mut worker = WebWorker::from_options(
+ "TEST".to_string(),
+ Permissions::allow_all(),
+ main_module,
+ 1,
+ &options,
+ );
+ worker.bootstrap(&options);
+ worker
+ }
+
+ #[tokio::test]
+ async fn test_worker_messages() {
+ let (handle_sender, handle_receiver) =
+ std::sync::mpsc::sync_channel::<WebWorkerHandle>(1);
+
+ let join_handle = std::thread::spawn(move || {
+ let mut worker = create_test_web_worker();
+ let source = r#"
+ onmessage = function(e) {
+ console.log("msg from main script", e.data);
+ if (e.data == "exit") {
+ return close();
+ } else {
+ console.assert(e.data === "hi");
+ }
+ postMessage([1, 2, 3]);
+ console.log("after postMessage");
+ }
+ "#;
+ worker.execute(source).unwrap();
+ let handle = worker.thread_safe_handle();
+ handle_sender.send(handle).unwrap();
+ let r = tokio_util::run_basic(worker.run_event_loop());
+ assert!(r.is_ok())
+ });
+
+ let mut handle = handle_receiver.recv().unwrap();
+
+ let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();
+ let r = handle.post_message(msg.clone());
+ assert!(r.is_ok());
+
+ let maybe_msg = handle.get_event().await.unwrap();
+ assert!(maybe_msg.is_some());
+
+ let r = handle.post_message(msg.clone());
+ assert!(r.is_ok());
+
+ let maybe_msg = handle.get_event().await.unwrap();
+ assert!(maybe_msg.is_some());
+ match maybe_msg {
+ Some(WorkerEvent::Message(buf)) => {
+ assert_eq!(*buf, *b"[1,2,3]");
+ }
+ _ => unreachable!(),
+ }
+
+ let msg = json!("exit")
+ .to_string()
+ .into_boxed_str()
+ .into_boxed_bytes();
+ let r = handle.post_message(msg);
+ assert!(r.is_ok());
+ let event = handle.get_event().await.unwrap();
+ assert!(event.is_none());
+ handle.sender.close_channel();
+ join_handle.join().expect("Failed to join worker thread");
+ }
+
+ #[tokio::test]
+ async fn removed_from_resource_table_on_close() {
+ let (handle_sender, handle_receiver) =
+ std::sync::mpsc::sync_channel::<WebWorkerHandle>(1);
+
+ let join_handle = std::thread::spawn(move || {
+ let mut worker = create_test_web_worker();
+ worker.execute("onmessage = () => { close(); }").unwrap();
+ let handle = worker.thread_safe_handle();
+ handle_sender.send(handle).unwrap();
+ let r = tokio_util::run_basic(worker.run_event_loop());
+ assert!(r.is_ok())
+ });
+
+ let mut handle = handle_receiver.recv().unwrap();
+
+ let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();
+ let r = handle.post_message(msg.clone());
+ assert!(r.is_ok());
+ let event = handle.get_event().await.unwrap();
+ assert!(event.is_none());
+ handle.sender.close_channel();
+
+ join_handle.join().expect("Failed to join worker thread");
+ }
+}
diff --git a/runtime/worker.rs b/runtime/worker.rs
new file mode 100644
index 000000000..a0e63afad
--- /dev/null
+++ b/runtime/worker.rs
@@ -0,0 +1,349 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use crate::inspector::DenoInspector;
+use crate::inspector::InspectorServer;
+use crate::inspector::InspectorSession;
+use crate::js;
+use crate::metrics::Metrics;
+use crate::ops;
+use crate::permissions::Permissions;
+use deno_core::error::AnyError;
+use deno_core::futures::future::poll_fn;
+use deno_core::futures::future::FutureExt;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::url::Url;
+use deno_core::GetErrorClassFn;
+use deno_core::JsErrorCreateFn;
+use deno_core::JsRuntime;
+use deno_core::ModuleId;
+use deno_core::ModuleLoader;
+use deno_core::ModuleSpecifier;
+use deno_core::RuntimeOptions;
+use std::env;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::task::Context;
+use std::task::Poll;
+
+/// This worker is created and used by almost all
+/// subcommands in Deno executable.
+///
+/// It provides ops available in the `Deno` namespace.
+///
+/// All `WebWorker`s created during program execution
+/// are descendants of this worker.
+pub struct MainWorker {
+ inspector: Option<Box<DenoInspector>>,
+ pub js_runtime: JsRuntime,
+ should_break_on_first_statement: bool,
+}
+
+pub struct WorkerOptions {
+ pub apply_source_maps: bool,
+ /// Sets `Deno.args` in JS runtime.
+ pub args: Vec<String>,
+ pub debug_flag: bool,
+ pub unstable: bool,
+ pub ca_filepath: Option<String>,
+ pub user_agent: String,
+ pub seed: Option<u64>,
+ pub module_loader: Rc<dyn ModuleLoader>,
+ // Callback that will be invoked when creating new instance
+ // of WebWorker
+ pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
+ pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
+ pub attach_inspector: bool,
+ pub maybe_inspector_server: Option<Arc<InspectorServer>>,
+ pub should_break_on_first_statement: bool,
+ /// Sets `Deno.version.deno` in JS runtime.
+ pub runtime_version: String,
+ /// Sets `Deno.version.typescript` in JS runtime.
+ pub ts_version: String,
+ /// Sets `Deno.noColor` in JS runtime.
+ pub no_color: bool,
+ pub get_error_class_fn: Option<GetErrorClassFn>,
+}
+
+impl MainWorker {
+ pub fn from_options(
+ main_module: ModuleSpecifier,
+ permissions: Permissions,
+ options: &WorkerOptions,
+ ) -> Self {
+ let mut js_runtime = JsRuntime::new(RuntimeOptions {
+ module_loader: Some(options.module_loader.clone()),
+ startup_snapshot: Some(js::deno_isolate_init()),
+ js_error_create_fn: options.js_error_create_fn.clone(),
+ get_error_class_fn: options.get_error_class_fn,
+ ..Default::default()
+ });
+
+ let inspector = if options.attach_inspector {
+ Some(DenoInspector::new(
+ &mut js_runtime,
+ options.maybe_inspector_server.clone(),
+ ))
+ } else {
+ None
+ };
+ let should_break_on_first_statement =
+ inspector.is_some() && options.should_break_on_first_statement;
+
+ let mut worker = Self {
+ inspector,
+ js_runtime,
+ should_break_on_first_statement,
+ };
+
+ let js_runtime = &mut worker.js_runtime;
+ {
+ // All ops registered in this function depend on these
+ {
+ let op_state = js_runtime.op_state();
+ let mut op_state = op_state.borrow_mut();
+ op_state.put::<Metrics>(Default::default());
+ op_state.put::<Permissions>(permissions);
+ op_state.put::<ops::UnstableChecker>(ops::UnstableChecker {
+ unstable: options.unstable,
+ });
+ }
+
+ ops::runtime::init(js_runtime, main_module);
+ ops::fetch::init(
+ js_runtime,
+ options.user_agent.clone(),
+ options.ca_filepath.as_deref(),
+ );
+ ops::timers::init(js_runtime);
+ ops::worker_host::init(
+ js_runtime,
+ None,
+ options.create_web_worker_cb.clone(),
+ );
+ ops::crypto::init(js_runtime, options.seed);
+ ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
+ ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
+ ops::reg_json_sync(
+ js_runtime,
+ "op_domain_to_ascii",
+ deno_web::op_domain_to_ascii,
+ );
+ ops::fs_events::init(js_runtime);
+ ops::fs::init(js_runtime);
+ ops::io::init(js_runtime);
+ ops::net::init(js_runtime);
+ ops::os::init(js_runtime);
+ ops::permissions::init(js_runtime);
+ ops::plugin::init(js_runtime);
+ ops::process::init(js_runtime);
+ ops::signal::init(js_runtime);
+ ops::tls::init(js_runtime);
+ ops::tty::init(js_runtime);
+ ops::websocket::init(
+ js_runtime,
+ options.ca_filepath.as_deref(),
+ options.user_agent.clone(),
+ );
+ }
+ {
+ let op_state = js_runtime.op_state();
+ let mut op_state = op_state.borrow_mut();
+ let t = &mut op_state.resource_table;
+ let (stdin, stdout, stderr) = ops::io::get_stdio();
+ if let Some(stream) = stdin {
+ t.add("stdin", Box::new(stream));
+ }
+ if let Some(stream) = stdout {
+ t.add("stdout", Box::new(stream));
+ }
+ if let Some(stream) = stderr {
+ t.add("stderr", Box::new(stream));
+ }
+ }
+
+ worker
+ }
+
+ pub fn bootstrap(&mut self, options: &WorkerOptions) {
+ let runtime_options = json!({
+ "args": options.args,
+ "applySourceMaps": options.apply_source_maps,
+ "debugFlag": options.debug_flag,
+ "denoVersion": options.runtime_version,
+ "noColor": options.no_color,
+ "pid": std::process::id(),
+ "ppid": ops::runtime::ppid(),
+ "target": env!("TARGET"),
+ "tsVersion": options.ts_version,
+ "unstableFlag": options.unstable,
+ "v8Version": deno_core::v8_version(),
+ });
+
+ let script = format!(
+ "bootstrap.mainRuntime({})",
+ serde_json::to_string_pretty(&runtime_options).unwrap()
+ );
+ self
+ .execute(&script)
+ .expect("Failed to execute bootstrap script");
+ }
+
+ /// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
+ pub fn execute(&mut self, js_source: &str) -> Result<(), AnyError> {
+ let path = env::current_dir().unwrap().join("__anonymous__");
+ let url = Url::from_file_path(path).unwrap();
+ self.js_runtime.execute(url.as_str(), js_source)
+ }
+
+ /// Loads and instantiates specified JavaScript module.
+ pub async fn preload_module(
+ &mut self,
+ module_specifier: &ModuleSpecifier,
+ ) -> Result<ModuleId, AnyError> {
+ self.js_runtime.load_module(module_specifier, None).await
+ }
+
+ /// Loads, instantiates and executes specified JavaScript module.
+ pub async fn execute_module(
+ &mut self,
+ module_specifier: &ModuleSpecifier,
+ ) -> Result<(), AnyError> {
+ let id = self.preload_module(module_specifier).await?;
+ self.wait_for_inspector_session();
+ self.js_runtime.mod_evaluate(id).await
+ }
+
+ fn wait_for_inspector_session(&mut self) {
+ if self.should_break_on_first_statement {
+ self
+ .inspector
+ .as_mut()
+ .unwrap()
+ .wait_for_session_and_break_on_next_statement()
+ }
+ }
+
+ /// Create new inspector session. This function panics if Worker
+ /// was not configured to create inspector.
+ pub fn create_inspector_session(&mut self) -> Box<InspectorSession> {
+ let inspector = self.inspector.as_mut().unwrap();
+
+ InspectorSession::new(&mut **inspector)
+ }
+
+ pub fn poll_event_loop(
+ &mut self,
+ cx: &mut Context,
+ ) -> Poll<Result<(), AnyError>> {
+ // We always poll the inspector if it exists.
+ let _ = self.inspector.as_mut().map(|i| i.poll_unpin(cx));
+ self.js_runtime.poll_event_loop(cx)
+ }
+
+ pub async fn run_event_loop(&mut self) -> Result<(), AnyError> {
+ poll_fn(|cx| self.poll_event_loop(cx)).await
+ }
+}
+
+impl Drop for MainWorker {
+ fn drop(&mut self) {
+ // The Isolate object must outlive the Inspector object, but this is
+ // currently not enforced by the type system.
+ self.inspector.take();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn create_test_worker() -> MainWorker {
+ let main_module =
+ ModuleSpecifier::resolve_url_or_path("./hello.js").unwrap();
+ let permissions = Permissions::default();
+
+ let options = WorkerOptions {
+ apply_source_maps: false,
+ user_agent: "x".to_string(),
+ args: vec![],
+ debug_flag: false,
+ unstable: false,
+ ca_filepath: None,
+ seed: None,
+ js_error_create_fn: None,
+ create_web_worker_cb: Arc::new(|_| unreachable!()),
+ attach_inspector: false,
+ maybe_inspector_server: None,
+ should_break_on_first_statement: false,
+ module_loader: Rc::new(deno_core::FsModuleLoader),
+ runtime_version: "x".to_string(),
+ ts_version: "x".to_string(),
+ no_color: true,
+ get_error_class_fn: None,
+ };
+
+ MainWorker::from_options(main_module, permissions, &options)
+ }
+
+ #[tokio::test]
+ async fn execute_mod_esm_imports_a() {
+ let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .join("cli/tests/esm_imports_a.js");
+ let module_specifier =
+ ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let mut worker = create_test_worker();
+ let result = worker.execute_module(&module_specifier).await;
+ if let Err(err) = result {
+ eprintln!("execute_mod err {:?}", err);
+ }
+ if let Err(e) = worker.run_event_loop().await {
+ panic!("Future got unexpected error: {:?}", e);
+ }
+ }
+
+ #[tokio::test]
+ async fn execute_mod_circular() {
+ let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .join("tests/circular1.js");
+ let module_specifier =
+ ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let mut worker = create_test_worker();
+ let result = worker.execute_module(&module_specifier).await;
+ if let Err(err) = result {
+ eprintln!("execute_mod err {:?}", err);
+ }
+ if let Err(e) = worker.run_event_loop().await {
+ panic!("Future got unexpected error: {:?}", e);
+ }
+ }
+
+ #[tokio::test]
+ async fn execute_mod_resolve_error() {
+ // "foo" is not a valid module specifier so this should return an error.
+ let mut worker = create_test_worker();
+ let module_specifier =
+ ModuleSpecifier::resolve_url_or_path("does-not-exist").unwrap();
+ let result = worker.execute_module(&module_specifier).await;
+ assert!(result.is_err());
+ }
+
+ #[tokio::test]
+ async fn execute_mod_002_hello() {
+ // This assumes cwd is project root (an assumption made throughout the
+ // tests).
+ let mut worker = create_test_worker();
+ let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .join("cli/tests/001_hello.js");
+ let module_specifier =
+ ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let result = worker.execute_module(&module_specifier).await;
+ assert!(result.is_ok());
+ }
+}