diff --git a/cli/main.rs b/cli/main.rs index d8d834e8f74812..ff757ed3664ad4 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -196,8 +196,10 @@ fn create_worker_and_state( s.status(status, msg).expect("shell problem"); } }); + // TODO(kevinkassimo): maybe make include_deno_namespace also configurable? let state = - ThreadSafeState::new(flags, argv, ops::op_selector_std, progress).unwrap(); + ThreadSafeState::new(flags, argv, ops::op_selector_std, progress, true) + .unwrap(); let worker = Worker::new( "main".to_string(), startup_data::deno_isolate_init(), diff --git a/cli/msg.fbs b/cli/msg.fbs index 014bfb7e939bff..8f6497afcbcb2e 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -204,6 +204,7 @@ table FormatErrorRes { // Create worker as host table CreateWorker { specifier: string; + include_deno_namespace: bool; } table CreateWorkerRes { diff --git a/cli/ops.rs b/cli/ops.rs index 0e90e19c42c473..06c4eae99853f2 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -2069,6 +2069,10 @@ fn op_create_worker( let cmd_id = base.cmd_id(); let inner = base.inner_as_create_worker().unwrap(); let specifier = inner.specifier().unwrap(); + // Only include deno namespace if requested AND current worker + // has included namespace (to avoid escalation). + let include_deno_namespace = + inner.include_deno_namespace() && state.include_deno_namespace; let parent_state = state.clone(); @@ -2077,13 +2081,15 @@ fn op_create_worker( parent_state.argv.clone(), op_selector_std, parent_state.progress.clone(), + include_deno_namespace, )?; let rid = child_state.resource.rid; let name = format!("USER-WORKER-{}", specifier); + let deno_main_call = format!("denoMain({})", include_deno_namespace); let mut worker = Worker::new(name, startup_data::deno_isolate_init(), child_state); - worker.execute("denoMain()").unwrap(); + worker.execute(&deno_main_call).unwrap(); worker.execute("workerMain()").unwrap(); let module_specifier = ModuleSpecifier::resolve_url_or_path(specifier)?; diff --git a/cli/state.rs b/cli/state.rs index 139584394a7c31..f4e3d9c84164a8 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -84,6 +84,8 @@ pub struct State { pub js_compiler: JsCompiler, pub json_compiler: JsonCompiler, pub ts_compiler: TsCompiler, + + pub include_deno_namespace: bool, } impl Clone for ThreadSafeState { @@ -151,6 +153,7 @@ impl ThreadSafeState { argv_rest: Vec, dispatch_selector: ops::OpSelector, progress: Progress, + include_deno_namespace: bool, ) -> Result { let custom_root = env::var("DENO_DIR").map(String::into).ok(); @@ -223,6 +226,7 @@ impl ThreadSafeState { ts_compiler, js_compiler: JsCompiler {}, json_compiler: JsonCompiler {}, + include_deno_namespace, }; Ok(ThreadSafeState(Arc::new(state))) @@ -302,6 +306,7 @@ impl ThreadSafeState { argv, ops::op_selector_std, Progress::new(), + true, ) .unwrap() } diff --git a/cli/worker.rs b/cli/worker.rs index befc69f854ee60..f1806283692822 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -126,6 +126,7 @@ mod tests { argv, op_selector_std, Progress::new(), + true, ) .unwrap(); let state_ = state.clone(); @@ -155,6 +156,7 @@ mod tests { argv, op_selector_std, Progress::new(), + true, ) .unwrap(); let state_ = state.clone(); @@ -182,7 +184,7 @@ mod tests { let mut flags = flags::DenoFlags::default(); flags.reload = true; let state = - ThreadSafeState::new(flags, argv, op_selector_std, Progress::new()) + ThreadSafeState::new(flags, argv, op_selector_std, Progress::new(), true) .unwrap(); let state_ = state.clone(); tokio_util::run(lazy(move || { diff --git a/core/shared_queue.js b/core/shared_queue.js index 75f370ce44fe57..d7ab382d6e9317 100644 --- a/core/shared_queue.js +++ b/core/shared_queue.js @@ -29,8 +29,12 @@ SharedQueue Binary Layout const INDEX_RECORDS = 3 + MAX_RECORDS; const HEAD_INIT = 4 * INDEX_RECORDS; + // Available on start due to bindings. const Deno = window[GLOBAL_NAMESPACE]; const core = Deno[CORE_NAMESPACE]; + // Warning: DO NOT use window.Deno after this point. + // It is possible that the Deno namespace has been deleted. + // Use the above local Deno and core variable instead. let sharedBytes; let shared32; @@ -165,7 +169,7 @@ SharedQueue Binary Layout const success = push(control); // If successful, don't use first argument of core.send. const arg0 = success ? null : control; - return window.Deno.core.send(arg0, zeroCopy); + return Deno.core.send(arg0, zeroCopy); } const denoCore = { diff --git a/js/compiler.ts b/js/compiler.ts index 4203f753ba9bb6..6bda5a1eca970e 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -25,7 +25,7 @@ const console = new Console(core.print); window.console = console; window.workerMain = workerMain; export default function denoMain(): void { - os.start("TS"); + os.start(true, "TS"); } const ASSETS = "$asset$"; diff --git a/js/core.ts b/js/core.ts index 03473ff3dc0c5d..436ba7ccbe0c25 100644 --- a/js/core.ts +++ b/js/core.ts @@ -1,4 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { window } from "./window"; +// This allows us to access core in API even if we +// dispose window.Deno export const core = window.Deno.core as DenoCore; diff --git a/js/globals.ts b/js/globals.ts index 27cef39098dd3e..92e4662aa7fef4 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -32,7 +32,6 @@ import * as request from "./request"; // These imports are not exposed and therefore are fine to just import the // symbols required. import { core } from "./core"; -import { immutableDefine } from "./util"; // During the build process, augmentations to the variable `window` in this // file are tracked and created as part of default library that is built into @@ -71,7 +70,7 @@ window.window = window; // This is the Deno namespace, it is handled differently from other window // properties when building the runtime type library, as the whole module // is flattened into a single namespace. -immutableDefine(window, "Deno", deno); +window.Deno = deno; Object.freeze(window.Deno); // Globally available functions and object instances. diff --git a/js/main.ts b/js/main.ts index 5687a69268e517..769f522a63c312 100644 --- a/js/main.ts +++ b/js/main.ts @@ -18,8 +18,11 @@ import { setLocation } from "./location"; // builtin modules import * as deno from "./deno"; -export default function denoMain(name?: string): void { - const startResMsg = os.start(name); +export default function denoMain( + preserveDenoNamespace: boolean = true, + name?: string +): void { + const startResMsg = os.start(preserveDenoNamespace, name); setVersions(startResMsg.denoVersion()!, startResMsg.v8Version()!); diff --git a/js/os.ts b/js/os.ts index 558f47efd7cade..e7d588a52fd555 100644 --- a/js/os.ts +++ b/js/os.ts @@ -112,7 +112,10 @@ function sendStart(): msg.StartRes { // This function bootstraps an environment within Deno, it is shared both by // the runtime and the compiler environments. // @internal -export function start(source?: string): msg.StartRes { +export function start( + preserveDenoNamespace = true, + source?: string +): msg.StartRes { core.setAsyncHandler(handleAsyncMsgFromRust); // First we send an empty `Start` message to let the privileged side know we @@ -124,9 +127,18 @@ export function start(source?: string): msg.StartRes { setGlobals(startResMsg.pid(), startResMsg.noColor(), startResMsg.execPath()!); - // Deno.core could ONLY be safely frozen here (not in globals.ts) - // since shared_queue.js will modify core properties. - Object.freeze(window.Deno.core); + if (preserveDenoNamespace) { + util.immutableDefine(window, "Deno", window.Deno); + // Deno.core could ONLY be safely frozen here (not in globals.ts) + // since shared_queue.js will modify core properties. + Object.freeze(window.Deno.core); + // core.sharedQueue is an object so we should also freeze it. + Object.freeze(window.Deno.core.sharedQueue); + } else { + // Remove window.Deno + delete window.Deno; + assert(window.Deno === undefined); + } return startResMsg; } diff --git a/js/workers.ts b/js/workers.ts index 1531b960b0aa8c..f008ccecf5ef01 100644 --- a/js/workers.ts +++ b/js/workers.ts @@ -20,10 +20,17 @@ export function decodeMessage(dataIntArray: Uint8Array): any { return JSON.parse(dataJson); } -function createWorker(specifier: string): number { +function createWorker( + specifier: string, + includeDenoNamespace: boolean +): number { const builder = flatbuffers.createBuilder(); const specifier_ = builder.createString(specifier); - const inner = msg.CreateWorker.createCreateWorker(builder, specifier_); + const inner = msg.CreateWorker.createCreateWorker( + builder, + specifier_, + includeDenoNamespace + ); const baseRes = sendSync(builder, msg.Any.CreateWorker, inner); assert(baseRes != null); assert( @@ -149,6 +156,18 @@ export interface Worker { closed: Promise; } +// TODO(kevinkassimo): Maybe implement reasonable web worker options? +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WorkerOptions {} + +/** Extended Deno Worker initialization options. + * `noDenoNamespace` hides global `window.Deno` namespace for + * spawned worker and nested workers spawned by it (default: false). + */ +export interface DenoWorkerOptions extends WorkerOptions { + noDenoNamespace?: boolean; +} + export class WorkerImpl implements Worker { private readonly rid: number; private isClosing: boolean = false; @@ -157,8 +176,12 @@ export class WorkerImpl implements Worker { public onmessage?: (data: any) => void; public onmessageerror?: () => void; - constructor(specifier: string) { - this.rid = createWorker(specifier); + constructor(specifier: string, options?: DenoWorkerOptions) { + let includeDenoNamespace = true; + if (options && options.noDenoNamespace) { + includeDenoNamespace = false; + } + this.rid = createWorker(specifier, includeDenoNamespace); this.run(); this.isClosedPromise = hostGetWorkerClosed(this.rid); this.isClosedPromise.then( diff --git a/tests/039_worker_deno_ns.test b/tests/039_worker_deno_ns.test new file mode 100644 index 00000000000000..da282cc7b1a95e --- /dev/null +++ b/tests/039_worker_deno_ns.test @@ -0,0 +1,2 @@ +args: run --reload tests/039_worker_deno_ns.ts +output: tests/039_worker_deno_ns.ts.out diff --git a/tests/039_worker_deno_ns.ts b/tests/039_worker_deno_ns.ts new file mode 100644 index 00000000000000..1c6830c2268826 --- /dev/null +++ b/tests/039_worker_deno_ns.ts @@ -0,0 +1,25 @@ +const w1 = new Worker("./tests/039_worker_deno_ns/has_ns.ts"); +const w2 = new Worker("./tests/039_worker_deno_ns/no_ns.ts", { + noDenoNamespace: true +}); +let w1MsgCount = 0; +let w2MsgCount = 0; +w1.onmessage = (msg): void => { + console.log(msg.data); + w1MsgCount++; + if (w1MsgCount === 1) { + w1.postMessage("CONTINUE"); + } else { + w2.postMessage("START"); + } +}; +w2.onmessage = (msg): void => { + console.log(msg.data); + w2MsgCount++; + if (w2MsgCount === 1) { + w2.postMessage("CONTINUE"); + } else { + Deno.exit(0); + } +}; +w1.postMessage("START"); diff --git a/tests/039_worker_deno_ns.ts.out b/tests/039_worker_deno_ns.ts.out new file mode 100644 index 00000000000000..9b2f90099cceab --- /dev/null +++ b/tests/039_worker_deno_ns.ts.out @@ -0,0 +1,4 @@ +has_ns.ts: is window.Deno available: true +[SPAWNED BY has_ns.ts] maybe_ns.ts: is window.Deno available: true +no_ns.ts: is window.Deno available: false +[SPAWNED BY no_ns.ts] maybe_ns.ts: is window.Deno available: false diff --git a/tests/039_worker_deno_ns/has_ns.ts b/tests/039_worker_deno_ns/has_ns.ts new file mode 100644 index 00000000000000..15e729f63a73cd --- /dev/null +++ b/tests/039_worker_deno_ns/has_ns.ts @@ -0,0 +1,10 @@ +onmessage = (msg): void => { + if (msg.data === "START") { + postMessage("has_ns.ts: is window.Deno available: " + !!window.Deno); + } else { + const worker = new Worker("./tests/039_worker_deno_ns/maybe_ns.ts"); + worker.onmessage = (msg): void => { + postMessage("[SPAWNED BY has_ns.ts] " + msg.data); + }; + } +}; diff --git a/tests/039_worker_deno_ns/maybe_ns.ts b/tests/039_worker_deno_ns/maybe_ns.ts new file mode 100644 index 00000000000000..0bcbd1f973c816 --- /dev/null +++ b/tests/039_worker_deno_ns/maybe_ns.ts @@ -0,0 +1 @@ +postMessage("maybe_ns.ts: is window.Deno available: " + !!window.Deno); diff --git a/tests/039_worker_deno_ns/no_ns.ts b/tests/039_worker_deno_ns/no_ns.ts new file mode 100644 index 00000000000000..4cee98bea31649 --- /dev/null +++ b/tests/039_worker_deno_ns/no_ns.ts @@ -0,0 +1,10 @@ +onmessage = (msg): void => { + if (msg.data === "START") { + postMessage("no_ns.ts: is window.Deno available: " + !!window.Deno); + } else { + const worker = new Worker("./tests/039_worker_deno_ns/maybe_ns.ts"); + worker.onmessage = (msg): void => { + postMessage("[SPAWNED BY no_ns.ts] " + msg.data); + }; + } +};