diff --git a/cli/flags.rs b/cli/flags.rs index ae5ef15be5..bafd8fe91d 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -228,7 +228,7 @@ lazy_static! { crate::version::deno(), env!("PROFILE"), env!("TARGET"), - crate::version::v8(), + deno_core::v8_version(), crate::version::TYPESCRIPT ); } diff --git a/cli/inspector.rs b/cli/inspector.rs index a8be68c8d9..fa1bfa2d04 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -203,7 +203,7 @@ async fn server( warp::reply::json(&json!({ "Browser": format!("Deno/{}", crate::version::deno()), "Protocol-Version": "1.3", - "V8-Version": crate::version::v8(), + "V8-Version": deno_core::v8_version(), })) }); diff --git a/cli/main.rs b/cli/main.rs index 2e40df66bd..8f8879ac96 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -55,15 +55,22 @@ use crate::file_fetcher::FileFetcher; use crate::file_watcher::ModuleResolutionResult; use crate::flags::DenoSubcommand; use crate::flags::Flags; +use crate::fmt_errors::PrettyJsError; use crate::import_map::ImportMap; use crate::media_type::MediaType; +use crate::module_loader::CliModuleLoader; +use crate::ops::worker_host::CreateWebWorkerCb; use crate::permissions::Permissions; use crate::program_state::exit_unstable; use crate::program_state::ProgramState; +use crate::source_maps::apply_source_map; use crate::specifier_handler::FetchHandler; use crate::standalone::create_standalone_binary; use crate::tools::installer::infer_name_from_url; +use crate::web_worker::WebWorker; +use crate::web_worker::WebWorkerOptions; use crate::worker::MainWorker; +use crate::worker::WorkerOptions; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::future::FutureExt; @@ -86,6 +93,134 @@ use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; +fn create_web_worker_callback( + program_state: Arc, +) -> Arc { + Arc::new(move |args| { + let global_state_ = program_state.clone(); + let js_error_create_fn = Rc::new(move |core_js_error| { + let source_mapped_error = + apply_source_map(&core_js_error, global_state_.clone()); + PrettyJsError::create(source_mapped_error) + }); + + let attach_inspector = program_state.maybe_inspector_server.is_some() + || program_state.flags.coverage; + let maybe_inspector_server = program_state.maybe_inspector_server.clone(); + + let module_loader = CliModuleLoader::new_for_worker(program_state.clone()); + let create_web_worker_cb = + create_web_worker_callback(program_state.clone()); + + let options = WebWorkerOptions { + args: program_state.flags.argv.clone(), + apply_source_maps: true, + debug_flag: program_state + .flags + .log_level + .map_or(false, |l| l == log::Level::Debug), + unstable: program_state.flags.unstable, + ca_filepath: program_state.flags.ca_file.clone(), + seed: program_state.flags.seed, + module_loader, + create_web_worker_cb, + js_error_create_fn: Some(js_error_create_fn), + use_deno_namespace: args.use_deno_namespace, + attach_inspector, + maybe_inspector_server, + }; + + let mut worker = WebWorker::from_options( + args.name, + args.permissions, + args.main_module, + args.worker_id, + &options, + ); + + // This block registers additional ops and state that + // are only available in the CLI + { + let js_runtime = &mut worker.js_runtime; + js_runtime + .op_state() + .borrow_mut() + .put::>(program_state.clone()); + // Applies source maps - works in conjuction with `js_error_create_fn` + // above + ops::errors::init(js_runtime); + if args.use_deno_namespace { + ops::runtime_compiler::init(js_runtime); + } + } + worker.bootstrap(&options); + + worker + }) +} + +pub fn create_main_worker( + program_state: &Arc, + main_module: ModuleSpecifier, + permissions: Permissions, +) -> MainWorker { + let module_loader = CliModuleLoader::new(program_state.clone()); + + let global_state_ = program_state.clone(); + + let js_error_create_fn = Rc::new(move |core_js_error| { + let source_mapped_error = + apply_source_map(&core_js_error, global_state_.clone()); + PrettyJsError::create(source_mapped_error) + }); + + let attach_inspector = program_state.maybe_inspector_server.is_some() + || program_state.flags.repl + || program_state.flags.coverage; + let maybe_inspector_server = program_state.maybe_inspector_server.clone(); + let should_break_on_first_statement = + program_state.flags.inspect_brk.is_some(); + + let create_web_worker_cb = create_web_worker_callback(program_state.clone()); + + let options = WorkerOptions { + apply_source_maps: true, + args: program_state.flags.argv.clone(), + debug_flag: program_state + .flags + .log_level + .map_or(false, |l| l == log::Level::Debug), + unstable: program_state.flags.unstable, + ca_filepath: program_state.flags.ca_file.clone(), + seed: program_state.flags.seed, + js_error_create_fn: Some(js_error_create_fn), + create_web_worker_cb, + attach_inspector, + maybe_inspector_server, + should_break_on_first_statement, + module_loader, + }; + + let mut worker = MainWorker::from_options(main_module, permissions, &options); + + // This block registers additional ops and state that + // are only available in the CLI + { + let js_runtime = &mut worker.js_runtime; + js_runtime + .op_state() + .borrow_mut() + .put::>(program_state.clone()); + // Applies source maps - works in conjuction with `js_error_create_fn` + // above + ops::errors::init(js_runtime); + ops::runtime_compiler::init(js_runtime); + } + worker.bootstrap(&options); + + worker +} + fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> { use std::io::ErrorKind; @@ -253,7 +388,7 @@ async fn install_command( let program_state = ProgramState::new(preload_flags)?; let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?; let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); // First, fetch and compile the module; this step ensures that the module exists. worker.preload_module(&main_module).await?; tools::installer::install(flags, &module_url, args, name, root, force) @@ -321,7 +456,7 @@ async fn eval_command( let permissions = Permissions::from_flags(&flags); let program_state = ProgramState::new(flags)?; let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); let main_module_url = main_module.as_url().to_owned(); // Create a dummy source file. let source_code = if print { @@ -664,7 +799,7 @@ async fn run_repl(flags: Flags) -> Result<(), AnyError> { let permissions = Permissions::from_flags(&flags); let program_state = ProgramState::new(flags)?; let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); worker.run_event_loop().await?; tools::repl::run(&program_state, worker).await @@ -675,8 +810,11 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> { let permissions = Permissions::from_flags(&flags); let main_module = ModuleSpecifier::resolve_url_or_path("./$deno$stdin.ts").unwrap(); - let mut worker = - MainWorker::new(&program_state.clone(), main_module.clone(), permissions); + let mut worker = create_main_worker( + &program_state.clone(), + main_module.clone(), + permissions, + ); let mut source = Vec::new(); std::io::stdin().read_to_end(&mut source)?; @@ -755,7 +893,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { let main_module = main_module.clone(); let program_state = ProgramState::new(flags)?; let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); debug!("main_module {}", main_module); worker.execute_module(&main_module).await?; worker.execute("window.dispatchEvent(new Event('load'))")?; @@ -788,7 +926,7 @@ async fn run_command(flags: Flags, script: String) -> Result<(), AnyError> { let program_state = ProgramState::new(flags.clone())?; let permissions = Permissions::from_flags(&flags); let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); debug!("main_module {}", main_module); worker.execute_module(&main_module).await?; worker.execute("window.dispatchEvent(new Event('load'))")?; @@ -857,7 +995,7 @@ async fn test_command( } let mut worker = - MainWorker::new(&program_state, main_module.clone(), permissions); + create_main_worker(&program_state, main_module.clone(), permissions); let mut maybe_coverage_collector = if flags.coverage { let session = worker.create_inspector_session(); diff --git a/cli/ops/errors.rs b/cli/ops/errors.rs index dbb72139dd..d9893b0ef8 100644 --- a/cli/ops/errors.rs +++ b/cli/ops/errors.rs @@ -1,6 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::diagnostics::Diagnostics; +use crate::program_state::ProgramState; use crate::source_maps::get_orig_position; use crate::source_maps::CachedMaps; use deno_core::error::AnyError; @@ -11,6 +12,7 @@ use deno_core::OpState; use deno_core::ZeroCopyBuf; use serde::Deserialize; use std::collections::HashMap; +use std::sync::Arc; pub fn init(rt: &mut deno_core::JsRuntime) { super::reg_json_sync(rt, "op_apply_source_map", op_apply_source_map); @@ -33,13 +35,15 @@ fn op_apply_source_map( let args: ApplySourceMap = serde_json::from_value(args)?; let mut mappings_map: CachedMaps = HashMap::new(); + let program_state = state.borrow::>().clone(); + let (orig_file_name, orig_line_number, orig_column_number) = get_orig_position( args.file_name, args.line_number.into(), args.column_number.into(), &mut mappings_map, - super::program_state(state), + program_state, ); Ok(json!({ diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index b450f8989e..56c0f1ad59 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -27,7 +27,6 @@ pub mod websocket; pub mod worker_host; use crate::metrics::metrics_op; -use crate::program_state::ProgramState; use deno_core::error::AnyError; use deno_core::json_op_async; use deno_core::json_op_sync; @@ -39,7 +38,6 @@ use deno_core::ZeroCopyBuf; use std::cell::RefCell; use std::future::Future; use std::rc::Rc; -use std::sync::Arc; pub fn reg_json_async(rt: &mut JsRuntime, name: &'static str, op_fn: F) where @@ -57,24 +55,33 @@ where rt.register_op(name, metrics_op(json_op_sync(op_fn))); } +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::>().check_unstable(api_name) + state.borrow::().check_unstable(api_name) } /// Helper for checking unstable features. Used for async ops. pub fn check_unstable2(state: &Rc>, api_name: &str) { let state = state.borrow(); - state.borrow::>().check_unstable(api_name) -} - -/// Helper for extracting the commonly used state. Used for sync ops. -pub fn program_state(state: &OpState) -> Arc { - state.borrow::>().clone() -} - -/// Helper for extracting the commonly used state. Used for async ops. -pub fn global_state2(state: &Rc>) -> Arc { - let state = state.borrow(); - state.borrow::>().clone() + state.borrow::().check_unstable(api_name) } diff --git a/cli/ops/runtime.rs b/cli/ops/runtime.rs index 38b23f3b30..cb3b53d537 100644 --- a/cli/ops/runtime.rs +++ b/cli/ops/runtime.rs @@ -1,9 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::colors; use crate::metrics::Metrics; use crate::permissions::Permissions; -use crate::version; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::serde_json::json; @@ -11,50 +9,17 @@ use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_core::ZeroCopyBuf; -use std::env; -type ApplySourceMaps = bool; - -pub fn init( - rt: &mut deno_core::JsRuntime, - main_module: ModuleSpecifier, - apply_source_maps: bool, -) { +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::(main_module); - state.put::(apply_source_maps); } - super::reg_json_sync(rt, "op_start", op_start); super::reg_json_sync(rt, "op_main_module", op_main_module); super::reg_json_sync(rt, "op_metrics", op_metrics); } -fn op_start( - state: &mut OpState, - _args: Value, - _zero_copy: &mut [ZeroCopyBuf], -) -> Result { - let apply_source_maps = *state.borrow::(); - let gs = &super::program_state(state); - - Ok(json!({ - "args": gs.flags.argv.clone(), - "applySourceMaps": apply_source_maps, - "debugFlag": gs.flags.log_level.map_or(false, |l| l == log::Level::Debug), - "denoVersion": version::deno(), - "noColor": !colors::use_color(), - "pid": std::process::id(), - "ppid": ppid(), - "target": env!("TARGET"), - "tsVersion": version::TYPESCRIPT, - "unstableFlag": gs.flags.unstable, - "v8Version": version::v8(), - "versionFlag": gs.flags.version, - })) -} - fn op_main_module( state: &mut OpState, _args: Value, @@ -93,7 +58,7 @@ fn op_metrics( })) } -fn ppid() -> Value { +pub fn ppid() -> Value { #[cfg(windows)] { // Adopted from rustup: diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index f47f2fdb38..03ba88c768 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -7,10 +7,12 @@ use crate::module_graph::BundleType; use crate::module_graph::EmitOptions; use crate::module_graph::GraphBuilder; use crate::permissions::Permissions; +use crate::program_state::ProgramState; use crate::specifier_handler::FetchHandler; use crate::specifier_handler::MemoryHandler; use crate::specifier_handler::SpecifierHandler; use crate::tsc_config; +use std::sync::Arc; use deno_core::error::AnyError; use deno_core::error::Context; @@ -51,7 +53,7 @@ async fn op_compile( } else { super::check_unstable2(&state, "Deno.compile"); } - let program_state = super::global_state2(&state); + let program_state = state.borrow().borrow::>().clone(); let runtime_permissions = { let state = state.borrow(); state.borrow::().clone() diff --git a/cli/ops/websocket.rs b/cli/ops/websocket.rs index c04c3b476b..de6357c87c 100644 --- a/cli/ops/websocket.rs +++ b/cli/ops/websocket.rs @@ -33,7 +33,17 @@ use tokio_tungstenite::tungstenite::{ use tokio_tungstenite::{client_async, WebSocketStream}; use webpki::DNSNameRef; -pub fn init(rt: &mut deno_core::JsRuntime) { +#[derive(Clone)] +struct WsCaFile(String); + +pub fn init(rt: &mut deno_core::JsRuntime, maybe_ca_file: Option<&str>) { + { + let op_state = rt.op_state(); + let mut state = op_state.borrow_mut(); + if let Some(ca_file) = maybe_ca_file { + state.put::(WsCaFile(ca_file.to_string())); + } + } 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); @@ -92,10 +102,7 @@ pub async fn op_ws_create( ); } - let ca_file = { - let program_state = super::global_state2(&state); - program_state.flags.ca_file.clone() - }; + let maybe_ca_file = state.borrow().try_borrow::().cloned(); let uri: Uri = args.url.parse()?; let mut request = Request::builder().method(Method::GET).uri(&uri); @@ -128,8 +135,8 @@ pub async fn op_ws_create( .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - if let Some(path) = ca_file { - let key_file = File::open(path)?; + 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(); } diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index 6a2d799682..871e4b9fe4 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -21,8 +21,27 @@ 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); + #[derive(Deserialize)] struct HostUnhandledErrorArgs { message: String, @@ -31,12 +50,16 @@ struct HostUnhandledErrorArgs { pub fn init( rt: &mut deno_core::JsRuntime, sender: Option>, + create_web_worker_cb: Arc, ) { { let op_state = rt.op_state(); let mut state = op_state.borrow_mut(); state.put::(WorkersTable::default()); state.put::(WorkerId::default()); + + let create_module_loader = CreateWebWorkerCbHolder(create_web_worker_cb); + state.put::(create_module_loader); } super::reg_json_sync(rt, "op_create_worker", op_create_worker); super::reg_json_sync( @@ -102,11 +125,12 @@ fn op_create_worker( } let permissions = state.borrow::().clone(); let worker_id = state.take::(); + let create_module_loader = state.take::(); + state.put::(create_module_loader.clone()); state.put::(worker_id + 1); let module_specifier = ModuleSpecifier::resolve_url(&specifier)?; let worker_name = args_name.unwrap_or_else(|| "".to_string()); - let program_state = super::program_state(state); let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::>(1); @@ -121,14 +145,14 @@ fn op_create_worker( // - 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 = WebWorker::new( - worker_name, - permissions, - module_specifier.clone(), - program_state, - use_deno_namespace, + + 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(); diff --git a/cli/program_state.rs b/cli/program_state.rs index 023e2604d1..41b7c51fe0 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -258,16 +258,6 @@ impl ProgramState { } } - /// 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. - pub fn check_unstable(&self, api_name: &str) { - if !self.flags.unstable { - exit_unstable(api_name); - } - } - #[cfg(test)] pub fn mock( argv: Vec, diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index 2aa140990e..f38d51936a 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -132,40 +132,31 @@ delete Object.prototype.__proto__; core.jsonOpSync("op_worker_close"); } - function opStart() { - return core.jsonOpSync("op_start"); - } - function opMainModule() { return core.jsonOpSync("op_main_module"); } - // TODO(bartlomieju): temporary solution, must be fixed when moving - // dispatches to separate crates - function initOps() { + 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); - } - function runtimeStart(source) { - initOps(); - // First we send an empty `Start` message to let the privileged side know we - // are ready. The response should be a `StartRes` message containing the CLI - // args and other info. - const s = opStart(); - version.setVersions(s.denoVersion, s.v8Version, s.tsVersion); - build.setBuildInfo(s.target); - util.setLogDebug(s.debugFlag, source); + 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 (s.applySourceMaps) { + if (runtimeOptions.applySourceMaps) { prepareStackTrace = core.createPrepareStackTrace( errorStack.opApplySourceMap, ); @@ -173,8 +164,6 @@ delete Object.prototype.__proto__; prepareStackTrace = core.createPrepareStackTrace(); } Error.prepareStackTrace = prepareStackTrace; - - return s; } function registerErrors() { @@ -283,7 +272,7 @@ delete Object.prototype.__proto__; let hasBootstrapped = false; - function bootstrapMainRuntime() { + function bootstrapMainRuntime(runtimeOptions) { if (hasBootstrapped) { throw new Error("Worker runtime already bootstrapped"); } @@ -300,7 +289,8 @@ delete Object.prototype.__proto__; defineEventHandler(window, "load", null); defineEventHandler(window, "unload", null); - const { args, noColor, pid, ppid, unstableFlag } = runtimeStart(); + runtimeStart(runtimeOptions); + const { args, noColor, pid, ppid, unstableFlag } = runtimeOptions; registerErrors(); @@ -335,7 +325,12 @@ delete Object.prototype.__proto__; util.log("args", args); } - function bootstrapWorkerRuntime(name, useDenoNamespace, internalName) { + function bootstrapWorkerRuntime( + runtimeOptions, + name, + useDenoNamespace, + internalName, + ) { if (hasBootstrapped) { throw new Error("Worker runtime already bootstrapped"); } @@ -349,9 +344,12 @@ delete Object.prototype.__proto__; Object.defineProperties(globalThis, { name: util.readOnly(name) }); Object.setPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); eventTarget.setEventTargetData(globalThis); - const { unstableFlag, pid, noColor, args } = runtimeStart( + + runtimeStart( + runtimeOptions, internalName ?? name, ); + const { unstableFlag, pid, noColor, args } = runtimeOptions; registerErrors(); diff --git a/cli/standalone.rs b/cli/standalone.rs index df7106c970..d7ffd0fd2e 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -1,9 +1,9 @@ use crate::colors; use crate::flags::Flags; use crate::permissions::Permissions; -use crate::program_state::ProgramState; use crate::tokio_util; use crate::worker::MainWorker; +use crate::worker::WorkerOptions; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::FutureExt; @@ -21,6 +21,7 @@ use std::io::Write; use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; +use std::sync::Arc; const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; @@ -109,16 +110,29 @@ async fn run(source_code: String, args: Vec) -> Result<(), AnyError> { // TODO(lucacasonato): remove once you can specify this correctly through embedded metadata flags.unstable = true; let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?; - let program_state = ProgramState::new(flags.clone())?; let permissions = Permissions::allow_all(); let module_loader = Rc::new(EmbeddedModuleLoader(source_code)); - let mut worker = MainWorker::from_options( - &program_state, - main_module.clone(), - permissions, + let create_web_worker_cb = Arc::new(|_| { + todo!("Worker are currently not supported in standalone binaries"); + }); + + let options = WorkerOptions { + apply_source_maps: false, + args: flags.argv.clone(), + debug_flag: false, + unstable: true, + ca_filepath: None, + 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, - None, - ); + }; + let mut worker = + MainWorker::from_options(main_module.clone(), permissions, &options); + worker.bootstrap(&options); worker.execute_module(&main_module).await?; worker.execute("window.dispatchEvent(new Event('load'))")?; worker.run_event_loop().await?; diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index c81152ec64..eeffb00970 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2646,11 +2646,6 @@ itest!(fmt_stdin_check_not_formatted { output_str: Some("Not formatted stdin\n"), }); -itest!(circular1 { - args: "run --reload circular1.js", - output: "circular1.js.out", -}); - itest!(config { args: "run --reload --config config.tsconfig.json config.ts", exit_code: 1, diff --git a/cli/version.rs b/cli/version.rs index 694ffc44ba..63253cec82 100644 --- a/cli/version.rs +++ b/cli/version.rs @@ -13,7 +13,3 @@ pub fn deno() -> String { pub fn is_canary() -> bool { option_env!("DENO_CANARY").is_some() } - -pub fn v8() -> &'static str { - deno_core::v8_version() -} diff --git a/cli/web_worker.rs b/cli/web_worker.rs index 44806d520c..9320c22d81 100644 --- a/cli/web_worker.rs +++ b/cli/web_worker.rs @@ -1,28 +1,31 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::colors; -use crate::fmt_errors::PrettyJsError; use crate::inspector::DenoInspector; +use crate::inspector::InspectorServer; use crate::js; use crate::metrics::Metrics; -use crate::module_loader::CliModuleLoader; use crate::ops; use crate::permissions::Permissions; -use crate::program_state::ProgramState; -use crate::source_maps::apply_source_map; use crate::tokio_util::create_basic_runtime; +use crate::version; 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::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; @@ -115,6 +118,7 @@ fn create_channels( /// Each `WebWorker` is either a child of `MainWorker` or other /// `WebWorker`. pub struct WebWorker { + id: u32, inspector: Option>, // Following fields are pub because they are accessed // when creating a new WebWorker instance. @@ -125,46 +129,48 @@ pub struct WebWorker { event_loop_idle: bool, terminate_rx: mpsc::Receiver<()>, handle: WebWorkerHandle, - pub has_deno_namespace: bool, + pub use_deno_namespace: bool, +} + +pub struct WebWorkerOptions { + pub args: Vec, + pub debug_flag: bool, + pub unstable: bool, + pub ca_filepath: Option, + pub seed: Option, + pub module_loader: Rc, + pub create_web_worker_cb: Arc, + pub js_error_create_fn: Option>, + pub use_deno_namespace: bool, + pub attach_inspector: bool, + pub maybe_inspector_server: Option>, + pub apply_source_maps: bool, } impl WebWorker { - pub fn new( + pub fn from_options( name: String, permissions: Permissions, main_module: ModuleSpecifier, - program_state: Arc, - has_deno_namespace: bool, worker_id: u32, + options: &WebWorkerOptions, ) -> Self { - let module_loader = CliModuleLoader::new_for_worker(program_state.clone()); - let global_state_ = program_state.clone(); - - let js_error_create_fn = Box::new(move |core_js_error| { - let source_mapped_error = - apply_source_map(&core_js_error, global_state_.clone()); - PrettyJsError::create(source_mapped_error) - }); - let mut js_runtime = JsRuntime::new(RuntimeOptions { - module_loader: Some(module_loader), + module_loader: Some(options.module_loader.clone()), startup_snapshot: Some(js::deno_isolate_init()), - js_error_create_fn: Some(js_error_create_fn), + js_error_create_fn: options.js_error_create_fn.clone(), get_error_class_fn: Some(&crate::errors::get_error_class_name), ..Default::default() }); - let inspector = - if let Some(inspector_server) = &program_state.maybe_inspector_server { - Some(DenoInspector::new( - &mut js_runtime, - Some(inspector_server.clone()), - )) - } else if program_state.flags.coverage || program_state.flags.repl { - Some(DenoInspector::new(&mut js_runtime, None)) - } else { - None - }; + 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(); @@ -172,15 +178,16 @@ impl WebWorker { create_channels(isolate_handle, terminate_tx); let mut worker = Self { + id: worker_id, inspector, internal_channels, js_runtime, - name: name.clone(), + name, waker: AtomicWaker::new(), event_loop_idle: false, terminate_rx, handle, - has_deno_namespace, + use_deno_namespace: options.use_deno_namespace, }; { @@ -192,15 +199,21 @@ impl WebWorker { let op_state = js_runtime.op_state(); let mut op_state = op_state.borrow_mut(); op_state.put::(Default::default()); - op_state.put::>(program_state.clone()); op_state.put::(permissions); + op_state.put::(ops::UnstableChecker { + unstable: options.unstable, + }); } ops::web_worker::init(js_runtime, sender.clone(), handle); - ops::runtime::init(js_runtime, main_module, true); - ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref()); + ops::runtime::init(js_runtime, main_module); + ops::fetch::init(js_runtime, options.ca_filepath.as_deref()); ops::timers::init(js_runtime); - ops::worker_host::init(js_runtime, Some(sender)); + 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( @@ -208,11 +221,10 @@ impl WebWorker { "op_domain_to_ascii", deno_web::op_domain_to_ascii, ); - ops::errors::init(js_runtime); ops::io::init(js_runtime); - ops::websocket::init(js_runtime); + ops::websocket::init(js_runtime, options.ca_filepath.as_deref()); - if has_deno_namespace { + if options.use_deno_namespace { ops::fs_events::init(js_runtime); ops::fs::init(js_runtime); ops::net::init(js_runtime); @@ -220,8 +232,7 @@ impl WebWorker { ops::permissions::init(js_runtime); ops::plugin::init(js_runtime); ops::process::init(js_runtime); - ops::crypto::init(js_runtime, program_state.flags.seed); - ops::runtime_compiler::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); @@ -239,19 +250,38 @@ impl WebWorker { 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": version::deno(), + "noColor": !colors::use_color(), + "pid": std::process::id(), + "ppid": ops::runtime::ppid(), + "target": env!("TARGET"), + "tsVersion": version::TYPESCRIPT, + "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-{}\")", - name, worker.has_deno_namespace, worker_id + "bootstrap.workerRuntime({}, \"{}\", {}, \"worker-{}\")", + runtime_options_str, self.name, options.use_deno_namespace, self.id ); - worker + self .execute(&script) .expect("Failed to execute worker bootstrap script"); - - worker } /// Same as execute2() but the filename defaults to "$CWD/__anonymous__". @@ -421,22 +451,39 @@ pub fn run_web_worker( #[cfg(test)] mod tests { use super::*; - use crate::program_state::ProgramState; 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 program_state = ProgramState::mock(vec!["deno".to_string()], None); - WebWorker::new( + 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, + seed: None, + module_loader, + create_web_worker_cb, + js_error_create_fn: None, + use_deno_namespace: false, + attach_inspector: false, + maybe_inspector_server: None, + }; + + let mut worker = WebWorker::from_options( "TEST".to_string(), Permissions::allow_all(), main_module, - program_state, - false, 1, - ) + &options, + ); + worker.bootstrap(&options); + worker } #[tokio::test] diff --git a/cli/worker.rs b/cli/worker.rs index dda26291a4..b119c3b0dd 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,18 +1,19 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::fmt_errors::PrettyJsError; +use crate::colors; use crate::inspector::DenoInspector; +use crate::inspector::InspectorServer; use crate::inspector::InspectorSession; use crate::js; use crate::metrics::Metrics; -use crate::module_loader::CliModuleLoader; use crate::ops; use crate::permissions::Permissions; -use crate::program_state::ProgramState; -use crate::source_maps::apply_source_map; +use crate::version; 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::JsErrorCreateFn; use deno_core::JsRuntime; @@ -35,68 +36,51 @@ use std::task::Poll; /// are descendants of this worker. pub struct MainWorker { inspector: Option>, - js_runtime: JsRuntime, + pub js_runtime: JsRuntime, should_break_on_first_statement: bool, } +pub struct WorkerOptions { + pub apply_source_maps: bool, + pub args: Vec, + pub debug_flag: bool, + pub unstable: bool, + pub ca_filepath: Option, + pub seed: Option, + pub module_loader: Rc, + // Callback that will be invoked when creating new instance + // of WebWorker + pub create_web_worker_cb: Arc, + pub js_error_create_fn: Option>, + pub attach_inspector: bool, + pub maybe_inspector_server: Option>, + pub should_break_on_first_statement: bool, +} + impl MainWorker { - pub fn new( - program_state: &Arc, - main_module: ModuleSpecifier, - permissions: Permissions, - ) -> Self { - let module_loader = CliModuleLoader::new(program_state.clone()); - - let global_state_ = program_state.clone(); - - let js_error_create_fn = Box::new(move |core_js_error| { - let source_mapped_error = - apply_source_map(&core_js_error, global_state_.clone()); - PrettyJsError::create(source_mapped_error) - }); - - Self::from_options( - program_state, - main_module, - permissions, - module_loader, - Some(js_error_create_fn), - ) - } - pub fn from_options( - program_state: &Arc, main_module: ModuleSpecifier, permissions: Permissions, - module_loader: Rc, - js_error_create_fn: Option>, + options: &WorkerOptions, ) -> Self { - // TODO(bartlomieju): this is hacky way to not apply source - // maps in JS - let apply_source_maps = js_error_create_fn.is_some(); - let mut js_runtime = JsRuntime::new(RuntimeOptions { - module_loader: Some(module_loader), + module_loader: Some(options.module_loader.clone()), startup_snapshot: Some(js::deno_isolate_init()), - js_error_create_fn, + js_error_create_fn: options.js_error_create_fn.clone(), get_error_class_fn: Some(&crate::errors::get_error_class_name), ..Default::default() }); - let inspector = - if let Some(inspector_server) = &program_state.maybe_inspector_server { - Some(DenoInspector::new( - &mut js_runtime, - Some(inspector_server.clone()), - )) - } else if program_state.flags.coverage || program_state.flags.repl { - Some(DenoInspector::new(&mut js_runtime, None)) - } else { - None - }; - + 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() && program_state.flags.inspect_brk.is_some(); + inspector.is_some() && options.should_break_on_first_statement; let mut worker = Self { inspector, @@ -111,15 +95,21 @@ impl MainWorker { let op_state = js_runtime.op_state(); let mut op_state = op_state.borrow_mut(); op_state.put::(Default::default()); - op_state.put::>(program_state.clone()); op_state.put::(permissions); + op_state.put::(ops::UnstableChecker { + unstable: options.unstable, + }); } - ops::runtime::init(js_runtime, main_module, apply_source_maps); - ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref()); + ops::runtime::init(js_runtime, main_module); + ops::fetch::init(js_runtime, options.ca_filepath.as_deref()); ops::timers::init(js_runtime); - ops::worker_host::init(js_runtime, None); - ops::crypto::init(js_runtime, program_state.flags.seed); + 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( @@ -127,7 +117,6 @@ impl MainWorker { "op_domain_to_ascii", deno_web::op_domain_to_ascii, ); - ops::errors::init(js_runtime); ops::fs_events::init(js_runtime); ops::fs::init(js_runtime); ops::io::init(js_runtime); @@ -136,11 +125,10 @@ impl MainWorker { ops::permissions::init(js_runtime); ops::plugin::init(js_runtime); ops::process::init(js_runtime); - ops::runtime_compiler::init(js_runtime); ops::signal::init(js_runtime); ops::tls::init(js_runtime); ops::tty::init(js_runtime); - ops::websocket::init(js_runtime); + ops::websocket::init(js_runtime, options.ca_filepath.as_deref()); } { let op_state = js_runtime.op_state(); @@ -157,10 +145,32 @@ impl MainWorker { t.add("stderr", Box::new(stream)); } } + worker - .execute("bootstrap.mainRuntime()") + } + + pub fn bootstrap(&mut self, options: &WorkerOptions) { + let runtime_options = json!({ + "args": options.args, + "applySourceMaps": options.apply_source_maps, + "debugFlag": options.debug_flag, + "denoVersion": version::deno(), + "noColor": !colors::use_color(), + "pid": std::process::id(), + "ppid": ops::runtime::ppid(), + "target": env!("TARGET"), + "tsVersion": version::TYPESCRIPT, + "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"); - worker } /// Same as execute2() but the filename defaults to "$CWD/__anonymous__". @@ -231,23 +241,28 @@ impl Drop for MainWorker { #[cfg(test)] mod tests { use super::*; - use crate::flags::DenoSubcommand; - use crate::flags::Flags; - use crate::program_state::ProgramState; fn create_test_worker() -> MainWorker { let main_module = ModuleSpecifier::resolve_url_or_path("./hello.js").unwrap(); - let flags = Flags { - subcommand: DenoSubcommand::Run { - script: main_module.to_string(), - }, - ..Default::default() + let permissions = Permissions::default(); + + let options = WorkerOptions { + apply_source_maps: false, + 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), }; - let permissions = Permissions::from_flags(&flags); - let program_state = - ProgramState::mock(vec!["deno".to_string()], Some(flags)); - MainWorker::new(&program_state, main_module, permissions) + + MainWorker::from_options(main_module, permissions, &options) } #[tokio::test] @@ -273,26 +288,7 @@ mod tests { let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() - .join("tests/circular1.ts"); - 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_006_url_imports() { - let _http_server_guard = test_util::http_server(); - let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("cli/tests/006_url_imports.ts"); + .join("tests/circular1.js"); let module_specifier = ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); let mut worker = create_test_worker(); @@ -323,7 +319,7 @@ mod tests { let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() - .join("cli/tests/002_hello.ts"); + .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; diff --git a/core/lib.rs b/core/lib.rs index 20ee5a3d5d..5846ad99d4 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -51,6 +51,7 @@ pub use crate::modules::ModuleLoadId; pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleSource; pub use crate::modules::ModuleSourceFuture; +pub use crate::modules::NoopModuleLoader; pub use crate::modules::RecursiveModuleLoad; pub use crate::normalize_path::normalize_path; pub use crate::ops::json_op_async; diff --git a/core/modules.rs b/core/modules.rs index ffae3a4767..6f330f5594 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -105,7 +105,7 @@ pub trait ModuleLoader { /// Placeholder structure used when creating /// a runtime that doesn't support module loading. -pub(crate) struct NoopModuleLoader; +pub struct NoopModuleLoader; impl ModuleLoader for NoopModuleLoader { fn resolve( diff --git a/core/runtime.rs b/core/runtime.rs index 0f09926f87..24bdf4dc29 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -107,7 +107,7 @@ pub(crate) struct JsRuntimeState { HashMap, v8::Global>, pending_dyn_mod_evaluate: HashMap, pending_mod_evaluate: Option, - pub(crate) js_error_create_fn: Box, + pub(crate) js_error_create_fn: Rc, pub(crate) shared: SharedQueue, pub(crate) pending_ops: FuturesUnordered, pub(crate) pending_unref_ops: FuturesUnordered, @@ -168,7 +168,7 @@ pub struct RuntimeOptions { /// Allows a callback to be set whenever a V8 exception is made. This allows /// the caller to wrap the JsError into an error. By default this callback /// is set to `JsError::create()`. - pub js_error_create_fn: Option>, + pub js_error_create_fn: Option>, /// Allows to map error type to a string "class" used to represent /// error in JavaScript. @@ -257,7 +257,7 @@ impl JsRuntime { let js_error_create_fn = options .js_error_create_fn - .unwrap_or_else(|| Box::new(JsError::create)); + .unwrap_or_else(|| Rc::new(JsError::create)); let mut op_state = OpState::default(); if let Some(get_error_class_fn) = options.get_error_class_fn {