| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.QuickJSWASMModule = exports.applyModuleEvalRuntimeOptions = exports.applyBaseRuntimeOptions = exports.QuickJSModuleCallbacks = void 0; |
| const debug_1 = require("./debug"); |
| const errors_1 = require("./errors"); |
| const lifetime_1 = require("./lifetime"); |
| const runtime_1 = require("./runtime"); |
| const types_1 = require("./types"); |
| class QuickJSEmscriptenModuleCallbacks { |
| constructor(args) { |
| this.callFunction = args.callFunction; |
| this.shouldInterrupt = args.shouldInterrupt; |
| this.loadModuleSource = args.loadModuleSource; |
| this.normalizeModule = args.normalizeModule; |
| } |
| } |
| /** |
| * We use static functions per module to dispatch runtime or context calls from |
| * C to the host. This class manages the indirection from a specific runtime or |
| * context pointer to the appropriate callback handler. |
| * |
| * @private |
| */ |
| class QuickJSModuleCallbacks { |
| constructor(module) { |
| this.contextCallbacks = new Map(); |
| this.runtimeCallbacks = new Map(); |
| this.suspendedCount = 0; |
| this.cToHostCallbacks = new QuickJSEmscriptenModuleCallbacks({ |
| callFunction: (asyncify, ctx, this_ptr, argc, argv, fn_id) => this.handleAsyncify(asyncify, () => { |
| try { |
| const vm = this.contextCallbacks.get(ctx); |
| if (!vm) { |
| throw new Error(`QuickJSContext(ctx = ${ctx}) not found for C function call "${fn_id}"`); |
| } |
| return vm.callFunction(ctx, this_ptr, argc, argv, fn_id); |
| } |
| catch (error) { |
| console.error("[C to host error: returning null]", error); |
| return 0; |
| } |
| }), |
| shouldInterrupt: (asyncify, rt) => this.handleAsyncify(asyncify, () => { |
| try { |
| const vm = this.runtimeCallbacks.get(rt); |
| if (!vm) { |
| throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C interrupt`); |
| } |
| return vm.shouldInterrupt(rt); |
| } |
| catch (error) { |
| console.error("[C to host interrupt: returning error]", error); |
| return 1; |
| } |
| }), |
| loadModuleSource: (asyncify, rt, ctx, moduleName) => this.handleAsyncify(asyncify, () => { |
| try { |
| const runtimeCallbacks = this.runtimeCallbacks.get(rt); |
| if (!runtimeCallbacks) { |
| throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`); |
| } |
| const loadModule = runtimeCallbacks.loadModuleSource; |
| if (!loadModule) { |
| throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`); |
| } |
| return loadModule(rt, ctx, moduleName); |
| } |
| catch (error) { |
| console.error("[C to host module loader error: returning null]", error); |
| return 0; |
| } |
| }), |
| normalizeModule: (asyncify, rt, ctx, moduleBaseName, moduleName) => this.handleAsyncify(asyncify, () => { |
| try { |
| const runtimeCallbacks = this.runtimeCallbacks.get(rt); |
| if (!runtimeCallbacks) { |
| throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`); |
| } |
| const normalizeModule = runtimeCallbacks.normalizeModule; |
| if (!normalizeModule) { |
| throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`); |
| } |
| return normalizeModule(rt, ctx, moduleBaseName, moduleName); |
| } |
| catch (error) { |
| console.error("[C to host module loader error: returning null]", error); |
| return 0; |
| } |
| }), |
| }); |
| this.module = module; |
| this.module.callbacks = this.cToHostCallbacks; |
| } |
| setRuntimeCallbacks(rt, callbacks) { |
| this.runtimeCallbacks.set(rt, callbacks); |
| } |
| deleteRuntime(rt) { |
| this.runtimeCallbacks.delete(rt); |
| } |
| setContextCallbacks(ctx, callbacks) { |
| this.contextCallbacks.set(ctx, callbacks); |
| } |
| deleteContext(ctx) { |
| this.contextCallbacks.delete(ctx); |
| } |
| handleAsyncify(asyncify, fn) { |
| if (asyncify) { |
| // We must always call asyncify.handleSync around our function. |
| // This allows asyncify to resume suspended execution on the second call. |
| // Asyncify internally can detect sync behavior, and avoid suspending. |
| return asyncify.handleSleep((done) => { |
| try { |
| const result = fn(); |
| if (!(result instanceof Promise)) { |
| (0, debug_1.debugLog)("asyncify.handleSleep: not suspending:", result); |
| done(result); |
| return; |
| } |
| // Is promise, we intend to suspend. |
| if (this.suspended) { |
| throw new errors_1.QuickJSAsyncifyError(`Already suspended at: ${this.suspended.stack}\nAttempted to suspend at:`); |
| } |
| else { |
| this.suspended = new errors_1.QuickJSAsyncifySuspended(`(${this.suspendedCount++})`); |
| (0, debug_1.debugLog)("asyncify.handleSleep: suspending:", this.suspended); |
| } |
| result.then((resolvedResult) => { |
| this.suspended = undefined; |
| (0, debug_1.debugLog)("asyncify.handleSleep: resolved:", resolvedResult); |
| done(resolvedResult); |
| }, (error) => { |
| (0, debug_1.debugLog)("asyncify.handleSleep: rejected:", error); |
| console.error("QuickJS: cannot handle error in suspended function", error); |
| this.suspended = undefined; |
| }); |
| } |
| catch (error) { |
| (0, debug_1.debugLog)("asyncify.handleSleep: error:", error); |
| this.suspended = undefined; |
| throw error; |
| } |
| }); |
| } |
| // No asyncify - we should never return a promise. |
| const value = fn(); |
| if (value instanceof Promise) { |
| throw new Error("Promise return value not supported in non-asyncify context."); |
| } |
| return value; |
| } |
| } |
| exports.QuickJSModuleCallbacks = QuickJSModuleCallbacks; |
| /** |
| * Process RuntimeOptions and apply them to a QuickJSRuntime. |
| * @private |
| */ |
| function applyBaseRuntimeOptions(runtime, options) { |
| if (options.interruptHandler) { |
| runtime.setInterruptHandler(options.interruptHandler); |
| } |
| if (options.maxStackSizeBytes !== undefined) { |
| runtime.setMaxStackSize(options.maxStackSizeBytes); |
| } |
| if (options.memoryLimitBytes !== undefined) { |
| runtime.setMemoryLimit(options.memoryLimitBytes); |
| } |
| } |
| exports.applyBaseRuntimeOptions = applyBaseRuntimeOptions; |
| /** |
| * Process ModuleEvalOptions and apply them to a QuickJSRuntime. |
| * @private |
| */ |
| function applyModuleEvalRuntimeOptions(runtime, options) { |
| if (options.moduleLoader) { |
| runtime.setModuleLoader(options.moduleLoader); |
| } |
| if (options.shouldInterrupt) { |
| runtime.setInterruptHandler(options.shouldInterrupt); |
| } |
| if (options.memoryLimitBytes !== undefined) { |
| runtime.setMemoryLimit(options.memoryLimitBytes); |
| } |
| if (options.maxStackSizeBytes !== undefined) { |
| runtime.setMaxStackSize(options.maxStackSizeBytes); |
| } |
| } |
| exports.applyModuleEvalRuntimeOptions = applyModuleEvalRuntimeOptions; |
| /** |
| * This class presents a Javascript interface to QuickJS, a Javascript interpreter |
| * that supports EcmaScript 2020 (ES2020). |
| * |
| * It wraps a single WebAssembly module containing the QuickJS library and |
| * associated helper C code. WebAssembly modules are completely isolated from |
| * each other by the host's WebAssembly runtime. Separate WebAssembly modules |
| * have the most isolation guarantees possible with this library. |
| * |
| * The simplest way to start running code is {@link evalCode}. This shortcut |
| * method will evaluate Javascript safely and return the result as a native |
| * Javascript value. |
| * |
| * For more control over the execution environment, or to interact with values |
| * inside QuickJS, create a context with {@link newContext} or a runtime with |
| * {@link newRuntime}. |
| */ |
| class QuickJSWASMModule { |
| /** @private */ |
| constructor(module, ffi) { |
| this.module = module; |
| this.ffi = ffi; |
| this.callbacks = new QuickJSModuleCallbacks(module); |
| } |
| /** |
| * Create a runtime. |
| * Use the runtime to set limits on CPU and memory usage and configure module |
| * loading for one or more [[QuickJSContext]]s inside the runtime. |
| */ |
| newRuntime(options = {}) { |
| const rt = new lifetime_1.Lifetime(this.ffi.QTS_NewRuntime(), undefined, (rt_ptr) => { |
| this.callbacks.deleteRuntime(rt_ptr); |
| this.ffi.QTS_FreeRuntime(rt_ptr); |
| }); |
| const runtime = new runtime_1.QuickJSRuntime({ |
| module: this.module, |
| callbacks: this.callbacks, |
| ffi: this.ffi, |
| rt, |
| }); |
| applyBaseRuntimeOptions(runtime, options); |
| if (options.moduleLoader) { |
| runtime.setModuleLoader(options.moduleLoader); |
| } |
| return runtime; |
| } |
| /** |
| * A simplified API to create a new [[QuickJSRuntime]] and a |
| * [[QuickJSContext]] inside that runtime at the same time. The runtime will |
| * be disposed when the context is disposed. |
| */ |
| newContext(options = {}) { |
| const runtime = this.newRuntime(); |
| const context = runtime.newContext({ |
| ...options, |
| ownedLifetimes: (0, types_1.concat)(runtime, options.ownedLifetimes), |
| }); |
| runtime.context = context; |
| return context; |
| } |
| /** |
| * One-off evaluate code without needing to create a [[QuickJSRuntime]] or |
| * [[QuickJSContext]] explicitly. |
| * |
| * To protect against infinite loops, use the `shouldInterrupt` option. The |
| * [[shouldInterruptAfterDeadline]] function will create a time-based deadline. |
| * |
| * If you need more control over how the code executes, create a |
| * [[QuickJSRuntime]] (with [[newRuntime]]) or a [[QuickJSContext]] (with |
| * [[newContext]] or [[QuickJSRuntime.newContext]]), and use its |
| * [[QuickJSContext.evalCode]] method. |
| * |
| * Asynchronous callbacks may not run during the first call to `evalCode`. If |
| * you need to work with async code inside QuickJS, create a runtime and use |
| * [[QuickJSRuntime.executePendingJobs]]. |
| * |
| * @returns The result is coerced to a native Javascript value using JSON |
| * serialization, so properties and values unsupported by JSON will be dropped. |
| * |
| * @throws If `code` throws during evaluation, the exception will be |
| * converted into a native Javascript value and thrown. |
| * |
| * @throws if `options.shouldInterrupt` interrupted execution, will throw a Error |
| * with name `"InternalError"` and message `"interrupted"`. |
| */ |
| evalCode(code, options = {}) { |
| return lifetime_1.Scope.withScope((scope) => { |
| const vm = scope.manage(this.newContext()); |
| applyModuleEvalRuntimeOptions(vm.runtime, options); |
| const result = vm.evalCode(code, "eval.js"); |
| if (options.memoryLimitBytes !== undefined) { |
| // Remove memory limit so we can dump the result without exceeding it. |
| vm.runtime.setMemoryLimit(-1); |
| } |
| if (result.error) { |
| const error = vm.dump(scope.manage(result.error)); |
| throw error; |
| } |
| const value = vm.dump(scope.manage(result.value)); |
| return value; |
| }); |
| } |
| /** |
| * Get a low-level interface to the QuickJS functions in this WebAssembly |
| * module. |
| * @experimental |
| * @unstable No warranty is provided with this API. It could change at any time. |
| * @private |
| */ |
| getFFI() { |
| return this.ffi; |
| } |
| } |
| exports.QuickJSWASMModule = QuickJSWASMModule; |
| //# sourceMappingURL=module.js.map |