Spaces:
Sleeping
Sleeping
| ; | |
| 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 |