From 161adfc51b750a7c8c62a898ea9948c2ad5b6cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 29 Jan 2020 18:54:23 +0100 Subject: [PATCH] workers: proper TS libs, more spec-compliant APIs (#3812) * split lib.deno_main.d.ts into: - lib.deno.shared_globals.d.ts - lib.deno.window.d.ts - lib.deno.worker.d.ts * remove no longer used libs: - lib.deno_main.d.ts - lib.deno_worker.d.ts * change module loading to use proper TS library for compilation * align to Worker API spec: - Worker.terminate() - self.close() - self.name --- cli/build.rs | 17 +- cli/compilers/mod.rs | 1 + cli/compilers/ts.rs | 14 +- cli/global_state.rs | 10 +- cli/js.rs | 6 +- cli/js/compiler.ts | 4 +- cli/js/compiler_bootstrap.ts | 24 +- cli/js/compiler_host.ts | 4 +- cli/js/globals.ts | 2 +- cli/js/{lib.deno.d.ts => lib.deno.ns.d.ts} | 0 ...main.d.ts => lib.deno.shared_globals.d.ts} | 260 ++---------------- cli/js/lib.deno.window.d.ts | 214 ++++++++++++++ cli/js/lib.deno.worker.d.ts | 45 +++ cli/js/lib.deno_worker.d.ts | 5 - cli/js/runtime_worker.ts | 5 +- cli/js/workers.ts | 19 +- cli/lib.rs | 10 +- cli/ops/worker_host.rs | 19 +- cli/state.rs | 46 +++- cli/tests/026_workers.ts | 10 +- cli/tests/subdir/bench_worker.ts | 2 +- cli/tests/subdir/test_worker.js | 7 +- cli/tests/subdir/test_worker.ts | 6 +- cli/tests/types.out | 6 +- 24 files changed, 449 insertions(+), 287 deletions(-) rename cli/js/{lib.deno.d.ts => lib.deno.ns.d.ts} (100%) rename cli/js/{lib.deno_main.d.ts => lib.deno.shared_globals.d.ts} (86%) create mode 100644 cli/js/lib.deno.window.d.ts create mode 100644 cli/js/lib.deno.worker.d.ts delete mode 100644 cli/js/lib.deno_worker.d.ts diff --git a/cli/build.rs b/cli/build.rs index b57b86b915..426403f1a9 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -71,14 +71,21 @@ fn main() { let snapshot_path = o.join("COMPILER_SNAPSHOT.bin"); let mut custom_libs: HashMap = HashMap::new(); custom_libs.insert( - "lib.deno_main.d.ts".to_string(), - c.join("js/lib.deno_main.d.ts"), + "lib.deno.window.d.ts".to_string(), + c.join("js/lib.deno.window.d.ts"), ); custom_libs.insert( - "lib.deno_worker.d.ts".to_string(), - c.join("js/lib.deno_worker.d.ts"), + "lib.deno.worker.d.ts".to_string(), + c.join("js/lib.deno.worker.d.ts"), + ); + custom_libs.insert( + "lib.deno.shared_globals.d.ts".to_string(), + c.join("js/lib.deno.shared_globals.d.ts"), + ); + custom_libs.insert( + "lib.deno.ns.d.ts".to_string(), + c.join("js/lib.deno.ns.d.ts"), ); - custom_libs.insert("lib.deno.d.ts".to_string(), c.join("js/lib.deno.d.ts")); let main_module_name = deno_typescript::compile_bundle(&bundle_path, root_names) diff --git a/cli/compilers/mod.rs b/cli/compilers/mod.rs index f4aac3681d..a2abbe2aab 100644 --- a/cli/compilers/mod.rs +++ b/cli/compilers/mod.rs @@ -13,6 +13,7 @@ pub use js::JsCompiler; pub use json::JsonCompiler; pub use ts::runtime_compile_async; pub use ts::runtime_transpile_async; +pub use ts::TargetLib; pub use ts::TsCompiler; pub use wasm::WasmCompiler; diff --git a/cli/compilers/ts.rs b/cli/compilers/ts.rs index 946617fa58..5aa2995185 100644 --- a/cli/compilers/ts.rs +++ b/cli/compilers/ts.rs @@ -37,6 +37,12 @@ lazy_static! { Regex::new(r#""checkJs"\s*?:\s*?true"#).unwrap(); } +#[derive(Clone, Copy)] +pub enum TargetLib { + Main, + Worker, +} + /// Struct which represents the state of the compiler /// configuration where the first is canonical name for the configuration file, /// second is a vector of the bytes of the contents of the configuration file, @@ -318,6 +324,7 @@ impl TsCompiler { &self, global_state: ThreadSafeGlobalState, source_file: &SourceFile, + target: TargetLib, ) -> Pin> { if self.has_compiled(&source_file.url) { return match self.get_compiled_module(&source_file.url) { @@ -360,7 +367,10 @@ impl TsCompiler { &source_file.url ); - let target = "main"; + let target = match target { + TargetLib::Main => "main", + TargetLib::Worker => "worker", + }; let root_names = vec![module_url.to_string()]; let req_msg = req( @@ -710,7 +720,7 @@ mod tests { let fut = async move { let result = mock_state .ts_compiler - .compile_async(mock_state.clone(), &out) + .compile_async(mock_state.clone(), &out, TargetLib::Main) .await; assert!(result.is_ok()); diff --git a/cli/global_state.rs b/cli/global_state.rs index 1709a3429b..3298799d12 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -2,6 +2,7 @@ use crate::compilers::CompiledModule; use crate::compilers::JsCompiler; use crate::compilers::JsonCompiler; +use crate::compilers::TargetLib; use crate::compilers::TsCompiler; use crate::compilers::WasmCompiler; use crate::deno_dir; @@ -122,6 +123,7 @@ impl ThreadSafeGlobalState { &self, module_specifier: &ModuleSpecifier, maybe_referrer: Option, + target_lib: TargetLib, ) -> impl Future> { let state1 = self.clone(); let state2 = self.clone(); @@ -141,11 +143,15 @@ impl ThreadSafeGlobalState { msg::MediaType::TypeScript | msg::MediaType::TSX | msg::MediaType::JSX => { - state1.ts_compiler.compile_async(state1.clone(), &out) + state1 + .ts_compiler + .compile_async(state1.clone(), &out, target_lib) } msg::MediaType::JavaScript => { if state1.ts_compiler.compile_js { - state1.ts_compiler.compile_async(state1.clone(), &out) + state1 + .ts_compiler + .compile_async(state1.clone(), &out, target_lib) } else { state1.js_compiler.compile_async(&out) } diff --git a/cli/js.rs b/cli/js.rs index 2e9adf1b40..746e49fe42 100644 --- a/cli/js.rs +++ b/cli/js.rs @@ -16,8 +16,10 @@ pub static COMPILER_SNAPSHOT_MAP: &[u8] = pub static COMPILER_SNAPSHOT_DTS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.d.ts")); -pub static DENO_NS_LIB: &str = include_str!("js/lib.deno.d.ts"); -pub static DENO_MAIN_LIB: &str = include_str!("js/lib.deno_main.d.ts"); +pub static DENO_NS_LIB: &str = include_str!("js/lib.deno.ns.d.ts"); +pub static SHARED_GLOBALS_LIB: &str = + include_str!("js/lib.deno.shared_globals.d.ts"); +pub static WINDOW_LIB: &str = include_str!("js/lib.deno.window.d.ts"); #[test] fn cli_snapshot() { diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts index 394c6cf522..f003d7d0b9 100644 --- a/cli/js/compiler.ts +++ b/cli/js/compiler.ts @@ -304,7 +304,7 @@ async function tsCompilerOnMessage({ } // The compiler isolate exits after a single message. - globalThis.workerClose(); + globalThis.close(); } async function wasmCompilerOnMessage({ @@ -332,7 +332,7 @@ async function wasmCompilerOnMessage({ util.log("<<< WASM compile end"); // The compiler isolate exits after a single message. - globalThis.workerClose(); + globalThis.close(); } function bootstrapTsCompilerRuntime(): void { diff --git a/cli/js/compiler_bootstrap.ts b/cli/js/compiler_bootstrap.ts index afb3d2be55..585aec016b 100644 --- a/cli/js/compiler_bootstrap.ts +++ b/cli/js/compiler_bootstrap.ts @@ -15,17 +15,27 @@ const options = host.getCompilationSettings(); // This is a hacky way of adding our libs to the libs available in TypeScript() // as these are internal APIs of TypeScript which maintain valid libs /* eslint-disable @typescript-eslint/no-explicit-any */ -(ts as any).libs.push("deno_main", "deno_worker", "deno"); -(ts as any).libMap.set("deno_main", "lib.deno_main.d.ts"); -(ts as any).libMap.set("deno_worker", "lib.deno_worker.d.ts"); -(ts as any).libMap.set("deno", "lib.deno.d.ts"); +(ts as any).libs.push( + "deno_ns", + "deno_window", + "deno_worker", + "deno_shared_globals" +); +(ts as any).libMap.set("deno_ns", "lib.deno.ns.d.ts"); +(ts as any).libMap.set("deno_window", "lib.deno.window.d.ts"); +(ts as any).libMap.set("deno_worker", "lib.deno.worker.d.ts"); +(ts as any).libMap.set("deno_shared_globals", "lib.deno.shared_globals.d.ts"); /* eslint-enable @typescript-eslint/no-explicit-any */ // this pre-populates the cache at snapshot time of our library files, so they // are available in the future when needed. -host.getSourceFile(`${ASSETS}/lib.deno_main.d.ts`, ts.ScriptTarget.ESNext); -host.getSourceFile(`${ASSETS}/lib.deno_worker.d.ts`, ts.ScriptTarget.ESNext); -host.getSourceFile(`${ASSETS}/lib.deno.d.ts`, ts.ScriptTarget.ESNext); +host.getSourceFile(`${ASSETS}/lib.deno.ns.d.ts`, ts.ScriptTarget.ESNext); +host.getSourceFile(`${ASSETS}/lib.deno.window.d.ts`, ts.ScriptTarget.ESNext); +host.getSourceFile(`${ASSETS}/lib.deno.worker.d.ts`, ts.ScriptTarget.ESNext); +host.getSourceFile( + `${ASSETS}/lib.deno.shared_globals.d.ts`, + ts.ScriptTarget.ESNext +); /** * This function spins up TS compiler and loads all available libraries diff --git a/cli/js/compiler_host.ts b/cli/js/compiler_host.ts index 619ce702a7..291f6fbc52 100644 --- a/cli/js/compiler_host.ts +++ b/cli/js/compiler_host.ts @@ -233,9 +233,9 @@ export class Host implements ts.CompilerHost { switch (this._target) { case CompilerHostTarget.Main: case CompilerHostTarget.Runtime: - return `${ASSETS}/lib.deno_main.d.ts`; + return `${ASSETS}/lib.deno.window.d.ts`; case CompilerHostTarget.Worker: - return `${ASSETS}/lib.deno_worker.d.ts`; + return `${ASSETS}/lib.deno.worker.d.ts`; } } diff --git a/cli/js/globals.ts b/cli/js/globals.ts index e6c1e855c2..7cce739d51 100644 --- a/cli/js/globals.ts +++ b/cli/js/globals.ts @@ -132,7 +132,7 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-explicit-any var onmessage: ((e: { data: any }) => Promise | void) | undefined; // Called in compiler - var workerClose: () => void; + var close: () => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any var postMessage: (msg: any) => void; // Assigned to `self` global - compiler diff --git a/cli/js/lib.deno.d.ts b/cli/js/lib.deno.ns.d.ts similarity index 100% rename from cli/js/lib.deno.d.ts rename to cli/js/lib.deno.ns.d.ts diff --git a/cli/js/lib.deno_main.d.ts b/cli/js/lib.deno.shared_globals.d.ts similarity index 86% rename from cli/js/lib.deno_main.d.ts rename to cli/js/lib.deno.shared_globals.d.ts index 52b6fb7f54..c8dfc833da 100644 --- a/cli/js/lib.deno_main.d.ts +++ b/cli/js/lib.deno.shared_globals.d.ts @@ -3,23 +3,23 @@ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ /// -/// +/// /// -declare interface Window { - window: Window & typeof globalThis; +// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope + +declare interface WindowOrWorkerGlobalScope { + // methods atob: typeof __textEncoding.atob; btoa: typeof __textEncoding.btoa; - fetch: typeof __fetch.fetch; - clearTimeout: typeof __timers.clearTimeout; clearInterval: typeof __timers.clearInterval; - console: __console.Console; - setTimeout: typeof __timers.setTimeout; + clearTimeout: typeof __timers.clearTimeout; + fetch: typeof __fetch.fetch; + queueMicrotask: (task: () => void) => void; setInterval: typeof __timers.setInterval; - location: __domTypes.Location; - onload: Function | undefined; - onunload: Function | undefined; - crypto: Crypto; + setTimeout: typeof __timers.setTimeout; + // properties + console: __console.Console; Blob: typeof __blob.DenoBlob; File: __domTypes.DomFileConstructor; CustomEvent: typeof __customEvent.CustomEvent; @@ -34,11 +34,9 @@ declare interface Window { Request: __domTypes.RequestConstructor; Response: typeof __fetch.Response; performance: __performanceUtil.Performance; - onmessage: (e: { data: any }) => void; - onerror: undefined | typeof onerror; - workerClose: typeof __workerMain.workerClose; - postMessage: typeof __workerMain.postMessage; Worker: typeof __workers.WorkerImpl; + location: __domTypes.Location; + addEventListener: ( type: string, callback: (event: __domTypes.Event) => void | null, @@ -50,23 +48,17 @@ declare interface Window { callback: (event: __domTypes.Event) => void | null, options?: boolean | __domTypes.EventListenerOptions | undefined ) => void; - queueMicrotask: (task: () => void) => void; - Deno: typeof Deno; } -declare const window: Window & typeof globalThis; declare const atob: typeof __textEncoding.atob; declare const btoa: typeof __textEncoding.btoa; -declare const fetch: typeof __fetch.fetch; -declare const clearTimeout: typeof __timers.clearTimeout; declare const clearInterval: typeof __timers.clearInterval; -declare const console: __console.Console; -declare const setTimeout: typeof __timers.setTimeout; +declare const clearTimeout: typeof __timers.clearTimeout; +declare const fetch: typeof __fetch.fetch; declare const setInterval: typeof __timers.setInterval; -declare const location: __domTypes.Location; -declare const onload: Function | undefined; -declare const onunload: Function | undefined; -declare const crypto: Crypto; +declare const setTimeout: typeof __timers.setTimeout; + +declare const console: __console.Console; declare const Blob: typeof __blob.DenoBlob; declare const File: __domTypes.DomFileConstructor; declare const CustomEventInit: typeof __customEvent.CustomEventInit; @@ -78,25 +70,15 @@ declare const EventTarget: typeof __eventTarget.EventTarget; declare const URL: typeof __url.URL; declare const URLSearchParams: typeof __urlSearchParams.URLSearchParams; declare const Headers: __domTypes.HeadersConstructor; +declare const location: __domTypes.Location; declare const FormData: __domTypes.FormDataConstructor; declare const TextEncoder: typeof __textEncoding.TextEncoder; declare const TextDecoder: typeof __textEncoding.TextDecoder; declare const Request: __domTypes.RequestConstructor; declare const Response: typeof __fetch.Response; declare const performance: __performanceUtil.Performance; -declare let onmessage: ((e: { data: any }) => Promise | void) | undefined; -declare let onerror: - | (( - msg: string, - source: string, - lineno: number, - colno: number, - e: Event - ) => boolean | void) - | undefined; -declare const workerClose: typeof __workerMain.workerClose; -declare const postMessage: typeof __workerMain.postMessage; declare const Worker: typeof __workers.WorkerImpl; + declare const addEventListener: ( type: string, callback: (event: __domTypes.Event) => void | null, @@ -133,198 +115,6 @@ declare interface ImportMeta { main: boolean; } -declare interface Crypto { - readonly subtle: null; - getRandomValues< - T extends - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | null - >( - array: T - ): T; -} - -// This follows the WebIDL at: https://webassembly.github.io/spec/js-api/ -// and: https://webassembly.github.io/spec/web-api/ - -declare namespace WebAssembly { - interface WebAssemblyInstantiatedSource { - module: Module; - instance: Instance; - } - - /** Compiles a `WebAssembly.Module` from WebAssembly binary code. This - * function is useful if it is necessary to a compile a module before it can - * be instantiated (otherwise, the `WebAssembly.instantiate()` function - * should be used). */ - function compile(bufferSource: __domTypes.BufferSource): Promise; - - /** Compiles a `WebAssembly.Module` directly from a streamed underlying - * source. This function is useful if it is necessary to a compile a module - * before it can be instantiated (otherwise, the - * `WebAssembly.instantiateStreaming()` function should be used). */ - function compileStreaming( - source: Promise<__domTypes.Response> - ): Promise; - - /** Takes the WebAssembly binary code, in the form of a typed array or - * `ArrayBuffer`, and performs both compilation and instantiation in one step. - * The returned `Promise` resolves to both a compiled `WebAssembly.Module` and - * its first `WebAssembly.Instance`. */ - function instantiate( - bufferSource: __domTypes.BufferSource, - importObject?: object - ): Promise; - - /** Takes an already-compiled `WebAssembly.Module` and returns a `Promise` - * that resolves to an `Instance` of that `Module`. This overload is useful if - * the `Module` has already been compiled. */ - function instantiate( - module: Module, - importObject?: object - ): Promise; - - /** Compiles and instantiates a WebAssembly module directly from a streamed - * underlying source. This is the most efficient, optimized way to load wasm - * code. */ - function instantiateStreaming( - source: Promise<__domTypes.Response>, - importObject?: object - ): Promise; - - /** Validates a given typed array of WebAssembly binary code, returning - * whether the bytes form a valid wasm module (`true`) or not (`false`). */ - function validate(bufferSource: __domTypes.BufferSource): boolean; - - type ImportExportKind = "function" | "table" | "memory" | "global"; - - interface ModuleExportDescriptor { - name: string; - kind: ImportExportKind; - } - interface ModuleImportDescriptor { - module: string; - name: string; - kind: ImportExportKind; - } - - class Module { - constructor(bufferSource: __domTypes.BufferSource); - - /** Given a `Module` and string, returns a copy of the contents of all - * custom sections in the module with the given string name. */ - static customSections( - moduleObject: Module, - sectionName: string - ): ArrayBuffer; - - /** Given a `Module`, returns an array containing descriptions of all the - * declared exports. */ - static exports(moduleObject: Module): ModuleExportDescriptor[]; - - /** Given a `Module`, returns an array containing descriptions of all the - * declared imports. */ - static imports(moduleObject: Module): ModuleImportDescriptor[]; - } - - class Instance { - constructor(module: Module, importObject?: object); - - /** An object containing as its members all the functions exported from the - * WebAssembly module instance, to allow them to be accessed and used by - * JavaScript. */ - readonly exports: T; - } - - interface MemoryDescriptor { - initial: number; - maximum?: number; - } - - class Memory { - constructor(descriptor: MemoryDescriptor); - - /** An accessor property that returns the buffer contained in the memory. */ - readonly buffer: ArrayBuffer; - - /** Increases the size of the memory instance by a specified number of - * WebAssembly pages (each one is 64KB in size). */ - grow(delta: number): number; - } - - type TableKind = "anyfunc"; - - interface TableDescriptor { - element: TableKind; - initial: number; - maximum?: number; - } - - class Table { - constructor(descriptor: TableDescriptor); - - /** Returns the length of the table, i.e. the number of elements. */ - readonly length: number; - - /** Accessor function — gets the element stored at a given index. */ - get(index: number): (...args: any[]) => any; - - /** Increases the size of the Table instance by a specified number of - * elements. */ - grow(delta: number): number; - - /** Sets an element stored at a given index to a given value. */ - set(index: number, value: (...args: any[]) => any): void; - } - - type ValueType = "i32" | "i64" | "f32" | "f64"; - - interface GlobalDescriptor { - value: ValueType; - mutable?: boolean; - } - - /** Represents a global variable instance, accessible from both JavaScript and - * importable/exportable across one or more `WebAssembly.Module` instances. - * This allows dynamic linking of multiple modules. */ - class Global { - constructor(descriptor: GlobalDescriptor, value?: any); - - /** Old-style method that returns the value contained inside the global - * variable. */ - valueOf(): any; - - /** The value contained inside the global variable — this can be used to - * directly set and get the global's value. */ - value: any; - } - - /** Indicates an error during WebAssembly decoding or validation */ - class CompileError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } - - /** Indicates an error during module instantiation (besides traps from the - * start function). */ - class LinkError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } - - /** Is thrown whenever WebAssembly specifies a trap. */ - class RuntimeError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } -} - declare namespace __domTypes { // @url js/dom_types.d.ts @@ -1519,13 +1309,6 @@ declare namespace __url { }; } -declare namespace __workerMain { - export let onmessage: (e: { data: any }) => void; - export function postMessage(data: any): void; - export function getMessage(): Promise; - export function workerClose(): void; -} - declare namespace __workers { // @url js/workers.d.ts export interface Worker { @@ -1533,9 +1316,11 @@ declare namespace __workers { onmessage?: (e: { data: any }) => void; onmessageerror?: () => void; postMessage(data: any): void; + terminate(): void; } export interface WorkerOptions { type?: "classic" | "module"; + name?: string; } export class WorkerImpl implements Worker { private readonly id; @@ -1546,6 +1331,7 @@ declare namespace __workers { onmessageerror?: () => void; constructor(specifier: string, options?: WorkerOptions); postMessage(data: any): void; + terminate(): void; private run; } } diff --git a/cli/js/lib.deno.window.d.ts b/cli/js/lib.deno.window.d.ts new file mode 100644 index 0000000000..d4dc08acb0 --- /dev/null +++ b/cli/js/lib.deno.window.d.ts @@ -0,0 +1,214 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ + +/// +/// +/// +/// + +declare interface Window extends WindowOrWorkerGlobalScope { + window: Window & WindowOrWorkerGlobalScope & typeof globalThis; + onload: Function | undefined; + onunload: Function | undefined; + crypto: Crypto; + Deno: typeof Deno; +} + +declare const window: Window & WindowOrWorkerGlobalScope & typeof globalThis; +declare const onload: Function | undefined; +declare const onunload: Function | undefined; +declare const crypto: Crypto; + +declare interface Crypto { + readonly subtle: null; + getRandomValues< + T extends + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | null + >( + array: T + ): T; +} + +// This follows the WebIDL at: https://webassembly.github.io/spec/js-api/ +// and: https://webassembly.github.io/spec/web-api/ + +declare namespace WebAssembly { + interface WebAssemblyInstantiatedSource { + module: Module; + instance: Instance; + } + + /** Compiles a `WebAssembly.Module` from WebAssembly binary code. This + * function is useful if it is necessary to a compile a module before it can + * be instantiated (otherwise, the `WebAssembly.instantiate()` function + * should be used). */ + function compile(bufferSource: __domTypes.BufferSource): Promise; + + /** Compiles a `WebAssembly.Module` directly from a streamed underlying + * source. This function is useful if it is necessary to a compile a module + * before it can be instantiated (otherwise, the + * `WebAssembly.instantiateStreaming()` function should be used). */ + function compileStreaming( + source: Promise<__domTypes.Response> + ): Promise; + + /** Takes the WebAssembly binary code, in the form of a typed array or + * `ArrayBuffer`, and performs both compilation and instantiation in one step. + * The returned `Promise` resolves to both a compiled `WebAssembly.Module` and + * its first `WebAssembly.Instance`. */ + function instantiate( + bufferSource: __domTypes.BufferSource, + importObject?: object + ): Promise; + + /** Takes an already-compiled `WebAssembly.Module` and returns a `Promise` + * that resolves to an `Instance` of that `Module`. This overload is useful if + * the `Module` has already been compiled. */ + function instantiate( + module: Module, + importObject?: object + ): Promise; + + /** Compiles and instantiates a WebAssembly module directly from a streamed + * underlying source. This is the most efficient, optimized way to load wasm + * code. */ + function instantiateStreaming( + source: Promise<__domTypes.Response>, + importObject?: object + ): Promise; + + /** Validates a given typed array of WebAssembly binary code, returning + * whether the bytes form a valid wasm module (`true`) or not (`false`). */ + function validate(bufferSource: __domTypes.BufferSource): boolean; + + type ImportExportKind = "function" | "table" | "memory" | "global"; + + interface ModuleExportDescriptor { + name: string; + kind: ImportExportKind; + } + interface ModuleImportDescriptor { + module: string; + name: string; + kind: ImportExportKind; + } + + class Module { + constructor(bufferSource: __domTypes.BufferSource); + + /** Given a `Module` and string, returns a copy of the contents of all + * custom sections in the module with the given string name. */ + static customSections( + moduleObject: Module, + sectionName: string + ): ArrayBuffer; + + /** Given a `Module`, returns an array containing descriptions of all the + * declared exports. */ + static exports(moduleObject: Module): ModuleExportDescriptor[]; + + /** Given a `Module`, returns an array containing descriptions of all the + * declared imports. */ + static imports(moduleObject: Module): ModuleImportDescriptor[]; + } + + class Instance { + constructor(module: Module, importObject?: object); + + /** An object containing as its members all the functions exported from the + * WebAssembly module instance, to allow them to be accessed and used by + * JavaScript. */ + readonly exports: T; + } + + interface MemoryDescriptor { + initial: number; + maximum?: number; + } + + class Memory { + constructor(descriptor: MemoryDescriptor); + + /** An accessor property that returns the buffer contained in the memory. */ + readonly buffer: ArrayBuffer; + + /** Increases the size of the memory instance by a specified number of + * WebAssembly pages (each one is 64KB in size). */ + grow(delta: number): number; + } + + type TableKind = "anyfunc"; + + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + + class Table { + constructor(descriptor: TableDescriptor); + + /** Returns the length of the table, i.e. the number of elements. */ + readonly length: number; + + /** Accessor function — gets the element stored at a given index. */ + get(index: number): (...args: any[]) => any; + + /** Increases the size of the Table instance by a specified number of + * elements. */ + grow(delta: number): number; + + /** Sets an element stored at a given index to a given value. */ + set(index: number, value: (...args: any[]) => any): void; + } + + type ValueType = "i32" | "i64" | "f32" | "f64"; + + interface GlobalDescriptor { + value: ValueType; + mutable?: boolean; + } + + /** Represents a global variable instance, accessible from both JavaScript and + * importable/exportable across one or more `WebAssembly.Module` instances. + * This allows dynamic linking of multiple modules. */ + class Global { + constructor(descriptor: GlobalDescriptor, value?: any); + + /** Old-style method that returns the value contained inside the global + * variable. */ + valueOf(): any; + + /** The value contained inside the global variable — this can be used to + * directly set and get the global's value. */ + value: any; + } + + /** Indicates an error during WebAssembly decoding or validation */ + class CompileError extends Error { + constructor(message: string, fileName?: string, lineNumber?: string); + } + + /** Indicates an error during module instantiation (besides traps from the + * start function). */ + class LinkError extends Error { + constructor(message: string, fileName?: string, lineNumber?: string); + } + + /** Is thrown whenever WebAssembly specifies a trap. */ + class RuntimeError extends Error { + constructor(message: string, fileName?: string, lineNumber?: string); + } +} +/* eslint-enable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ diff --git a/cli/js/lib.deno.worker.d.ts b/cli/js/lib.deno.worker.d.ts new file mode 100644 index 0000000000..07955345c8 --- /dev/null +++ b/cli/js/lib.deno.worker.d.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ + +/// +/// +/// + +declare interface DedicatedWorkerGlobalScope extends WindowOrWorkerGlobalScope { + self: DedicatedWorkerGlobalScope & + WindowOrWorkerGlobalScope & + typeof globalThis; + onmessage: (e: { data: any }) => void; + onerror: undefined | typeof onerror; + name: typeof __workerMain.name; + close: typeof __workerMain.close; + postMessage: typeof __workerMain.postMessage; +} + +declare const self: DedicatedWorkerGlobalScope & + WindowOrWorkerGlobalScope & + typeof globalThis; +declare let onmessage: ((e: { data: any }) => Promise | void) | undefined; +declare let onerror: + | (( + msg: string, + source: string, + lineno: number, + colno: number, + e: Event + ) => boolean | void) + | undefined; +declare const close: typeof __workerMain.close; +declare const name: typeof __workerMain.name; +declare const postMessage: typeof __workerMain.postMessage; + +declare namespace __workerMain { + export let onmessage: (e: { data: any }) => void; + export function postMessage(data: any): void; + export function getMessage(): Promise; + export function close(): void; + export const name: string; +} + +/* eslint-enable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ diff --git a/cli/js/lib.deno_worker.d.ts b/cli/js/lib.deno_worker.d.ts deleted file mode 100644 index d7652807dc..0000000000 --- a/cli/js/lib.deno_worker.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -/// -/// -/// diff --git a/cli/js/runtime_worker.ts b/cli/js/runtime_worker.ts index 7f1d1b69c4..0dc65fdb62 100644 --- a/cli/js/runtime_worker.ts +++ b/cli/js/runtime_worker.ts @@ -54,7 +54,7 @@ export async function getMessage(): Promise { let isClosing = false; let hasBootstrapped = false; -export function workerClose(): void { +export function close(): void { isClosing = true; } @@ -102,7 +102,7 @@ export const workerRuntimeGlobalProperties = { self: readOnly(globalThis), onmessage: writable(onmessage), onerror: writable(onerror), - workerClose: nonEnumerable(workerClose), + close: nonEnumerable(close), postMessage: writable(postMessage) }; @@ -122,5 +122,6 @@ export function bootstrapWorkerRuntime(name: string): void { Object.defineProperties(globalThis, windowOrWorkerGlobalScopeProperties); Object.defineProperties(globalThis, workerRuntimeGlobalProperties); Object.defineProperties(globalThis, eventTargetProperties); + Object.defineProperties(globalThis, { name: readOnly(name) }); runtime.start(false, name); } diff --git a/cli/js/workers.ts b/cli/js/workers.ts index 60ef73da0b..2a5d4d1909 100644 --- a/cli/js/workers.ts +++ b/cli/js/workers.ts @@ -27,12 +27,14 @@ function decodeMessage(dataIntArray: Uint8Array): any { function createWorker( specifier: string, hasSourceCode: boolean, - sourceCode: Uint8Array + sourceCode: Uint8Array, + name?: string ): { id: number; loaded: boolean } { return sendSync(dispatch.OP_CREATE_WORKER, { specifier, hasSourceCode, - sourceCode: new TextDecoder().decode(sourceCode) + sourceCode: new TextDecoder().decode(sourceCode), + name }); } @@ -72,10 +74,12 @@ export interface Worker { onmessage?: (e: { data: any }) => void; onmessageerror?: () => void; postMessage(data: any): void; + terminate(): void; } export interface WorkerOptions { type?: "classic" | "module"; + name?: string; } export class WorkerImpl extends EventTarget implements Worker { @@ -121,7 +125,12 @@ export class WorkerImpl extends EventTarget implements Worker { } */ - const { id, loaded } = createWorker(specifier, hasSourceCode, sourceCode); + const { id, loaded } = createWorker( + specifier, + hasSourceCode, + sourceCode, + options?.name + ); this.id = id; this.ready = loaded; this.poll(); @@ -196,6 +205,10 @@ export class WorkerImpl extends EventTarget implements Worker { hostPostMessage(this.id, data); } + terminate(): void { + throw new Error("Not yet implemented"); + } + private async run(): Promise { while (!this.isClosing) { const data = await hostGetMessage(this.id); diff --git a/cli/lib.rs b/cli/lib.rs index 311d8ce305..56705289f2 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -54,6 +54,7 @@ pub mod version; mod web_worker; pub mod worker; +use crate::compilers::TargetLib; use crate::deno_error::js_check; use crate::deno_error::{print_err_and_exit, print_msg_and_exit}; use crate::global_state::ThreadSafeGlobalState; @@ -146,7 +147,12 @@ fn create_worker_and_state( } fn types_command() { - println!("{}\n{}", crate::js::DENO_NS_LIB, crate::js::DENO_MAIN_LIB); + println!( + "{}\n{}\n{}", + crate::js::DENO_NS_LIB, + crate::js::SHARED_GLOBALS_LIB, + crate::js::WINDOW_LIB + ); } fn print_cache_info(worker: MainWorker) { @@ -198,7 +204,7 @@ async fn print_file_info( let maybe_compiled = global_state_ .clone() - .fetch_compiled_module(&module_specifier, None) + .fetch_compiled_module(&module_specifier, None, TargetLib::Main) .await; if let Err(e) = maybe_compiled { debug!("compiler error exiting!"); diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index 976c32219b..a1509d2f7c 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -4,7 +4,6 @@ use crate::deno_error::bad_resource; use crate::deno_error::js_check; use crate::deno_error::DenoError; use crate::deno_error::ErrorKind; -use crate::deno_error::GetErrorKind; use crate::fmt_errors::JSError; use crate::ops::json_op; use crate::startup_data; @@ -60,6 +59,7 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CreateWorkerArgs { + name: Option, specifier: String, has_source_code: bool, source_code: String, @@ -89,23 +89,28 @@ fn op_create_worker( } let (int, ext) = ThreadSafeState::create_channels(); - let child_state = ThreadSafeState::new( + let child_state = ThreadSafeState::new_for_worker( state.global_state.clone(), Some(parent_state.permissions.clone()), // by default share with parent Some(module_specifier.clone()), int, )?; + let worker_name = if let Some(name) = args.name { + name + } else { + // TODO(bartlomieju): change it to something more descriptive + format!("USER-WORKER-{}", specifier) + }; + // TODO: add a new option to make child worker not sharing permissions // with parent (aka .clone(), requests from child won't reflect in parent) - // TODO(bartlomieju): get it from "name" argument when creating worker - let name = format!("USER-WORKER-{}", specifier); let mut worker = WebWorker::new( - name.to_string(), + worker_name.to_string(), startup_data::deno_isolate_init(), child_state, ext, ); - let script = format!("bootstrapWorkerRuntime(\"{}\")", name); + let script = format!("bootstrapWorkerRuntime(\"{}\")", worker_name); js_check(worker.execute(&script)); js_check(worker.execute("runWorkerMessageLoop()")); @@ -158,6 +163,8 @@ impl Future for WorkerPollFuture { } fn serialize_worker_result(result: Result<(), ErrBox>) -> Value { + use crate::deno_error::GetErrorKind; + if let Err(error) = result { match error.kind() { ErrorKind::JSError => { diff --git a/cli/state.rs b/cli/state.rs index b224451c56..c4835d6f59 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -1,4 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::compilers::TargetLib; use crate::deno_error::permission_denied; use crate::global_state::ThreadSafeGlobalState; use crate::global_timer::GlobalTimer; @@ -59,6 +60,7 @@ pub struct State { pub start_time: Instant, pub seeded_rng: Option>, pub resource_table: Mutex, + pub target_lib: TargetLib, } impl Clone for ThreadSafeState { @@ -200,7 +202,7 @@ impl Loader for ThreadSafeState { let module_url_specified = module_specifier.to_string(); let fut = self .global_state - .fetch_compiled_module(module_specifier, maybe_referrer) + .fetch_compiled_module(module_specifier, maybe_referrer, self.target_lib) .map_ok(|compiled_module| deno_core::SourceCodeInfo { // Real module name, might be different from initial specifier // due to redirections. @@ -228,9 +230,9 @@ impl ThreadSafeState { (internal_channels, external_channels) } + /// If `shared_permission` is None then permissions from globa state are used. pub fn new( global_state: ThreadSafeGlobalState, - // If Some(perm), use perm. Else copy from global_state. shared_permissions: Option>>, main_module: Option, internal_channels: WorkerChannels, @@ -267,6 +269,46 @@ impl ThreadSafeState { seeded_rng, resource_table: Mutex::new(ResourceTable::default()), + target_lib: TargetLib::Main, + }; + + Ok(ThreadSafeState(Arc::new(state))) + } + + /// If `shared_permission` is None then permissions from globa state are used. + pub fn new_for_worker( + global_state: ThreadSafeGlobalState, + shared_permissions: Option>>, + main_module: Option, + internal_channels: WorkerChannels, + ) -> Result { + let seeded_rng = match global_state.flags.seed { + Some(seed) => Some(Mutex::new(StdRng::seed_from_u64(seed))), + None => None, + }; + + let permissions = if let Some(perm) = shared_permissions { + perm + } else { + Arc::new(Mutex::new(global_state.permissions.clone())) + }; + + let state = State { + global_state, + main_module, + permissions, + import_map: None, + worker_channels: internal_channels, + metrics: Metrics::default(), + global_timer: Mutex::new(GlobalTimer::new()), + workers: Mutex::new(HashMap::new()), + loading_workers: Mutex::new(HashMap::new()), + next_worker_id: AtomicUsize::new(0), + start_time: Instant::now(), + seeded_rng, + + resource_table: Mutex::new(ResourceTable::default()), + target_lib: TargetLib::Worker, }; Ok(ThreadSafeState(Arc::new(state))) diff --git a/cli/tests/026_workers.ts b/cli/tests/026_workers.ts index 3043cc7b9b..7ec6996e11 100644 --- a/cli/tests/026_workers.ts +++ b/cli/tests/026_workers.ts @@ -1,5 +1,11 @@ -const jsWorker = new Worker("./subdir/test_worker.js", { type: "module" }); -const tsWorker = new Worker("./subdir/test_worker.ts", { type: "module" }); +const jsWorker = new Worker("./subdir/test_worker.js", { + type: "module", + name: "jsWorker" +}); +const tsWorker = new Worker("./subdir/test_worker.ts", { + type: "module", + name: "tsWorker" +}); tsWorker.onmessage = (e): void => { console.log("Received ts: " + e.data); diff --git a/cli/tests/subdir/bench_worker.ts b/cli/tests/subdir/bench_worker.ts index 696a84b9f9..619a35fa26 100644 --- a/cli/tests/subdir/bench_worker.ts +++ b/cli/tests/subdir/bench_worker.ts @@ -15,7 +15,7 @@ onmessage = function(e): void { break; case 3: // Close postMessage({ cmdId: 3 }); - workerClose(); + close(); break; } }; diff --git a/cli/tests/subdir/test_worker.js b/cli/tests/subdir/test_worker.js index cec5bdf9be..f0d9fbed63 100644 --- a/cli/tests/subdir/test_worker.js +++ b/cli/tests/subdir/test_worker.js @@ -1,5 +1,10 @@ let thrown = false; +// TODO(bartlomieju): add test for throwing in web worker +if (self.name !== "jsWorker") { + throw Error(`Bad worker name: ${self.name}, expected jsWorker`); +} + onmessage = function(e) { console.log(e.data); @@ -10,7 +15,7 @@ onmessage = function(e) { postMessage(e.data); - workerClose(); + close(); }; onerror = function() { diff --git a/cli/tests/subdir/test_worker.ts b/cli/tests/subdir/test_worker.ts index c8109d1318..bc3f358f85 100644 --- a/cli/tests/subdir/test_worker.ts +++ b/cli/tests/subdir/test_worker.ts @@ -1,7 +1,11 @@ +if (self.name !== "tsWorker") { + throw Error(`Invalid worker name: ${self.name}, expected tsWorker`); +} + onmessage = function(e): void { console.log(e.data); postMessage(e.data); - workerClose(); + close(); }; diff --git a/cli/tests/types.out b/cli/tests/types.out index df79ff821e..a212c01e8e 100644 --- a/cli/tests/types.out +++ b/cli/tests/types.out @@ -5,10 +5,12 @@ declare namespace Deno { [WILDCARD] } [WILDCARD] -declare interface Window { +declare interface WindowOrWorkerGlobalScope { +[WILDCARD] +declare interface Window extends WindowOrWorkerGlobalScope { [WILDCARD] Deno: typeof Deno; } -declare const window: Window & typeof globalThis; +declare const window: Window & WindowOrWorkerGlobalScope & typeof globalThis; [WILDCARD]