diff --git a/bench_util/js_runtime.rs b/bench_util/js_runtime.rs index 7868c433ab..103b974304 100644 --- a/bench_util/js_runtime.rs +++ b/bench_util/js_runtime.rs @@ -10,6 +10,9 @@ use crate::profiling::is_profiling; pub fn create_js_runtime(setup: impl FnOnce() -> Vec) -> JsRuntime { JsRuntime::new(RuntimeOptions { extensions_with_js: setup(), + module_loader: Some(std::rc::Rc::new( + deno_core::InternalModuleLoader::new(None), + )), ..Default::default() }) } diff --git a/cli/build.rs b/cli/build.rs index bb048d0d42..d4cfc47e95 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -275,6 +275,7 @@ mod ts { .build()], extensions_with_js: vec![], additional_files: files, + additional_esm_files: vec![], compression_cb: Some(Box::new(|vec, snapshot_slice| { vec.extend_from_slice( &zstd::bulk::compress(snapshot_slice, 22) @@ -306,7 +307,7 @@ mod ts { } } -fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec) { +fn create_cli_snapshot(snapshot_path: PathBuf, esm_files: Vec) { let extensions: Vec = vec![ deno_webidl::init(), deno_console::init(), @@ -343,7 +344,8 @@ fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec) { startup_snapshot: Some(deno_runtime::js::deno_isolate_init()), extensions, extensions_with_js: vec![], - additional_files: files, + additional_files: vec![], + additional_esm_files: esm_files, compression_cb: Some(Box::new(|vec, snapshot_slice| { lzzzz::lz4_hc::compress_to_vec( snapshot_slice, @@ -448,13 +450,13 @@ fn main() { let o = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin"); - let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc"); + let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc", None); ts::create_compiler_snapshot(compiler_snapshot_path, js_files, &c); let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin"); - let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); - js_files.push(deno_runtime::js::get_99_main()); - create_cli_snapshot(cli_snapshot_path, js_files); + let mut esm_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js", None); + esm_files.push(deno_runtime::js::get_99_main()); + create_cli_snapshot(cli_snapshot_path, esm_files); #[cfg(target_os = "windows")] { diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js index 0693fba1a1..374dc10ee0 100644 --- a/cli/js/40_testing.js +++ b/cli/js/40_testing.js @@ -1,899 +1,930 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { setExitHandler } = window.__bootstrap.os; - const { Console } = window.__bootstrap.console; - const { serializePermissions } = window.__bootstrap.permissions; - const { assert } = window.__bootstrap.infra; - const { - ArrayFrom, - ArrayPrototypeFilter, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeShift, - ArrayPrototypeSort, - BigInt, - DateNow, - Error, - FunctionPrototype, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - MathCeil, - ObjectKeys, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - Promise, - SafeArrayIterator, - Set, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +import { setExitHandler } from "internal:runtime/js/30_os.js"; +import { Console } from "internal:ext/console/02_console.js"; +import { serializePermissions } from "internal:runtime/js/10_permissions.js"; +import { assert } from "internal:ext/web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayFrom, + ArrayPrototypeFilter, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + ArrayPrototypeSort, + BigInt, + DateNow, + Error, + FunctionPrototype, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + MathCeil, + ObjectKeys, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + Promise, + SafeArrayIterator, + Set, + SymbolToStringTag, + TypeError, +} = primordials; - const opSanitizerDelayResolveQueue = []; +const opSanitizerDelayResolveQueue = []; - // Even if every resource is closed by the end of a test, there can be a delay - // until the pending ops have all finished. This function returns a promise - // that resolves when it's (probably) fine to run the op sanitizer. - // - // This is implemented by adding a macrotask callback that runs after the - // timer macrotasks, so we can guarantee that a currently running interval - // will have an associated op. An additional `setTimeout` of 0 is needed - // before that, though, in order to give time for worker message ops to finish - // (since timeouts of 0 don't queue tasks in the timer queue immediately). - function opSanitizerDelay() { - return new Promise((resolve) => { - setTimeout(() => { - ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve); - }, 0); - }); - } +// Even if every resource is closed by the end of a test, there can be a delay +// until the pending ops have all finished. This function returns a promise +// that resolves when it's (probably) fine to run the op sanitizer. +// +// This is implemented by adding a macrotask callback that runs after the +// timer macrotasks, so we can guarantee that a currently running interval +// will have an associated op. An additional `setTimeout` of 0 is needed +// before that, though, in order to give time for worker message ops to finish +// (since timeouts of 0 don't queue tasks in the timer queue immediately). +function opSanitizerDelay() { + return new Promise((resolve) => { + setTimeout(() => { + ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve); + }, 0); + }); +} - function handleOpSanitizerDelayMacrotask() { - ArrayPrototypeShift(opSanitizerDelayResolveQueue)?.(); - return opSanitizerDelayResolveQueue.length === 0; - } +function handleOpSanitizerDelayMacrotask() { + ArrayPrototypeShift(opSanitizerDelayResolveQueue)?.(); + return opSanitizerDelayResolveQueue.length === 0; +} - // An async operation to $0 was started in this test, but never completed. This is often caused by not $1. - // An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test. - // deno-fmt-ignore - const OP_DETAILS = { - "op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"], - "op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"], - "op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"], - "op_chmod_async": ["change the permissions of a file", "awaiting the result of a `Deno.chmod` call"], - "op_chown_async": ["change the owner of a file", "awaiting the result of a `Deno.chown` call"], - "op_copy_file_async": ["copy a file", "awaiting the result of a `Deno.copyFile` call"], - "op_crypto_decrypt": ["decrypt data", "awaiting the result of a `crypto.subtle.decrypt` call"], - "op_crypto_derive_bits": ["derive bits from a key", "awaiting the result of a `crypto.subtle.deriveBits` call"], - "op_crypto_encrypt": ["encrypt data", "awaiting the result of a `crypto.subtle.encrypt` call"], - "op_crypto_generate_key": ["generate a key", "awaiting the result of a `crypto.subtle.generateKey` call"], - "op_crypto_sign_key": ["sign data", "awaiting the result of a `crypto.subtle.sign` call"], - "op_crypto_subtle_digest": ["digest data", "awaiting the result of a `crypto.subtle.digest` call"], - "op_crypto_verify_key": ["verify data", "awaiting the result of a `crypto.subtle.verify` call"], - "op_net_recv_udp": ["receive a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], - "op_net_recv_unixpacket": ["receive a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], - "op_net_send_udp": ["send a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#send` call"], - "op_net_send_unixpacket": ["send a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#send` call"], - "op_dns_resolve": ["resolve a DNS name", "awaiting the result of a `Deno.resolveDns` call"], - "op_fdatasync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` call"], - "op_fetch_send": ["send a HTTP request", "awaiting the result of a `fetch` call"], - "op_ffi_call_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"] , - "op_ffi_call_ptr_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"], - "op_flock_async": ["lock a file", "awaiting the result of a `Deno.flock` call"], - "op_fs_events_poll": ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"], - "op_fstat_async": ["get file metadata", "awaiting the result of a `Deno.File#fstat` call"], - "op_fsync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` call"], - "op_ftruncate_async": ["truncate a file", "awaiting the result of a `Deno.ftruncate` call"], - "op_funlock_async": ["unlock a file", "awaiting the result of a `Deno.funlock` call"], - "op_futime_async": ["change file timestamps", "awaiting the result of a `Deno.futime` call"], - "op_http_accept": ["accept a HTTP request", "closing a `Deno.HttpConn`"], - "op_http_shutdown": ["shutdown a HTTP connection", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_upgrade_websocket": ["upgrade a HTTP connection to a WebSocket", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_write_headers": ["write HTTP response headers", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_write": ["write HTTP response body", "awaiting `Deno.HttpEvent#respondWith`"], - "op_link_async": ["create a hard link", "awaiting the result of a `Deno.link` call"], - "op_make_temp_dir_async": ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"], - "op_make_temp_file_async": ["create a temporary file", "awaiting the result of a `Deno.makeTempFile` call"], - "op_message_port_recv_message": ["receive a message from a MessagePort", "awaiting the result of not closing a `MessagePort`"], - "op_mkdir_async": ["create a directory", "awaiting the result of a `Deno.mkdir` call"], - "op_net_accept_tcp": ["accept a TCP stream", "closing a `Deno.Listener`"], - "op_net_accept_unix": ["accept a Unix stream", "closing a `Deno.Listener`"], - "op_net_connect_tcp": ["connect to a TCP server", "awaiting a `Deno.connect` call"], - "op_net_connect_unix": ["connect to a Unix server", "awaiting a `Deno.connect` call"], - "op_open_async": ["open a file", "awaiting the result of a `Deno.open` call"], - "op_read_dir_async": ["read a directory", "collecting all items in the async iterable returned from a `Deno.readDir` call"], - "op_read_link_async": ["read a symlink", "awaiting the result of a `Deno.readLink` call"], - "op_realpath_async": ["resolve a path", "awaiting the result of a `Deno.realpath` call"], - "op_remove_async": ["remove a file or directory", "awaiting the result of a `Deno.remove` call"], - "op_rename_async": ["rename a file or directory", "awaiting the result of a `Deno.rename` call"], - "op_run_status": ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"], - "op_seek_async": ["seek in a file", "awaiting the result of a `Deno.File#seek` call"], - "op_signal_poll": ["get the next signal", "un-registering a OS signal handler"], - "op_sleep": ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], - "op_stat_async": ["get file metadata", "awaiting the result of a `Deno.stat` call"], - "op_symlink_async": ["create a symlink", "awaiting the result of a `Deno.symlink` call"], - "op_net_accept_tls": ["accept a TLS stream", "closing a `Deno.TlsListener`"], - "op_net_connect_tls": ["connect to a TLS server", "awaiting a `Deno.connectTls` call"], - "op_tls_handshake": ["perform a TLS handshake", "awaiting a `Deno.TlsConn#handshake` call"], - "op_tls_start": ["start a TLS connection", "awaiting a `Deno.startTls` call"], - "op_truncate_async": ["truncate a file", "awaiting the result of a `Deno.truncate` call"], - "op_utime_async": ["change file timestamps", "awaiting the result of a `Deno.utime` call"], - "op_webgpu_buffer_get_map_async": ["map a WebGPU buffer", "awaiting the result of a `GPUBuffer#mapAsync` call"], - "op_webgpu_request_adapter": ["request a WebGPU adapter", "awaiting the result of a `navigator.gpu.requestAdapter` call"], - "op_webgpu_request_device": ["request a WebGPU device", "awaiting the result of a `GPUAdapter#requestDevice` call"], - "op_worker_recv_message": ["receive a message from a web worker", "terminating a `Worker`"], - "op_ws_close": ["close a WebSocket", "awaiting until the `close` event is emitted on a `WebSocket`, or the `WebSocketStream#closed` promise resolves"], - "op_ws_create": ["create a WebSocket", "awaiting until the `open` event is emitted on a `WebSocket`, or the result of a `WebSocketStream#connection` promise"], - "op_ws_next_event": ["receive the next message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], - "op_ws_send": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], - }; +// An async operation to $0 was started in this test, but never completed. This is often caused by not $1. +// An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test. +// deno-fmt-ignore +const OP_DETAILS = { + "op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"], + "op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"], + "op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"], + "op_chmod_async": ["change the permissions of a file", "awaiting the result of a `Deno.chmod` call"], + "op_chown_async": ["change the owner of a file", "awaiting the result of a `Deno.chown` call"], + "op_copy_file_async": ["copy a file", "awaiting the result of a `Deno.copyFile` call"], + "op_crypto_decrypt": ["decrypt data", "awaiting the result of a `crypto.subtle.decrypt` call"], + "op_crypto_derive_bits": ["derive bits from a key", "awaiting the result of a `crypto.subtle.deriveBits` call"], + "op_crypto_encrypt": ["encrypt data", "awaiting the result of a `crypto.subtle.encrypt` call"], + "op_crypto_generate_key": ["generate a key", "awaiting the result of a `crypto.subtle.generateKey` call"], + "op_crypto_sign_key": ["sign data", "awaiting the result of a `crypto.subtle.sign` call"], + "op_crypto_subtle_digest": ["digest data", "awaiting the result of a `crypto.subtle.digest` call"], + "op_crypto_verify_key": ["verify data", "awaiting the result of a `crypto.subtle.verify` call"], + "op_net_recv_udp": ["receive a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], + "op_net_recv_unixpacket": ["receive a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], + "op_net_send_udp": ["send a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#send` call"], + "op_net_send_unixpacket": ["send a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#send` call"], + "op_dns_resolve": ["resolve a DNS name", "awaiting the result of a `Deno.resolveDns` call"], + "op_fdatasync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` call"], + "op_fetch_send": ["send a HTTP request", "awaiting the result of a `fetch` call"], + "op_ffi_call_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"] , + "op_ffi_call_ptr_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"], + "op_flock_async": ["lock a file", "awaiting the result of a `Deno.flock` call"], + "op_fs_events_poll": ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"], + "op_fstat_async": ["get file metadata", "awaiting the result of a `Deno.File#fstat` call"], + "op_fsync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` call"], + "op_ftruncate_async": ["truncate a file", "awaiting the result of a `Deno.ftruncate` call"], + "op_funlock_async": ["unlock a file", "awaiting the result of a `Deno.funlock` call"], + "op_futime_async": ["change file timestamps", "awaiting the result of a `Deno.futime` call"], + "op_http_accept": ["accept a HTTP request", "closing a `Deno.HttpConn`"], + "op_http_shutdown": ["shutdown a HTTP connection", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_upgrade_websocket": ["upgrade a HTTP connection to a WebSocket", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_write_headers": ["write HTTP response headers", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_write": ["write HTTP response body", "awaiting `Deno.HttpEvent#respondWith`"], + "op_link_async": ["create a hard link", "awaiting the result of a `Deno.link` call"], + "op_make_temp_dir_async": ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"], + "op_make_temp_file_async": ["create a temporary file", "awaiting the result of a `Deno.makeTempFile` call"], + "op_message_port_recv_message": ["receive a message from a MessagePort", "awaiting the result of not closing a `MessagePort`"], + "op_mkdir_async": ["create a directory", "awaiting the result of a `Deno.mkdir` call"], + "op_net_accept_tcp": ["accept a TCP stream", "closing a `Deno.Listener`"], + "op_net_accept_unix": ["accept a Unix stream", "closing a `Deno.Listener`"], + "op_net_connect_tcp": ["connect to a TCP server", "awaiting a `Deno.connect` call"], + "op_net_connect_unix": ["connect to a Unix server", "awaiting a `Deno.connect` call"], + "op_open_async": ["open a file", "awaiting the result of a `Deno.open` call"], + "op_read_dir_async": ["read a directory", "collecting all items in the async iterable returned from a `Deno.readDir` call"], + "op_read_link_async": ["read a symlink", "awaiting the result of a `Deno.readLink` call"], + "op_realpath_async": ["resolve a path", "awaiting the result of a `Deno.realpath` call"], + "op_remove_async": ["remove a file or directory", "awaiting the result of a `Deno.remove` call"], + "op_rename_async": ["rename a file or directory", "awaiting the result of a `Deno.rename` call"], + "op_run_status": ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"], + "op_seek_async": ["seek in a file", "awaiting the result of a `Deno.File#seek` call"], + "op_signal_poll": ["get the next signal", "un-registering a OS signal handler"], + "op_sleep": ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], + "op_stat_async": ["get file metadata", "awaiting the result of a `Deno.stat` call"], + "op_symlink_async": ["create a symlink", "awaiting the result of a `Deno.symlink` call"], + "op_net_accept_tls": ["accept a TLS stream", "closing a `Deno.TlsListener`"], + "op_net_connect_tls": ["connect to a TLS server", "awaiting a `Deno.connectTls` call"], + "op_tls_handshake": ["perform a TLS handshake", "awaiting a `Deno.TlsConn#handshake` call"], + "op_tls_start": ["start a TLS connection", "awaiting a `Deno.startTls` call"], + "op_truncate_async": ["truncate a file", "awaiting the result of a `Deno.truncate` call"], + "op_utime_async": ["change file timestamps", "awaiting the result of a `Deno.utime` call"], + "op_webgpu_buffer_get_map_async": ["map a WebGPU buffer", "awaiting the result of a `GPUBuffer#mapAsync` call"], + "op_webgpu_request_adapter": ["request a WebGPU adapter", "awaiting the result of a `navigator.gpu.requestAdapter` call"], + "op_webgpu_request_device": ["request a WebGPU device", "awaiting the result of a `GPUAdapter#requestDevice` call"], + "op_worker_recv_message": ["receive a message from a web worker", "terminating a `Worker`"], + "op_ws_close": ["close a WebSocket", "awaiting until the `close` event is emitted on a `WebSocket`, or the `WebSocketStream#closed` promise resolves"], + "op_ws_create": ["create a WebSocket", "awaiting until the `open` event is emitted on a `WebSocket`, or the result of a `WebSocketStream#connection` promise"], + "op_ws_next_event": ["receive the next message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], + "op_ws_send": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], +}; - // Wrap test function in additional assertion that makes sure - // the test case does not leak async "ops" - ie. number of async - // completed ops after the test is the same as number of dispatched - // ops. Note that "unref" ops are ignored since in nature that are - // optional. - function assertOps(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function asyncOpSanitizer(desc) { - const pre = core.metrics(); - const preTraces = new Map(core.opCallTraces); - try { - await fn(desc); - } finally { - // Defer until next event loop turn - that way timeouts and intervals - // cleared can actually be removed from resource table, otherwise - // false positives may occur (https://github.com/denoland/deno/issues/4591) - await opSanitizerDelay(); - await opSanitizerDelay(); +// Wrap test function in additional assertion that makes sure +// the test case does not leak async "ops" - ie. number of async +// completed ops after the test is the same as number of dispatched +// ops. Note that "unref" ops are ignored since in nature that are +// optional. +function assertOps(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function asyncOpSanitizer(desc) { + const pre = core.metrics(); + const preTraces = new Map(core.opCallTraces); + try { + await fn(desc); + } finally { + // Defer until next event loop turn - that way timeouts and intervals + // cleared can actually be removed from resource table, otherwise + // false positives may occur (https://github.com/denoland/deno/issues/4591) + await opSanitizerDelay(); + await opSanitizerDelay(); + } + + if (shouldSkipSanitizers(desc)) return; + + const post = core.metrics(); + const postTraces = new Map(core.opCallTraces); + + // We're checking diff because one might spawn HTTP server in the background + // that will be a pending async op before test starts. + const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync; + const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync; + + if (dispatchedDiff === completedDiff) return; + + const details = []; + for (const key in post.ops) { + if (!ObjectPrototypeHasOwnProperty(post.ops, key)) { + continue; } + const preOp = pre.ops[key] ?? + { opsDispatchedAsync: 0, opsCompletedAsync: 0 }; + const postOp = post.ops[key]; + const dispatchedDiff = postOp.opsDispatchedAsync - + preOp.opsDispatchedAsync; + const completedDiff = postOp.opsCompletedAsync - + preOp.opsCompletedAsync; - if (shouldSkipSanitizers(desc)) return; - - const post = core.metrics(); - const postTraces = new Map(core.opCallTraces); - - // We're checking diff because one might spawn HTTP server in the background - // that will be a pending async op before test starts. - const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync; - const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync; - - if (dispatchedDiff === completedDiff) return; - - const details = []; - for (const key in post.ops) { - if (!ObjectPrototypeHasOwnProperty(post.ops, key)) { - continue; + if (dispatchedDiff > completedDiff) { + const [name, hint] = OP_DETAILS[key] || [key, null]; + const count = dispatchedDiff - completedDiff; + let message = `${count} async operation${ + count === 1 ? "" : "s" + } to ${name} ${ + count === 1 ? "was" : "were" + } started in this test, but never completed.`; + if (hint) { + message += ` This is often caused by not ${hint}.`; } - const preOp = pre.ops[key] ?? - { opsDispatchedAsync: 0, opsCompletedAsync: 0 }; - const postOp = post.ops[key]; - const dispatchedDiff = postOp.opsDispatchedAsync - - preOp.opsDispatchedAsync; - const completedDiff = postOp.opsCompletedAsync - - preOp.opsCompletedAsync; - - if (dispatchedDiff > completedDiff) { - const [name, hint] = OP_DETAILS[key] || [key, null]; - const count = dispatchedDiff - completedDiff; - let message = `${count} async operation${ - count === 1 ? "" : "s" - } to ${name} ${ + const traces = []; + for (const [id, { opName, stack }] of postTraces) { + if (opName !== key) continue; + if (MapPrototypeHas(preTraces, id)) continue; + ArrayPrototypePush(traces, stack); + } + if (traces.length === 1) { + message += " The operation was started here:\n"; + message += traces[0]; + } else if (traces.length > 1) { + message += " The operations were started here:\n"; + message += ArrayPrototypeJoin(traces, "\n\n"); + } + ArrayPrototypePush(details, message); + } else if (dispatchedDiff < completedDiff) { + const [name, hint] = OP_DETAILS[key] || [key, null]; + const count = completedDiff - dispatchedDiff; + ArrayPrototypePush( + details, + `${count} async operation${count === 1 ? "" : "s"} to ${name} ${ count === 1 ? "was" : "were" - } started in this test, but never completed.`; - if (hint) { - message += ` This is often caused by not ${hint}.`; - } - const traces = []; - for (const [id, { opName, stack }] of postTraces) { - if (opName !== key) continue; - if (MapPrototypeHas(preTraces, id)) continue; - ArrayPrototypePush(traces, stack); - } - if (traces.length === 1) { - message += " The operation was started here:\n"; - message += traces[0]; - } else if (traces.length > 1) { - message += " The operations were started here:\n"; - message += ArrayPrototypeJoin(traces, "\n\n"); - } - ArrayPrototypePush(details, message); - } else if (dispatchedDiff < completedDiff) { - const [name, hint] = OP_DETAILS[key] || [key, null]; - const count = completedDiff - dispatchedDiff; - ArrayPrototypePush( - details, - `${count} async operation${count === 1 ? "" : "s"} to ${name} ${ - count === 1 ? "was" : "were" - } started before this test, but ${ - count === 1 ? "was" : "were" - } completed during the test. Async operations should not complete in a test if they were not started in that test. + } started before this test, but ${ + count === 1 ? "was" : "were" + } completed during the test. Async operations should not complete in a test if they were not started in that test. ${hint ? `This is often caused by not ${hint}.` : ""}`, - ); - } + ); } + } - let msg = `Test case is leaking async ops. + let msg = `Test case is leaking async ops. - ${ArrayPrototypeJoin(details, "\n - ")}`; - if (!core.isOpCallTracingEnabled()) { - msg += - `\n\nTo get more details where ops were leaked, run again with --trace-ops flag.`; + if (!core.isOpCallTracingEnabled()) { + msg += + `\n\nTo get more details where ops were leaked, run again with --trace-ops flag.`; + } else { + msg += "\n"; + } + + throw assert(false, msg); + }; +} + +function prettyResourceNames(name) { + switch (name) { + case "fsFile": + return ["A file", "opened", "closed"]; + case "fetchRequest": + return ["A fetch request", "started", "finished"]; + case "fetchRequestBody": + return ["A fetch request body", "created", "closed"]; + case "fetchResponseBody": + return ["A fetch response body", "created", "consumed"]; + case "httpClient": + return ["An HTTP client", "created", "closed"]; + case "dynamicLibrary": + return ["A dynamic library", "loaded", "unloaded"]; + case "httpConn": + return ["An inbound HTTP connection", "accepted", "closed"]; + case "httpStream": + return ["An inbound HTTP request", "accepted", "closed"]; + case "tcpStream": + return ["A TCP connection", "opened/accepted", "closed"]; + case "unixStream": + return ["A Unix connection", "opened/accepted", "closed"]; + case "tlsStream": + return ["A TLS connection", "opened/accepted", "closed"]; + case "tlsListener": + return ["A TLS listener", "opened", "closed"]; + case "unixListener": + return ["A Unix listener", "opened", "closed"]; + case "unixDatagram": + return ["A Unix datagram", "opened", "closed"]; + case "tcpListener": + return ["A TCP listener", "opened", "closed"]; + case "udpSocket": + return ["A UDP socket", "opened", "closed"]; + case "timer": + return ["A timer", "started", "fired/cleared"]; + case "textDecoder": + return ["A text decoder", "created", "finished"]; + case "messagePort": + return ["A message port", "created", "closed"]; + case "webSocketStream": + return ["A WebSocket", "opened", "closed"]; + case "fsEvents": + return ["A file system watcher", "created", "closed"]; + case "childStdin": + return ["A child process stdin", "opened", "closed"]; + case "childStdout": + return ["A child process stdout", "opened", "closed"]; + case "childStderr": + return ["A child process stderr", "opened", "closed"]; + case "child": + return ["A child process", "started", "closed"]; + case "signal": + return ["A signal listener", "created", "fired/cleared"]; + case "stdin": + return ["The stdin pipe", "opened", "closed"]; + case "stdout": + return ["The stdout pipe", "opened", "closed"]; + case "stderr": + return ["The stderr pipe", "opened", "closed"]; + case "compression": + return ["A CompressionStream", "created", "closed"]; + default: + return [`A "${name}" resource`, "created", "cleaned up"]; + } +} + +function resourceCloseHint(name) { + switch (name) { + case "fsFile": + return "Close the file handle by calling `file.close()`."; + case "fetchRequest": + return "Await the promise returned from `fetch()` or abort the fetch with an abort signal."; + case "fetchRequestBody": + return "Terminate the request body `ReadableStream` by closing or erroring it."; + case "fetchResponseBody": + return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`."; + case "httpClient": + return "Close the HTTP client by calling `httpClient.close()`."; + case "dynamicLibrary": + return "Unload the dynamic library by calling `dynamicLibrary.close()`."; + case "httpConn": + return "Close the inbound HTTP connection by calling `httpConn.close()`."; + case "httpStream": + return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection."; + case "tcpStream": + return "Close the TCP connection by calling `tcpConn.close()`."; + case "unixStream": + return "Close the Unix socket connection by calling `unixConn.close()`."; + case "tlsStream": + return "Close the TLS connection by calling `tlsConn.close()`."; + case "tlsListener": + return "Close the TLS listener by calling `tlsListener.close()`."; + case "unixListener": + return "Close the Unix socket listener by calling `unixListener.close()`."; + case "unixDatagram": + return "Close the Unix datagram socket by calling `unixDatagram.close()`."; + case "tcpListener": + return "Close the TCP listener by calling `tcpListener.close()`."; + case "udpSocket": + return "Close the UDP socket by calling `udpSocket.close()`."; + case "timer": + return "Clear the timer by calling `clearInterval` or `clearTimeout`."; + case "textDecoder": + return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`."; + case "messagePort": + return "Close the message port by calling `messagePort.close()`."; + case "webSocketStream": + return "Close the WebSocket by calling `webSocket.close()`."; + case "fsEvents": + return "Close the file system watcher by calling `watcher.close()`."; + case "childStdin": + return "Close the child process stdin by calling `proc.stdin.close()`."; + case "childStdout": + return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`."; + case "childStderr": + return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`."; + case "child": + return "Close the child process by calling `proc.kill()` or `proc.close()`."; + case "signal": + return "Clear the signal listener by calling `Deno.removeSignalListener`."; + case "stdin": + return "Close the stdin pipe by calling `Deno.stdin.close()`."; + case "stdout": + return "Close the stdout pipe by calling `Deno.stdout.close()`."; + case "stderr": + return "Close the stderr pipe by calling `Deno.stderr.close()`."; + case "compression": + return "Close the compression stream by calling `await stream.writable.close()`."; + default: + return "Close the resource before the end of the test."; + } +} + +// Wrap test function in additional assertion that makes sure +// the test case does not "leak" resources - ie. resource table after +// the test has exactly the same contents as before the test. +function assertResources(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function resourceSanitizer(desc) { + const pre = core.resources(); + await fn(desc); + + if (shouldSkipSanitizers(desc)) { + return; + } + + const post = core.resources(); + + const allResources = new Set([ + ...new SafeArrayIterator(ObjectKeys(pre)), + ...new SafeArrayIterator(ObjectKeys(post)), + ]); + + const details = []; + for (const resource of allResources) { + const preResource = pre[resource]; + const postResource = post[resource]; + if (preResource === postResource) continue; + + if (preResource === undefined) { + const [name, action1, action2] = prettyResourceNames(postResource); + const hint = resourceCloseHint(postResource); + const detail = + `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`; + ArrayPrototypePush(details, detail); } else { - msg += "\n"; + const [name, action1, action2] = prettyResourceNames(preResource); + const detail = + `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`; + ArrayPrototypePush(details, detail); } - - throw assert(false, msg); - }; - } - - function prettyResourceNames(name) { - switch (name) { - case "fsFile": - return ["A file", "opened", "closed"]; - case "fetchRequest": - return ["A fetch request", "started", "finished"]; - case "fetchRequestBody": - return ["A fetch request body", "created", "closed"]; - case "fetchResponseBody": - return ["A fetch response body", "created", "consumed"]; - case "httpClient": - return ["An HTTP client", "created", "closed"]; - case "dynamicLibrary": - return ["A dynamic library", "loaded", "unloaded"]; - case "httpConn": - return ["An inbound HTTP connection", "accepted", "closed"]; - case "httpStream": - return ["An inbound HTTP request", "accepted", "closed"]; - case "tcpStream": - return ["A TCP connection", "opened/accepted", "closed"]; - case "unixStream": - return ["A Unix connection", "opened/accepted", "closed"]; - case "tlsStream": - return ["A TLS connection", "opened/accepted", "closed"]; - case "tlsListener": - return ["A TLS listener", "opened", "closed"]; - case "unixListener": - return ["A Unix listener", "opened", "closed"]; - case "unixDatagram": - return ["A Unix datagram", "opened", "closed"]; - case "tcpListener": - return ["A TCP listener", "opened", "closed"]; - case "udpSocket": - return ["A UDP socket", "opened", "closed"]; - case "timer": - return ["A timer", "started", "fired/cleared"]; - case "textDecoder": - return ["A text decoder", "created", "finished"]; - case "messagePort": - return ["A message port", "created", "closed"]; - case "webSocketStream": - return ["A WebSocket", "opened", "closed"]; - case "fsEvents": - return ["A file system watcher", "created", "closed"]; - case "childStdin": - return ["A child process stdin", "opened", "closed"]; - case "childStdout": - return ["A child process stdout", "opened", "closed"]; - case "childStderr": - return ["A child process stderr", "opened", "closed"]; - case "child": - return ["A child process", "started", "closed"]; - case "signal": - return ["A signal listener", "created", "fired/cleared"]; - case "stdin": - return ["The stdin pipe", "opened", "closed"]; - case "stdout": - return ["The stdout pipe", "opened", "closed"]; - case "stderr": - return ["The stderr pipe", "opened", "closed"]; - case "compression": - return ["A CompressionStream", "created", "closed"]; - default: - return [`A "${name}" resource`, "created", "cleaned up"]; } - } - function resourceCloseHint(name) { - switch (name) { - case "fsFile": - return "Close the file handle by calling `file.close()`."; - case "fetchRequest": - return "Await the promise returned from `fetch()` or abort the fetch with an abort signal."; - case "fetchRequestBody": - return "Terminate the request body `ReadableStream` by closing or erroring it."; - case "fetchResponseBody": - return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`."; - case "httpClient": - return "Close the HTTP client by calling `httpClient.close()`."; - case "dynamicLibrary": - return "Unload the dynamic library by calling `dynamicLibrary.close()`."; - case "httpConn": - return "Close the inbound HTTP connection by calling `httpConn.close()`."; - case "httpStream": - return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection."; - case "tcpStream": - return "Close the TCP connection by calling `tcpConn.close()`."; - case "unixStream": - return "Close the Unix socket connection by calling `unixConn.close()`."; - case "tlsStream": - return "Close the TLS connection by calling `tlsConn.close()`."; - case "tlsListener": - return "Close the TLS listener by calling `tlsListener.close()`."; - case "unixListener": - return "Close the Unix socket listener by calling `unixListener.close()`."; - case "unixDatagram": - return "Close the Unix datagram socket by calling `unixDatagram.close()`."; - case "tcpListener": - return "Close the TCP listener by calling `tcpListener.close()`."; - case "udpSocket": - return "Close the UDP socket by calling `udpSocket.close()`."; - case "timer": - return "Clear the timer by calling `clearInterval` or `clearTimeout`."; - case "textDecoder": - return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`."; - case "messagePort": - return "Close the message port by calling `messagePort.close()`."; - case "webSocketStream": - return "Close the WebSocket by calling `webSocket.close()`."; - case "fsEvents": - return "Close the file system watcher by calling `watcher.close()`."; - case "childStdin": - return "Close the child process stdin by calling `proc.stdin.close()`."; - case "childStdout": - return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`."; - case "childStderr": - return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`."; - case "child": - return "Close the child process by calling `proc.kill()` or `proc.close()`."; - case "signal": - return "Clear the signal listener by calling `Deno.removeSignalListener`."; - case "stdin": - return "Close the stdin pipe by calling `Deno.stdin.close()`."; - case "stdout": - return "Close the stdout pipe by calling `Deno.stdout.close()`."; - case "stderr": - return "Close the stderr pipe by calling `Deno.stderr.close()`."; - case "compression": - return "Close the compression stream by calling `await stream.writable.close()`."; - default: - return "Close the resource before the end of the test."; - } - } - - // Wrap test function in additional assertion that makes sure - // the test case does not "leak" resources - ie. resource table after - // the test has exactly the same contents as before the test. - function assertResources(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function resourceSanitizer(desc) { - const pre = core.resources(); - await fn(desc); - - if (shouldSkipSanitizers(desc)) { - return; - } - - const post = core.resources(); - - const allResources = new Set([ - ...new SafeArrayIterator(ObjectKeys(pre)), - ...new SafeArrayIterator(ObjectKeys(post)), - ]); - - const details = []; - for (const resource of allResources) { - const preResource = pre[resource]; - const postResource = post[resource]; - if (preResource === postResource) continue; - - if (preResource === undefined) { - const [name, action1, action2] = prettyResourceNames(postResource); - const hint = resourceCloseHint(postResource); - const detail = - `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`; - ArrayPrototypePush(details, detail); - } else { - const [name, action1, action2] = prettyResourceNames(preResource); - const detail = - `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`; - ArrayPrototypePush(details, detail); - } - } - - const message = `Test case is leaking ${details.length} resource${ - details.length === 1 ? "" : "s" - }: + const message = `Test case is leaking ${details.length} resource${ + details.length === 1 ? "" : "s" + }: - ${details.join("\n - ")} `; - assert(details.length === 0, message); - }; - } + assert(details.length === 0, message); + }; +} - // Wrap test function in additional assertion that makes sure - // that the test case does not accidentally exit prematurely. - function assertExit(fn, isTest) { - return async function exitSanitizer(...params) { - setExitHandler((exitCode) => { - assert( - false, - `${ - isTest ? "Test case" : "Bench" - } attempted to exit with exit code: ${exitCode}`, +// Wrap test function in additional assertion that makes sure +// that the test case does not accidentally exit prematurely. +function assertExit(fn, isTest) { + return async function exitSanitizer(...params) { + setExitHandler((exitCode) => { + assert( + false, + `${ + isTest ? "Test case" : "Bench" + } attempted to exit with exit code: ${exitCode}`, + ); + }); + + try { + await fn(...new SafeArrayIterator(params)); + } catch (err) { + throw err; + } finally { + setExitHandler(null); + } + }; +} + +function assertTestStepScopes(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function testStepSanitizer(desc) { + preValidation(); + // only report waiting after pre-validation + if (canStreamReporting(desc) && "parent" in desc) { + stepReportWait(desc); + } + await fn(MapPrototypeGet(testStates, desc.id).context); + testStepPostValidation(desc); + + function preValidation() { + const runningStepDescs = getRunningStepDescs(); + const runningStepDescsWithSanitizers = ArrayPrototypeFilter( + runningStepDescs, + (d) => usesSanitizer(d), + ); + + if (runningStepDescsWithSanitizers.length > 0) { + throw new Error( + "Cannot start test step while another test step with sanitizers is running.\n" + + runningStepDescsWithSanitizers + .map((d) => ` * ${getFullName(d)}`) + .join("\n"), ); - }); - - try { - await fn(...new SafeArrayIterator(params)); - } catch (err) { - throw err; - } finally { - setExitHandler(null); } - }; - } - function assertTestStepScopes(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function testStepSanitizer(desc) { - preValidation(); - // only report waiting after pre-validation - if (canStreamReporting(desc) && "parent" in desc) { - stepReportWait(desc); - } - await fn(MapPrototypeGet(testStates, desc.id).context); - testStepPostValidation(desc); - - function preValidation() { - const runningStepDescs = getRunningStepDescs(); - const runningStepDescsWithSanitizers = ArrayPrototypeFilter( - runningStepDescs, - (d) => usesSanitizer(d), + if (usesSanitizer(desc) && runningStepDescs.length > 0) { + throw new Error( + "Cannot start test step with sanitizers while another test step is running.\n" + + runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"), ); + } - if (runningStepDescsWithSanitizers.length > 0) { - throw new Error( - "Cannot start test step while another test step with sanitizers is running.\n" + - runningStepDescsWithSanitizers - .map((d) => ` * ${getFullName(d)}`) - .join("\n"), - ); - } - - if (usesSanitizer(desc) && runningStepDescs.length > 0) { - throw new Error( - "Cannot start test step with sanitizers while another test step is running.\n" + - runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"), - ); - } - - function getRunningStepDescs() { - const results = []; - let childDesc = desc; - while (childDesc.parent != null) { - const state = MapPrototypeGet(testStates, childDesc.parent.id); - for (const siblingDesc of state.children) { - if (siblingDesc.id == childDesc.id) { - continue; - } - const siblingState = MapPrototypeGet(testStates, siblingDesc.id); - if (!siblingState.finalized) { - ArrayPrototypePush(results, siblingDesc); - } + function getRunningStepDescs() { + const results = []; + let childDesc = desc; + while (childDesc.parent != null) { + const state = MapPrototypeGet(testStates, childDesc.parent.id); + for (const siblingDesc of state.children) { + if (siblingDesc.id == childDesc.id) { + continue; + } + const siblingState = MapPrototypeGet(testStates, siblingDesc.id); + if (!siblingState.finalized) { + ArrayPrototypePush(results, siblingDesc); } - childDesc = childDesc.parent; } - return results; + childDesc = childDesc.parent; } + return results; } - }; + } + }; +} + +function testStepPostValidation(desc) { + // check for any running steps + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + if (MapPrototypeGet(testStates, childDesc.id).status == "pending") { + throw new Error( + "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); + } } - function testStepPostValidation(desc) { - // check for any running steps - for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { - if (MapPrototypeGet(testStates, childDesc.id).status == "pending") { - throw new Error( - "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + // check if an ancestor already completed + let currentDesc = desc.parent; + while (currentDesc != null) { + if (MapPrototypeGet(testStates, currentDesc.id).finalized) { + throw new Error( + "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); + } + currentDesc = currentDesc.parent; + } +} + +function pledgePermissions(permissions) { + return ops.op_pledge_test_permissions( + serializePermissions(permissions), + ); +} + +function restorePermissions(token) { + ops.op_restore_test_permissions(token); +} + +function withPermissions(fn, permissions) { + return async function applyPermissions(...params) { + const token = pledgePermissions(permissions); + + try { + await fn(...new SafeArrayIterator(params)); + } finally { + restorePermissions(token); + } + }; +} + +/** + * @typedef {{ + * id: number, + * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * filteredOut: boolean, + * ignore: boolean, + * only: boolean. + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * permissions: PermissionOptions, + * }} TestDescription + * + * @typedef {{ + * id: number, + * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * ignore: boolean, + * level: number, + * parent: TestDescription | TestStepDescription, + * rootId: number, + * rootName: String, + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * }} TestStepDescription + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * }} TestState + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * status: "pending" | "ok" | ""failed" | ignored", + * error: unknown, + * elapsed: number | null, + * reportedWait: boolean, + * reportedResult: boolean, + * }} TestStepState + * + * @typedef {{ + * id: number, + * name: string, + * fn: BenchFunction + * origin: string, + * filteredOut: boolean, + * ignore: boolean, + * only: boolean. + * sanitizeExit: boolean, + * permissions: PermissionOptions, + * }} BenchDescription + */ + +/** @type {TestDescription[]} */ +const testDescs = []; +/** @type {Map} */ +const testStates = new Map(); +/** @type {BenchDescription[]} */ +const benchDescs = []; +let isTestSubcommand = false; +let isBenchSubcommand = false; + +// Main test function provided by Deno. +function test( + nameOrFnOrOptions, + optionsOrFn, + maybeFn, +) { + if (!isTestSubcommand) { + return; + } + + let testDesc; + const defaults = { + ignore: false, + only: false, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true, + permissions: null, + }; + + if (typeof nameOrFnOrOptions === "string") { + if (!nameOrFnOrOptions) { + throw new TypeError("The test name can't be empty"); + } + if (typeof optionsOrFn === "function") { + testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; + } else { + if (!maybeFn || typeof maybeFn !== "function") { + throw new TypeError("Missing test function"); + } + if (optionsOrFn.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, test function is already provided as the third argument.", ); } - } - - // check if an ancestor already completed - let currentDesc = desc.parent; - while (currentDesc != null) { - if (MapPrototypeGet(testStates, currentDesc.id).finalized) { - throw new Error( - "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + if (optionsOrFn.name != undefined) { + throw new TypeError( + "Unexpected 'name' field in options, test name is already provided as the first argument.", ); } - currentDesc = currentDesc.parent; - } - } - - function pledgePermissions(permissions) { - return ops.op_pledge_test_permissions( - serializePermissions(permissions), - ); - } - - function restorePermissions(token) { - ops.op_restore_test_permissions(token); - } - - function withPermissions(fn, permissions) { - return async function applyPermissions(...params) { - const token = pledgePermissions(permissions); - - try { - await fn(...new SafeArrayIterator(params)); - } finally { - restorePermissions(token); - } - }; - } - - /** - * @typedef {{ - * id: number, - * name: string, - * fn: TestFunction - * origin: string, - * location: TestLocation, - * filteredOut: boolean, - * ignore: boolean, - * only: boolean. - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * permissions: PermissionOptions, - * }} TestDescription - * - * @typedef {{ - * id: number, - * name: string, - * fn: TestFunction - * origin: string, - * location: TestLocation, - * ignore: boolean, - * level: number, - * parent: TestDescription | TestStepDescription, - * rootId: number, - * rootName: String, - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} TestStepDescription - * - * @typedef {{ - * context: TestContext, - * children: TestStepDescription[], - * finalized: boolean, - * }} TestState - * - * @typedef {{ - * context: TestContext, - * children: TestStepDescription[], - * finalized: boolean, - * status: "pending" | "ok" | ""failed" | ignored", - * error: unknown, - * elapsed: number | null, - * reportedWait: boolean, - * reportedResult: boolean, - * }} TestStepState - * - * @typedef {{ - * id: number, - * name: string, - * fn: BenchFunction - * origin: string, - * filteredOut: boolean, - * ignore: boolean, - * only: boolean. - * sanitizeExit: boolean, - * permissions: PermissionOptions, - * }} BenchDescription - */ - - /** @type {TestDescription[]} */ - const testDescs = []; - /** @type {Map} */ - const testStates = new Map(); - /** @type {BenchDescription[]} */ - const benchDescs = []; - let isTestSubcommand = false; - let isBenchSubcommand = false; - - // Main test function provided by Deno. - function test( - nameOrFnOrOptions, - optionsOrFn, - maybeFn, - ) { - if (!isTestSubcommand) { - return; - } - - let testDesc; - const defaults = { - ignore: false, - only: false, - sanitizeOps: true, - sanitizeResources: true, - sanitizeExit: true, - permissions: null, - }; - - if (typeof nameOrFnOrOptions === "string") { - if (!nameOrFnOrOptions) { - throw new TypeError("The test name can't be empty"); - } - if (typeof optionsOrFn === "function") { - testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; - } else { - if (!maybeFn || typeof maybeFn !== "function") { - throw new TypeError("Missing test function"); - } - if (optionsOrFn.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, test function is already provided as the third argument.", - ); - } - if (optionsOrFn.name != undefined) { - throw new TypeError( - "Unexpected 'name' field in options, test name is already provided as the first argument.", - ); - } - testDesc = { - ...defaults, - ...optionsOrFn, - fn: maybeFn, - name: nameOrFnOrOptions, - }; - } - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The test function must have a name"); - } - if (optionsOrFn != undefined) { - throw new TypeError("Unexpected second argument to Deno.test()"); - } - if (maybeFn != undefined) { - throw new TypeError("Unexpected third argument to Deno.test()"); - } testDesc = { ...defaults, - fn: nameOrFnOrOptions, - name: nameOrFnOrOptions.name, + ...optionsOrFn, + fn: maybeFn, + name: nameOrFnOrOptions, }; - } else { - let fn; - let name; - if (typeof optionsOrFn === "function") { - fn = optionsOrFn; - if (nameOrFnOrOptions.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, test function is already provided as the second argument.", - ); - } - name = nameOrFnOrOptions.name ?? fn.name; - } else { - if ( - !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" - ) { - throw new TypeError( - "Expected 'fn' field in the first argument to be a test function.", - ); - } - fn = nameOrFnOrOptions.fn; - name = nameOrFnOrOptions.name ?? fn.name; - } - if (!name) { - throw new TypeError("The test name can't be empty"); - } - testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - - // Delete this prop in case the user passed it. It's used to detect steps. - delete testDesc.parent; - testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc); - if (testDesc.permissions) { - testDesc.fn = withPermissions( - testDesc.fn, - testDesc.permissions, - ); + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The test function must have a name"); } - testDesc.origin = getTestOrigin(); - const jsError = core.destructureError(new Error()); - testDesc.location = { - fileName: jsError.frames[1].fileName, - lineNumber: jsError.frames[1].lineNumber, - columnNumber: jsError.frames[1].columnNumber, + if (optionsOrFn != undefined) { + throw new TypeError("Unexpected second argument to Deno.test()"); + } + if (maybeFn != undefined) { + throw new TypeError("Unexpected third argument to Deno.test()"); + } + testDesc = { + ...defaults, + fn: nameOrFnOrOptions, + name: nameOrFnOrOptions.name, }; - - const { id, filteredOut } = ops.op_register_test(testDesc); - testDesc.id = id; - testDesc.filteredOut = filteredOut; - - ArrayPrototypePush(testDescs, testDesc); - MapPrototypeSet(testStates, testDesc.id, { - context: createTestContext(testDesc), - children: [], - finalized: false, - }); + } else { + let fn; + let name; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + if (nameOrFnOrOptions.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, test function is already provided as the second argument.", + ); + } + name = nameOrFnOrOptions.name ?? fn.name; + } else { + if ( + !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" + ) { + throw new TypeError( + "Expected 'fn' field in the first argument to be a test function.", + ); + } + fn = nameOrFnOrOptions.fn; + name = nameOrFnOrOptions.name ?? fn.name; + } + if (!name) { + throw new TypeError("The test name can't be empty"); + } + testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - // Main bench function provided by Deno. - function bench( - nameOrFnOrOptions, - optionsOrFn, - maybeFn, - ) { - if (!isBenchSubcommand) { - return; + // Delete this prop in case the user passed it. It's used to detect steps. + delete testDesc.parent; + testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc); + if (testDesc.permissions) { + testDesc.fn = withPermissions( + testDesc.fn, + testDesc.permissions, + ); + } + testDesc.origin = getTestOrigin(); + const jsError = core.destructureError(new Error()); + testDesc.location = { + fileName: jsError.frames[1].fileName, + lineNumber: jsError.frames[1].lineNumber, + columnNumber: jsError.frames[1].columnNumber, + }; + + const { id, filteredOut } = ops.op_register_test(testDesc); + testDesc.id = id; + testDesc.filteredOut = filteredOut; + + ArrayPrototypePush(testDescs, testDesc); + MapPrototypeSet(testStates, testDesc.id, { + context: createTestContext(testDesc), + children: [], + finalized: false, + }); +} + +// Main bench function provided by Deno. +function bench( + nameOrFnOrOptions, + optionsOrFn, + maybeFn, +) { + if (!isBenchSubcommand) { + return; + } + + let benchDesc; + const defaults = { + ignore: false, + baseline: false, + only: false, + sanitizeExit: true, + permissions: null, + }; + + if (typeof nameOrFnOrOptions === "string") { + if (!nameOrFnOrOptions) { + throw new TypeError("The bench name can't be empty"); } - - let benchDesc; - const defaults = { - ignore: false, - baseline: false, - only: false, - sanitizeExit: true, - permissions: null, - }; - - if (typeof nameOrFnOrOptions === "string") { - if (!nameOrFnOrOptions) { - throw new TypeError("The bench name can't be empty"); + if (typeof optionsOrFn === "function") { + benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; + } else { + if (!maybeFn || typeof maybeFn !== "function") { + throw new TypeError("Missing bench function"); } - if (typeof optionsOrFn === "function") { - benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; - } else { - if (!maybeFn || typeof maybeFn !== "function") { - throw new TypeError("Missing bench function"); - } - if (optionsOrFn.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, bench function is already provided as the third argument.", - ); - } - if (optionsOrFn.name != undefined) { - throw new TypeError( - "Unexpected 'name' field in options, bench name is already provided as the first argument.", - ); - } - benchDesc = { - ...defaults, - ...optionsOrFn, - fn: maybeFn, - name: nameOrFnOrOptions, - }; + if (optionsOrFn.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, bench function is already provided as the third argument.", + ); } - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The bench function must have a name"); - } - if (optionsOrFn != undefined) { - throw new TypeError("Unexpected second argument to Deno.bench()"); - } - if (maybeFn != undefined) { - throw new TypeError("Unexpected third argument to Deno.bench()"); + if (optionsOrFn.name != undefined) { + throw new TypeError( + "Unexpected 'name' field in options, bench name is already provided as the first argument.", + ); } benchDesc = { ...defaults, - fn: nameOrFnOrOptions, - name: nameOrFnOrOptions.name, + ...optionsOrFn, + fn: maybeFn, + name: nameOrFnOrOptions, }; - } else { - let fn; - let name; - if (typeof optionsOrFn === "function") { - fn = optionsOrFn; - if (nameOrFnOrOptions.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, bench function is already provided as the second argument.", - ); - } - name = nameOrFnOrOptions.name ?? fn.name; - } else { - if ( - !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" - ) { - throw new TypeError( - "Expected 'fn' field in the first argument to be a bench function.", - ); - } - fn = nameOrFnOrOptions.fn; - name = nameOrFnOrOptions.name ?? fn.name; - } - if (!name) { - throw new TypeError("The bench name can't be empty"); - } - benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - - benchDesc.origin = getBenchOrigin(); - const AsyncFunction = (async () => {}).constructor; - benchDesc.async = AsyncFunction === benchDesc.fn.constructor; - - const { id, filteredOut } = ops.op_register_bench(benchDesc); - benchDesc.id = id; - benchDesc.filteredOut = filteredOut; - - ArrayPrototypePush(benchDescs, benchDesc); - } - - async function runTest(desc) { - if (desc.ignore) { - return "ignored"; + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The bench function must have a name"); } - - try { - await desc.fn(desc); - const failCount = failedChildStepsCount(desc); - return failCount === 0 ? "ok" : { - "failed": core.destructureError( - new Error( - `${failCount} test step${failCount === 1 ? "" : "s"} failed.`, - ), - ), - }; - } catch (error) { - return { - "failed": core.destructureError(error), - }; - } finally { - const state = MapPrototypeGet(testStates, desc.id); - state.finalized = true; - // ensure the children report their result - for (const childDesc of state.children) { - stepReportResult(childDesc); - } + if (optionsOrFn != undefined) { + throw new TypeError("Unexpected second argument to Deno.bench()"); } - } - - function compareMeasurements(a, b) { - if (a > b) return 1; - if (a < b) return -1; - - return 0; - } - - function benchStats(n, highPrecision, avg, min, max, all) { - return { - n, - min, - max, - p75: all[MathCeil(n * (75 / 100)) - 1], - p99: all[MathCeil(n * (99 / 100)) - 1], - p995: all[MathCeil(n * (99.5 / 100)) - 1], - p999: all[MathCeil(n * (99.9 / 100)) - 1], - avg: !highPrecision ? (avg / n) : MathCeil(avg / n), + if (maybeFn != undefined) { + throw new TypeError("Unexpected third argument to Deno.bench()"); + } + benchDesc = { + ...defaults, + fn: nameOrFnOrOptions, + name: nameOrFnOrOptions.name, }; + } else { + let fn; + let name; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + if (nameOrFnOrOptions.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, bench function is already provided as the second argument.", + ); + } + name = nameOrFnOrOptions.name ?? fn.name; + } else { + if ( + !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" + ) { + throw new TypeError( + "Expected 'fn' field in the first argument to be a bench function.", + ); + } + fn = nameOrFnOrOptions.fn; + name = nameOrFnOrOptions.name ?? fn.name; + } + if (!name) { + throw new TypeError("The bench name can't be empty"); + } + benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - async function benchMeasure(timeBudget, desc) { - const fn = desc.fn; - let n = 0; - let avg = 0; - let wavg = 0; - const all = []; - let min = Infinity; - let max = -Infinity; - const lowPrecisionThresholdInNs = 1e4; + benchDesc.origin = getBenchOrigin(); + const AsyncFunction = (async () => {}).constructor; + benchDesc.async = AsyncFunction === benchDesc.fn.constructor; - // warmup step - let c = 0; - let iterations = 20; - let budget = 10 * 1e6; + const { id, filteredOut } = ops.op_register_bench(benchDesc); + benchDesc.id = id; + benchDesc.filteredOut = filteredOut; + + ArrayPrototypePush(benchDescs, benchDesc); +} + +async function runTest(desc) { + if (desc.ignore) { + return "ignored"; + } + + try { + await desc.fn(desc); + const failCount = failedChildStepsCount(desc); + return failCount === 0 ? "ok" : { + "failed": core.destructureError( + new Error( + `${failCount} test step${failCount === 1 ? "" : "s"} failed.`, + ), + ), + }; + } catch (error) { + return { + "failed": core.destructureError(error), + }; + } finally { + const state = MapPrototypeGet(testStates, desc.id); + state.finalized = true; + // ensure the children report their result + for (const childDesc of state.children) { + stepReportResult(childDesc); + } + } +} + +function compareMeasurements(a, b) { + if (a > b) return 1; + if (a < b) return -1; + + return 0; +} + +function benchStats(n, highPrecision, avg, min, max, all) { + return { + n, + min, + max, + p75: all[MathCeil(n * (75 / 100)) - 1], + p99: all[MathCeil(n * (99 / 100)) - 1], + p995: all[MathCeil(n * (99.5 / 100)) - 1], + p999: all[MathCeil(n * (99.9 / 100)) - 1], + avg: !highPrecision ? (avg / n) : MathCeil(avg / n), + }; +} + +async function benchMeasure(timeBudget, desc) { + const fn = desc.fn; + let n = 0; + let avg = 0; + let wavg = 0; + const all = []; + let min = Infinity; + let max = -Infinity; + const lowPrecisionThresholdInNs = 1e4; + + // warmup step + let c = 0; + let iterations = 20; + let budget = 10 * 1e6; + + if (!desc.async) { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + + fn(); + const iterationTime = benchNow() - t1; + + c++; + wavg += iterationTime; + budget -= iterationTime; + } + } else { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + + await fn(); + const iterationTime = benchNow() - t1; + + c++; + wavg += iterationTime; + budget -= iterationTime; + } + } + + wavg /= c; + + // measure step + if (wavg > lowPrecisionThresholdInNs) { + let iterations = 10; + let budget = timeBudget * 1e6; if (!desc.async) { while (budget > 0 || iterations-- > 0) { @@ -902,9 +933,12 @@ fn(); const iterationTime = benchNow() - t1; - c++; - wavg += iterationTime; + n++; + avg += iterationTime; budget -= iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; } } else { while (budget > 0 || iterations-- > 0) { @@ -913,520 +947,483 @@ await fn(); const iterationTime = benchNow() - t1; - c++; - wavg += iterationTime; + n++; + avg += iterationTime; budget -= iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; } } + } else { + let iterations = 10; + let budget = timeBudget * 1e6; - wavg /= c; + if (!desc.async) { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); + const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - // measure step - if (wavg > lowPrecisionThresholdInNs) { - let iterations = 10; - let budget = timeBudget * 1e6; - - if (!desc.async) { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - - fn(); - const iterationTime = benchNow() - t1; - - n++; - avg += iterationTime; - budget -= iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - } - } else { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - - await fn(); - const iterationTime = benchNow() - t1; - - n++; - avg += iterationTime; - budget -= iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - } + n++; + avg += iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; + budget -= iterationTime * lowPrecisionThresholdInNs; } } else { - let iterations = 10; - let budget = timeBudget * 1e6; + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn(); + const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - if (!desc.async) { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); - const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - - n++; - avg += iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - budget -= iterationTime * lowPrecisionThresholdInNs; - } - } else { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn(); - const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - - n++; - avg += iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - budget -= iterationTime * lowPrecisionThresholdInNs; - } + n++; + avg += iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; + budget -= iterationTime * lowPrecisionThresholdInNs; } } - - all.sort(compareMeasurements); - return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); } - async function runBench(desc) { - let token = null; + all.sort(compareMeasurements); + return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); +} - try { - if (desc.permissions) { - token = pledgePermissions(desc.permissions); - } +async function runBench(desc) { + let token = null; - if (desc.sanitizeExit) { - setExitHandler((exitCode) => { - assert( - false, - `Bench attempted to exit with exit code: ${exitCode}`, - ); - }); - } + try { + if (desc.permissions) { + token = pledgePermissions(desc.permissions); + } - const benchTimeInMs = 500; - const stats = await benchMeasure(benchTimeInMs, desc); + if (desc.sanitizeExit) { + setExitHandler((exitCode) => { + assert( + false, + `Bench attempted to exit with exit code: ${exitCode}`, + ); + }); + } - return { ok: stats }; - } catch (error) { - return { failed: core.destructureError(error) }; - } finally { - if (bench.sanitizeExit) setExitHandler(null); - if (token !== null) restorePermissions(token); + const benchTimeInMs = 500; + const stats = await benchMeasure(benchTimeInMs, desc); + + return { ok: stats }; + } catch (error) { + return { failed: core.destructureError(error) }; + } finally { + if (bench.sanitizeExit) setExitHandler(null); + if (token !== null) restorePermissions(token); + } +} + +let origin = null; + +function getTestOrigin() { + if (origin == null) { + origin = ops.op_get_test_origin(); + } + return origin; +} + +function getBenchOrigin() { + if (origin == null) { + origin = ops.op_get_bench_origin(); + } + return origin; +} + +function benchNow() { + return ops.op_bench_now(); +} + +function enableTest() { + isTestSubcommand = true; +} + +function enableBench() { + isBenchSubcommand = true; +} + +async function runTests({ + shuffle = null, +} = {}) { + core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); + + const origin = getTestOrigin(); + const only = ArrayPrototypeFilter(testDescs, (test) => test.only); + const filtered = ArrayPrototypeFilter( + only.length > 0 ? only : testDescs, + (desc) => !desc.filteredOut, + ); + + ops.op_dispatch_test_event({ + plan: { + origin, + total: filtered.length, + filteredOut: testDescs.length - filtered.length, + usedOnly: only.length > 0, + }, + }); + + if (shuffle !== null) { + // http://en.wikipedia.org/wiki/Linear_congruential_generator + // Use BigInt for everything because the random seed is u64. + const nextInt = function (state) { + const m = 0x80000000n; + const a = 1103515245n; + const c = 12345n; + + return function (max) { + return state = ((a * state + c) % m) % BigInt(max); + }; + }(BigInt(shuffle)); + + for (let i = filtered.length - 1; i > 0; i--) { + const j = nextInt(i); + [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; } } - let origin = null; - - function getTestOrigin() { - if (origin == null) { - origin = ops.op_get_test_origin(); + for (const desc of filtered) { + if (ops.op_tests_should_stop()) { + break; } - return origin; - } - - function getBenchOrigin() { - if (origin == null) { - origin = ops.op_get_bench_origin(); - } - return origin; - } - - function benchNow() { - return ops.op_bench_now(); - } - - function enableTest() { - isTestSubcommand = true; - } - - function enableBench() { - isBenchSubcommand = true; - } - - async function runTests({ - shuffle = null, - } = {}) { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - - const origin = getTestOrigin(); - const only = ArrayPrototypeFilter(testDescs, (test) => test.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : testDescs, - (desc) => !desc.filteredOut, - ); - + ops.op_dispatch_test_event({ wait: desc.id }); + const earlier = DateNow(); + const result = await runTest(desc); + const elapsed = DateNow() - earlier; ops.op_dispatch_test_event({ - plan: { - origin, - total: filtered.length, - filteredOut: testDescs.length - filtered.length, - usedOnly: only.length > 0, - }, + result: [desc.id, result, elapsed], }); + } +} - if (shuffle !== null) { - // http://en.wikipedia.org/wiki/Linear_congruential_generator - // Use BigInt for everything because the random seed is u64. - const nextInt = function (state) { - const m = 0x80000000n; - const a = 1103515245n; - const c = 12345n; +async function runBenchmarks() { + core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - return function (max) { - return state = ((a * state + c) % m) % BigInt(max); - }; - }(BigInt(shuffle)); + const origin = getBenchOrigin(); + const originalConsole = globalThis.console; - for (let i = filtered.length - 1; i > 0; i--) { - const j = nextInt(i); - [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; - } - } + globalThis.console = new Console((s) => { + ops.op_dispatch_bench_event({ output: s }); + }); - for (const desc of filtered) { - if (ops.op_tests_should_stop()) { - break; - } - ops.op_dispatch_test_event({ wait: desc.id }); - const earlier = DateNow(); - const result = await runTest(desc); - const elapsed = DateNow() - earlier; - ops.op_dispatch_test_event({ - result: [desc.id, result, elapsed], - }); - } + const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only); + const filtered = ArrayPrototypeFilter( + only.length > 0 ? only : benchDescs, + (desc) => !desc.filteredOut && !desc.ignore, + ); + + let groups = new Set(); + // make sure ungrouped benchmarks are placed above grouped + groups.add(undefined); + + for (const desc of filtered) { + desc.group ||= undefined; + groups.add(desc.group); } - async function runBenchmarks() { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); + groups = ArrayFrom(groups); + ArrayPrototypeSort( + filtered, + (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), + ); - const origin = getBenchOrigin(); - const originalConsole = globalThis.console; - - globalThis.console = new Console((s) => { - ops.op_dispatch_bench_event({ output: s }); - }); - - const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : benchDescs, - (desc) => !desc.filteredOut && !desc.ignore, - ); - - let groups = new Set(); - // make sure ungrouped benchmarks are placed above grouped - groups.add(undefined); - - for (const desc of filtered) { - desc.group ||= undefined; - groups.add(desc.group); - } - - groups = ArrayFrom(groups); - ArrayPrototypeSort( - filtered, - (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), - ); + ops.op_dispatch_bench_event({ + plan: { + origin, + total: filtered.length, + usedOnly: only.length > 0, + names: ArrayPrototypeMap(filtered, (desc) => desc.name), + }, + }); + for (const desc of filtered) { + desc.baseline = !!desc.baseline; + ops.op_dispatch_bench_event({ wait: desc.id }); ops.op_dispatch_bench_event({ - plan: { - origin, - total: filtered.length, - usedOnly: only.length > 0, - names: ArrayPrototypeMap(filtered, (desc) => desc.name), - }, + result: [desc.id, await runBench(desc)], }); - - for (const desc of filtered) { - desc.baseline = !!desc.baseline; - ops.op_dispatch_bench_event({ wait: desc.id }); - ops.op_dispatch_bench_event({ - result: [desc.id, await runBench(desc)], - }); - } - - globalThis.console = originalConsole; } - function getFullName(desc) { - if ("parent" in desc) { - return `${desc.parent.name} > ${desc.name}`; - } - return desc.name; - } + globalThis.console = originalConsole; +} - function usesSanitizer(desc) { - return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit; +function getFullName(desc) { + if ("parent" in desc) { + return `${desc.parent.name} > ${desc.name}`; } + return desc.name; +} - function canStreamReporting(desc) { - let currentDesc = desc; - while (currentDesc != null) { - if (!usesSanitizer(currentDesc)) { - return false; - } - currentDesc = currentDesc.parent; +function usesSanitizer(desc) { + return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit; +} + +function canStreamReporting(desc) { + let currentDesc = desc; + while (currentDesc != null) { + if (!usesSanitizer(currentDesc)) { + return false; } - for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { - const state = MapPrototypeGet(testStates, childDesc.id); - if (!usesSanitizer(childDesc) && !state.finalized) { - return false; - } + currentDesc = currentDesc.parent; + } + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + const state = MapPrototypeGet(testStates, childDesc.id); + if (!usesSanitizer(childDesc) && !state.finalized) { + return false; } + } + return true; +} + +function stepReportWait(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedWait) { + return; + } + ops.op_dispatch_test_event({ stepWait: desc.id }); + state.reportedWait = true; +} + +function stepReportResult(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedResult) { + return; + } + stepReportWait(desc); + for (const childDesc of state.children) { + stepReportResult(childDesc); + } + let result; + if (state.status == "pending" || state.status == "failed") { + result = { + [state.status]: state.error && core.destructureError(state.error), + }; + } else { + result = state.status; + } + ops.op_dispatch_test_event({ + stepResult: [desc.id, result, state.elapsed], + }); + state.reportedResult = true; +} + +function failedChildStepsCount(desc) { + return ArrayPrototypeFilter( + MapPrototypeGet(testStates, desc.id).children, + (d) => MapPrototypeGet(testStates, d.id).status === "failed", + ).length; +} + +/** If a test validation error already occurred then don't bother checking + * the sanitizers as that will create extra noise. + */ +function shouldSkipSanitizers(desc) { + try { + testStepPostValidation(desc); + return false; + } catch { return true; } +} - function stepReportWait(desc) { - const state = MapPrototypeGet(testStates, desc.id); - if (state.reportedWait) { - return; - } - ops.op_dispatch_test_event({ stepWait: desc.id }); - state.reportedWait = true; +/** @param desc {TestDescription | TestStepDescription} */ +function createTestContext(desc) { + let parent; + let level; + let rootId; + let rootName; + if ("parent" in desc) { + parent = MapPrototypeGet(testStates, desc.parent.id).context; + level = desc.level; + rootId = desc.rootId; + rootName = desc.rootName; + } else { + parent = undefined; + level = 0; + rootId = desc.id; + rootName = desc.name; } - - function stepReportResult(desc) { - const state = MapPrototypeGet(testStates, desc.id); - if (state.reportedResult) { - return; - } - stepReportWait(desc); - for (const childDesc of state.children) { - stepReportResult(childDesc); - } - let result; - if (state.status == "pending" || state.status == "failed") { - result = { - [state.status]: state.error && core.destructureError(state.error), - }; - } else { - result = state.status; - } - ops.op_dispatch_test_event({ - stepResult: [desc.id, result, state.elapsed], - }); - state.reportedResult = true; - } - - function failedChildStepsCount(desc) { - return ArrayPrototypeFilter( - MapPrototypeGet(testStates, desc.id).children, - (d) => MapPrototypeGet(testStates, d.id).status === "failed", - ).length; - } - - /** If a test validation error already occurred then don't bother checking - * the sanitizers as that will create extra noise. - */ - function shouldSkipSanitizers(desc) { - try { - testStepPostValidation(desc); - return false; - } catch { - return true; - } - } - - /** @param desc {TestDescription | TestStepDescription} */ - function createTestContext(desc) { - let parent; - let level; - let rootId; - let rootName; - if ("parent" in desc) { - parent = MapPrototypeGet(testStates, desc.parent.id).context; - level = desc.level; - rootId = desc.rootId; - rootName = desc.rootName; - } else { - parent = undefined; - level = 0; - rootId = desc.id; - rootName = desc.name; - } - return { - [SymbolToStringTag]: "TestContext", - /** - * The current test name. - */ - name: desc.name, - /** - * Parent test context. - */ - parent, - /** - * File Uri of the test code. - */ - origin: desc.origin, - /** - * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise)} - * @param maybeFn {((t: TestContext) => void | Promise) | undefined} - */ - async step(nameOrFnOrOptions, maybeFn) { - if (MapPrototypeGet(testStates, desc.id).finalized) { - throw new Error( - "Cannot run test step after parent scope has finished execution. " + - "Ensure any `.step(...)` calls are executed before their parent scope completes execution.", - ); - } - - let stepDesc; - if (typeof nameOrFnOrOptions === "string") { - if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) { - throw new TypeError("Expected function for second argument."); - } - stepDesc = { - name: nameOrFnOrOptions, - fn: maybeFn, - }; - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The step function must have a name."); - } - if (maybeFn != undefined) { - throw new TypeError( - "Unexpected second argument to TestContext.step()", - ); - } - stepDesc = { - name: nameOrFnOrOptions.name, - fn: nameOrFnOrOptions, - }; - } else if (typeof nameOrFnOrOptions === "object") { - stepDesc = nameOrFnOrOptions; - } else { - throw new TypeError( - "Expected a test definition or name and function.", - ); - } - stepDesc.ignore ??= false; - stepDesc.sanitizeOps ??= desc.sanitizeOps; - stepDesc.sanitizeResources ??= desc.sanitizeResources; - stepDesc.sanitizeExit ??= desc.sanitizeExit; - stepDesc.origin = getTestOrigin(); - const jsError = core.destructureError(new Error()); - stepDesc.location = { - fileName: jsError.frames[1].fileName, - lineNumber: jsError.frames[1].lineNumber, - columnNumber: jsError.frames[1].columnNumber, - }; - stepDesc.level = level + 1; - stepDesc.parent = desc; - stepDesc.rootId = rootId; - stepDesc.rootName = rootName; - const { id } = ops.op_register_test_step(stepDesc); - stepDesc.id = id; - const state = { - context: createTestContext(stepDesc), - children: [], - finalized: false, - status: "pending", - error: null, - elapsed: null, - reportedWait: false, - reportedResult: false, - }; - MapPrototypeSet(testStates, stepDesc.id, state); - ArrayPrototypePush( - MapPrototypeGet(testStates, stepDesc.parent.id).children, - stepDesc, + return { + [SymbolToStringTag]: "TestContext", + /** + * The current test name. + */ + name: desc.name, + /** + * Parent test context. + */ + parent, + /** + * File Uri of the test code. + */ + origin: desc.origin, + /** + * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise)} + * @param maybeFn {((t: TestContext) => void | Promise) | undefined} + */ + async step(nameOrFnOrOptions, maybeFn) { + if (MapPrototypeGet(testStates, desc.id).finalized) { + throw new Error( + "Cannot run test step after parent scope has finished execution. " + + "Ensure any `.step(...)` calls are executed before their parent scope completes execution.", ); + } - try { - if (stepDesc.ignore) { - state.status = "ignored"; - state.finalized = true; - if (canStreamReporting(stepDesc)) { - stepReportResult(stepDesc); - } - return false; - } - - const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc); - const start = DateNow(); - - try { - await testFn(stepDesc); - - if (failedChildStepsCount(stepDesc) > 0) { - state.status = "failed"; - } else { - state.status = "ok"; - } - } catch (error) { - state.error = error; - state.status = "failed"; - } - - state.elapsed = DateNow() - start; - - if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) { - // always point this test out as one that was still running - // if the parent step finalized - state.status = "pending"; - } + let stepDesc; + if (typeof nameOrFnOrOptions === "string") { + if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) { + throw new TypeError("Expected function for second argument."); + } + stepDesc = { + name: nameOrFnOrOptions, + fn: maybeFn, + }; + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The step function must have a name."); + } + if (maybeFn != undefined) { + throw new TypeError( + "Unexpected second argument to TestContext.step()", + ); + } + stepDesc = { + name: nameOrFnOrOptions.name, + fn: nameOrFnOrOptions, + }; + } else if (typeof nameOrFnOrOptions === "object") { + stepDesc = nameOrFnOrOptions; + } else { + throw new TypeError( + "Expected a test definition or name and function.", + ); + } + stepDesc.ignore ??= false; + stepDesc.sanitizeOps ??= desc.sanitizeOps; + stepDesc.sanitizeResources ??= desc.sanitizeResources; + stepDesc.sanitizeExit ??= desc.sanitizeExit; + stepDesc.origin = getTestOrigin(); + const jsError = core.destructureError(new Error()); + stepDesc.location = { + fileName: jsError.frames[1].fileName, + lineNumber: jsError.frames[1].lineNumber, + columnNumber: jsError.frames[1].columnNumber, + }; + stepDesc.level = level + 1; + stepDesc.parent = desc; + stepDesc.rootId = rootId; + stepDesc.rootName = rootName; + const { id } = ops.op_register_test_step(stepDesc); + stepDesc.id = id; + const state = { + context: createTestContext(stepDesc), + children: [], + finalized: false, + status: "pending", + error: null, + elapsed: null, + reportedWait: false, + reportedResult: false, + }; + MapPrototypeSet(testStates, stepDesc.id, state); + ArrayPrototypePush( + MapPrototypeGet(testStates, stepDesc.parent.id).children, + stepDesc, + ); + try { + if (stepDesc.ignore) { + state.status = "ignored"; state.finalized = true; - - if (state.reportedWait && canStreamReporting(stepDesc)) { + if (canStreamReporting(stepDesc)) { stepReportResult(stepDesc); } + return false; + } - return state.status === "ok"; - } finally { - if (canStreamReporting(stepDesc.parent)) { - const parentState = MapPrototypeGet(testStates, stepDesc.parent.id); - // flush any buffered steps - for (const childDesc of parentState.children) { - stepReportResult(childDesc); - } + const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc); + const start = DateNow(); + + try { + await testFn(stepDesc); + + if (failedChildStepsCount(stepDesc) > 0) { + state.status = "failed"; + } else { + state.status = "ok"; + } + } catch (error) { + state.error = error; + state.status = "failed"; + } + + state.elapsed = DateNow() - start; + + if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) { + // always point this test out as one that was still running + // if the parent step finalized + state.status = "pending"; + } + + state.finalized = true; + + if (state.reportedWait && canStreamReporting(stepDesc)) { + stepReportResult(stepDesc); + } + + return state.status === "ok"; + } finally { + if (canStreamReporting(stepDesc.parent)) { + const parentState = MapPrototypeGet(testStates, stepDesc.parent.id); + // flush any buffered steps + for (const childDesc of parentState.children) { + stepReportResult(childDesc); } } - }, - }; - } - - /** - * @template T {Function} - * @param testFn {T} - * @param opts {{ - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} - * @returns {T} - */ - function wrapTestFnWithSanitizers(testFn, opts) { - testFn = assertTestStepScopes(testFn); - - if (opts.sanitizeOps) { - testFn = assertOps(testFn); - } - if (opts.sanitizeResources) { - testFn = assertResources(testFn); - } - if (opts.sanitizeExit) { - testFn = assertExit(testFn, true); - } - return testFn; - } - - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - testing: { - runTests, - runBenchmarks, - enableTest, - enableBench, + } }, }; +} - window.__bootstrap.denoNs.bench = bench; - window.__bootstrap.denoNs.test = test; -})(this); +/** + * @template T {Function} + * @param testFn {T} + * @param opts {{ + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * }} + * @returns {T} + */ +function wrapTestFnWithSanitizers(testFn, opts) { + testFn = assertTestStepScopes(testFn); + + if (opts.sanitizeOps) { + testFn = assertOps(testFn); + } + if (opts.sanitizeResources) { + testFn = assertResources(testFn); + } + if (opts.sanitizeExit) { + testFn = assertExit(testFn, true); + } + return testFn; +} + +internals.testing = { + runTests, + runBenchmarks, + enableTest, + enableBench, +}; + +import { denoNs } from "internal:runtime/js/90_deno_ns.js"; +denoNs.bench = bench; +denoNs.test = test; diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 923232f9d4..9c0319a7f2 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -3844,3 +3844,15 @@ itest!(node_prefix_missing { envs: env_vars_for_npm_tests_no_sync_download(), exit_code: 1, }); + +itest!(internal_import { + args: "run run/internal_import.ts", + output: "run/internal_import.ts.out", + exit_code: 1, +}); + +itest!(internal_dynamic_import { + args: "run run/internal_dynamic_import.ts", + output: "run/internal_dynamic_import.ts.out", + exit_code: 1, +}); diff --git a/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out b/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out index 1fb3ce76a4..8f03f71b81 100644 --- a/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out +++ b/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out @@ -1,5 +1,4 @@ 1 -queueMicrotask error: Uncaught Error: bar throw new Error("bar"); ^ diff --git a/cli/tests/testdata/run/internal_dynamic_import.ts b/cli/tests/testdata/run/internal_dynamic_import.ts new file mode 100644 index 0000000000..3fb2791e37 --- /dev/null +++ b/cli/tests/testdata/run/internal_dynamic_import.ts @@ -0,0 +1 @@ +await import("internal:runtime/js/01_build.js"); diff --git a/cli/tests/testdata/run/internal_dynamic_import.ts.out b/cli/tests/testdata/run/internal_dynamic_import.ts.out new file mode 100644 index 0000000000..fa98b8733c --- /dev/null +++ b/cli/tests/testdata/run/internal_dynamic_import.ts.out @@ -0,0 +1,4 @@ +error: Uncaught TypeError: Cannot load internal module from external code +await import("internal:runtime/js/01_build.js"); +^ + at [WILDCARD]/internal_dynamic_import.ts:1:1 diff --git a/cli/tests/testdata/run/internal_import.ts b/cli/tests/testdata/run/internal_import.ts new file mode 100644 index 0000000000..666b68769a --- /dev/null +++ b/cli/tests/testdata/run/internal_import.ts @@ -0,0 +1 @@ +import "internal:runtime/js/01_build.js"; diff --git a/cli/tests/testdata/run/internal_import.ts.out b/cli/tests/testdata/run/internal_import.ts.out new file mode 100644 index 0000000000..142308a385 --- /dev/null +++ b/cli/tests/testdata/run/internal_import.ts.out @@ -0,0 +1,8 @@ +error: Unsupported scheme "internal" for module "internal:runtime/js/01_build.js". Supported schemes: [ + "data", + "blob", + "file", + "http", + "https", +] + at [WILDCARD] diff --git a/core/01_core.js b/core/01_core.js index d3cd1d7bea..6a6696bf83 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -427,6 +427,8 @@ }); ObjectAssign(globalThis.__bootstrap, { core }); + const internals = {}; + ObjectAssign(globalThis.__bootstrap, { internals }); ObjectAssign(globalThis.Deno, { core }); // Direct bindings on `globalThis` diff --git a/core/bindings.rs b/core/bindings.rs index d5f38d3c2f..dce97000c9 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -267,45 +267,47 @@ pub fn host_import_module_dynamically_callback<'s>( .unwrap() .to_rust_string_lossy(scope); + let is_internal_module = specifier_str.starts_with("internal:"); let resolver = v8::PromiseResolver::new(scope).unwrap(); let promise = resolver.get_promise(scope); - let assertions = parse_import_assertions( - scope, - import_assertions, - ImportAssertionsKind::DynamicImport, - ); + if !is_internal_module { + let assertions = parse_import_assertions( + scope, + import_assertions, + ImportAssertionsKind::DynamicImport, + ); - { - let tc_scope = &mut v8::TryCatch::new(scope); - validate_import_assertions(tc_scope, &assertions); - if tc_scope.has_caught() { - let e = tc_scope.exception().unwrap(); - resolver.reject(tc_scope, e); + { + let tc_scope = &mut v8::TryCatch::new(scope); + validate_import_assertions(tc_scope, &assertions); + if tc_scope.has_caught() { + let e = tc_scope.exception().unwrap(); + resolver.reject(tc_scope, e); + } + } + let asserted_module_type = + get_asserted_module_type_from_assertions(&assertions); + + let resolver_handle = v8::Global::new(scope, resolver); + { + let state_rc = JsRuntime::state(scope); + let module_map_rc = JsRuntime::module_map(scope); + + debug!( + "dyn_import specifier {} referrer {} ", + specifier_str, referrer_name_str + ); + ModuleMap::load_dynamic_import( + module_map_rc, + &specifier_str, + &referrer_name_str, + asserted_module_type, + resolver_handle, + ); + state_rc.borrow_mut().notify_new_dynamic_import(); } } - let asserted_module_type = - get_asserted_module_type_from_assertions(&assertions); - - let resolver_handle = v8::Global::new(scope, resolver); - { - let state_rc = JsRuntime::state(scope); - let module_map_rc = JsRuntime::module_map(scope); - - debug!( - "dyn_import specifier {} referrer {} ", - specifier_str, referrer_name_str - ); - ModuleMap::load_dynamic_import( - module_map_rc, - &specifier_str, - &referrer_name_str, - asserted_module_type, - resolver_handle, - ); - state_rc.borrow_mut().notify_new_dynamic_import(); - } - // Map errors from module resolution (not JS errors from module execution) to // ones rethrown from this scope, so they include the call stack of the // dynamic import site. Error objects without any stack frames are assumed to @@ -317,6 +319,14 @@ pub fn host_import_module_dynamically_callback<'s>( let promise = promise.catch(scope, map_err).unwrap(); + if is_internal_module { + let message = + v8::String::new(scope, "Cannot load internal module from external code") + .unwrap(); + let exception = v8::Exception::type_error(scope, message); + resolver.reject(scope, exception); + } + Some(promise) } diff --git a/core/extensions.rs b/core/extensions.rs index 129e7b62a8..b981e6da2a 100644 --- a/core/extensions.rs +++ b/core/extensions.rs @@ -38,6 +38,7 @@ impl OpDecl { #[derive(Default)] pub struct Extension { js_files: Option>, + esm_files: Option>, ops: Option>, opstate_fn: Option>, middleware_fn: Option>, @@ -81,13 +82,20 @@ impl Extension { /// returns JS source code to be loaded into the isolate (either at snapshotting, /// or at startup). as a vector of a tuple of the file name, and the source code. - pub fn init_js(&self) -> &[SourcePair] { + pub fn get_js_sources(&self) -> &[SourcePair] { match &self.js_files { Some(files) => files, None => &[], } } + pub fn get_esm_sources(&self) -> &[SourcePair] { + match &self.esm_files { + Some(files) => files, + None => &[], + } + } + /// Called at JsRuntime startup to initialize ops in the isolate. pub fn init_ops(&mut self) -> Option> { // TODO(@AaronO): maybe make op registration idempotent @@ -145,6 +153,7 @@ impl Extension { #[derive(Default)] pub struct ExtensionBuilder { js: Vec, + esm: Vec, ops: Vec, state: Option>, middleware: Option>, @@ -164,6 +173,11 @@ impl ExtensionBuilder { self } + pub fn esm(&mut self, js_files: Vec) -> &mut Self { + self.esm.extend(js_files); + self + } + pub fn ops(&mut self, ops: Vec) -> &mut Self { self.ops.extend(ops); self @@ -195,10 +209,12 @@ impl ExtensionBuilder { pub fn build(&mut self) -> Extension { let js_files = Some(std::mem::take(&mut self.js)); + let esm_files = Some(std::mem::take(&mut self.esm)); let ops = Some(std::mem::take(&mut self.ops)); let deps = Some(std::mem::take(&mut self.deps)); Extension { js_files, + esm_files, ops, opstate_fn: self.state.take(), middleware_fn: self.middleware.take(), diff --git a/core/lib.rs b/core/lib.rs index 461b4fd205..868d6b7493 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -73,6 +73,7 @@ pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; pub use crate::module_specifier::DUMMY_SPECIFIER; pub use crate::modules::FsModuleLoader; +pub use crate::modules::InternalModuleLoader; pub use crate::modules::ModuleId; pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleSource; diff --git a/core/modules.rs b/core/modules.rs index 1b7169ea4a..b57428070d 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -292,6 +292,69 @@ impl ModuleLoader for NoopModuleLoader { } } +pub struct InternalModuleLoader(Rc); + +impl InternalModuleLoader { + pub fn new(module_loader: Option>) -> Self { + InternalModuleLoader( + module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)), + ) + } +} + +impl ModuleLoader for InternalModuleLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + kind: ResolutionKind, + ) -> Result { + if let Ok(url_specifier) = ModuleSpecifier::parse(specifier) { + if url_specifier.scheme() == "internal" { + let referrer_specifier = ModuleSpecifier::parse(referrer).ok(); + if referrer == "." || referrer_specifier.unwrap().scheme() == "internal" + { + return Ok(url_specifier); + } else { + return Err(generic_error( + "Cannot load internal module from external code", + )); + }; + } + } + + self.0.resolve(specifier, referrer, kind) + } + + fn load( + &self, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, + is_dyn_import: bool, + ) -> Pin> { + self.0.load(module_specifier, maybe_referrer, is_dyn_import) + } + + fn prepare_load( + &self, + op_state: Rc>, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, + is_dyn_import: bool, + ) -> Pin>>> { + if module_specifier.scheme() == "internal" { + return async { Ok(()) }.boxed_local(); + } + + self.0.prepare_load( + op_state, + module_specifier, + maybe_referrer, + is_dyn_import, + ) + } +} + /// Basic file system module loader. /// /// Note that this loader will **block** event loop @@ -2508,4 +2571,33 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error(); ) .unwrap(); } + + #[test] + fn internal_module_loader() { + let loader = InternalModuleLoader::new(None); + assert!(loader + .resolve("internal:foo", "internal:bar", ResolutionKind::Import) + .is_ok()); + assert_eq!( + loader + .resolve("internal:foo", "file://bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some("Cannot load internal module from external code".to_string()) + ); + assert_eq!( + loader + .resolve("file://foo", "file://bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some("Module loading is not supported".to_string()) + ); + assert_eq!( + loader + .resolve("file://foo", "internal:bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some("Module loading is not supported".to_string()) + ); + } } diff --git a/core/runtime.rs b/core/runtime.rs index 1418e57917..096d26ca3a 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -13,17 +13,18 @@ use crate::modules::ModuleId; use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; use crate::modules::ModuleMap; -use crate::modules::NoopModuleLoader; use crate::op_void_async; use crate::op_void_sync; use crate::ops::*; use crate::source_map::SourceMapCache; use crate::source_map::SourceMapGetter; use crate::Extension; +use crate::NoopModuleLoader; use crate::OpMiddlewareFn; use crate::OpResult; use crate::OpState; use crate::PromiseId; +use anyhow::Context as AnyhowContext; use anyhow::Error; use futures::channel::oneshot; use futures::future::poll_fn; @@ -605,9 +606,16 @@ impl JsRuntime { None }; - let loader = options - .module_loader - .unwrap_or_else(|| Rc::new(NoopModuleLoader)); + let loader = if snapshot_options != SnapshotOptions::Load { + Rc::new(crate::modules::InternalModuleLoader::new( + options.module_loader, + )) + } else { + options + .module_loader + .unwrap_or_else(|| Rc::new(NoopModuleLoader)) + }; + { let mut state = state_rc.borrow_mut(); state.global_realm = Some(JsRealm(global_context.clone())); @@ -805,10 +813,30 @@ impl JsRuntime { // Take extensions to avoid double-borrow let extensions = std::mem::take(&mut self.extensions_with_js); for ext in &extensions { - let js_files = ext.init_js(); - for (filename, source) in js_files { - // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap - realm.execute_script(self.v8_isolate(), filename, source)?; + { + let js_files = ext.get_esm_sources(); + for (filename, source) in js_files { + futures::executor::block_on(async { + let id = self + .load_side_module( + &ModuleSpecifier::parse(filename)?, + Some(source.to_string()), + ) + .await?; + let receiver = self.mod_evaluate(id); + self.run_event_loop(false).await?; + receiver.await? + }) + .with_context(|| format!("Couldn't execute '{filename}'"))?; + } + } + + { + let js_files = ext.get_js_sources(); + for (filename, source) in js_files { + // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap + realm.execute_script(self.v8_isolate(), filename, source)?; + } } } // Restore extensions diff --git a/core/snapshot_util.rs b/core/snapshot_util.rs index 8e397e2621..8daaa98660 100644 --- a/core/snapshot_util.rs +++ b/core/snapshot_util.rs @@ -1,10 +1,12 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use anyhow::Context; use std::path::Path; use std::path::PathBuf; use crate::Extension; use crate::JsRuntime; +use crate::ModuleSpecifier; use crate::RuntimeOptions; use crate::Snapshot; @@ -17,6 +19,7 @@ pub struct CreateSnapshotOptions { pub extensions: Vec, pub extensions_with_js: Vec, pub additional_files: Vec, + pub additional_esm_files: Vec, pub compression_cb: Option>, } @@ -44,6 +47,27 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) { ) .unwrap(); } + for file in create_snapshot_options.additional_esm_files { + let display_path = file.strip_prefix(display_root).unwrap_or(&file); + let display_path_str = display_path.display().to_string(); + + let filename = + &("internal:".to_string() + &display_path_str.replace('\\', "/")); + + futures::executor::block_on(async { + let id = js_runtime + .load_side_module( + &ModuleSpecifier::parse(filename)?, + Some(std::fs::read_to_string(&file)?), + ) + .await?; + let receiver = js_runtime.mod_evaluate(id); + js_runtime.run_event_loop(false).await?; + receiver.await? + }) + .with_context(|| format!("Couldn't execute '{}'", file.display())) + .unwrap(); + } let snapshot = js_runtime.snapshot(); let snapshot_slice: &[u8] = &snapshot; @@ -79,9 +103,12 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) { ); } +pub type FilterFn = Box bool>; + pub fn get_js_files( cargo_manifest_dir: &'static str, directory: &str, + filter: Option, ) -> Vec { let manifest_dir = Path::new(cargo_manifest_dir); let mut js_files = std::fs::read_dir(directory) @@ -92,7 +119,7 @@ pub fn get_js_files( }) .filter(|path| { path.extension().unwrap_or_default() == "js" - && !path.ends_with("99_main.js") + && filter.as_ref().map(|filter| filter(path)).unwrap_or(true) }) .collect::>(); js_files.sort(); diff --git a/ext/broadcast_channel/01_broadcast_channel.js b/ext/broadcast_channel/01_broadcast_channel.js index 82bede8b06..fb23554bf5 100644 --- a/ext/broadcast_channel/01_broadcast_channel.js +++ b/ext/broadcast_channel/01_broadcast_channel.js @@ -2,150 +2,149 @@ /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + defineEventHandler, + EventTarget, + setTarget, +} from "internal:ext/web/02_event.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIndexOf, + ArrayPrototypeSplice, + ArrayPrototypePush, + Symbol, + Uint8Array, +} = primordials; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { MessageEvent, defineEventHandler, setTarget } = - window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayPrototypeIndexOf, - ArrayPrototypeSplice, - ArrayPrototypePush, - Symbol, - Uint8Array, - } = window.__bootstrap.primordials; +const _name = Symbol("[[name]]"); +const _closed = Symbol("[[closed]]"); - const _name = Symbol("[[name]]"); - const _closed = Symbol("[[closed]]"); +const channels = []; +let rid = null; - const channels = []; - let rid = null; +async function recv() { + while (channels.length > 0) { + const message = await core.opAsync("op_broadcast_recv", rid); - async function recv() { - while (channels.length > 0) { - const message = await core.opAsync("op_broadcast_recv", rid); - - if (message === null) { - break; - } - - const { 0: name, 1: data } = message; - dispatch(null, name, new Uint8Array(data)); + if (message === null) { + break; } - core.close(rid); - rid = null; + const { 0: name, 1: data } = message; + dispatch(null, name, new Uint8Array(data)); } - function dispatch(source, name, data) { - for (let i = 0; i < channels.length; ++i) { - const channel = channels[i]; + core.close(rid); + rid = null; +} - if (channel === source) continue; // Don't self-send. - if (channel[_name] !== name) continue; - if (channel[_closed]) continue; +function dispatch(source, name, data) { + for (let i = 0; i < channels.length; ++i) { + const channel = channels[i]; - const go = () => { - if (channel[_closed]) return; - const event = new MessageEvent("message", { - data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables. - origin: "http://127.0.0.1", - }); - setTarget(event, channel); - channel.dispatchEvent(event); - }; + if (channel === source) continue; // Don't self-send. + if (channel[_name] !== name) continue; + if (channel[_closed]) continue; - defer(go); - } - } - - // Defer to avoid starving the event loop. Not using queueMicrotask() - // for that reason: it lets promises make forward progress but can - // still starve other parts of the event loop. - function defer(go) { - setTimeout(go, 1); - } - - class BroadcastChannel extends EventTarget { - [_name]; - [_closed] = false; - - get name() { - return this[_name]; - } - - constructor(name) { - super(); - - const prefix = "Failed to construct 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - this[_name] = webidl.converters["DOMString"](name, { - prefix, - context: "Argument 1", + const go = () => { + if (channel[_closed]) return; + const event = new MessageEvent("message", { + data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables. + origin: "http://127.0.0.1", }); + setTarget(event, channel); + channel.dispatchEvent(event); + }; - this[webidl.brand] = webidl.brand; + defer(go); + } +} - ArrayPrototypePush(channels, this); +// Defer to avoid starving the event loop. Not using queueMicrotask() +// for that reason: it lets promises make forward progress but can +// still starve other parts of the event loop. +function defer(go) { + setTimeout(go, 1); +} - if (rid === null) { - // Create the rid immediately, otherwise there is a time window (and a - // race condition) where messages can get lost, because recv() is async. - rid = ops.op_broadcast_subscribe(); - recv(); - } - } +class BroadcastChannel extends EventTarget { + [_name]; + [_closed] = false; - postMessage(message) { - webidl.assertBranded(this, BroadcastChannelPrototype); + get name() { + return this[_name]; + } - const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + constructor(name) { + super(); - if (this[_closed]) { - throw new DOMException("Already closed", "InvalidStateError"); - } + const prefix = "Failed to construct 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - if (typeof message === "function" || typeof message === "symbol") { - throw new DOMException("Uncloneable value", "DataCloneError"); - } + this[_name] = webidl.converters["DOMString"](name, { + prefix, + context: "Argument 1", + }); - const data = core.serialize(message); + this[webidl.brand] = webidl.brand; - // Send to other listeners in this VM. - dispatch(this, this[_name], new Uint8Array(data)); + ArrayPrototypePush(channels, this); - // Send to listeners in other VMs. - defer(() => { - if (!this[_closed]) { - core.opAsync("op_broadcast_send", rid, this[_name], data); - } - }); - } - - close() { - webidl.assertBranded(this, BroadcastChannelPrototype); - this[_closed] = true; - - const index = ArrayPrototypeIndexOf(channels, this); - if (index === -1) return; - - ArrayPrototypeSplice(channels, index, 1); - if (channels.length === 0) { - ops.op_broadcast_unsubscribe(rid); - } + if (rid === null) { + // Create the rid immediately, otherwise there is a time window (and a + // race condition) where messages can get lost, because recv() is async. + rid = ops.op_broadcast_subscribe(); + recv(); } } - defineEventHandler(BroadcastChannel.prototype, "message"); - defineEventHandler(BroadcastChannel.prototype, "messageerror"); - const BroadcastChannelPrototype = BroadcastChannel.prototype; + postMessage(message) { + webidl.assertBranded(this, BroadcastChannelPrototype); - window.__bootstrap.broadcastChannel = { BroadcastChannel }; -})(this); + const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + if (this[_closed]) { + throw new DOMException("Already closed", "InvalidStateError"); + } + + if (typeof message === "function" || typeof message === "symbol") { + throw new DOMException("Uncloneable value", "DataCloneError"); + } + + const data = core.serialize(message); + + // Send to other listeners in this VM. + dispatch(this, this[_name], new Uint8Array(data)); + + // Send to listeners in other VMs. + defer(() => { + if (!this[_closed]) { + core.opAsync("op_broadcast_send", rid, this[_name], data); + } + }); + } + + close() { + webidl.assertBranded(this, BroadcastChannelPrototype); + this[_closed] = true; + + const index = ArrayPrototypeIndexOf(channels, this); + if (index === -1) return; + + ArrayPrototypeSplice(channels, index, 1); + if (channels.length === 0) { + ops.op_broadcast_unsubscribe(rid); + } + } +} + +defineEventHandler(BroadcastChannel.prototype, "message"); +defineEventHandler(BroadcastChannel.prototype, "messageerror"); +const BroadcastChannelPrototype = BroadcastChannel.prototype; + +export { BroadcastChannel }; diff --git a/ext/broadcast_channel/lib.rs b/ext/broadcast_channel/lib.rs index 674d2414dd..0cd17a0269 100644 --- a/ext/broadcast_channel/lib.rs +++ b/ext/broadcast_channel/lib.rs @@ -112,7 +112,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/broadcast_channel", "01_broadcast_channel.js", )) diff --git a/ext/cache/01_cache.js b/ext/cache/01_cache.js index bf0243e3c5..f49db3b84c 100644 --- a/ext/cache/01_cache.js +++ b/ext/cache/01_cache.js @@ -1,296 +1,292 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.__bootstrap.core; - const webidl = window.__bootstrap.webidl; - const { - Symbol, - TypeError, - ObjectPrototypeIsPrototypeOf, - } = window.__bootstrap.primordials; - const { - Request, - toInnerResponse, - toInnerRequest, - } = window.__bootstrap.fetch; - const { URLPrototype } = window.__bootstrap.url; - const RequestPrototype = Request.prototype; - const { getHeader } = window.__bootstrap.headers; - const { readableStreamForRid } = window.__bootstrap.streams; +const core = globalThis.Deno.core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Symbol, + TypeError, + ObjectPrototypeIsPrototypeOf, +} = primordials; +import { + Request, + RequestPrototype, + toInnerRequest, +} from "internal:ext/fetch/23_request.js"; +import { toInnerResponse } from "internal:ext/fetch/23_response.js"; +import { URLPrototype } from "internal:ext/url/00_url.js"; +import { getHeader } from "internal:ext/fetch/20_headers.js"; +import { readableStreamForRid } from "internal:ext/web/06_streams.js"; - class CacheStorage { - constructor() { - webidl.illegalConstructor(); +class CacheStorage { + constructor() { + webidl.illegalConstructor(); + } + + async open(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'open' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + const cacheId = await core.opAsync("op_cache_storage_open", cacheName); + const cache = webidl.createBranded(Cache); + cache[_id] = cacheId; + return cache; + } + + async has(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'has' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + return await core.opAsync("op_cache_storage_has", cacheName); + } + + async delete(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'delete' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + return await core.opAsync("op_cache_storage_delete", cacheName); + } +} + +const _matchAll = Symbol("[[matchAll]]"); +const _id = Symbol("id"); + +class Cache { + /** @type {number} */ + [_id]; + + constructor() { + webidl.illegalConstructor(); + } + + /** See https://w3c.github.io/ServiceWorker/#dom-cache-put */ + async put(request, response) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'put' on 'Cache'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + response = webidl.converters["Response"](response, { + prefix, + context: "Argument 2", + }); + // Step 1. + let innerRequest = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + innerRequest = toInnerRequest(request); + } else { + // Step 3. + innerRequest = toInnerRequest(new Request(request)); + } + // Step 4. + const reqUrl = new URL(innerRequest.url()); + if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") { + throw new TypeError( + "Request url protocol must be 'http:' or 'https:'", + ); + } + if (innerRequest.method !== "GET") { + throw new TypeError("Request method must be GET"); + } + // Step 5. + const innerResponse = toInnerResponse(response); + // Step 6. + if (innerResponse.status === 206) { + throw new TypeError("Response status must not be 206"); + } + // Step 7. + const varyHeader = getHeader(innerResponse.headerList, "vary"); + if (varyHeader) { + const fieldValues = varyHeader.split(","); + for (let i = 0; i < fieldValues.length; ++i) { + const field = fieldValues[i]; + if (field.trim() === "*") { + throw new TypeError("Vary header must not contain '*'"); + } + } } - async open(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'open' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - const cacheId = await core.opAsync("op_cache_storage_open", cacheName); - const cache = webidl.createBranded(Cache); - cache[_id] = cacheId; - return cache; + // Step 8. + if (innerResponse.body !== null && innerResponse.body.unusable()) { + throw new TypeError("Response body is already used"); } + // acquire lock before async op + const reader = innerResponse.body?.stream.getReader(); - async has(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'has' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - return await core.opAsync("op_cache_storage_has", cacheName); + // Remove fragment from request URL before put. + reqUrl.hash = ""; + + // Step 9-11. + const rid = await core.opAsync( + "op_cache_put", + { + cacheId: this[_id], + requestUrl: reqUrl.toString(), + responseHeaders: innerResponse.headerList, + requestHeaders: innerRequest.headerList, + responseHasBody: innerResponse.body !== null, + responseStatus: innerResponse.status, + responseStatusText: innerResponse.statusMessage, + }, + ); + if (reader) { + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + await core.shutdown(rid); + break; + } + await core.writeAll(rid, value); + } + } finally { + core.close(rid); + } } + // Step 12-19: TODO(@satyarohith): do the insertion in background. + } - async delete(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'delete' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - return await core.opAsync("op_cache_storage_delete", cacheName); + /** See https://w3c.github.io/ServiceWorker/#cache-match */ + async match(request, options) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'match' on 'Cache'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + const p = await this[_matchAll](request, options); + if (p.length > 0) { + return p[0]; + } else { + return undefined; } } - const _matchAll = Symbol("[[matchAll]]"); - const _id = Symbol("id"); + /** See https://w3c.github.io/ServiceWorker/#cache-delete */ + async delete(request, _options) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'delete' on 'Cache'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + // Step 1. + let r = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + r = request; + if (request.method !== "GET") { + return false; + } + } else if ( + typeof request === "string" || + ObjectPrototypeIsPrototypeOf(URLPrototype, request) + ) { + r = new Request(request); + } + return await core.opAsync("op_cache_delete", { + cacheId: this[_id], + requestUrl: r.url, + }); + } - class Cache { - /** @type {number} */ - [_id]; - - constructor() { - webidl.illegalConstructor(); + /** See https://w3c.github.io/ServiceWorker/#cache-matchall + * + * Note: the function is private as we don't want to expose + * this API to the public yet. + * + * The function will return an array of responses. + */ + async [_matchAll](request, _options) { + // Step 1. + let r = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + r = request; + if (request.method !== "GET") { + return []; + } + } else if ( + typeof request === "string" || + ObjectPrototypeIsPrototypeOf(URLPrototype, request) + ) { + r = new Request(request); } - /** See https://w3c.github.io/ServiceWorker/#dom-cache-put */ - async put(request, response) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'put' on 'Cache'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - response = webidl.converters["Response"](response, { - prefix, - context: "Argument 2", - }); - // Step 1. - let innerRequest = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - innerRequest = toInnerRequest(request); - } else { - // Step 3. - innerRequest = toInnerRequest(new Request(request)); - } - // Step 4. - const reqUrl = new URL(innerRequest.url()); - if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") { - throw new TypeError( - "Request url protocol must be 'http:' or 'https:'", - ); - } - if (innerRequest.method !== "GET") { - throw new TypeError("Request method must be GET"); - } - // Step 5. - const innerResponse = toInnerResponse(response); - // Step 6. - if (innerResponse.status === 206) { - throw new TypeError("Response status must not be 206"); - } - // Step 7. - const varyHeader = getHeader(innerResponse.headerList, "vary"); - if (varyHeader) { - const fieldValues = varyHeader.split(","); - for (let i = 0; i < fieldValues.length; ++i) { - const field = fieldValues[i]; - if (field.trim() === "*") { - throw new TypeError("Vary header must not contain '*'"); - } - } - } - - // Step 8. - if (innerResponse.body !== null && innerResponse.body.unusable()) { - throw new TypeError("Response body is already used"); - } - // acquire lock before async op - const reader = innerResponse.body?.stream.getReader(); - - // Remove fragment from request URL before put. - reqUrl.hash = ""; - - // Step 9-11. - const rid = await core.opAsync( - "op_cache_put", + // Step 5. + const responses = []; + // Step 5.2 + if (r === null) { + // Step 5.3 + // Note: we have to return all responses in the cache when + // the request is null. + // We deviate from the spec here and return an empty array + // as we don't expose matchAll() API. + return responses; + } else { + // Remove the fragment from the request URL. + const url = new URL(r.url); + url.hash = ""; + const innerRequest = toInnerRequest(r); + const matchResult = await core.opAsync( + "op_cache_match", { cacheId: this[_id], - requestUrl: reqUrl.toString(), - responseHeaders: innerResponse.headerList, + requestUrl: url.toString(), requestHeaders: innerRequest.headerList, - responseHasBody: innerResponse.body !== null, - responseStatus: innerResponse.status, - responseStatusText: innerResponse.statusMessage, }, ); - if (reader) { - try { - while (true) { - const { value, done } = await reader.read(); - if (done) { - await core.shutdown(rid); - break; - } - await core.writeAll(rid, value); - } - } finally { - core.close(rid); + if (matchResult) { + const { 0: meta, 1: responseBodyRid } = matchResult; + let body = null; + if (responseBodyRid !== null) { + body = readableStreamForRid(responseBodyRid); } - } - // Step 12-19: TODO(@satyarohith): do the insertion in background. - } - - /** See https://w3c.github.io/ServiceWorker/#cache-match */ - async match(request, options) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'match' on 'Cache'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - const p = await this[_matchAll](request, options); - if (p.length > 0) { - return p[0]; - } else { - return undefined; - } - } - - /** See https://w3c.github.io/ServiceWorker/#cache-delete */ - async delete(request, _options) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'delete' on 'Cache'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - // Step 1. - let r = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - r = request; - if (request.method !== "GET") { - return false; - } - } else if ( - typeof request === "string" || - ObjectPrototypeIsPrototypeOf(URLPrototype, request) - ) { - r = new Request(request); - } - return await core.opAsync("op_cache_delete", { - cacheId: this[_id], - requestUrl: r.url, - }); - } - - /** See https://w3c.github.io/ServiceWorker/#cache-matchall - * - * Note: the function is private as we don't want to expose - * this API to the public yet. - * - * The function will return an array of responses. - */ - async [_matchAll](request, _options) { - // Step 1. - let r = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - r = request; - if (request.method !== "GET") { - return []; - } - } else if ( - typeof request === "string" || - ObjectPrototypeIsPrototypeOf(URLPrototype, request) - ) { - r = new Request(request); - } - - // Step 5. - const responses = []; - // Step 5.2 - if (r === null) { - // Step 5.3 - // Note: we have to return all responses in the cache when - // the request is null. - // We deviate from the spec here and return an empty array - // as we don't expose matchAll() API. - return responses; - } else { - // Remove the fragment from the request URL. - const url = new URL(r.url); - url.hash = ""; - const innerRequest = toInnerRequest(r); - const matchResult = await core.opAsync( - "op_cache_match", + const response = new Response( + body, { - cacheId: this[_id], - requestUrl: url.toString(), - requestHeaders: innerRequest.headerList, + headers: meta.responseHeaders, + status: meta.responseStatus, + statusText: meta.responseStatusText, }, ); - if (matchResult) { - const { 0: meta, 1: responseBodyRid } = matchResult; - let body = null; - if (responseBodyRid !== null) { - body = readableStreamForRid(responseBodyRid); - } - const response = new Response( - body, - { - headers: meta.responseHeaders, - status: meta.responseStatus, - statusText: meta.responseStatusText, - }, - ); - responses.push(response); - } + responses.push(response); } - // Step 5.4-5.5: don't apply in this context. - - return responses; } + // Step 5.4-5.5: don't apply in this context. + + return responses; } +} - webidl.configurePrototype(CacheStorage); - webidl.configurePrototype(Cache); - const CacheStoragePrototype = CacheStorage.prototype; - const CachePrototype = Cache.prototype; +webidl.configurePrototype(CacheStorage); +webidl.configurePrototype(Cache); +const CacheStoragePrototype = CacheStorage.prototype; +const CachePrototype = Cache.prototype; - let cacheStorage; - window.__bootstrap.caches = { - CacheStorage, - Cache, - cacheStorage() { - if (!cacheStorage) { - cacheStorage = webidl.createBranded(CacheStorage); - } - return cacheStorage; - }, - }; -})(this); +let cacheStorageStorage; +function cacheStorage() { + if (!cacheStorageStorage) { + cacheStorageStorage = webidl.createBranded(CacheStorage); + } + return cacheStorageStorage; +} + +export { Cache, CacheStorage, cacheStorage }; diff --git a/ext/cache/lib.rs b/ext/cache/lib.rs index 8884071534..a35ac6246e 100644 --- a/ext/cache/lib.rs +++ b/ext/cache/lib.rs @@ -27,7 +27,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_fetch"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/cache", "01_cache.js", )) diff --git a/ext/console/01_colors.js b/ext/console/01_colors.js index 00425f08b7..d01edd2471 100644 --- a/ext/console/01_colors.js +++ b/ext/console/01_colors.js @@ -2,110 +2,107 @@ /// -"use strict"; +const primordials = globalThis.__bootstrap.primordials; +const { + RegExp, + StringPrototypeReplace, + ArrayPrototypeJoin, +} = primordials; -((window) => { - const { - RegExp, - StringPrototypeReplace, - ArrayPrototypeJoin, - } = window.__bootstrap.primordials; +let noColor = false; - let noColor = false; +function setNoColor(value) { + noColor = value; +} - function setNoColor(value) { - noColor = value; - } +function getNoColor() { + return noColor; +} - function getNoColor() { - return noColor; - } - - function code(open, close) { - return { - open: `\x1b[${open}m`, - close: `\x1b[${close}m`, - regexp: new RegExp(`\\x1b\\[${close}m`, "g"), - }; - } - - function run(str, code) { - return `${code.open}${ - StringPrototypeReplace(str, code.regexp, code.open) - }${code.close}`; - } - - function bold(str) { - return run(str, code(1, 22)); - } - - function italic(str) { - return run(str, code(3, 23)); - } - - function yellow(str) { - return run(str, code(33, 39)); - } - - function cyan(str) { - return run(str, code(36, 39)); - } - - function red(str) { - return run(str, code(31, 39)); - } - - function green(str) { - return run(str, code(32, 39)); - } - - function bgRed(str) { - return run(str, code(41, 49)); - } - - function white(str) { - return run(str, code(37, 39)); - } - - function gray(str) { - return run(str, code(90, 39)); - } - - function magenta(str) { - return run(str, code(35, 39)); - } - - // https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js - const ANSI_PATTERN = new RegExp( - ArrayPrototypeJoin([ - "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", - "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", - ], "|"), - "g", - ); - - function stripColor(string) { - return StringPrototypeReplace(string, ANSI_PATTERN, ""); - } - - function maybeColor(fn) { - return !noColor ? fn : (s) => s; - } - - window.__bootstrap.colors = { - bold, - italic, - yellow, - cyan, - red, - green, - bgRed, - white, - gray, - magenta, - stripColor, - maybeColor, - setNoColor, - getNoColor, +function code(open, close) { + return { + open: `\x1b[${open}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g"), }; -})(this); +} + +function run(str, code) { + return `${code.open}${ + StringPrototypeReplace(str, code.regexp, code.open) + }${code.close}`; +} + +function bold(str) { + return run(str, code(1, 22)); +} + +function italic(str) { + return run(str, code(3, 23)); +} + +function yellow(str) { + return run(str, code(33, 39)); +} + +function cyan(str) { + return run(str, code(36, 39)); +} + +function red(str) { + return run(str, code(31, 39)); +} + +function green(str) { + return run(str, code(32, 39)); +} + +function bgRed(str) { + return run(str, code(41, 49)); +} + +function white(str) { + return run(str, code(37, 39)); +} + +function gray(str) { + return run(str, code(90, 39)); +} + +function magenta(str) { + return run(str, code(35, 39)); +} + +// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js +const ANSI_PATTERN = new RegExp( + ArrayPrototypeJoin([ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", + ], "|"), + "g", +); + +function stripColor(string) { + return StringPrototypeReplace(string, ANSI_PATTERN, ""); +} + +function maybeColor(fn) { + return !noColor ? fn : (s) => s; +} + +export { + bgRed, + bold, + cyan, + getNoColor, + gray, + green, + italic, + magenta, + maybeColor, + red, + setNoColor, + stripColor, + white, + yellow, +}; diff --git a/ext/console/02_console.js b/ext/console/02_console.js index a9ec524887..22524e6b9c 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -2,2385 +2,2379 @@ /// -"use strict"; +const core = globalThis.Deno.core; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + AggregateErrorPrototype, + ArrayPrototypeUnshift, + isNaN, + DatePrototype, + DateNow, + DatePrototypeGetTime, + DatePrototypeToISOString, + Boolean, + BooleanPrototype, + BooleanPrototypeToString, + ObjectKeys, + ObjectCreate, + ObjectAssign, + ObjectIs, + ObjectValues, + ObjectFromEntries, + ObjectGetPrototypeOf, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertySymbols, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + ObjectPrototypePropertyIsEnumerable, + PromisePrototype, + String, + StringPrototype, + StringPrototypeRepeat, + StringPrototypeReplace, + StringPrototypeReplaceAll, + StringPrototypeSplit, + StringPrototypeSlice, + StringPrototypeCodePointAt, + StringPrototypeCharCodeAt, + StringPrototypeNormalize, + StringPrototypeMatch, + StringPrototypePadStart, + StringPrototypeLocaleCompare, + StringPrototypeToString, + StringPrototypeTrim, + StringPrototypeIncludes, + StringPrototypeStartsWith, + TypeError, + NumberParseInt, + RegExp, + RegExpPrototype, + RegExpPrototypeTest, + RegExpPrototypeToString, + SafeArrayIterator, + SafeStringIterator, + SafeSet, + SetPrototype, + SetPrototypeEntries, + SetPrototypeGetSize, + Symbol, + SymbolPrototype, + SymbolPrototypeToString, + SymbolPrototypeValueOf, + SymbolToStringTag, + SymbolHasInstance, + SymbolFor, + Array, + ArrayIsArray, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypeReduce, + ArrayPrototypeEntries, + ArrayPrototypePush, + ArrayPrototypePop, + ArrayPrototypeSort, + ArrayPrototypeSlice, + ArrayPrototypeShift, + ArrayPrototypeIncludes, + ArrayPrototypeFill, + ArrayPrototypeFilter, + ArrayPrototypeFind, + FunctionPrototypeBind, + FunctionPrototypeToString, + Map, + MapPrototype, + MapPrototypeHas, + MapPrototypeGet, + MapPrototypeSet, + MapPrototypeDelete, + MapPrototypeEntries, + MapPrototypeForEach, + MapPrototypeGetSize, + Error, + ErrorPrototype, + ErrorCaptureStackTrace, + MathAbs, + MathMax, + MathMin, + MathSqrt, + MathRound, + MathFloor, + Number, + NumberPrototype, + NumberPrototypeToString, + NumberPrototypeValueOf, + BigIntPrototype, + BigIntPrototypeToString, + Proxy, + ReflectGet, + ReflectGetOwnPropertyDescriptor, + ReflectGetPrototypeOf, + ReflectHas, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + WeakMapPrototype, + WeakSetPrototype, +} = primordials; +import * as colors from "internal:ext/console/01_colors.js"; -((window) => { - const core = window.Deno.core; - const colors = window.__bootstrap.colors; - const { - AggregateErrorPrototype, - ArrayPrototypeUnshift, - isNaN, - DatePrototype, - DateNow, - DatePrototypeGetTime, - DatePrototypeToISOString, - Boolean, - BooleanPrototype, - BooleanPrototypeToString, - ObjectKeys, - ObjectCreate, - ObjectAssign, - ObjectIs, - ObjectValues, - ObjectFromEntries, - ObjectGetPrototypeOf, - ObjectGetOwnPropertyDescriptor, - ObjectGetOwnPropertySymbols, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - ObjectPrototypePropertyIsEnumerable, - PromisePrototype, - String, - StringPrototype, - StringPrototypeRepeat, - StringPrototypeReplace, - StringPrototypeReplaceAll, - StringPrototypeSplit, - StringPrototypeSlice, - StringPrototypeCodePointAt, - StringPrototypeCharCodeAt, - StringPrototypeNormalize, - StringPrototypeMatch, - StringPrototypePadStart, - StringPrototypeLocaleCompare, - StringPrototypeToString, - StringPrototypeTrim, - StringPrototypeIncludes, - StringPrototypeStartsWith, - TypeError, - NumberParseInt, - RegExp, - RegExpPrototype, - RegExpPrototypeTest, - RegExpPrototypeToString, - SafeArrayIterator, - SafeStringIterator, - SafeSet, - SetPrototype, - SetPrototypeEntries, - SetPrototypeGetSize, - Symbol, - SymbolPrototype, - SymbolPrototypeToString, - SymbolPrototypeValueOf, - SymbolToStringTag, - SymbolHasInstance, - SymbolFor, - Array, - ArrayIsArray, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeReduce, - ArrayPrototypeEntries, - ArrayPrototypePush, - ArrayPrototypePop, - ArrayPrototypeSort, - ArrayPrototypeSlice, - ArrayPrototypeShift, - ArrayPrototypeIncludes, - ArrayPrototypeFill, - ArrayPrototypeFilter, - ArrayPrototypeFind, - FunctionPrototypeBind, - FunctionPrototypeToString, - Map, - MapPrototype, - MapPrototypeHas, - MapPrototypeGet, - MapPrototypeSet, - MapPrototypeDelete, - MapPrototypeEntries, - MapPrototypeForEach, - MapPrototypeGetSize, - Error, - ErrorPrototype, - ErrorCaptureStackTrace, - MathAbs, - MathMax, - MathMin, - MathSqrt, - MathRound, - MathFloor, - Number, - NumberPrototype, - NumberPrototypeToString, - NumberPrototypeValueOf, - BigIntPrototype, - BigIntPrototypeToString, - Proxy, - ReflectGet, - ReflectGetOwnPropertyDescriptor, - ReflectGetPrototypeOf, - ReflectHas, - TypedArrayPrototypeGetLength, - TypedArrayPrototypeGetSymbolToStringTag, - WeakMapPrototype, - WeakSetPrototype, - } = window.__bootstrap.primordials; +function isInvalidDate(x) { + return isNaN(DatePrototypeGetTime(x)); +} - function isInvalidDate(x) { - return isNaN(DatePrototypeGetTime(x)); +function hasOwnProperty(obj, v) { + if (obj == null) { + return false; + } + return ObjectPrototypeHasOwnProperty(obj, v); +} + +function propertyIsEnumerable(obj, prop) { + if ( + obj == null || + typeof obj.propertyIsEnumerable !== "function" + ) { + return false; } - function hasOwnProperty(obj, v) { - if (obj == null) { - return false; + return ObjectPrototypePropertyIsEnumerable(obj, prop); +} + +// Copyright Joyent, Inc. and other Node contributors. MIT license. +// Forked from Node's lib/internal/cli_table.js + +function isTypedArray(x) { + return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined; +} + +const tableChars = { + middleMiddle: "─", + rowMiddle: "┼", + topRight: "┐", + topLeft: "┌", + leftMiddle: "├", + topMiddle: "┬", + bottomRight: "┘", + bottomLeft: "└", + bottomMiddle: "┴", + rightMiddle: "┤", + left: "│ ", + right: " │", + middle: " │ ", +}; + +function isFullWidthCodePoint(code) { + // Code points are partially derived from: + // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return ( + code >= 0x1100 && + (code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd)) + ); +} + +function getStringWidth(str) { + str = StringPrototypeNormalize(colors.stripColor(str), "NFC"); + let width = 0; + + for (const ch of new SafeStringIterator(str)) { + width += isFullWidthCodePoint(StringPrototypeCodePointAt(ch, 0)) ? 2 : 1; + } + + return width; +} + +function renderRow(row, columnWidths, columnRightAlign) { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const padding = StringPrototypeRepeat(" ", columnWidths[i] - len); + if (columnRightAlign?.[i]) { + out += `${padding}${cell}`; + } else { + out += `${cell}${padding}`; } - return ObjectPrototypeHasOwnProperty(obj, v); - } - - function propertyIsEnumerable(obj, prop) { - if ( - obj == null || - typeof obj.propertyIsEnumerable !== "function" - ) { - return false; + if (i !== row.length - 1) { + out += tableChars.middle; } - - return ObjectPrototypePropertyIsEnumerable(obj, prop); } + out += tableChars.right; + return out; +} - // Copyright Joyent, Inc. and other Node contributors. MIT license. - // Forked from Node's lib/internal/cli_table.js +function canRightAlign(value) { + const isNumber = !isNaN(value); + return isNumber; +} - function isTypedArray(x) { - return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined; - } +function cliTable(head, columns) { + const rows = []; + const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h)); + const longestColumn = ArrayPrototypeReduce( + columns, + (n, a) => MathMax(n, a.length), + 0, + ); + const columnRightAlign = new Array(columnWidths.length).fill(true); - const tableChars = { - middleMiddle: "─", - rowMiddle: "┼", - topRight: "┐", - topLeft: "┌", - leftMiddle: "├", - topMiddle: "┬", - bottomRight: "┘", - bottomLeft: "└", - bottomMiddle: "┴", - rightMiddle: "┤", - left: "│ ", - right: " │", - middle: " │ ", - }; - - function isFullWidthCodePoint(code) { - // Code points are partially derived from: - // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt - return ( - code >= 0x1100 && - (code <= 0x115f || // Hangul Jamo - code === 0x2329 || // LEFT-POINTING ANGLE BRACKET - code === 0x232a || // RIGHT-POINTING ANGLE BRACKET - // CJK Radicals Supplement .. Enclosed CJK Letters and Months - (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || - // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A - (code >= 0x3250 && code <= 0x4dbf) || - // CJK Unified Ideographs .. Yi Radicals - (code >= 0x4e00 && code <= 0xa4c6) || - // Hangul Jamo Extended-A - (code >= 0xa960 && code <= 0xa97c) || - // Hangul Syllables - (code >= 0xac00 && code <= 0xd7a3) || - // CJK Compatibility Ideographs - (code >= 0xf900 && code <= 0xfaff) || - // Vertical Forms - (code >= 0xfe10 && code <= 0xfe19) || - // CJK Compatibility Forms .. Small Form Variants - (code >= 0xfe30 && code <= 0xfe6b) || - // Halfwidth and Fullwidth Forms - (code >= 0xff01 && code <= 0xff60) || - (code >= 0xffe0 && code <= 0xffe6) || - // Kana Supplement - (code >= 0x1b000 && code <= 0x1b001) || - // Enclosed Ideographic Supplement - (code >= 0x1f200 && code <= 0x1f251) || - // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff - // Emoticons 0x1f600 - 0x1f64f - (code >= 0x1f300 && code <= 0x1f64f) || - // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane - (code >= 0x20000 && code <= 0x3fffd)) - ); - } - - function getStringWidth(str) { - str = StringPrototypeNormalize(colors.stripColor(str), "NFC"); - let width = 0; - - for (const ch of new SafeStringIterator(str)) { - width += isFullWidthCodePoint(StringPrototypeCodePointAt(ch, 0)) ? 2 : 1; - } - - return width; - } - - function renderRow(row, columnWidths, columnRightAlign) { - let out = tableChars.left; - for (let i = 0; i < row.length; i++) { - const cell = row[i]; - const len = getStringWidth(cell); - const padding = StringPrototypeRepeat(" ", columnWidths[i] - len); - if (columnRightAlign?.[i]) { - out += `${padding}${cell}`; - } else { - out += `${cell}${padding}`; - } - if (i !== row.length - 1) { - out += tableChars.middle; + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) { + rows[j] = []; } + const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = MathMax(width, counted); + columnRightAlign[i] &= canRightAlign(value); } - out += tableChars.right; - return out; } - function canRightAlign(value) { - const isNumber = !isNaN(value); - return isNumber; + const divider = ArrayPrototypeMap( + columnWidths, + (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2), + ); + + let result = + `${tableChars.topLeft}${ + ArrayPrototypeJoin(divider, tableChars.topMiddle) + }` + + `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + + `${tableChars.leftMiddle}${ + ArrayPrototypeJoin(divider, tableChars.rowMiddle) + }` + + `${tableChars.rightMiddle}\n`; + + for (let i = 0; i < rows.length; ++i) { + const row = rows[i]; + result += `${renderRow(row, columnWidths, columnRightAlign)}\n`; } - function cliTable(head, columns) { - const rows = []; - const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h)); - const longestColumn = ArrayPrototypeReduce( - columns, - (n, a) => MathMax(n, a.length), - 0, - ); - const columnRightAlign = new Array(columnWidths.length).fill(true); + result += + `${tableChars.bottomLeft}${ + ArrayPrototypeJoin(divider, tableChars.bottomMiddle) + }` + + tableChars.bottomRight; - for (let i = 0; i < head.length; i++) { - const column = columns[i]; - for (let j = 0; j < longestColumn; j++) { - if (rows[j] === undefined) { - rows[j] = []; - } - const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); - const width = columnWidths[i] || 0; - const counted = getStringWidth(value); - columnWidths[i] = MathMax(width, counted); - columnRightAlign[i] &= canRightAlign(value); - } - } + return result; +} +/* End of forked part */ - const divider = ArrayPrototypeMap( - columnWidths, - (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2), - ); +const DEFAULT_INSPECT_OPTIONS = { + depth: 4, + indentLevel: 0, + sorted: false, + trailingComma: false, + compact: true, + iterableLimit: 100, + showProxy: false, + colors: false, + getters: false, + showHidden: false, + strAbbreviateSize: 100, +}; - let result = - `${tableChars.topLeft}${ - ArrayPrototypeJoin(divider, tableChars.topMiddle) - }` + - `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + - `${tableChars.leftMiddle}${ - ArrayPrototypeJoin(divider, tableChars.rowMiddle) - }` + - `${tableChars.rightMiddle}\n`; +const DEFAULT_INDENT = " "; // Default indent string - for (let i = 0; i < rows.length; ++i) { - const row = rows[i]; - result += `${renderRow(row, columnWidths, columnRightAlign)}\n`; - } +const LINE_BREAKING_LENGTH = 80; +const MIN_GROUP_LENGTH = 6; +const STR_ABBREVIATE_SIZE = 100; - result += - `${tableChars.bottomLeft}${ - ArrayPrototypeJoin(divider, tableChars.bottomMiddle) - }` + - tableChars.bottomRight; +const PROMISE_STRING_BASE_LENGTH = 12; - return result; - } - /* End of forked part */ +class CSI { + static kClear = "\x1b[1;1H"; + static kClearScreenDown = "\x1b[0J"; +} - const DEFAULT_INSPECT_OPTIONS = { - depth: 4, - indentLevel: 0, - sorted: false, - trailingComma: false, - compact: true, - iterableLimit: 100, - showProxy: false, - colors: false, - getters: false, - showHidden: false, - strAbbreviateSize: 100, - }; - - const DEFAULT_INDENT = " "; // Default indent string - - const LINE_BREAKING_LENGTH = 80; - const MIN_GROUP_LENGTH = 6; - const STR_ABBREVIATE_SIZE = 100; - - const PROMISE_STRING_BASE_LENGTH = 12; - - class CSI { - static kClear = "\x1b[1;1H"; - static kClearScreenDown = "\x1b[0J"; - } - - function getClassInstanceName(instance) { - if (typeof instance != "object") { - return ""; - } - const constructor = instance?.constructor; - if (typeof constructor == "function") { - return constructor.name ?? ""; - } +function getClassInstanceName(instance) { + if (typeof instance != "object") { return ""; } + const constructor = instance?.constructor; + if (typeof constructor == "function") { + return constructor.name ?? ""; + } + return ""; +} - function maybeColor(fn, inspectOptions) { - return inspectOptions.colors ? fn : (s) => s; +function maybeColor(fn, inspectOptions) { + return inspectOptions.colors ? fn : (s) => s; +} + +function inspectFunction(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + if ( + ReflectHas(value, customInspect) && + typeof value[customInspect] === "function" + ) { + return String(value[customInspect](inspect, inspectOptions)); + } + // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction + let cstrName = ObjectGetPrototypeOf(value)?.constructor?.name; + if (!cstrName) { + // If prototype is removed or broken, + // use generic 'Function' instead. + cstrName = "Function"; + } + const stringValue = FunctionPrototypeToString(value); + // Might be Class + if (StringPrototypeStartsWith(stringValue, "class")) { + cstrName = "Class"; } - function inspectFunction(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); + // Our function may have properties, so we want to format those + // as if our function was an object + // If we didn't find any properties, we will just append an + // empty suffix. + let suffix = ``; + let refStr = ""; + if ( + ObjectKeys(value).length > 0 || + ObjectGetOwnPropertySymbols(value).length > 0 + ) { + const { 0: propString, 1: refIndex } = inspectRawObject( + value, + inspectOptions, + ); + refStr = refIndex; + // Filter out the empty string for the case we only have + // non-enumerable symbols. if ( - ReflectHas(value, customInspect) && - typeof value[customInspect] === "function" + propString.length > 0 && + propString !== "{}" ) { - return String(value[customInspect](inspect, inspectOptions)); - } - // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction - let cstrName = ObjectGetPrototypeOf(value)?.constructor?.name; - if (!cstrName) { - // If prototype is removed or broken, - // use generic 'Function' instead. - cstrName = "Function"; - } - const stringValue = FunctionPrototypeToString(value); - // Might be Class - if (StringPrototypeStartsWith(stringValue, "class")) { - cstrName = "Class"; + suffix = ` ${propString}`; } + } - // Our function may have properties, so we want to format those - // as if our function was an object - // If we didn't find any properties, we will just append an - // empty suffix. - let suffix = ``; - let refStr = ""; - if ( - ObjectKeys(value).length > 0 || - ObjectGetOwnPropertySymbols(value).length > 0 + if (value.name && value.name !== "anonymous") { + // from MDN spec + return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; + } + return cyan(`${refStr}[${cstrName}]`) + suffix; +} + +function inspectIterable( + value, + options, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); + if (inspectOptions.indentLevel >= inspectOptions.depth) { + return cyan(`[${options.typeName}]`); + } + + const entries = []; + let iter; + let valueIsTypedArray = false; + let entriesLength; + + switch (options.typeName) { + case "Map": + iter = MapPrototypeEntries(value); + entriesLength = MapPrototypeGetSize(value); + break; + case "Set": + iter = SetPrototypeEntries(value); + entriesLength = SetPrototypeGetSize(value); + break; + case "Array": + entriesLength = value.length; + break; + default: + if (isTypedArray(value)) { + entriesLength = TypedArrayPrototypeGetLength(value); + iter = ArrayPrototypeEntries(value); + valueIsTypedArray = true; + } else { + throw new TypeError("unreachable"); + } + } + + let entriesLengthWithoutEmptyItems = entriesLength; + if (options.typeName === "Array") { + for ( + let i = 0, j = 0; + i < entriesLength && j < inspectOptions.iterableLimit; + i++, j++ ) { - const { 0: propString, 1: refIndex } = inspectRawObject( - value, + inspectOptions.indentLevel++; + const { entry, skipTo } = options.entryHandler( + [i, value[i]], inspectOptions, ); - refStr = refIndex; - // Filter out the empty string for the case we only have - // non-enumerable symbols. - if ( - propString.length > 0 && - propString !== "{}" - ) { - suffix = ` ${propString}`; + ArrayPrototypePush(entries, entry); + inspectOptions.indentLevel--; + + if (skipTo) { + // subtract skipped (empty) items + entriesLengthWithoutEmptyItems -= skipTo - i; + i = skipTo; } } - - if (value.name && value.name !== "anonymous") { - // from MDN spec - return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; + } else { + let i = 0; + while (true) { + let el; + try { + const res = iter.next(); + if (res.done) { + break; + } + el = res.value; + } catch (err) { + if (valueIsTypedArray) { + // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer + // is detached. We don't want to show the exception in that case, so + // we catch it here and pretend the ArrayBuffer has no entries (like + // Chrome DevTools does). + break; + } + throw err; + } + if (i < inspectOptions.iterableLimit) { + inspectOptions.indentLevel++; + ArrayPrototypePush( + entries, + options.entryHandler( + el, + inspectOptions, + ), + ); + inspectOptions.indentLevel--; + } else { + break; + } + i++; } - return cyan(`${refStr}[${cstrName}]`) + suffix; } - function inspectIterable( + if (options.sort) { + ArrayPrototypeSort(entries); + } + + if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { + const nmore = entriesLengthWithoutEmptyItems - + inspectOptions.iterableLimit; + ArrayPrototypePush(entries, `... ${nmore} more items`); + } + + const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; + + const level = inspectOptions.indentLevel; + const initIndentation = `\n${ + StringPrototypeRepeat(DEFAULT_INDENT, level + 1) + }`; + const entryIndentation = `,\n${ + StringPrototypeRepeat(DEFAULT_INDENT, level + 1) + }`; + const closingDelimIndentation = StringPrototypeRepeat( + DEFAULT_INDENT, + level, + ); + const closingIndentation = `${ + inspectOptions.trailingComma ? "," : "" + }\n${closingDelimIndentation}`; + + let iContent; + if (entries.length === 0 && !inspectOptions.compact) { + iContent = `\n${closingDelimIndentation}`; + } else if (options.group && entries.length > MIN_GROUP_LENGTH) { + const groups = groupEntries(entries, level, value); + iContent = `${initIndentation}${ + ArrayPrototypeJoin(groups, entryIndentation) + }${closingIndentation}`; + } else { + iContent = entries.length === 0 + ? "" + : ` ${ArrayPrototypeJoin(entries, ", ")} `; + if ( + colors.stripColor(iContent).length > LINE_BREAKING_LENGTH || + !inspectOptions.compact + ) { + iContent = `${initIndentation}${ + ArrayPrototypeJoin(entries, entryIndentation) + }${closingIndentation}`; + } + } + + return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`; +} + +// Ported from Node.js +// Copyright Node.js contributors. All rights reserved. +function groupEntries( + entries, + level, + value, + iterableLimit = 100, +) { + let totalLength = 0; + let maxLength = 0; + let entriesLength = entries.length; + if (iterableLimit < entriesLength) { + // This makes sure the "... n more items" part is not taken into account. + entriesLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(entriesLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. + // IN PROGRESS: Colors are being taken into account. + for (let i = 0; i < entriesLength; i++) { + // Taking colors into account: removing the ANSI color + // codes from the string before measuring its length + const len = colors.stripColor(entries[i]).length; + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) maxLength = len; + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH && + (totalLength / actualMax > 5 || maxLength <= 6) + ) { + const approxCharHeights = 2.5; + const averageBias = MathSqrt(actualMax - totalLength / entries.length); + const biasedMax = MathMax(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = MathMin( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + MathRound( + MathSqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax, + ), + // Do not exceed the breakLength. + MathFloor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax), + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return entries; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < entries.length; j += columns) { + if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j]; + } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = "padStart"; + if (value !== undefined) { + for (let i = 0; i < entries.length; i++) { + if ( + typeof value[i] !== "number" && + typeof value[i] !== "bigint" + ) { + order = "padEnd"; + break; + } + } + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < entriesLength; i += columns) { + // The last lines may contain less entries than columns. + const max = MathMin(i + columns, entriesLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + const lengthOfColorCodes = entries[j].length - dataLen[j]; + const padding = maxLineLength[j - i] + lengthOfColorCodes; + str += `${entries[j]}, `[order](padding, " "); + } + if (order === "padStart") { + const lengthOfColorCodes = entries[j].length - dataLen[j]; + const padding = maxLineLength[j - i] + + lengthOfColorCodes - + separatorSpace; + str += StringPrototypePadStart(entries[j], padding, " "); + } else { + str += entries[j]; + } + ArrayPrototypePush(tmp, str); + } + if (iterableLimit < entries.length) { + ArrayPrototypePush(tmp, entries[entriesLength]); + } + entries = tmp; + } + return entries; +} + +let circular; +function handleCircular(value, cyan) { + let index = 1; + if (circular === undefined) { + circular = new Map(); + MapPrototypeSet(circular, value, index); + } else { + index = MapPrototypeGet(circular, value); + if (index === undefined) { + index = circular.size + 1; + MapPrototypeSet(circular, value, index); + } + } + // Circular string is cyan + return cyan(`[Circular *${index}]`); +} + +function _inspectValue( + value, + inspectOptions, +) { + const proxyDetails = core.getProxyDetails(value); + if (proxyDetails != null && inspectOptions.showProxy) { + return inspectProxy(proxyDetails, inspectOptions); + } + + const green = maybeColor(colors.green, inspectOptions); + const yellow = maybeColor(colors.yellow, inspectOptions); + const gray = maybeColor(colors.gray, inspectOptions); + const cyan = maybeColor(colors.cyan, inspectOptions); + const bold = maybeColor(colors.bold, inspectOptions); + const red = maybeColor(colors.red, inspectOptions); + + switch (typeof value) { + case "string": + return green(quoteString(value)); + case "number": // Numbers are yellow + // Special handling of -0 + return yellow(ObjectIs(value, -0) ? "-0" : `${value}`); + case "boolean": // booleans are yellow + return yellow(String(value)); + case "undefined": // undefined is gray + return gray(String(value)); + case "symbol": // Symbols are green + return green(maybeQuoteSymbol(value)); + case "bigint": // Bigints are yellow + return yellow(`${value}n`); + case "function": // Function string is cyan + if (ctxHas(value)) { + // Circular string is cyan + return handleCircular(value, cyan); + } + + return inspectFunction(value, inspectOptions); + case "object": // null is bold + if (value === null) { + return bold("null"); + } + + if (ctxHas(value)) { + return handleCircular(value, cyan); + } + + return inspectObject( + value, + inspectOptions, + proxyDetails, + ); + default: + // Not implemented is red + return red("[Not Implemented]"); + } +} + +function inspectValue( + value, + inspectOptions, +) { + ArrayPrototypePush(CTX_STACK, value); + let x; + try { + x = _inspectValue(value, inspectOptions); + } finally { + ArrayPrototypePop(CTX_STACK); + } + return x; +} + +// We can match Node's quoting behavior exactly by swapping the double quote and +// single quote in this array. That would give preference to single quotes. +// However, we prefer double quotes as the default. +const QUOTES = ['"', "'", "`"]; + +/** Surround the string in quotes. + * + * The quote symbol is chosen by taking the first of the `QUOTES` array which + * does not occur in the string. If they all occur, settle with `QUOTES[0]`. + * + * Insert a backslash before any occurrence of the chosen quote symbol and + * before any backslash. + */ +function quoteString(string) { + const quote = + ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ?? + QUOTES[0]; + const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); + string = StringPrototypeReplace(string, escapePattern, "\\"); + string = replaceEscapeSequences(string); + return `${quote}${string}${quote}`; +} + +// Replace escape sequences that can modify output. +function replaceEscapeSequences(string) { + const escapeMap = { + "\b": "\\b", + "\f": "\\f", + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\v": "\\v", + }; + + return StringPrototypeReplace( + StringPrototypeReplace( + string, + /([\b\f\n\r\t\v])/g, + (c) => escapeMap[c], + ), + // deno-lint-ignore no-control-regex + /[\x00-\x1f\x7f-\x9f]/g, + (c) => + "\\x" + + StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16), + 2, + "0", + ), + ); +} + +// Surround a string with quotes when it is required (e.g the string not a valid identifier). +function maybeQuoteString(string) { + if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_0-9]*$/, string)) { + return replaceEscapeSequences(string); + } + + return quoteString(string); +} + +// Surround a symbol's description in quotes when it is required (e.g the description has non printable characters). +function maybeQuoteSymbol(symbol) { + if (symbol.description === undefined) { + return SymbolPrototypeToString(symbol); + } + + if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { + return SymbolPrototypeToString(symbol); + } + + return `Symbol(${quoteString(symbol.description)})`; +} + +const CTX_STACK = []; +function ctxHas(x) { + // Only check parent contexts + return ArrayPrototypeIncludes( + ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1), + x, + ); +} + +// Print strings when they are inside of arrays or objects with quotes +function inspectValueWithQuotes( + value, + inspectOptions, +) { + const abbreviateSize = typeof inspectOptions.strAbbreviateSize === "undefined" + ? STR_ABBREVIATE_SIZE + : inspectOptions.strAbbreviateSize; + const green = maybeColor(colors.green, inspectOptions); + switch (typeof value) { + case "string": { + const trunc = value.length > abbreviateSize + ? StringPrototypeSlice(value, 0, abbreviateSize) + "..." + : value; + return green(quoteString(trunc)); // Quoted strings are green + } + default: + return inspectValue(value, inspectOptions); + } +} + +function inspectArray( + value, + inspectOptions, +) { + const gray = maybeColor(colors.gray, inspectOptions); + let lastValidIndex = 0; + let keys; + const options = { + typeName: "Array", + displayName: "", + delims: ["[", "]"], + entryHandler: (entry, inspectOptions) => { + const { 0: index, 1: val } = entry; + let i = index; + lastValidIndex = index; + if (!ObjectPrototypeHasOwnProperty(value, i)) { + let skipTo; + keys = keys || ObjectKeys(value); + i = value.length; + if (keys.length === 0) { + // fast path, all items are empty + skipTo = i; + } else { + // Not all indexes are empty or there's a non-index property + // Find first non-empty array index + while (keys.length) { + const key = ArrayPrototypeShift(keys); + // check if it's a valid array index + if (key > lastValidIndex && key < 2 ** 32 - 1) { + i = Number(key); + break; + } + } + + skipTo = i - 1; + } + const emptyItems = i - index; + const ending = emptyItems > 1 ? "s" : ""; + return { + entry: gray(`<${emptyItems} empty item${ending}>`), + skipTo, + }; + } else { + return { entry: inspectValueWithQuotes(val, inspectOptions) }; + } + }, + group: inspectOptions.compact, + sort: false, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectTypedArray( + typedArrayName, + value, + inspectOptions, +) { + const valueLength = value.length; + const options = { + typeName: typedArrayName, + displayName: `${typedArrayName}(${valueLength})`, + delims: ["[", "]"], + entryHandler: (entry, inspectOptions) => { + const val = entry[1]; + inspectOptions.indentLevel++; + const inspectedValue = inspectValueWithQuotes(val, inspectOptions); + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: inspectOptions.compact, + sort: false, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectSet( + value, + inspectOptions, +) { + const options = { + typeName: "Set", + displayName: "Set", + delims: ["{", "}"], + entryHandler: (entry, inspectOptions) => { + const val = entry[1]; + inspectOptions.indentLevel++; + const inspectedValue = inspectValueWithQuotes(val, inspectOptions); + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: false, + sort: inspectOptions.sorted, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectMap( + value, + inspectOptions, +) { + const options = { + typeName: "Map", + displayName: "Map", + delims: ["{", "}"], + entryHandler: (entry, inspectOptions) => { + const { 0: key, 1: val } = entry; + inspectOptions.indentLevel++; + const inspectedValue = `${ + inspectValueWithQuotes(key, inspectOptions) + } => ${inspectValueWithQuotes(val, inspectOptions)}`; + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: false, + sort: inspectOptions.sorted, + }; + return inspectIterable( value, options, inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return cyan(`[${options.typeName}]`); - } + ); +} - const entries = []; - let iter; - let valueIsTypedArray = false; - let entriesLength; +function inspectWeakSet(inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color +} - switch (options.typeName) { - case "Map": - iter = MapPrototypeEntries(value); - entriesLength = MapPrototypeGetSize(value); - break; - case "Set": - iter = SetPrototypeEntries(value); - entriesLength = SetPrototypeGetSize(value); - break; - case "Array": - entriesLength = value.length; - break; - default: - if (isTypedArray(value)) { - entriesLength = TypedArrayPrototypeGetLength(value); - iter = ArrayPrototypeEntries(value); - valueIsTypedArray = true; - } else { - throw new TypeError("unreachable"); - } - } +function inspectWeakMap(inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color +} - let entriesLengthWithoutEmptyItems = entriesLength; - if (options.typeName === "Array") { - for ( - let i = 0, j = 0; - i < entriesLength && j < inspectOptions.iterableLimit; - i++, j++ - ) { - inspectOptions.indentLevel++; - const { entry, skipTo } = options.entryHandler( - [i, value[i]], - inspectOptions, - ); - ArrayPrototypePush(entries, entry); - inspectOptions.indentLevel--; +function inspectDate(value, inspectOptions) { + // without quotes, ISO format, in magenta like before + const magenta = maybeColor(colors.magenta, inspectOptions); + return magenta( + isInvalidDate(value) ? "Invalid Date" : DatePrototypeToISOString(value), + ); +} - if (skipTo) { - // subtract skipped (empty) items - entriesLengthWithoutEmptyItems -= skipTo - i; - i = skipTo; - } - } +function inspectRegExp(value, inspectOptions) { + const red = maybeColor(colors.red, inspectOptions); + return red(RegExpPrototypeToString(value)); // RegExps are red +} + +function inspectError(value, cyan) { + const causes = [value]; + + let err = value; + while (err.cause) { + if (ArrayPrototypeIncludes(causes, err.cause)) { + ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); + break; } else { - let i = 0; - while (true) { - let el; - try { - const res = iter.next(); - if (res.done) { - break; - } - el = res.value; - } catch (err) { - if (valueIsTypedArray) { - // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer - // is detached. We don't want to show the exception in that case, so - // we catch it here and pretend the ArrayBuffer has no entries (like - // Chrome DevTools does). - break; - } - throw err; - } - if (i < inspectOptions.iterableLimit) { - inspectOptions.indentLevel++; - ArrayPrototypePush( - entries, - options.entryHandler( - el, - inspectOptions, - ), - ); - inspectOptions.indentLevel--; - } else { - break; - } - i++; - } - } - - if (options.sort) { - ArrayPrototypeSort(entries); - } - - if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { - const nmore = entriesLengthWithoutEmptyItems - - inspectOptions.iterableLimit; - ArrayPrototypePush(entries, `... ${nmore} more items`); - } - - const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; - - const level = inspectOptions.indentLevel; - const initIndentation = `\n${ - StringPrototypeRepeat(DEFAULT_INDENT, level + 1) - }`; - const entryIndentation = `,\n${ - StringPrototypeRepeat(DEFAULT_INDENT, level + 1) - }`; - const closingDelimIndentation = StringPrototypeRepeat( - DEFAULT_INDENT, - level, - ); - const closingIndentation = `${ - inspectOptions.trailingComma ? "," : "" - }\n${closingDelimIndentation}`; - - let iContent; - if (entries.length === 0 && !inspectOptions.compact) { - iContent = `\n${closingDelimIndentation}`; - } else if (options.group && entries.length > MIN_GROUP_LENGTH) { - const groups = groupEntries(entries, level, value); - iContent = `${initIndentation}${ - ArrayPrototypeJoin(groups, entryIndentation) - }${closingIndentation}`; - } else { - iContent = entries.length === 0 - ? "" - : ` ${ArrayPrototypeJoin(entries, ", ")} `; - if ( - colors.stripColor(iContent).length > LINE_BREAKING_LENGTH || - !inspectOptions.compact - ) { - iContent = `${initIndentation}${ - ArrayPrototypeJoin(entries, entryIndentation) - }${closingIndentation}`; - } - } - - return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`; - } - - // Ported from Node.js - // Copyright Node.js contributors. All rights reserved. - function groupEntries( - entries, - level, - value, - iterableLimit = 100, - ) { - let totalLength = 0; - let maxLength = 0; - let entriesLength = entries.length; - if (iterableLimit < entriesLength) { - // This makes sure the "... n more items" part is not taken into account. - entriesLength--; - } - const separatorSpace = 2; // Add 1 for the space and 1 for the separator. - const dataLen = new Array(entriesLength); - // Calculate the total length of all output entries and the individual max - // entries length of all output entries. - // IN PROGRESS: Colors are being taken into account. - for (let i = 0; i < entriesLength; i++) { - // Taking colors into account: removing the ANSI color - // codes from the string before measuring its length - const len = colors.stripColor(entries[i]).length; - dataLen[i] = len; - totalLength += len + separatorSpace; - if (maxLength < len) maxLength = len; - } - // Add two to `maxLength` as we add a single whitespace character plus a comma - // in-between two entries. - const actualMax = maxLength + separatorSpace; - // Check if at least three entries fit next to each other and prevent grouping - // of arrays that contains entries of very different length (i.e., if a single - // entry is longer than 1/5 of all other entries combined). Otherwise the - // space in-between small entries would be enormous. - if ( - actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH && - (totalLength / actualMax > 5 || maxLength <= 6) - ) { - const approxCharHeights = 2.5; - const averageBias = MathSqrt(actualMax - totalLength / entries.length); - const biasedMax = MathMax(actualMax - 3 - averageBias, 1); - // Dynamically check how many columns seem possible. - const columns = MathMin( - // Ideally a square should be drawn. We expect a character to be about 2.5 - // times as high as wide. This is the area formula to calculate a square - // which contains n rectangles of size `actualMax * approxCharHeights`. - // Divide that by `actualMax` to receive the correct number of columns. - // The added bias increases the columns for short entries. - MathRound( - MathSqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax, - ), - // Do not exceed the breakLength. - MathFloor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax), - // Limit the columns to a maximum of fifteen. - 15, - ); - // Return with the original output if no grouping should happen. - if (columns <= 1) { - return entries; - } - const tmp = []; - const maxLineLength = []; - for (let i = 0; i < columns; i++) { - let lineMaxLength = 0; - for (let j = i; j < entries.length; j += columns) { - if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j]; - } - lineMaxLength += separatorSpace; - maxLineLength[i] = lineMaxLength; - } - let order = "padStart"; - if (value !== undefined) { - for (let i = 0; i < entries.length; i++) { - if ( - typeof value[i] !== "number" && - typeof value[i] !== "bigint" - ) { - order = "padEnd"; - break; - } - } - } - // Each iteration creates a single line of grouped entries. - for (let i = 0; i < entriesLength; i += columns) { - // The last lines may contain less entries than columns. - const max = MathMin(i + columns, entriesLength); - let str = ""; - let j = i; - for (; j < max - 1; j++) { - const lengthOfColorCodes = entries[j].length - dataLen[j]; - const padding = maxLineLength[j - i] + lengthOfColorCodes; - str += `${entries[j]}, `[order](padding, " "); - } - if (order === "padStart") { - const lengthOfColorCodes = entries[j].length - dataLen[j]; - const padding = maxLineLength[j - i] + - lengthOfColorCodes - - separatorSpace; - str += StringPrototypePadStart(entries[j], padding, " "); - } else { - str += entries[j]; - } - ArrayPrototypePush(tmp, str); - } - if (iterableLimit < entries.length) { - ArrayPrototypePush(tmp, entries[entriesLength]); - } - entries = tmp; - } - return entries; - } - - let circular; - function handleCircular(value, cyan) { - let index = 1; - if (circular === undefined) { - circular = new Map(); - MapPrototypeSet(circular, value, index); - } else { - index = MapPrototypeGet(circular, value); - if (index === undefined) { - index = circular.size + 1; - MapPrototypeSet(circular, value, index); - } - } - // Circular string is cyan - return cyan(`[Circular *${index}]`); - } - - function _inspectValue( - value, - inspectOptions, - ) { - const proxyDetails = core.getProxyDetails(value); - if (proxyDetails != null && inspectOptions.showProxy) { - return inspectProxy(proxyDetails, inspectOptions); - } - - const green = maybeColor(colors.green, inspectOptions); - const yellow = maybeColor(colors.yellow, inspectOptions); - const gray = maybeColor(colors.gray, inspectOptions); - const cyan = maybeColor(colors.cyan, inspectOptions); - const bold = maybeColor(colors.bold, inspectOptions); - const red = maybeColor(colors.red, inspectOptions); - - switch (typeof value) { - case "string": - return green(quoteString(value)); - case "number": // Numbers are yellow - // Special handling of -0 - return yellow(ObjectIs(value, -0) ? "-0" : `${value}`); - case "boolean": // booleans are yellow - return yellow(String(value)); - case "undefined": // undefined is gray - return gray(String(value)); - case "symbol": // Symbols are green - return green(maybeQuoteSymbol(value)); - case "bigint": // Bigints are yellow - return yellow(`${value}n`); - case "function": // Function string is cyan - if (ctxHas(value)) { - // Circular string is cyan - return handleCircular(value, cyan); - } - - return inspectFunction(value, inspectOptions); - case "object": // null is bold - if (value === null) { - return bold("null"); - } - - if (ctxHas(value)) { - return handleCircular(value, cyan); - } - - return inspectObject( - value, - inspectOptions, - proxyDetails, - ); - default: - // Not implemented is red - return red("[Not Implemented]"); + ArrayPrototypePush(causes, err.cause); + err = err.cause; } } - function inspectValue( - value, - inspectOptions, - ) { - ArrayPrototypePush(CTX_STACK, value); - let x; - try { - x = _inspectValue(value, inspectOptions); - } finally { - ArrayPrototypePop(CTX_STACK); + const refMap = new Map(); + for (let i = 0; i < causes.length; ++i) { + const cause = causes[i]; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, cause); + if (index !== undefined) { + MapPrototypeSet(refMap, cause, cyan(` `)); + } } - return x; } + ArrayPrototypeShift(causes); - // We can match Node's quoting behavior exactly by swapping the double quote and - // single quote in this array. That would give preference to single quotes. - // However, we prefer double quotes as the default. - const QUOTES = ['"', "'", "`"]; + let finalMessage = MapPrototypeGet(refMap, value) ?? ""; - /** Surround the string in quotes. - * - * The quote symbol is chosen by taking the first of the `QUOTES` array which - * does not occur in the string. If they all occur, settle with `QUOTES[0]`. - * - * Insert a backslash before any occurrence of the chosen quote symbol and - * before any backslash. - */ - function quoteString(string) { - const quote = - ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ?? - QUOTES[0]; - const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); - string = StringPrototypeReplace(string, escapePattern, "\\"); - string = replaceEscapeSequences(string); - return `${quote}${string}${quote}`; - } + if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) { + const stackLines = StringPrototypeSplit(value.stack, "\n"); + while (true) { + const line = ArrayPrototypeShift(stackLines); + if (RegExpPrototypeTest(/\s+at/, line)) { + ArrayPrototypeUnshift(stackLines, line); + break; + } else if (typeof line === "undefined") { + break; + } - // Replace escape sequences that can modify output. - function replaceEscapeSequences(string) { - const escapeMap = { - "\b": "\\b", - "\f": "\\f", - "\n": "\\n", - "\r": "\\r", - "\t": "\\t", - "\v": "\\v", - }; - - return StringPrototypeReplace( - StringPrototypeReplace( - string, - /([\b\f\n\r\t\v])/g, - (c) => escapeMap[c], + finalMessage += line; + finalMessage += "\n"; + } + const aggregateMessage = ArrayPrototypeJoin( + ArrayPrototypeMap( + value.errors, + (error) => + StringPrototypeReplace( + inspectArgs([error]), + /^(?!\s*$)/gm, + StringPrototypeRepeat(" ", 4), + ), ), - // deno-lint-ignore no-control-regex - /[\x00-\x1f\x7f-\x9f]/g, - (c) => - "\\x" + - StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16), - 2, - "0", + "\n", + ); + finalMessage += aggregateMessage; + finalMessage += "\n"; + finalMessage += ArrayPrototypeJoin(stackLines, "\n"); + } else { + finalMessage += value.stack; + } + + finalMessage += ArrayPrototypeJoin( + ArrayPrototypeMap( + causes, + (cause) => + "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + + (cause?.stack ?? cause), + ), + "", + ); + + return finalMessage; +} + +function inspectStringObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan +} + +function inspectBooleanObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan +} + +function inspectNumberObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + // Special handling of -0 + return cyan( + `[Number: ${ + ObjectIs(NumberPrototypeValueOf(value), -0) + ? "-0" + : NumberPrototypeToString(value) + }]`, + ); // wrappers are in cyan +} + +function inspectBigIntObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan +} + +function inspectSymbolObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[Symbol: ${maybeQuoteSymbol(SymbolPrototypeValueOf(value))}]`); // wrappers are in cyan +} + +const PromiseState = { + Pending: 0, + Fulfilled: 1, + Rejected: 2, +}; + +function inspectPromise( + value, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); + const red = maybeColor(colors.red, inspectOptions); + + const { 0: state, 1: result } = core.getPromiseDetails(value); + + if (state === PromiseState.Pending) { + return `Promise { ${cyan("")} }`; + } + + const prefix = state === PromiseState.Fulfilled + ? "" + : `${red("")} `; + + inspectOptions.indentLevel++; + const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; + inspectOptions.indentLevel--; + + if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { + return `Promise {\n${ + StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) + }${str}\n}`; + } + + return `Promise { ${str} }`; +} + +function inspectProxy( + targetAndHandler, + inspectOptions, +) { + return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; +} + +function inspectRawObject( + value, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); + + if (inspectOptions.indentLevel >= inspectOptions.depth) { + return [cyan("[Object]"), ""]; // wrappers are in cyan + } + + let baseString; + + let shouldShowDisplayName = false; + let displayName = value[ + SymbolToStringTag + ]; + if (!displayName) { + displayName = getClassInstanceName(value); + } + if ( + displayName && displayName !== "Object" && displayName !== "anonymous" + ) { + shouldShowDisplayName = true; + } + + const entries = []; + const stringKeys = ObjectKeys(value); + const symbolKeys = ObjectGetOwnPropertySymbols(value); + if (inspectOptions.sorted) { + ArrayPrototypeSort(stringKeys); + ArrayPrototypeSort( + symbolKeys, + (s1, s2) => + StringPrototypeLocaleCompare( + s1.description ?? "", + s2.description ?? "", ), ); } - // Surround a string with quotes when it is required (e.g the string not a valid identifier). - function maybeQuoteString(string) { - if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_0-9]*$/, string)) { - return replaceEscapeSequences(string); - } + const red = maybeColor(colors.red, inspectOptions); - return quoteString(string); + inspectOptions.indentLevel++; + + for (let i = 0; i < stringKeys.length; ++i) { + const key = stringKeys[i]; + if (inspectOptions.getters) { + let propertyValue; + let error = null; + try { + propertyValue = value[key]; + } catch (error_) { + error = error_; + } + const inspectedValue = error == null + ? inspectValueWithQuotes(propertyValue, inspectOptions) + : red(`[Thrown ${error.name}: ${error.message}]`); + ArrayPrototypePush( + entries, + `${maybeQuoteString(key)}: ${inspectedValue}`, + ); + } else { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor.get !== undefined && descriptor.set !== undefined) { + ArrayPrototypePush( + entries, + `${maybeQuoteString(key)}: [Getter/Setter]`, + ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `${maybeQuoteString(key)}: [Getter]`); + } else { + ArrayPrototypePush( + entries, + `${maybeQuoteString(key)}: ${ + inspectValueWithQuotes(value[key], inspectOptions) + }`, + ); + } + } } - // Surround a symbol's description in quotes when it is required (e.g the description has non printable characters). - function maybeQuoteSymbol(symbol) { - if (symbol.description === undefined) { - return SymbolPrototypeToString(symbol); + for (let i = 0; i < symbolKeys.length; ++i) { + const key = symbolKeys[i]; + if ( + !inspectOptions.showHidden && + !propertyIsEnumerable(value, key) + ) { + continue; } - if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { - return SymbolPrototypeToString(symbol); + if (inspectOptions.getters) { + let propertyValue; + let error; + try { + propertyValue = value[key]; + } catch (error_) { + error = error_; + } + const inspectedValue = error == null + ? inspectValueWithQuotes(propertyValue, inspectOptions) + : red(`Thrown ${error.name}: ${error.message}`); + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: ${inspectedValue}`, + ); + } else { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor.get !== undefined && descriptor.set !== undefined) { + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: [Getter/Setter]`, + ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `[${maybeQuoteSymbol(key)}]: [Getter]`); + } else { + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: ${ + inspectValueWithQuotes(value[key], inspectOptions) + }`, + ); + } } - - return `Symbol(${quoteString(symbol.description)})`; } - const CTX_STACK = []; - function ctxHas(x) { - // Only check parent contexts - return ArrayPrototypeIncludes( - ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1), - x, + inspectOptions.indentLevel--; + + // Making sure color codes are ignored when calculating the total length + const totalLength = entries.length + inspectOptions.indentLevel + + colors.stripColor(ArrayPrototypeJoin(entries, "")).length; + + if (entries.length === 0) { + baseString = "{}"; + } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) { + const entryIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + inspectOptions.indentLevel + 1, + ); + const closingIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + inspectOptions.indentLevel, + ); + baseString = `{\n${entryIndent}${ + ArrayPrototypeJoin(entries, `,\n${entryIndent}`) + }${inspectOptions.trailingComma ? "," : ""}\n${closingIndent}}`; + } else { + baseString = `{ ${ArrayPrototypeJoin(entries, ", ")} }`; + } + + if (shouldShowDisplayName) { + baseString = `${displayName} ${baseString}`; + } + + let refIndex = ""; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, value); + if (index !== undefined) { + refIndex = cyan(` `); + } + } + + return [baseString, refIndex]; +} + +function inspectObject(value, inspectOptions, proxyDetails) { + if ( + ReflectHas(value, customInspect) && + typeof value[customInspect] === "function" + ) { + return String(value[customInspect](inspect, inspectOptions)); + } + // This non-unique symbol is used to support op_crates, ie. + // in extensions/web we don't want to depend on public + // Symbol.for("Deno.customInspect") symbol defined in the public API. + // Internal only, shouldn't be used by users. + const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); + if ( + ReflectHas(value, privateCustomInspect) && + typeof value[privateCustomInspect] === "function" + ) { + // TODO(nayeemrmn): `inspect` is passed as an argument because custom + // inspect implementations in `extensions` need it, but may not have access + // to the `Deno` namespace in web workers. Remove when the `Deno` + // namespace is always enabled. + return String( + value[privateCustomInspect](inspect, inspectOptions), ); } - - // Print strings when they are inside of arrays or objects with quotes - function inspectValueWithQuotes( - value, - inspectOptions, - ) { - const abbreviateSize = - typeof inspectOptions.strAbbreviateSize === "undefined" - ? STR_ABBREVIATE_SIZE - : inspectOptions.strAbbreviateSize; - const green = maybeColor(colors.green, inspectOptions); - switch (typeof value) { - case "string": { - const trunc = value.length > abbreviateSize - ? StringPrototypeSlice(value, 0, abbreviateSize) + "..." - : value; - return green(quoteString(trunc)); // Quoted strings are green - } - default: - return inspectValue(value, inspectOptions); - } - } - - function inspectArray( - value, - inspectOptions, - ) { - const gray = maybeColor(colors.gray, inspectOptions); - let lastValidIndex = 0; - let keys; - const options = { - typeName: "Array", - displayName: "", - delims: ["[", "]"], - entryHandler: (entry, inspectOptions) => { - const { 0: index, 1: val } = entry; - let i = index; - lastValidIndex = index; - if (!ObjectPrototypeHasOwnProperty(value, i)) { - let skipTo; - keys = keys || ObjectKeys(value); - i = value.length; - if (keys.length === 0) { - // fast path, all items are empty - skipTo = i; - } else { - // Not all indexes are empty or there's a non-index property - // Find first non-empty array index - while (keys.length) { - const key = ArrayPrototypeShift(keys); - // check if it's a valid array index - if (key > lastValidIndex && key < 2 ** 32 - 1) { - i = Number(key); - break; - } - } - - skipTo = i - 1; - } - const emptyItems = i - index; - const ending = emptyItems > 1 ? "s" : ""; - return { - entry: gray(`<${emptyItems} empty item${ending}>`), - skipTo, - }; - } else { - return { entry: inspectValueWithQuotes(val, inspectOptions) }; - } - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, options, inspectOptions); - } - - function inspectTypedArray( - typedArrayName, - value, - inspectOptions, - ) { - const valueLength = value.length; - const options = { - typeName: typedArrayName, - displayName: `${typedArrayName}(${valueLength})`, - delims: ["[", "]"], - entryHandler: (entry, inspectOptions) => { - const val = entry[1]; - inspectOptions.indentLevel++; - const inspectedValue = inspectValueWithQuotes(val, inspectOptions); - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, options, inspectOptions); - } - - function inspectSet( - value, - inspectOptions, - ) { - const options = { - typeName: "Set", - displayName: "Set", - delims: ["{", "}"], - entryHandler: (entry, inspectOptions) => { - const val = entry[1]; - inspectOptions.indentLevel++; - const inspectedValue = inspectValueWithQuotes(val, inspectOptions); - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable(value, options, inspectOptions); - } - - function inspectMap( - value, - inspectOptions, - ) { - const options = { - typeName: "Map", - displayName: "Map", - delims: ["{", "}"], - entryHandler: (entry, inspectOptions) => { - const { 0: key, 1: val } = entry; - inspectOptions.indentLevel++; - const inspectedValue = `${ - inspectValueWithQuotes(key, inspectOptions) - } => ${inspectValueWithQuotes(val, inspectOptions)}`; - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable( - value, - options, + if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { + return inspectError(value, maybeColor(colors.cyan, inspectOptions)); + } else if (ArrayIsArray(value)) { + return inspectArray(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) { + return inspectNumberObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(BigIntPrototype, value)) { + return inspectBigIntObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(BooleanPrototype, value)) { + return inspectBooleanObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(StringPrototype, value)) { + return inspectStringObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(SymbolPrototype, value)) { + return inspectSymbolObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(PromisePrototype, value)) { + return inspectPromise(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) { + return inspectRegExp(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + return inspectDate( + proxyDetails ? proxyDetails[0] : value, inspectOptions, ); - } - - function inspectWeakSet(inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color - } - - function inspectWeakMap(inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color - } - - function inspectDate(value, inspectOptions) { - // without quotes, ISO format, in magenta like before - const magenta = maybeColor(colors.magenta, inspectOptions); - return magenta( - isInvalidDate(value) ? "Invalid Date" : DatePrototypeToISOString(value), + } else if (ObjectPrototypeIsPrototypeOf(SetPrototype, value)) { + return inspectSet( + proxyDetails ? proxyDetails[0] : value, + inspectOptions, ); - } - - function inspectRegExp(value, inspectOptions) { - const red = maybeColor(colors.red, inspectOptions); - return red(RegExpPrototypeToString(value)); // RegExps are red - } - - function inspectError(value, cyan) { - const causes = [value]; - - let err = value; - while (err.cause) { - if (ArrayPrototypeIncludes(causes, err.cause)) { - ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); - break; - } else { - ArrayPrototypePush(causes, err.cause); - err = err.cause; - } - } - - const refMap = new Map(); - for (let i = 0; i < causes.length; ++i) { - const cause = causes[i]; - if (circular !== undefined) { - const index = MapPrototypeGet(circular, cause); - if (index !== undefined) { - MapPrototypeSet(refMap, cause, cyan(` `)); - } - } - } - ArrayPrototypeShift(causes); - - let finalMessage = MapPrototypeGet(refMap, value) ?? ""; - - if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) { - const stackLines = StringPrototypeSplit(value.stack, "\n"); - while (true) { - const line = ArrayPrototypeShift(stackLines); - if (RegExpPrototypeTest(/\s+at/, line)) { - ArrayPrototypeUnshift(stackLines, line); - break; - } else if (typeof line === "undefined") { - break; - } - - finalMessage += line; - finalMessage += "\n"; - } - const aggregateMessage = ArrayPrototypeJoin( - ArrayPrototypeMap( - value.errors, - (error) => - StringPrototypeReplace( - inspectArgs([error]), - /^(?!\s*$)/gm, - StringPrototypeRepeat(" ", 4), - ), - ), - "\n", - ); - finalMessage += aggregateMessage; - finalMessage += "\n"; - finalMessage += ArrayPrototypeJoin(stackLines, "\n"); - } else { - finalMessage += value.stack; - } - - finalMessage += ArrayPrototypeJoin( - ArrayPrototypeMap( - causes, - (cause) => - "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + - (cause?.stack ?? cause), - ), - "", + } else if (ObjectPrototypeIsPrototypeOf(MapPrototype, value)) { + return inspectMap( + proxyDetails ? proxyDetails[0] : value, + inspectOptions, ); - - return finalMessage; + } else if (ObjectPrototypeIsPrototypeOf(WeakSetPrototype, value)) { + return inspectWeakSet(inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(WeakMapPrototype, value)) { + return inspectWeakMap(inspectOptions); + } else if (isTypedArray(value)) { + return inspectTypedArray( + ObjectGetPrototypeOf(value).constructor.name, + value, + inspectOptions, + ); + } else { + // Otherwise, default object formatting + let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions); + insp = refIndex + insp; + return insp; } +} - function inspectStringObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan +const colorKeywords = new Map([ + ["black", "#000000"], + ["silver", "#c0c0c0"], + ["gray", "#808080"], + ["white", "#ffffff"], + ["maroon", "#800000"], + ["red", "#ff0000"], + ["purple", "#800080"], + ["fuchsia", "#ff00ff"], + ["green", "#008000"], + ["lime", "#00ff00"], + ["olive", "#808000"], + ["yellow", "#ffff00"], + ["navy", "#000080"], + ["blue", "#0000ff"], + ["teal", "#008080"], + ["aqua", "#00ffff"], + ["orange", "#ffa500"], + ["aliceblue", "#f0f8ff"], + ["antiquewhite", "#faebd7"], + ["aquamarine", "#7fffd4"], + ["azure", "#f0ffff"], + ["beige", "#f5f5dc"], + ["bisque", "#ffe4c4"], + ["blanchedalmond", "#ffebcd"], + ["blueviolet", "#8a2be2"], + ["brown", "#a52a2a"], + ["burlywood", "#deb887"], + ["cadetblue", "#5f9ea0"], + ["chartreuse", "#7fff00"], + ["chocolate", "#d2691e"], + ["coral", "#ff7f50"], + ["cornflowerblue", "#6495ed"], + ["cornsilk", "#fff8dc"], + ["crimson", "#dc143c"], + ["cyan", "#00ffff"], + ["darkblue", "#00008b"], + ["darkcyan", "#008b8b"], + ["darkgoldenrod", "#b8860b"], + ["darkgray", "#a9a9a9"], + ["darkgreen", "#006400"], + ["darkgrey", "#a9a9a9"], + ["darkkhaki", "#bdb76b"], + ["darkmagenta", "#8b008b"], + ["darkolivegreen", "#556b2f"], + ["darkorange", "#ff8c00"], + ["darkorchid", "#9932cc"], + ["darkred", "#8b0000"], + ["darksalmon", "#e9967a"], + ["darkseagreen", "#8fbc8f"], + ["darkslateblue", "#483d8b"], + ["darkslategray", "#2f4f4f"], + ["darkslategrey", "#2f4f4f"], + ["darkturquoise", "#00ced1"], + ["darkviolet", "#9400d3"], + ["deeppink", "#ff1493"], + ["deepskyblue", "#00bfff"], + ["dimgray", "#696969"], + ["dimgrey", "#696969"], + ["dodgerblue", "#1e90ff"], + ["firebrick", "#b22222"], + ["floralwhite", "#fffaf0"], + ["forestgreen", "#228b22"], + ["gainsboro", "#dcdcdc"], + ["ghostwhite", "#f8f8ff"], + ["gold", "#ffd700"], + ["goldenrod", "#daa520"], + ["greenyellow", "#adff2f"], + ["grey", "#808080"], + ["honeydew", "#f0fff0"], + ["hotpink", "#ff69b4"], + ["indianred", "#cd5c5c"], + ["indigo", "#4b0082"], + ["ivory", "#fffff0"], + ["khaki", "#f0e68c"], + ["lavender", "#e6e6fa"], + ["lavenderblush", "#fff0f5"], + ["lawngreen", "#7cfc00"], + ["lemonchiffon", "#fffacd"], + ["lightblue", "#add8e6"], + ["lightcoral", "#f08080"], + ["lightcyan", "#e0ffff"], + ["lightgoldenrodyellow", "#fafad2"], + ["lightgray", "#d3d3d3"], + ["lightgreen", "#90ee90"], + ["lightgrey", "#d3d3d3"], + ["lightpink", "#ffb6c1"], + ["lightsalmon", "#ffa07a"], + ["lightseagreen", "#20b2aa"], + ["lightskyblue", "#87cefa"], + ["lightslategray", "#778899"], + ["lightslategrey", "#778899"], + ["lightsteelblue", "#b0c4de"], + ["lightyellow", "#ffffe0"], + ["limegreen", "#32cd32"], + ["linen", "#faf0e6"], + ["magenta", "#ff00ff"], + ["mediumaquamarine", "#66cdaa"], + ["mediumblue", "#0000cd"], + ["mediumorchid", "#ba55d3"], + ["mediumpurple", "#9370db"], + ["mediumseagreen", "#3cb371"], + ["mediumslateblue", "#7b68ee"], + ["mediumspringgreen", "#00fa9a"], + ["mediumturquoise", "#48d1cc"], + ["mediumvioletred", "#c71585"], + ["midnightblue", "#191970"], + ["mintcream", "#f5fffa"], + ["mistyrose", "#ffe4e1"], + ["moccasin", "#ffe4b5"], + ["navajowhite", "#ffdead"], + ["oldlace", "#fdf5e6"], + ["olivedrab", "#6b8e23"], + ["orangered", "#ff4500"], + ["orchid", "#da70d6"], + ["palegoldenrod", "#eee8aa"], + ["palegreen", "#98fb98"], + ["paleturquoise", "#afeeee"], + ["palevioletred", "#db7093"], + ["papayawhip", "#ffefd5"], + ["peachpuff", "#ffdab9"], + ["peru", "#cd853f"], + ["pink", "#ffc0cb"], + ["plum", "#dda0dd"], + ["powderblue", "#b0e0e6"], + ["rosybrown", "#bc8f8f"], + ["royalblue", "#4169e1"], + ["saddlebrown", "#8b4513"], + ["salmon", "#fa8072"], + ["sandybrown", "#f4a460"], + ["seagreen", "#2e8b57"], + ["seashell", "#fff5ee"], + ["sienna", "#a0522d"], + ["skyblue", "#87ceeb"], + ["slateblue", "#6a5acd"], + ["slategray", "#708090"], + ["slategrey", "#708090"], + ["snow", "#fffafa"], + ["springgreen", "#00ff7f"], + ["steelblue", "#4682b4"], + ["tan", "#d2b48c"], + ["thistle", "#d8bfd8"], + ["tomato", "#ff6347"], + ["turquoise", "#40e0d0"], + ["violet", "#ee82ee"], + ["wheat", "#f5deb3"], + ["whitesmoke", "#f5f5f5"], + ["yellowgreen", "#9acd32"], + ["rebeccapurple", "#663399"], +]); + +function parseCssColor(colorString) { + if (MapPrototypeHas(colorKeywords, colorString)) { + colorString = MapPrototypeGet(colorKeywords, colorString); } - - function inspectBooleanObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan - } - - function inspectNumberObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - // Special handling of -0 - return cyan( - `[Number: ${ - ObjectIs(NumberPrototypeValueOf(value), -0) - ? "-0" - : NumberPrototypeToString(value) - }]`, - ); // wrappers are in cyan - } - - function inspectBigIntObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan - } - - function inspectSymbolObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Symbol: ${maybeQuoteSymbol(SymbolPrototypeValueOf(value))}]`); // wrappers are in cyan - } - - const PromiseState = { - Pending: 0, - Fulfilled: 1, - Rejected: 2, - }; - - function inspectPromise( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - const red = maybeColor(colors.red, inspectOptions); - - const { 0: state, 1: result } = core.getPromiseDetails(value); - - if (state === PromiseState.Pending) { - return `Promise { ${cyan("")} }`; - } - - const prefix = state === PromiseState.Fulfilled - ? "" - : `${red("")} `; - - inspectOptions.indentLevel++; - const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; - inspectOptions.indentLevel--; - - if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { - return `Promise {\n${ - StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) - }${str}\n}`; - } - - return `Promise { ${str} }`; - } - - function inspectProxy( - targetAndHandler, - inspectOptions, - ) { - return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; - } - - function inspectRawObject( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return [cyan("[Object]"), ""]; // wrappers are in cyan - } - - let baseString; - - let shouldShowDisplayName = false; - let displayName = value[ - SymbolToStringTag + // deno-fmt-ignore + const hashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/); + if (hashMatch != null) { + return [ + Number(`0x${hashMatch[1]}`), + Number(`0x${hashMatch[2]}`), + Number(`0x${hashMatch[3]}`), ]; - if (!displayName) { - displayName = getClassInstanceName(value); + } + // deno-fmt-ignore + const smallHashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/); + if (smallHashMatch != null) { + return [ + Number(`0x${smallHashMatch[1]}0`), + Number(`0x${smallHashMatch[2]}0`), + Number(`0x${smallHashMatch[3]}0`), + ]; + } + // deno-fmt-ignore + const rgbMatch = StringPrototypeMatch(colorString, /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); + if (rgbMatch != null) { + return [ + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))), + ]; + } + // deno-fmt-ignore + const hslMatch = StringPrototypeMatch(colorString, /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); + if (hslMatch != null) { + // https://www.rapidtables.com/convert/color/hsl-to-rgb.html + let h = Number(hslMatch[1]) % 360; + if (h < 0) { + h += 360; } - if ( - displayName && displayName !== "Object" && displayName !== "anonymous" - ) { - shouldShowDisplayName = true; - } - - const entries = []; - const stringKeys = ObjectKeys(value); - const symbolKeys = ObjectGetOwnPropertySymbols(value); - if (inspectOptions.sorted) { - ArrayPrototypeSort(stringKeys); - ArrayPrototypeSort( - symbolKeys, - (s1, s2) => - StringPrototypeLocaleCompare( - s1.description ?? "", - s2.description ?? "", - ), - ); - } - - const red = maybeColor(colors.red, inspectOptions); - - inspectOptions.indentLevel++; - - for (let i = 0; i < stringKeys.length; ++i) { - const key = stringKeys[i]; - if (inspectOptions.getters) { - let propertyValue; - let error = null; - try { - propertyValue = value[key]; - } catch (error_) { - error = error_; - } - const inspectedValue = error == null - ? inspectValueWithQuotes(propertyValue, inspectOptions) - : red(`[Thrown ${error.name}: ${error.message}]`); - ArrayPrototypePush( - entries, - `${maybeQuoteString(key)}: ${inspectedValue}`, - ); - } else { - const descriptor = ObjectGetOwnPropertyDescriptor(value, key); - if (descriptor.get !== undefined && descriptor.set !== undefined) { - ArrayPrototypePush( - entries, - `${maybeQuoteString(key)}: [Getter/Setter]`, - ); - } else if (descriptor.get !== undefined) { - ArrayPrototypePush(entries, `${maybeQuoteString(key)}: [Getter]`); - } else { - ArrayPrototypePush( - entries, - `${maybeQuoteString(key)}: ${ - inspectValueWithQuotes(value[key], inspectOptions) - }`, - ); - } - } - } - - for (let i = 0; i < symbolKeys.length; ++i) { - const key = symbolKeys[i]; - if ( - !inspectOptions.showHidden && - !propertyIsEnumerable(value, key) - ) { - continue; - } - - if (inspectOptions.getters) { - let propertyValue; - let error; - try { - propertyValue = value[key]; - } catch (error_) { - error = error_; - } - const inspectedValue = error == null - ? inspectValueWithQuotes(propertyValue, inspectOptions) - : red(`Thrown ${error.name}: ${error.message}`); - ArrayPrototypePush( - entries, - `[${maybeQuoteSymbol(key)}]: ${inspectedValue}`, - ); - } else { - const descriptor = ObjectGetOwnPropertyDescriptor(value, key); - if (descriptor.get !== undefined && descriptor.set !== undefined) { - ArrayPrototypePush( - entries, - `[${maybeQuoteSymbol(key)}]: [Getter/Setter]`, - ); - } else if (descriptor.get !== undefined) { - ArrayPrototypePush(entries, `[${maybeQuoteSymbol(key)}]: [Getter]`); - } else { - ArrayPrototypePush( - entries, - `[${maybeQuoteSymbol(key)}]: ${ - inspectValueWithQuotes(value[key], inspectOptions) - }`, - ); - } - } - } - - inspectOptions.indentLevel--; - - // Making sure color codes are ignored when calculating the total length - const totalLength = entries.length + inspectOptions.indentLevel + - colors.stripColor(ArrayPrototypeJoin(entries, "")).length; - - if (entries.length === 0) { - baseString = "{}"; - } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) { - const entryIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - inspectOptions.indentLevel + 1, - ); - const closingIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - inspectOptions.indentLevel, - ); - baseString = `{\n${entryIndent}${ - ArrayPrototypeJoin(entries, `,\n${entryIndent}`) - }${inspectOptions.trailingComma ? "," : ""}\n${closingIndent}}`; + const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100; + const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100; + const c = (1 - MathAbs(2 * l - 1)) * s; + const x = c * (1 - MathAbs((h / 60) % 2 - 1)); + const m = l - c / 2; + let r_; + let g_; + let b_; + if (h < 60) { + ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]); + } else if (h < 120) { + ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]); + } else if (h < 180) { + ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]); + } else if (h < 240) { + ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]); + } else if (h < 300) { + ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]); } else { - baseString = `{ ${ArrayPrototypeJoin(entries, ", ")} }`; + ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); } + return [ + MathRound((r_ + m) * 255), + MathRound((g_ + m) * 255), + MathRound((b_ + m) * 255), + ]; + } + return null; +} - if (shouldShowDisplayName) { - baseString = `${displayName} ${baseString}`; - } +function getDefaultCss() { + return { + backgroundColor: null, + color: null, + fontWeight: null, + fontStyle: null, + textDecorationColor: null, + textDecorationLine: [], + }; +} - let refIndex = ""; - if (circular !== undefined) { - const index = MapPrototypeGet(circular, value); - if (index !== undefined) { - refIndex = cyan(` `); +function parseCss(cssString) { + const css = getDefaultCss(); + + const rawEntries = []; + let inValue = false; + let currentKey = null; + let parenthesesDepth = 0; + let currentPart = ""; + for (let i = 0; i < cssString.length; i++) { + const c = cssString[i]; + if (c == "(") { + parenthesesDepth++; + } else if (parenthesesDepth > 0) { + if (c == ")") { + parenthesesDepth--; } - } - - return [baseString, refIndex]; - } - - function inspectObject(value, inspectOptions, proxyDetails) { - if ( - ReflectHas(value, customInspect) && - typeof value[customInspect] === "function" - ) { - return String(value[customInspect](inspect, inspectOptions)); - } - // This non-unique symbol is used to support op_crates, ie. - // in extensions/web we don't want to depend on public - // Symbol.for("Deno.customInspect") symbol defined in the public API. - // Internal only, shouldn't be used by users. - const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); - if ( - ReflectHas(value, privateCustomInspect) && - typeof value[privateCustomInspect] === "function" - ) { - // TODO(nayeemrmn): `inspect` is passed as an argument because custom - // inspect implementations in `extensions` need it, but may not have access - // to the `Deno` namespace in web workers. Remove when the `Deno` - // namespace is always enabled. - return String( - value[privateCustomInspect](inspect, inspectOptions), - ); - } - if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { - return inspectError(value, maybeColor(colors.cyan, inspectOptions)); - } else if (ArrayIsArray(value)) { - return inspectArray(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) { - return inspectNumberObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(BigIntPrototype, value)) { - return inspectBigIntObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(BooleanPrototype, value)) { - return inspectBooleanObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(StringPrototype, value)) { - return inspectStringObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(SymbolPrototype, value)) { - return inspectSymbolObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(PromisePrototype, value)) { - return inspectPromise(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) { - return inspectRegExp(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { - return inspectDate( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(SetPrototype, value)) { - return inspectSet( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(MapPrototype, value)) { - return inspectMap( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(WeakSetPrototype, value)) { - return inspectWeakSet(inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(WeakMapPrototype, value)) { - return inspectWeakMap(inspectOptions); - } else if (isTypedArray(value)) { - return inspectTypedArray( - ObjectGetPrototypeOf(value).constructor.name, - value, - inspectOptions, - ); - } else { - // Otherwise, default object formatting - let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions); - insp = refIndex + insp; - return insp; - } - } - - const colorKeywords = new Map([ - ["black", "#000000"], - ["silver", "#c0c0c0"], - ["gray", "#808080"], - ["white", "#ffffff"], - ["maroon", "#800000"], - ["red", "#ff0000"], - ["purple", "#800080"], - ["fuchsia", "#ff00ff"], - ["green", "#008000"], - ["lime", "#00ff00"], - ["olive", "#808000"], - ["yellow", "#ffff00"], - ["navy", "#000080"], - ["blue", "#0000ff"], - ["teal", "#008080"], - ["aqua", "#00ffff"], - ["orange", "#ffa500"], - ["aliceblue", "#f0f8ff"], - ["antiquewhite", "#faebd7"], - ["aquamarine", "#7fffd4"], - ["azure", "#f0ffff"], - ["beige", "#f5f5dc"], - ["bisque", "#ffe4c4"], - ["blanchedalmond", "#ffebcd"], - ["blueviolet", "#8a2be2"], - ["brown", "#a52a2a"], - ["burlywood", "#deb887"], - ["cadetblue", "#5f9ea0"], - ["chartreuse", "#7fff00"], - ["chocolate", "#d2691e"], - ["coral", "#ff7f50"], - ["cornflowerblue", "#6495ed"], - ["cornsilk", "#fff8dc"], - ["crimson", "#dc143c"], - ["cyan", "#00ffff"], - ["darkblue", "#00008b"], - ["darkcyan", "#008b8b"], - ["darkgoldenrod", "#b8860b"], - ["darkgray", "#a9a9a9"], - ["darkgreen", "#006400"], - ["darkgrey", "#a9a9a9"], - ["darkkhaki", "#bdb76b"], - ["darkmagenta", "#8b008b"], - ["darkolivegreen", "#556b2f"], - ["darkorange", "#ff8c00"], - ["darkorchid", "#9932cc"], - ["darkred", "#8b0000"], - ["darksalmon", "#e9967a"], - ["darkseagreen", "#8fbc8f"], - ["darkslateblue", "#483d8b"], - ["darkslategray", "#2f4f4f"], - ["darkslategrey", "#2f4f4f"], - ["darkturquoise", "#00ced1"], - ["darkviolet", "#9400d3"], - ["deeppink", "#ff1493"], - ["deepskyblue", "#00bfff"], - ["dimgray", "#696969"], - ["dimgrey", "#696969"], - ["dodgerblue", "#1e90ff"], - ["firebrick", "#b22222"], - ["floralwhite", "#fffaf0"], - ["forestgreen", "#228b22"], - ["gainsboro", "#dcdcdc"], - ["ghostwhite", "#f8f8ff"], - ["gold", "#ffd700"], - ["goldenrod", "#daa520"], - ["greenyellow", "#adff2f"], - ["grey", "#808080"], - ["honeydew", "#f0fff0"], - ["hotpink", "#ff69b4"], - ["indianred", "#cd5c5c"], - ["indigo", "#4b0082"], - ["ivory", "#fffff0"], - ["khaki", "#f0e68c"], - ["lavender", "#e6e6fa"], - ["lavenderblush", "#fff0f5"], - ["lawngreen", "#7cfc00"], - ["lemonchiffon", "#fffacd"], - ["lightblue", "#add8e6"], - ["lightcoral", "#f08080"], - ["lightcyan", "#e0ffff"], - ["lightgoldenrodyellow", "#fafad2"], - ["lightgray", "#d3d3d3"], - ["lightgreen", "#90ee90"], - ["lightgrey", "#d3d3d3"], - ["lightpink", "#ffb6c1"], - ["lightsalmon", "#ffa07a"], - ["lightseagreen", "#20b2aa"], - ["lightskyblue", "#87cefa"], - ["lightslategray", "#778899"], - ["lightslategrey", "#778899"], - ["lightsteelblue", "#b0c4de"], - ["lightyellow", "#ffffe0"], - ["limegreen", "#32cd32"], - ["linen", "#faf0e6"], - ["magenta", "#ff00ff"], - ["mediumaquamarine", "#66cdaa"], - ["mediumblue", "#0000cd"], - ["mediumorchid", "#ba55d3"], - ["mediumpurple", "#9370db"], - ["mediumseagreen", "#3cb371"], - ["mediumslateblue", "#7b68ee"], - ["mediumspringgreen", "#00fa9a"], - ["mediumturquoise", "#48d1cc"], - ["mediumvioletred", "#c71585"], - ["midnightblue", "#191970"], - ["mintcream", "#f5fffa"], - ["mistyrose", "#ffe4e1"], - ["moccasin", "#ffe4b5"], - ["navajowhite", "#ffdead"], - ["oldlace", "#fdf5e6"], - ["olivedrab", "#6b8e23"], - ["orangered", "#ff4500"], - ["orchid", "#da70d6"], - ["palegoldenrod", "#eee8aa"], - ["palegreen", "#98fb98"], - ["paleturquoise", "#afeeee"], - ["palevioletred", "#db7093"], - ["papayawhip", "#ffefd5"], - ["peachpuff", "#ffdab9"], - ["peru", "#cd853f"], - ["pink", "#ffc0cb"], - ["plum", "#dda0dd"], - ["powderblue", "#b0e0e6"], - ["rosybrown", "#bc8f8f"], - ["royalblue", "#4169e1"], - ["saddlebrown", "#8b4513"], - ["salmon", "#fa8072"], - ["sandybrown", "#f4a460"], - ["seagreen", "#2e8b57"], - ["seashell", "#fff5ee"], - ["sienna", "#a0522d"], - ["skyblue", "#87ceeb"], - ["slateblue", "#6a5acd"], - ["slategray", "#708090"], - ["slategrey", "#708090"], - ["snow", "#fffafa"], - ["springgreen", "#00ff7f"], - ["steelblue", "#4682b4"], - ["tan", "#d2b48c"], - ["thistle", "#d8bfd8"], - ["tomato", "#ff6347"], - ["turquoise", "#40e0d0"], - ["violet", "#ee82ee"], - ["wheat", "#f5deb3"], - ["whitesmoke", "#f5f5f5"], - ["yellowgreen", "#9acd32"], - ["rebeccapurple", "#663399"], - ]); - - function parseCssColor(colorString) { - if (MapPrototypeHas(colorKeywords, colorString)) { - colorString = MapPrototypeGet(colorKeywords, colorString); - } - // deno-fmt-ignore - const hashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/); - if (hashMatch != null) { - return [ - Number(`0x${hashMatch[1]}`), - Number(`0x${hashMatch[2]}`), - Number(`0x${hashMatch[3]}`), - ]; - } - // deno-fmt-ignore - const smallHashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/); - if (smallHashMatch != null) { - return [ - Number(`0x${smallHashMatch[1]}0`), - Number(`0x${smallHashMatch[2]}0`), - Number(`0x${smallHashMatch[3]}0`), - ]; - } - // deno-fmt-ignore - const rgbMatch = StringPrototypeMatch(colorString, /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); - if (rgbMatch != null) { - return [ - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))), - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))), - ]; - } - // deno-fmt-ignore - const hslMatch = StringPrototypeMatch(colorString, /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); - if (hslMatch != null) { - // https://www.rapidtables.com/convert/color/hsl-to-rgb.html - let h = Number(hslMatch[1]) % 360; - if (h < 0) { - h += 360; - } - const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100; - const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100; - const c = (1 - MathAbs(2 * l - 1)) * s; - const x = c * (1 - MathAbs((h / 60) % 2 - 1)); - const m = l - c / 2; - let r_; - let g_; - let b_; - if (h < 60) { - ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]); - } else if (h < 120) { - ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]); - } else if (h < 180) { - ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]); - } else if (h < 240) { - ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]); - } else if (h < 300) { - ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]); - } else { - ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); - } - return [ - MathRound((r_ + m) * 255), - MathRound((g_ + m) * 255), - MathRound((b_ + m) * 255), - ]; - } - return null; - } - - function getDefaultCss() { - return { - backgroundColor: null, - color: null, - fontWeight: null, - fontStyle: null, - textDecorationColor: null, - textDecorationLine: [], - }; - } - - function parseCss(cssString) { - const css = getDefaultCss(); - - const rawEntries = []; - let inValue = false; - let currentKey = null; - let parenthesesDepth = 0; - let currentPart = ""; - for (let i = 0; i < cssString.length; i++) { - const c = cssString[i]; - if (c == "(") { - parenthesesDepth++; - } else if (parenthesesDepth > 0) { - if (c == ")") { - parenthesesDepth--; + } else if (inValue) { + if (c == ";") { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); } - } else if (inValue) { - if (c == ";") { - const value = StringPrototypeTrim(currentPart); - if (value != "") { - ArrayPrototypePush(rawEntries, [currentKey, value]); - } - currentKey = null; - currentPart = ""; - inValue = false; - continue; - } - } else if (c == ":") { - currentKey = StringPrototypeTrim(currentPart); + currentKey = null; currentPart = ""; - inValue = true; + inValue = false; continue; } - currentPart += c; - } - if (inValue && parenthesesDepth == 0) { - const value = StringPrototypeTrim(currentPart); - if (value != "") { - ArrayPrototypePush(rawEntries, [currentKey, value]); - } - currentKey = null; + } else if (c == ":") { + currentKey = StringPrototypeTrim(currentPart); currentPart = ""; + inValue = true; + continue; } + currentPart += c; + } + if (inValue && parenthesesDepth == 0) { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); + } + currentKey = null; + currentPart = ""; + } - for (let i = 0; i < rawEntries.length; ++i) { - const { 0: key, 1: value } = rawEntries[i]; - if (key == "background-color") { - if (value != null) { - css.backgroundColor = value; - } - } else if (key == "color") { - if (value != null) { - css.color = value; - } - } else if (key == "font-weight") { - if (value == "bold") { - css.fontWeight = value; - } - } else if (key == "font-style") { + for (let i = 0; i < rawEntries.length; ++i) { + const { 0: key, 1: value } = rawEntries[i]; + if (key == "background-color") { + if (value != null) { + css.backgroundColor = value; + } + } else if (key == "color") { + if (value != null) { + css.color = value; + } + } else if (key == "font-weight") { + if (value == "bold") { + css.fontWeight = value; + } + } else if (key == "font-style") { + if ( + ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value) + ) { + css.fontStyle = "italic"; + } + } else if (key == "text-decoration-line") { + css.textDecorationLine = []; + const lineTypes = StringPrototypeSplit(value, /\s+/g); + for (let i = 0; i < lineTypes.length; ++i) { + const lineType = lineTypes[i]; if ( - ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value) + ArrayPrototypeIncludes( + ["line-through", "overline", "underline"], + lineType, + ) ) { - css.fontStyle = "italic"; + ArrayPrototypePush(css.textDecorationLine, lineType); } - } else if (key == "text-decoration-line") { - css.textDecorationLine = []; - const lineTypes = StringPrototypeSplit(value, /\s+/g); - for (let i = 0; i < lineTypes.length; ++i) { - const lineType = lineTypes[i]; - if ( - ArrayPrototypeIncludes( - ["line-through", "overline", "underline"], - lineType, - ) - ) { - ArrayPrototypePush(css.textDecorationLine, lineType); - } - } - } else if (key == "text-decoration-color") { - const color = parseCssColor(value); - if (color != null) { - css.textDecorationColor = color; - } - } else if (key == "text-decoration") { - css.textDecorationColor = null; - css.textDecorationLine = []; - const args = StringPrototypeSplit(value, /\s+/g); - for (let i = 0; i < args.length; ++i) { - const arg = args[i]; - const maybeColor = parseCssColor(arg); - if (maybeColor != null) { - css.textDecorationColor = maybeColor; - } else if ( - ArrayPrototypeIncludes( - ["line-through", "overline", "underline"], - arg, - ) - ) { - ArrayPrototypePush(css.textDecorationLine, arg); - } + } + } else if (key == "text-decoration-color") { + const color = parseCssColor(value); + if (color != null) { + css.textDecorationColor = color; + } + } else if (key == "text-decoration") { + css.textDecorationColor = null; + css.textDecorationLine = []; + const args = StringPrototypeSplit(value, /\s+/g); + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + const maybeColor = parseCssColor(arg); + if (maybeColor != null) { + css.textDecorationColor = maybeColor; + } else if ( + ArrayPrototypeIncludes( + ["line-through", "overline", "underline"], + arg, + ) + ) { + ArrayPrototypePush(css.textDecorationLine, arg); } } } - - return css; } - function colorEquals(color1, color2) { - return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] && - color1?.[2] == color2?.[2]; - } + return css; +} - function cssToAnsi(css, prevCss = null) { - prevCss = prevCss ?? getDefaultCss(); - let ansi = ""; - if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) { - if (css.backgroundColor == null) { - ansi += "\x1b[49m"; - } else if (css.backgroundColor == "black") { - ansi += `\x1b[40m`; - } else if (css.backgroundColor == "red") { - ansi += `\x1b[41m`; - } else if (css.backgroundColor == "green") { - ansi += `\x1b[42m`; - } else if (css.backgroundColor == "yellow") { - ansi += `\x1b[43m`; - } else if (css.backgroundColor == "blue") { - ansi += `\x1b[44m`; - } else if (css.backgroundColor == "magenta") { - ansi += `\x1b[45m`; - } else if (css.backgroundColor == "cyan") { - ansi += `\x1b[46m`; - } else if (css.backgroundColor == "white") { - ansi += `\x1b[47m`; +function colorEquals(color1, color2) { + return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] && + color1?.[2] == color2?.[2]; +} + +function cssToAnsi(css, prevCss = null) { + prevCss = prevCss ?? getDefaultCss(); + let ansi = ""; + if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) { + if (css.backgroundColor == null) { + ansi += "\x1b[49m"; + } else if (css.backgroundColor == "black") { + ansi += `\x1b[40m`; + } else if (css.backgroundColor == "red") { + ansi += `\x1b[41m`; + } else if (css.backgroundColor == "green") { + ansi += `\x1b[42m`; + } else if (css.backgroundColor == "yellow") { + ansi += `\x1b[43m`; + } else if (css.backgroundColor == "blue") { + ansi += `\x1b[44m`; + } else if (css.backgroundColor == "magenta") { + ansi += `\x1b[45m`; + } else if (css.backgroundColor == "cyan") { + ansi += `\x1b[46m`; + } else if (css.backgroundColor == "white") { + ansi += `\x1b[47m`; + } else { + if (ArrayIsArray(css.backgroundColor)) { + const { 0: r, 1: g, 2: b } = css.backgroundColor; + ansi += `\x1b[48;2;${r};${g};${b}m`; } else { - if (ArrayIsArray(css.backgroundColor)) { - const { 0: r, 1: g, 2: b } = css.backgroundColor; + const parsed = parseCssColor(css.backgroundColor); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; ansi += `\x1b[48;2;${r};${g};${b}m`; } else { - const parsed = parseCssColor(css.backgroundColor); - if (parsed !== null) { - const { 0: r, 1: g, 2: b } = parsed; - ansi += `\x1b[48;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[49m"; - } + ansi += "\x1b[49m"; } } } - if (!colorEquals(css.color, prevCss.color)) { - if (css.color == null) { - ansi += "\x1b[39m"; - } else if (css.color == "black") { - ansi += `\x1b[30m`; - } else if (css.color == "red") { - ansi += `\x1b[31m`; - } else if (css.color == "green") { - ansi += `\x1b[32m`; - } else if (css.color == "yellow") { - ansi += `\x1b[33m`; - } else if (css.color == "blue") { - ansi += `\x1b[34m`; - } else if (css.color == "magenta") { - ansi += `\x1b[35m`; - } else if (css.color == "cyan") { - ansi += `\x1b[36m`; - } else if (css.color == "white") { - ansi += `\x1b[37m`; + } + if (!colorEquals(css.color, prevCss.color)) { + if (css.color == null) { + ansi += "\x1b[39m"; + } else if (css.color == "black") { + ansi += `\x1b[30m`; + } else if (css.color == "red") { + ansi += `\x1b[31m`; + } else if (css.color == "green") { + ansi += `\x1b[32m`; + } else if (css.color == "yellow") { + ansi += `\x1b[33m`; + } else if (css.color == "blue") { + ansi += `\x1b[34m`; + } else if (css.color == "magenta") { + ansi += `\x1b[35m`; + } else if (css.color == "cyan") { + ansi += `\x1b[36m`; + } else if (css.color == "white") { + ansi += `\x1b[37m`; + } else { + if (ArrayIsArray(css.color)) { + const { 0: r, 1: g, 2: b } = css.color; + ansi += `\x1b[38;2;${r};${g};${b}m`; } else { - if (ArrayIsArray(css.color)) { - const { 0: r, 1: g, 2: b } = css.color; + const parsed = parseCssColor(css.color); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; ansi += `\x1b[38;2;${r};${g};${b}m`; } else { - const parsed = parseCssColor(css.color); - if (parsed !== null) { - const { 0: r, 1: g, 2: b } = parsed; - ansi += `\x1b[38;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[39m"; - } + ansi += "\x1b[39m"; } } } - if (css.fontWeight != prevCss.fontWeight) { - if (css.fontWeight == "bold") { - ansi += `\x1b[1m`; - } else { - ansi += "\x1b[22m"; - } - } - if (css.fontStyle != prevCss.fontStyle) { - if (css.fontStyle == "italic") { - ansi += `\x1b[3m`; - } else { - ansi += "\x1b[23m"; - } - } - if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) { - if (css.textDecorationColor != null) { - const { 0: r, 1: g, 2: b } = css.textDecorationColor; - ansi += `\x1b[58;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[59m"; - } - } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "line-through") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) { - ansi += "\x1b[9m"; - } else { - ansi += "\x1b[29m"; - } - } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "overline") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) { - ansi += "\x1b[53m"; - } else { - ansi += "\x1b[55m"; - } - } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "underline") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) { - ansi += "\x1b[4m"; - } else { - ansi += "\x1b[24m"; - } - } - return ansi; } + if (css.fontWeight != prevCss.fontWeight) { + if (css.fontWeight == "bold") { + ansi += `\x1b[1m`; + } else { + ansi += "\x1b[22m"; + } + } + if (css.fontStyle != prevCss.fontStyle) { + if (css.fontStyle == "italic") { + ansi += `\x1b[3m`; + } else { + ansi += "\x1b[23m"; + } + } + if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) { + if (css.textDecorationColor != null) { + const { 0: r, 1: g, 2: b } = css.textDecorationColor; + ansi += `\x1b[58;2;${r};${g};${b}m`; + } else { + ansi += "\x1b[59m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "line-through") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) { + ansi += "\x1b[9m"; + } else { + ansi += "\x1b[29m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "overline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) { + ansi += "\x1b[53m"; + } else { + ansi += "\x1b[55m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "underline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) { + ansi += "\x1b[4m"; + } else { + ansi += "\x1b[24m"; + } + } + return ansi; +} - function inspectArgs(args, inspectOptions = {}) { - circular = undefined; +function inspectArgs(args, inspectOptions = {}) { + circular = undefined; - const noColor = colors.getNoColor(); - const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; - const first = args[0]; - let a = 0; - let string = ""; + const noColor = colors.getNoColor(); + const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; + const first = args[0]; + let a = 0; + let string = ""; - if (typeof first == "string" && args.length > 1) { - a++; - // Index of the first not-yet-appended character. Use this so we only - // have to append to `string` when a substitution occurs / at the end. - let appendedChars = 0; - let usedStyle = false; - let prevCss = null; - for (let i = 0; i < first.length - 1; i++) { - if (first[i] == "%") { - const char = first[++i]; - if (a < args.length) { - let formattedArg = null; - if (char == "s") { - // Format as a string. - formattedArg = String(args[a++]); - } else if (ArrayPrototypeIncludes(["d", "i"], char)) { - // Format as an integer. - const value = args[a++]; - if (typeof value == "bigint") { - formattedArg = `${value}n`; - } else if (typeof value == "number") { - formattedArg = `${NumberParseInt(String(value))}`; - } else { - formattedArg = "NaN"; - } - } else if (char == "f") { - // Format as a floating point value. - const value = args[a++]; - if (typeof value == "number") { - formattedArg = `${value}`; - } else { - formattedArg = "NaN"; - } - } else if (ArrayPrototypeIncludes(["O", "o"], char)) { - // Format as an object. - formattedArg = inspectValue(args[a++], rInspectOptions); - } else if (char == "c") { - const value = args[a++]; - if (!noColor) { - const css = parseCss(value); - formattedArg = cssToAnsi(css, prevCss); - if (formattedArg != "") { - usedStyle = true; - prevCss = css; - } - } else { - formattedArg = ""; - } + if (typeof first == "string" && args.length > 1) { + a++; + // Index of the first not-yet-appended character. Use this so we only + // have to append to `string` when a substitution occurs / at the end. + let appendedChars = 0; + let usedStyle = false; + let prevCss = null; + for (let i = 0; i < first.length - 1; i++) { + if (first[i] == "%") { + const char = first[++i]; + if (a < args.length) { + let formattedArg = null; + if (char == "s") { + // Format as a string. + formattedArg = String(args[a++]); + } else if (ArrayPrototypeIncludes(["d", "i"], char)) { + // Format as an integer. + const value = args[a++]; + if (typeof value == "bigint") { + formattedArg = `${value}n`; + } else if (typeof value == "number") { + formattedArg = `${NumberParseInt(String(value))}`; + } else { + formattedArg = "NaN"; } - - if (formattedArg != null) { - string += StringPrototypeSlice(first, appendedChars, i - 1) + - formattedArg; - appendedChars = i + 1; + } else if (char == "f") { + // Format as a floating point value. + const value = args[a++]; + if (typeof value == "number") { + formattedArg = `${value}`; + } else { + formattedArg = "NaN"; + } + } else if (ArrayPrototypeIncludes(["O", "o"], char)) { + // Format as an object. + formattedArg = inspectValue(args[a++], rInspectOptions); + } else if (char == "c") { + const value = args[a++]; + if (!noColor) { + const css = parseCss(value); + formattedArg = cssToAnsi(css, prevCss); + if (formattedArg != "") { + usedStyle = true; + prevCss = css; + } + } else { + formattedArg = ""; } } - if (char == "%") { - string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + + if (formattedArg != null) { + string += StringPrototypeSlice(first, appendedChars, i - 1) + + formattedArg; appendedChars = i + 1; } } - } - string += StringPrototypeSlice(first, appendedChars); - if (usedStyle) { - string += "\x1b[0m"; - } - } - - for (; a < args.length; a++) { - if (a > 0) { - string += " "; - } - if (typeof args[a] == "string") { - string += args[a]; - } else { - // Use default maximum depth for null or undefined arguments. - string += inspectValue(args[a], rInspectOptions); - } - } - - if (rInspectOptions.indentLevel > 0) { - const groupIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - rInspectOptions.indentLevel, - ); - string = groupIndent + - StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); - } - - return string; - } - - const countMap = new Map(); - const timerMap = new Map(); - const isConsoleInstance = Symbol("isConsoleInstance"); - - function getConsoleInspectOptions() { - return { - ...DEFAULT_INSPECT_OPTIONS, - colors: !colors.getNoColor(), - }; - } - - class Console { - #printFunc = null; - [isConsoleInstance] = false; - - constructor(printFunc) { - this.#printFunc = printFunc; - this.indentLevel = 0; - this[isConsoleInstance] = true; - - // ref https://console.spec.whatwg.org/#console-namespace - // For historical web-compatibility reasons, the namespace object for - // console must have as its [[Prototype]] an empty object, created as if - // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. - const console = ObjectCreate({}, { - [SymbolToStringTag]: { - enumerable: false, - writable: false, - configurable: true, - value: "console", - }, - }); - ObjectAssign(console, this); - return console; - } - - log = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 1, - ); - }; - - debug = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 0, - ); - }; - - info = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 1, - ); - }; - - dir = (obj = undefined, options = {}) => { - this.#printFunc( - inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + - "\n", - 1, - ); - }; - - dirxml = this.dir; - - warn = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 2, - ); - }; - - error = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 3, - ); - }; - - assert = (condition = false, ...args) => { - if (condition) { - return; - } - - if (args.length === 0) { - this.error("Assertion failed"); - return; - } - - const [first, ...rest] = new SafeArrayIterator(args); - - if (typeof first === "string") { - this.error( - `Assertion failed: ${first}`, - ...new SafeArrayIterator(rest), - ); - return; - } - - this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); - }; - - count = (label = "default") => { - label = String(label); - - if (MapPrototypeHas(countMap, label)) { - const current = MapPrototypeGet(countMap, label) || 0; - MapPrototypeSet(countMap, label, current + 1); - } else { - MapPrototypeSet(countMap, label, 1); - } - - this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); - }; - - countReset = (label = "default") => { - label = String(label); - - if (MapPrototypeHas(countMap, label)) { - MapPrototypeSet(countMap, label, 0); - } else { - this.warn(`Count for '${label}' does not exist`); - } - }; - - table = (data = undefined, properties = undefined) => { - if (properties !== undefined && !ArrayIsArray(properties)) { - throw new Error( - "The 'properties' argument must be of type Array. " + - "Received type string", - ); - } - - if (data === null || typeof data !== "object") { - return this.log(data); - } - - const stringifyValue = (value) => - inspectValueWithQuotes(value, { - ...DEFAULT_INSPECT_OPTIONS, - depth: 1, - }); - const toTable = (header, body) => this.log(cliTable(header, body)); - - let resultData; - const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data); - const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data); - const valuesKey = "Values"; - const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; - - if (isSet) { - resultData = [...new SafeSet(data)]; - } else if (isMap) { - let idx = 0; - resultData = {}; - - MapPrototypeForEach(data, (v, k) => { - resultData[idx] = { Key: k, Values: v }; - idx++; - }); - } else { - resultData = data; - } - - const keys = ObjectKeys(resultData); - const numRows = keys.length; - - const objectValues = properties - ? ObjectFromEntries( - ArrayPrototypeMap( - properties, - (name) => [name, ArrayPrototypeFill(new Array(numRows), "")], - ), - ) - : {}; - const indexKeys = []; - const values = []; - - let hasPrimitives = false; - keys.forEach((k, idx) => { - const value = resultData[k]; - const primitive = value === null || - (typeof value !== "function" && typeof value !== "object"); - if (properties === undefined && primitive) { - hasPrimitives = true; - ArrayPrototypePush(values, stringifyValue(value)); - } else { - const valueObj = value || {}; - const keys = properties || ObjectKeys(valueObj); - for (let i = 0; i < keys.length; ++i) { - const k = keys[i]; - if (!primitive && ReflectHas(valueObj, k)) { - if (!(ReflectHas(objectValues, k))) { - objectValues[k] = ArrayPrototypeFill(new Array(numRows), ""); - } - objectValues[k][idx] = stringifyValue(valueObj[k]); - } - } - ArrayPrototypePush(values, ""); + if (char == "%") { + string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + appendedChars = i + 1; } - - ArrayPrototypePush(indexKeys, k); - }); - - const headerKeys = ObjectKeys(objectValues); - const bodyValues = ObjectValues(objectValues); - const headerProps = properties || - [ - ...new SafeArrayIterator(headerKeys), - !isMap && hasPrimitives && valuesKey, - ]; - const header = ArrayPrototypeFilter([ - indexKey, - ...new SafeArrayIterator(headerProps), - ], Boolean); - const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values]; - - toTable(header, body); - }; - - time = (label = "default") => { - label = String(label); - - if (MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' already exists`); - return; } - - MapPrototypeSet(timerMap, label, DateNow()); - }; - - timeLog = (label = "default", ...args) => { - label = String(label); - - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exists`); - return; - } - - const startTime = MapPrototypeGet(timerMap, label); - const duration = DateNow() - startTime; - - this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); - }; - - timeEnd = (label = "default") => { - label = String(label); - - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exist`); - return; - } - - const startTime = MapPrototypeGet(timerMap, label); - MapPrototypeDelete(timerMap, label); - const duration = DateNow() - startTime; - - this.info(`${label}: ${duration}ms`); - }; - - group = (...label) => { - if (label.length > 0) { - this.log(...new SafeArrayIterator(label)); - } - this.indentLevel += 2; - }; - - groupCollapsed = this.group; - - groupEnd = () => { - if (this.indentLevel > 0) { - this.indentLevel -= 2; - } - }; - - clear = () => { - this.indentLevel = 0; - this.#printFunc(CSI.kClear, 1); - this.#printFunc(CSI.kClearScreenDown, 1); - }; - - trace = (...args) => { - const message = inspectArgs( - args, - { ...getConsoleInspectOptions(), indentLevel: 0 }, - ); - const err = { - name: "Trace", - message, - }; - ErrorCaptureStackTrace(err, this.trace); - this.error(err.stack); - }; - - // These methods are noops, but when the inspector is connected, they - // call into V8. - profile = (_label) => {}; - profileEnd = (_label) => {}; - timeStamp = (_label) => {}; - - static [SymbolHasInstance](instance) { - return instance[isConsoleInstance]; + } + string += StringPrototypeSlice(first, appendedChars); + if (usedStyle) { + string += "\x1b[0m"; } } - const customInspect = SymbolFor("Deno.customInspect"); - - function inspect( - value, - inspectOptions = {}, - ) { - circular = undefined; - return inspectValue(value, { - ...DEFAULT_INSPECT_OPTIONS, - ...inspectOptions, - }); + for (; a < args.length; a++) { + if (a > 0) { + string += " "; + } + if (typeof args[a] == "string") { + string += args[a]; + } else { + // Use default maximum depth for null or undefined arguments. + string += inspectValue(args[a], rInspectOptions); + } } - /** Creates a proxy that represents a subset of the properties - * of the original object optionally without evaluating the properties - * in order to get the values. */ - function createFilteredInspectProxy({ object, keys, evaluate }) { - return new Proxy({}, { - get(_target, key) { - if (key === SymbolToStringTag) { - return object.constructor?.name; - } else if (ArrayPrototypeIncludes(keys, key)) { - return ReflectGet(object, key); - } else { - return undefined; - } - }, - getOwnPropertyDescriptor(_target, key) { - if (!ArrayPrototypeIncludes(keys, key)) { - return undefined; - } else if (evaluate) { - return getEvaluatedDescriptor(object, key); - } else { - return getDescendantPropertyDescriptor(object, key) ?? - getEvaluatedDescriptor(object, key); - } - }, - has(_target, key) { - return ArrayPrototypeIncludes(keys, key); - }, - ownKeys() { - return keys; - }, - }); + if (rInspectOptions.indentLevel > 0) { + const groupIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + rInspectOptions.indentLevel, + ); + string = groupIndent + + StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); + } - function getDescendantPropertyDescriptor(object, key) { - let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); - if (!propertyDescriptor) { - const prototype = ReflectGetPrototypeOf(object); - if (prototype) { - propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); - } - } - return propertyDescriptor; - } + return string; +} - function getEvaluatedDescriptor(object, key) { - return { +const countMap = new Map(); +const timerMap = new Map(); +const isConsoleInstance = Symbol("isConsoleInstance"); + +function getConsoleInspectOptions() { + return { + ...DEFAULT_INSPECT_OPTIONS, + colors: !colors.getNoColor(), + }; +} + +class Console { + #printFunc = null; + [isConsoleInstance] = false; + + constructor(printFunc) { + this.#printFunc = printFunc; + this.indentLevel = 0; + this[isConsoleInstance] = true; + + // ref https://console.spec.whatwg.org/#console-namespace + // For historical web-compatibility reasons, the namespace object for + // console must have as its [[Prototype]] an empty object, created as if + // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. + const console = ObjectCreate({}, { + [SymbolToStringTag]: { + enumerable: false, + writable: false, configurable: true, - enumerable: true, - value: object[key], - }; - } + value: "console", + }, + }); + ObjectAssign(console, this); + return console; } - // A helper function that will bind our own console implementation - // with default implementation of Console from V8. This will cause - // console messages to be piped to inspector console. - // - // We are using `Deno.core.callConsole` binding to preserve proper stack - // frames in inspector console. This has to be done because V8 considers - // the last JS stack frame as gospel for the inspector. In our case we - // specifically want the latest user stack frame to be the one that matters - // though. - // - // Inspired by: - // https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/internal/util/inspector.js#L39-L61 - function wrapConsole(consoleFromDeno, consoleFromV8) { - const callConsole = core.callConsole; + log = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; - const keys = ObjectKeys(consoleFromV8); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - if (ObjectPrototypeHasOwnProperty(consoleFromDeno, key)) { - consoleFromDeno[key] = FunctionPrototypeBind( - callConsole, - consoleFromDeno, - consoleFromV8[key], - consoleFromDeno[key], - ); + debug = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 0, + ); + }; + + info = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; + + dir = (obj = undefined, options = {}) => { + this.#printFunc( + inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + + "\n", + 1, + ); + }; + + dirxml = this.dir; + + warn = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 2, + ); + }; + + error = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 3, + ); + }; + + assert = (condition = false, ...args) => { + if (condition) { + return; + } + + if (args.length === 0) { + this.error("Assertion failed"); + return; + } + + const [first, ...rest] = new SafeArrayIterator(args); + + if (typeof first === "string") { + this.error( + `Assertion failed: ${first}`, + ...new SafeArrayIterator(rest), + ); + return; + } + + this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); + }; + + count = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(countMap, label)) { + const current = MapPrototypeGet(countMap, label) || 0; + MapPrototypeSet(countMap, label, current + 1); + } else { + MapPrototypeSet(countMap, label, 1); + } + + this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); + }; + + countReset = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(countMap, label)) { + MapPrototypeSet(countMap, label, 0); + } else { + this.warn(`Count for '${label}' does not exist`); + } + }; + + table = (data = undefined, properties = undefined) => { + if (properties !== undefined && !ArrayIsArray(properties)) { + throw new Error( + "The 'properties' argument must be of type Array. " + + "Received type string", + ); + } + + if (data === null || typeof data !== "object") { + return this.log(data); + } + + const stringifyValue = (value) => + inspectValueWithQuotes(value, { + ...DEFAULT_INSPECT_OPTIONS, + depth: 1, + }); + const toTable = (header, body) => this.log(cliTable(header, body)); + + let resultData; + const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data); + const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data); + const valuesKey = "Values"; + const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; + + if (isSet) { + resultData = [...new SafeSet(data)]; + } else if (isMap) { + let idx = 0; + resultData = {}; + + MapPrototypeForEach(data, (v, k) => { + resultData[idx] = { Key: k, Values: v }; + idx++; + }); + } else { + resultData = data; + } + + const keys = ObjectKeys(resultData); + const numRows = keys.length; + + const objectValues = properties + ? ObjectFromEntries( + ArrayPrototypeMap( + properties, + (name) => [name, ArrayPrototypeFill(new Array(numRows), "")], + ), + ) + : {}; + const indexKeys = []; + const values = []; + + let hasPrimitives = false; + keys.forEach((k, idx) => { + const value = resultData[k]; + const primitive = value === null || + (typeof value !== "function" && typeof value !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + ArrayPrototypePush(values, stringifyValue(value)); } else { - // Add additional console APIs from the inspector - consoleFromDeno[key] = consoleFromV8[key]; + const valueObj = value || {}; + const keys = properties || ObjectKeys(valueObj); + for (let i = 0; i < keys.length; ++i) { + const k = keys[i]; + if (!primitive && ReflectHas(valueObj, k)) { + if (!(ReflectHas(objectValues, k))) { + objectValues[k] = ArrayPrototypeFill(new Array(numRows), ""); + } + objectValues[k][idx] = stringifyValue(valueObj[k]); + } + } + ArrayPrototypePush(values, ""); + } + + ArrayPrototypePush(indexKeys, k); + }); + + const headerKeys = ObjectKeys(objectValues); + const bodyValues = ObjectValues(objectValues); + const headerProps = properties || + [ + ...new SafeArrayIterator(headerKeys), + !isMap && hasPrimitives && valuesKey, + ]; + const header = ArrayPrototypeFilter([ + indexKey, + ...new SafeArrayIterator(headerProps), + ], Boolean); + const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values]; + + toTable(header, body); + }; + + time = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' already exists`); + return; + } + + MapPrototypeSet(timerMap, label, DateNow()); + }; + + timeLog = (label = "default", ...args) => { + label = String(label); + + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exists`); + return; + } + + const startTime = MapPrototypeGet(timerMap, label); + const duration = DateNow() - startTime; + + this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); + }; + + timeEnd = (label = "default") => { + label = String(label); + + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exist`); + return; + } + + const startTime = MapPrototypeGet(timerMap, label); + MapPrototypeDelete(timerMap, label); + const duration = DateNow() - startTime; + + this.info(`${label}: ${duration}ms`); + }; + + group = (...label) => { + if (label.length > 0) { + this.log(...new SafeArrayIterator(label)); + } + this.indentLevel += 2; + }; + + groupCollapsed = this.group; + + groupEnd = () => { + if (this.indentLevel > 0) { + this.indentLevel -= 2; + } + }; + + clear = () => { + this.indentLevel = 0; + this.#printFunc(CSI.kClear, 1); + this.#printFunc(CSI.kClearScreenDown, 1); + }; + + trace = (...args) => { + const message = inspectArgs( + args, + { ...getConsoleInspectOptions(), indentLevel: 0 }, + ); + const err = { + name: "Trace", + message, + }; + ErrorCaptureStackTrace(err, this.trace); + this.error(err.stack); + }; + + // These methods are noops, but when the inspector is connected, they + // call into V8. + profile = (_label) => {}; + profileEnd = (_label) => {}; + timeStamp = (_label) => {}; + + static [SymbolHasInstance](instance) { + return instance[isConsoleInstance]; + } +} + +const customInspect = SymbolFor("Deno.customInspect"); + +function inspect( + value, + inspectOptions = {}, +) { + circular = undefined; + return inspectValue(value, { + ...DEFAULT_INSPECT_OPTIONS, + ...inspectOptions, + }); +} + +/** Creates a proxy that represents a subset of the properties + * of the original object optionally without evaluating the properties + * in order to get the values. */ +function createFilteredInspectProxy({ object, keys, evaluate }) { + return new Proxy({}, { + get(_target, key) { + if (key === SymbolToStringTag) { + return object.constructor?.name; + } else if (ArrayPrototypeIncludes(keys, key)) { + return ReflectGet(object, key); + } else { + return undefined; + } + }, + getOwnPropertyDescriptor(_target, key) { + if (!ArrayPrototypeIncludes(keys, key)) { + return undefined; + } else if (evaluate) { + return getEvaluatedDescriptor(object, key); + } else { + return getDescendantPropertyDescriptor(object, key) ?? + getEvaluatedDescriptor(object, key); + } + }, + has(_target, key) { + return ArrayPrototypeIncludes(keys, key); + }, + ownKeys() { + return keys; + }, + }); + + function getDescendantPropertyDescriptor(object, key) { + let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); + if (!propertyDescriptor) { + const prototype = ReflectGetPrototypeOf(object); + if (prototype) { + propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); } } + return propertyDescriptor; } - // Expose these fields to internalObject for tests. - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - Console, - cssToAnsi, - inspectArgs, - parseCss, - parseCssColor, - }; + function getEvaluatedDescriptor(object, key) { + return { + configurable: true, + enumerable: true, + value: object[key], + }; + } +} - window.__bootstrap.console = { - CSI, - inspectArgs, - Console, - customInspect, - inspect, - wrapConsole, - createFilteredInspectProxy, - quoteString, - }; -})(this); +// A helper function that will bind our own console implementation +// with default implementation of Console from V8. This will cause +// console messages to be piped to inspector console. +// +// We are using `Deno.core.callConsole` binding to preserve proper stack +// frames in inspector console. This has to be done because V8 considers +// the last JS stack frame as gospel for the inspector. In our case we +// specifically want the latest user stack frame to be the one that matters +// though. +// +// Inspired by: +// https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/internal/util/inspector.js#L39-L61 +function wrapConsole(consoleFromDeno, consoleFromV8) { + const callConsole = core.callConsole; + + const keys = ObjectKeys(consoleFromV8); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (ObjectPrototypeHasOwnProperty(consoleFromDeno, key)) { + consoleFromDeno[key] = FunctionPrototypeBind( + callConsole, + consoleFromDeno, + consoleFromV8[key], + consoleFromDeno[key], + ); + } else { + // Add additional console APIs from the inspector + consoleFromDeno[key] = consoleFromV8[key]; + } + } +} + +// Expose these fields to internalObject for tests. +internals.Console = Console; +internals.cssToAnsi = cssToAnsi; +internals.inspectArgs = inspectArgs; +internals.parseCss = parseCss; +internals.parseCssColor = parseCssColor; + +export { + Console, + createFilteredInspectProxy, + CSI, + customInspect, + inspect, + inspectArgs, + quoteString, + wrapConsole, +}; diff --git a/ext/console/internal.d.ts b/ext/console/internal.d.ts index 57c5d120ba..965a879e72 100644 --- a/ext/console/internal.d.ts +++ b/ext/console/internal.d.ts @@ -3,14 +3,10 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare namespace console { - declare function createFilteredInspectProxy(params: { - object: TObject; - keys: (keyof TObject)[]; - evaluate: boolean; - }): Record; - } - } +declare module "internal:ext/console/02_console.js" { + function createFilteredInspectProxy(params: { + object: TObject; + keys: (keyof TObject)[]; + evaluate: boolean; + }): Record; } diff --git a/ext/console/lib.rs b/ext/console/lib.rs index 4b3b450299..31ec884c1d 100644 --- a/ext/console/lib.rs +++ b/ext/console/lib.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/console", "01_colors.js", "02_console.js", diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 7d30dcccf6..c79a1e4e83 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -6,4493 +6,573 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeEvery, + ArrayPrototypeFind, + ArrayPrototypeIncludes, + BigInt64ArrayPrototype, + BigUint64ArrayPrototype, + Int16ArrayPrototype, + Int32ArrayPrototype, + Int8ArrayPrototype, + JSONParse, + JSONStringify, + MathCeil, + ObjectAssign, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + StringPrototypeToLowerCase, + StringPrototypeToUpperCase, + StringPrototypeCharCodeAt, + StringFromCharCode, + SafeArrayIterator, + Symbol, + SymbolFor, + SyntaxError, + TypedArrayPrototypeSlice, + TypeError, + Uint16ArrayPrototype, + Uint32ArrayPrototype, + Uint8Array, + Uint8ArrayPrototype, + Uint8ClampedArrayPrototype, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; +// P-521 is not yet supported. +const supportedNamedCurves = ["P-256", "P-384"]; +const recognisedUsages = [ + "encrypt", + "decrypt", + "sign", + "verify", + "deriveKey", + "deriveBits", + "wrapKey", + "unwrapKey", +]; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeEvery, - ArrayPrototypeFind, - ArrayPrototypeIncludes, - BigInt64ArrayPrototype, - BigUint64ArrayPrototype, - Int16ArrayPrototype, - Int32ArrayPrototype, - Int8ArrayPrototype, - JSONParse, - JSONStringify, - MathCeil, - ObjectAssign, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - StringPrototypeToLowerCase, - StringPrototypeToUpperCase, - StringPrototypeCharCodeAt, - StringFromCharCode, - SafeArrayIterator, - Symbol, - SymbolFor, - SyntaxError, - TypedArrayPrototypeSlice, - TypeError, - Uint16ArrayPrototype, - Uint32ArrayPrototype, - Uint8Array, - Uint8ArrayPrototype, - Uint8ClampedArrayPrototype, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; +const simpleAlgorithmDictionaries = { + AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" }, + RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" }, + EcKeyGenParams: {}, + HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" }, + RsaPssParams: {}, + EcdsaParams: { hash: "HashAlgorithmIdentifier" }, + HmacImportParams: { hash: "HashAlgorithmIdentifier" }, + HkdfParams: { + hash: "HashAlgorithmIdentifier", + salt: "BufferSource", + info: "BufferSource", + }, + Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" }, + RsaOaepParams: { label: "BufferSource" }, + RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" }, + EcKeyImportParams: {}, +}; - // P-521 is not yet supported. - const supportedNamedCurves = ["P-256", "P-384"]; - const recognisedUsages = [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]; +const supportedAlgorithms = { + "digest": { + "SHA-1": null, + "SHA-256": null, + "SHA-384": null, + "SHA-512": null, + }, + "generateKey": { + "RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams", + "RSA-PSS": "RsaHashedKeyGenParams", + "RSA-OAEP": "RsaHashedKeyGenParams", + "ECDSA": "EcKeyGenParams", + "ECDH": "EcKeyGenParams", + "AES-CTR": "AesKeyGenParams", + "AES-CBC": "AesKeyGenParams", + "AES-GCM": "AesKeyGenParams", + "AES-KW": "AesKeyGenParams", + "HMAC": "HmacKeyGenParams", + "X25519": null, + "Ed25519": null, + }, + "sign": { + "RSASSA-PKCS1-v1_5": null, + "RSA-PSS": "RsaPssParams", + "ECDSA": "EcdsaParams", + "HMAC": null, + "Ed25519": null, + }, + "verify": { + "RSASSA-PKCS1-v1_5": null, + "RSA-PSS": "RsaPssParams", + "ECDSA": "EcdsaParams", + "HMAC": null, + "Ed25519": null, + }, + "importKey": { + "RSASSA-PKCS1-v1_5": "RsaHashedImportParams", + "RSA-PSS": "RsaHashedImportParams", + "RSA-OAEP": "RsaHashedImportParams", + "ECDSA": "EcKeyImportParams", + "ECDH": "EcKeyImportParams", + "HMAC": "HmacImportParams", + "HKDF": null, + "PBKDF2": null, + "AES-CTR": null, + "AES-CBC": null, + "AES-GCM": null, + "AES-KW": null, + "Ed25519": null, + "X25519": null, + }, + "deriveBits": { + "HKDF": "HkdfParams", + "PBKDF2": "Pbkdf2Params", + "ECDH": "EcdhKeyDeriveParams", + "X25519": "EcdhKeyDeriveParams", + }, + "encrypt": { + "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", + "AES-CTR": "AesCtrParams", + }, + "decrypt": { + "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", + "AES-CTR": "AesCtrParams", + }, + "get key length": { + "AES-CBC": "AesDerivedKeyParams", + "AES-CTR": "AesDerivedKeyParams", + "AES-GCM": "AesDerivedKeyParams", + "AES-KW": "AesDerivedKeyParams", + "HMAC": "HmacImportParams", + "HKDF": null, + "PBKDF2": null, + }, + "wrapKey": { + "AES-KW": null, + }, + "unwrapKey": { + "AES-KW": null, + }, +}; - const simpleAlgorithmDictionaries = { - AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" }, - RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" }, - EcKeyGenParams: {}, - HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" }, - RsaPssParams: {}, - EcdsaParams: { hash: "HashAlgorithmIdentifier" }, - HmacImportParams: { hash: "HashAlgorithmIdentifier" }, - HkdfParams: { - hash: "HashAlgorithmIdentifier", - salt: "BufferSource", - info: "BufferSource", - }, - Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" }, - RsaOaepParams: { label: "BufferSource" }, - RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" }, - EcKeyImportParams: {}, - }; +const aesJwkAlg = { + "AES-CTR": { + 128: "A128CTR", + 192: "A192CTR", + 256: "A256CTR", + }, + "AES-CBC": { + 128: "A128CBC", + 192: "A192CBC", + 256: "A256CBC", + }, + "AES-GCM": { + 128: "A128GCM", + 192: "A192GCM", + 256: "A256GCM", + }, + "AES-KW": { + 128: "A128KW", + 192: "A192KW", + 256: "A256KW", + }, +}; - const supportedAlgorithms = { - "digest": { - "SHA-1": null, - "SHA-256": null, - "SHA-384": null, - "SHA-512": null, - }, - "generateKey": { - "RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams", - "RSA-PSS": "RsaHashedKeyGenParams", - "RSA-OAEP": "RsaHashedKeyGenParams", - "ECDSA": "EcKeyGenParams", - "ECDH": "EcKeyGenParams", - "AES-CTR": "AesKeyGenParams", - "AES-CBC": "AesKeyGenParams", - "AES-GCM": "AesKeyGenParams", - "AES-KW": "AesKeyGenParams", - "HMAC": "HmacKeyGenParams", - "X25519": null, - "Ed25519": null, - }, - "sign": { - "RSASSA-PKCS1-v1_5": null, - "RSA-PSS": "RsaPssParams", - "ECDSA": "EcdsaParams", - "HMAC": null, - "Ed25519": null, - }, - "verify": { - "RSASSA-PKCS1-v1_5": null, - "RSA-PSS": "RsaPssParams", - "ECDSA": "EcdsaParams", - "HMAC": null, - "Ed25519": null, - }, - "importKey": { - "RSASSA-PKCS1-v1_5": "RsaHashedImportParams", - "RSA-PSS": "RsaHashedImportParams", - "RSA-OAEP": "RsaHashedImportParams", - "ECDSA": "EcKeyImportParams", - "ECDH": "EcKeyImportParams", - "HMAC": "HmacImportParams", - "HKDF": null, - "PBKDF2": null, - "AES-CTR": null, - "AES-CBC": null, - "AES-GCM": null, - "AES-KW": null, - "Ed25519": null, - "X25519": null, - }, - "deriveBits": { - "HKDF": "HkdfParams", - "PBKDF2": "Pbkdf2Params", - "ECDH": "EcdhKeyDeriveParams", - "X25519": "EcdhKeyDeriveParams", - }, - "encrypt": { - "RSA-OAEP": "RsaOaepParams", - "AES-CBC": "AesCbcParams", - "AES-GCM": "AesGcmParams", - "AES-CTR": "AesCtrParams", - }, - "decrypt": { - "RSA-OAEP": "RsaOaepParams", - "AES-CBC": "AesCbcParams", - "AES-GCM": "AesGcmParams", - "AES-CTR": "AesCtrParams", - }, - "get key length": { - "AES-CBC": "AesDerivedKeyParams", - "AES-CTR": "AesDerivedKeyParams", - "AES-GCM": "AesDerivedKeyParams", - "AES-KW": "AesDerivedKeyParams", - "HMAC": "HmacImportParams", - "HKDF": null, - "PBKDF2": null, - }, - "wrapKey": { - "AES-KW": null, - }, - "unwrapKey": { - "AES-KW": null, - }, - }; - - const aesJwkAlg = { - "AES-CTR": { - 128: "A128CTR", - 192: "A192CTR", - 256: "A256CTR", - }, - "AES-CBC": { - 128: "A128CBC", - 192: "A192CBC", - 256: "A256CBC", - }, - "AES-GCM": { - 128: "A128GCM", - 192: "A192GCM", - 256: "A256GCM", - }, - "AES-KW": { - 128: "A128KW", - 192: "A192KW", - 256: "A256KW", - }, - }; - - // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm - // 18.4.4 - function normalizeAlgorithm(algorithm, op) { - if (typeof algorithm == "string") { - return normalizeAlgorithm({ name: algorithm }, op); - } - - // 1. - const registeredAlgorithms = supportedAlgorithms[op]; - // 2. 3. - const initialAlg = webidl.converters.Algorithm(algorithm, { - prefix: "Failed to normalize algorithm", - context: "passed algorithm", - }); - // 4. - let algName = initialAlg.name; - - // 5. - let desiredType = undefined; - for (const key in registeredAlgorithms) { - if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { - continue; - } - if ( - StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) - ) { - algName = key; - desiredType = registeredAlgorithms[key]; - } - } - if (desiredType === undefined) { - throw new DOMException( - "Unrecognized algorithm name", - "NotSupportedError", - ); - } - - // Fast path everything below if the registered dictionary is "None". - if (desiredType === null) { - return { name: algName }; - } - - // 6. - const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { - prefix: "Failed to normalize algorithm", - context: "passed algorithm", - }); - // 7. - normalizedAlgorithm.name = algName; - - // 9. - const dict = simpleAlgorithmDictionaries[desiredType]; - // 10. - for (const member in dict) { - if (!ObjectPrototypeHasOwnProperty(dict, member)) { - continue; - } - const idlType = dict[member]; - const idlValue = normalizedAlgorithm[member]; - // 3. - if (idlType === "BufferSource" && idlValue) { - normalizedAlgorithm[member] = TypedArrayPrototypeSlice( - new Uint8Array( - ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue, - idlValue.byteOffset ?? 0, - idlValue.byteLength, - ), - ); - } else if (idlType === "HashAlgorithmIdentifier") { - normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest"); - } else if (idlType === "AlgorithmIdentifier") { - // TODO(lucacasonato): implement - throw new TypeError("unimplemented"); - } - } - - return normalizedAlgorithm; +// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm +// 18.4.4 +function normalizeAlgorithm(algorithm, op) { + if (typeof algorithm == "string") { + return normalizeAlgorithm({ name: algorithm }, op); } - /** - * @param {ArrayBufferView | ArrayBuffer} input - * @returns {Uint8Array} - */ - function copyBuffer(input) { - return TypedArrayPrototypeSlice( - ArrayBufferIsView(input) - ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) - : new Uint8Array(input), + // 1. + const registeredAlgorithms = supportedAlgorithms[op]; + // 2. 3. + const initialAlg = webidl.converters.Algorithm(algorithm, { + prefix: "Failed to normalize algorithm", + context: "passed algorithm", + }); + // 4. + let algName = initialAlg.name; + + // 5. + let desiredType = undefined; + for (const key in registeredAlgorithms) { + if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { + continue; + } + if ( + StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) + ) { + algName = key; + desiredType = registeredAlgorithms[key]; + } + } + if (desiredType === undefined) { + throw new DOMException( + "Unrecognized algorithm name", + "NotSupportedError", ); } - const _handle = Symbol("[[handle]]"); - const _algorithm = Symbol("[[algorithm]]"); - const _extractable = Symbol("[[extractable]]"); - const _usages = Symbol("[[usages]]"); - const _type = Symbol("[[type]]"); - - class CryptoKey { - /** @type {string} */ - [_type]; - /** @type {boolean} */ - [_extractable]; - /** @type {object} */ - [_algorithm]; - /** @type {string[]} */ - [_usages]; - /** @type {object} */ - [_handle]; - - constructor() { - webidl.illegalConstructor(); - } - - /** @returns {string} */ - get type() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_type]; - } - - /** @returns {boolean} */ - get extractable() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_extractable]; - } - - /** @returns {string[]} */ - get usages() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_usages]; - } - - /** @returns {object} */ - get algorithm() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_algorithm]; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - type: this.type, - extractable: this.extractable, - algorithm: this.algorithm, - usages: this.usages, - }) - }`; - } + // Fast path everything below if the registered dictionary is "None". + if (desiredType === null) { + return { name: algName }; } - webidl.configurePrototype(CryptoKey); - const CryptoKeyPrototype = CryptoKey.prototype; + // 6. + const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { + prefix: "Failed to normalize algorithm", + context: "passed algorithm", + }); + // 7. + normalizedAlgorithm.name = algName; - /** - * @param {string} type - * @param {boolean} extractable - * @param {string[]} usages - * @param {object} algorithm - * @param {object} handle - * @returns - */ - function constructKey(type, extractable, usages, algorithm, handle) { - const key = webidl.createBranded(CryptoKey); - key[_type] = type; - key[_extractable] = extractable; - key[_usages] = usages; - key[_algorithm] = algorithm; - key[_handle] = handle; - return key; - } - - // https://w3c.github.io/webcrypto/#concept-usage-intersection - /** - * @param {string[]} a - * @param {string[]} b - * @returns - */ - function usageIntersection(a, b) { - return a.filter((i) => b.includes(i)); - } - - // TODO(lucacasonato): this should be moved to rust - /** @type {WeakMap} */ - const KEY_STORE = new WeakMap(); - - function getKeyLength(algorithm) { - switch (algorithm.name) { - case "AES-CBC": - case "AES-CTR": - case "AES-GCM": - case "AES-KW": { - // 1. - if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) { - throw new DOMException( - "length must be 128, 192, or 256", - "OperationError", - ); - } - - // 2. - return algorithm.length; - } - case "HMAC": { - // 1. - let length; - if (algorithm.length === undefined) { - switch (algorithm.hash.name) { - case "SHA-1": - length = 512; - break; - case "SHA-256": - length = 512; - break; - case "SHA-384": - length = 1024; - break; - case "SHA-512": - length = 1024; - break; - default: - throw new DOMException( - "Unrecognized hash algorithm", - "NotSupportedError", - ); - } - } else if (algorithm.length !== 0) { - length = algorithm.length; - } else { - throw new TypeError("Invalid length."); - } - - // 2. - return length; - } - case "HKDF": { - // 1. - return null; - } - case "PBKDF2": { - // 1. - return null; - } - default: - throw new TypeError("unreachable"); + // 9. + const dict = simpleAlgorithmDictionaries[desiredType]; + // 10. + for (const member in dict) { + if (!ObjectPrototypeHasOwnProperty(dict, member)) { + continue; } - } - - class SubtleCrypto { - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {string} algorithm - * @param {BufferSource} data - * @returns {Promise} - */ - async digest(algorithm, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 2", - }); - - data = copyBuffer(data); - - algorithm = normalizeAlgorithm(algorithm, "digest"); - - const result = await core.opAsync( - "op_crypto_subtle_digest", - algorithm.name, - data, + const idlType = dict[member]; + const idlValue = normalizedAlgorithm[member]; + // 3. + if (idlType === "BufferSource" && idlValue) { + normalizedAlgorithm[member] = TypedArrayPrototypeSlice( + new Uint8Array( + ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue, + idlValue.byteOffset ?? 0, + idlValue.byteLength, + ), ); - - return result.buffer; + } else if (idlType === "HashAlgorithmIdentifier") { + normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest"); + } else if (idlType === "AlgorithmIdentifier") { + // TODO(lucacasonato): implement + throw new TypeError("unimplemented"); } + } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async encrypt(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); + return normalizedAlgorithm; +} - // 2. - data = copyBuffer(data); +/** + * @param {ArrayBufferView | ArrayBuffer} input + * @returns {Uint8Array} + */ +function copyBuffer(input) { + return TypedArrayPrototypeSlice( + ArrayBufferIsView(input) + ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + : new Uint8Array(input), + ); +} - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); +const _handle = Symbol("[[handle]]"); +const _algorithm = Symbol("[[algorithm]]"); +const _extractable = Symbol("[[extractable]]"); +const _usages = Symbol("[[usages]]"); +const _type = Symbol("[[type]]"); - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { +class CryptoKey { + /** @type {string} */ + [_type]; + /** @type {boolean} */ + [_extractable]; + /** @type {object} */ + [_algorithm]; + /** @type {string[]} */ + [_usages]; + /** @type {object} */ + [_handle]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {string} */ + get type() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_type]; + } + + /** @returns {boolean} */ + get extractable() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_extractable]; + } + + /** @returns {string[]} */ + get usages() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_usages]; + } + + /** @returns {object} */ + get algorithm() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_algorithm]; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + type: this.type, + extractable: this.extractable, + algorithm: this.algorithm, + usages: this.usages, + }) + }`; + } +} + +webidl.configurePrototype(CryptoKey); +const CryptoKeyPrototype = CryptoKey.prototype; + +/** + * @param {string} type + * @param {boolean} extractable + * @param {string[]} usages + * @param {object} algorithm + * @param {object} handle + * @returns + */ +function constructKey(type, extractable, usages, algorithm, handle) { + const key = webidl.createBranded(CryptoKey); + key[_type] = type; + key[_extractable] = extractable; + key[_usages] = usages; + key[_algorithm] = algorithm; + key[_handle] = handle; + return key; +} + +// https://w3c.github.io/webcrypto/#concept-usage-intersection +/** + * @param {string[]} a + * @param {string[]} b + * @returns + */ +function usageIntersection(a, b) { + return a.filter((i) => b.includes(i)); +} + +// TODO(lucacasonato): this should be moved to rust +/** @type {WeakMap} */ +const KEY_STORE = new WeakMap(); + +function getKeyLength(algorithm) { + switch (algorithm.name) { + case "AES-CBC": + case "AES-CTR": + case "AES-GCM": + case "AES-KW": { + // 1. + if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) { throw new DOMException( - "Encryption algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } - - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { - throw new DOMException( - "Key does not support the 'encrypt' operation.", - "InvalidAccessError", - ); - } - - return await encrypt(normalizedAlgorithm, key, data); - } - - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async decrypt(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); - - // 2. - data = copyBuffer(data); - - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); - - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Decryption algorithm doesn't match key algorithm.", + "length must be 128, 192, or 256", "OperationError", ); } - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { - throw new DOMException( - "Key does not support the 'decrypt' operation.", - "InvalidAccessError", - ); - } - - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - switch (normalizedAlgorithm.name) { - case "RSA-OAEP": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // 2. - if (normalizedAlgorithm.label) { - normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); - } else { - normalizedAlgorithm.label = new Uint8Array(); - } - - // 3-5. - const hashAlgorithm = key[_algorithm].hash.name; - const plainText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "RSA-OAEP", - hash: hashAlgorithm, - label: normalizedAlgorithm.label, - }, data); - - // 6. - return plainText.buffer; - } - case "AES-CBC": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - - // 1. - if (normalizedAlgorithm.iv.byteLength !== 16) { - throw new DOMException( - "Counter must be 16 bytes", - "OperationError", - ); - } - - const plainText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-CBC", - iv: normalizedAlgorithm.iv, - length: key[_algorithm].length, - }, data); - - // 6. - return plainText.buffer; - } - case "AES-CTR": { - normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - - // 1. - if (normalizedAlgorithm.counter.byteLength !== 16) { - throw new DOMException( - "Counter vector must be 16 bytes", - "OperationError", - ); - } - - // 2. - if ( - normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 - ) { - throw new DOMException( - "Counter length must not be 0 or greater than 128", - "OperationError", - ); - } - - // 3. - const cipherText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-CTR", - keyLength: key[_algorithm].length, - counter: normalizedAlgorithm.counter, - ctrLength: normalizedAlgorithm.length, - }, data); - - // 4. - return cipherText.buffer; - } - case "AES-GCM": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - - // 1. - if (normalizedAlgorithm.tagLength === undefined) { - normalizedAlgorithm.tagLength = 128; - } else if ( - !ArrayPrototypeIncludes( - [32, 64, 96, 104, 112, 120, 128], - normalizedAlgorithm.tagLength, - ) - ) { - throw new DOMException( - "Invalid tag length", - "OperationError", - ); - } - - // 2. - if (data.byteLength < normalizedAlgorithm.tagLength / 8) { - throw new DOMException( - "Tag length overflows ciphertext", - "OperationError", - ); - } - - // 3. We only support 96-bit and 128-bit nonce. - if ( - ArrayPrototypeIncludes( - [12, 16], - normalizedAlgorithm.iv.byteLength, - ) === undefined - ) { - throw new DOMException( - "Initialization vector length not supported", - "NotSupportedError", - ); - } - - // 4. - if (normalizedAlgorithm.additionalData !== undefined) { - if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { - throw new DOMException( - "Additional data too large", - "OperationError", - ); - } - normalizedAlgorithm.additionalData = copyBuffer( - normalizedAlgorithm.additionalData, - ); - } - - // 5-8. - const plaintext = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-GCM", - length: key[_algorithm].length, - iv: normalizedAlgorithm.iv, - additionalData: normalizedAlgorithm.additionalData || - null, - tagLength: normalizedAlgorithm.tagLength, - }, data); - - // 9. - return plaintext.buffer; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } + // 2. + return algorithm.length; } - - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async sign(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); - + case "HMAC": { // 1. - data = copyBuffer(data); - - // 2. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); - - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Signing algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } - - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "sign")) { - throw new DOMException( - "Key does not support the 'sign' operation.", - "InvalidAccessError", - ); - } - - switch (normalizedAlgorithm.name) { - case "RSASSA-PKCS1-v1_5": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // 2. - const hashAlgorithm = key[_algorithm].hash.name; - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "RSASSA-PKCS1-v1_5", - hash: hashAlgorithm, - }, data); - - return signature.buffer; - } - case "RSA-PSS": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // 2. - const hashAlgorithm = key[_algorithm].hash.name; - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "RSA-PSS", - hash: hashAlgorithm, - saltLength: normalizedAlgorithm.saltLength, - }, data); - - return signature.buffer; - } - case "ECDSA": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // 2. - const hashAlgorithm = normalizedAlgorithm.hash.name; - const namedCurve = key[_algorithm].namedCurve; - if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) { - throw new DOMException("Curve not supported", "NotSupportedError"); - } - - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "ECDSA", - hash: hashAlgorithm, - namedCurve, - }, data); - - return signature.buffer; - } - case "HMAC": { - const hashAlgorithm = key[_algorithm].hash.name; - - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "HMAC", - hash: hashAlgorithm, - }, data); - - return signature.buffer; - } - case "Ed25519": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260 - const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN - const signature = new Uint8Array(SIGNATURE_LEN); - if (!ops.op_sign_ed25519(keyData, data, signature)) { - throw new DOMException( - "Failed to sign", - "OperationError", - ); - } - return signature.buffer; - } - } - - throw new TypeError("unreachable"); - } - - /** - * @param {string} format - * @param {BufferSource} keyData - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsages[]} keyUsages - * @returns {Promise} - */ - // deno-lint-ignore require-await - async importKey(format, keyData, algorithm, extractable, keyUsages) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, { - prefix, - context: "Argument 2", - }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 3", - }); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: "Argument 4", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 5", - }); - - // 2. - if (format !== "jwk") { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - keyData = copyBuffer(keyData); - } else { - throw new TypeError("keyData is a JsonWebKey"); - } - } else { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - throw new TypeError("keyData is not a JsonWebKey"); - } - } - - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); - - const algorithmName = normalizedAlgorithm.name; - - switch (algorithmName) { - case "HMAC": { - return importKeyHMAC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "ECDH": - case "ECDSA": { - return importKeyEC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": - case "RSA-OAEP": { - return importKeyRSA( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "HKDF": { - return importKeyHKDF(format, keyData, extractable, keyUsages); - } - case "PBKDF2": { - return importKeyPBKDF2(format, keyData, extractable, keyUsages); - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": { - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - ); - } - case "AES-KW": { - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ["wrapKey", "unwrapKey"], - ); - } - case "X25519": { - return importKeyX25519( - format, - keyData, - extractable, - keyUsages, - ); - } - case "Ed25519": { - return importKeyEd25519( - format, - keyData, - extractable, - keyUsages, - ); - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - /** - * @param {string} format - * @param {CryptoKey} key - * @returns {Promise} - */ - // deno-lint-ignore require-await - async exportKey(format, key) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - - const handle = key[_handle]; - // 2. - const innerKey = WeakMapPrototypeGet(KEY_STORE, handle); - - const algorithmName = key[_algorithm].name; - - let result; - - switch (algorithmName) { - case "HMAC": { - result = exportKeyHMAC(format, key, innerKey); - break; - } - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": - case "RSA-OAEP": { - result = exportKeyRSA(format, key, innerKey); - break; - } - case "ECDH": - case "ECDSA": { - result = exportKeyEC(format, key, innerKey); - break; - } - case "Ed25519": { - result = exportKeyEd25519(format, key, innerKey); - break; - } - case "X25519": { - result = exportKeyX25519(format, key, innerKey); - break; - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": - case "AES-KW": { - result = exportKeyAES(format, key, innerKey); - break; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - - if (key.extractable === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); - } - - return result; - } - - /** - * @param {AlgorithmIdentifier} algorithm - * @param {CryptoKey} baseKey - * @param {number | null} length - * @returns {Promise} - */ - async deriveBits(algorithm, baseKey, length) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - baseKey = webidl.converters.CryptoKey(baseKey, { - prefix, - context: "Argument 2", - }); - if (length !== null) { - length = webidl.converters["unsigned long"](length, { - prefix, - context: "Argument 3", - }); - } - - // 2. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); - // 4-6. - const result = await deriveBits(normalizedAlgorithm, baseKey, length); - // 7. - if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { - throw new DOMException("Invalid algorithm name", "InvalidAccessError"); - } - // 8. - if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) { - throw new DOMException( - "baseKey usages does not contain `deriveBits`", - "InvalidAccessError", - ); - } - // 9-10. - return result; - } - - /** - * @param {AlgorithmIdentifier} algorithm - * @param {CryptoKey} baseKey - * @param {number} length - * @returns {Promise} - */ - async deriveKey( - algorithm, - baseKey, - derivedKeyType, - extractable, - keyUsages, - ) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - baseKey = webidl.converters.CryptoKey(baseKey, { - prefix, - context: "Argument 2", - }); - derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { - prefix, - context: "Argument 3", - }); - extractable = webidl.converters["boolean"](extractable, { - prefix, - context: "Argument 4", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 5", - }); - - // 2-3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); - - // 4-5. - const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm( - derivedKeyType, - "importKey", - ); - - // 6-7. - const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm( - derivedKeyType, - "get key length", - ); - - // 8-10. - - // 11. - if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { - throw new DOMException( - "Invalid algorithm name", - "InvalidAccessError", - ); - } - - // 12. - if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) { - throw new DOMException( - "baseKey usages does not contain `deriveKey`", - "InvalidAccessError", - ); - } - - // 13. - const length = getKeyLength(normalizedDerivedKeyAlgorithmLength); - - // 14. - const secret = await this.deriveBits( - normalizedAlgorithm, - baseKey, - length, - ); - - // 15. - const result = await this.importKey( - "raw", - secret, - normalizedDerivedKeyAlgorithmImport, - extractable, - keyUsages, - ); - - // 16. - if ( - ArrayPrototypeIncludes(["private", "secret"], result[_type]) && - keyUsages.length == 0 - ) { - throw new SyntaxError("Invalid key usages"); - } - // 17. - return result; - } - - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} signature - * @param {BufferSource} data - * @returns {Promise} - */ - async verify(algorithm, key, signature, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - signature = webidl.converters.BufferSource(signature, { - prefix, - context: "Argument 3", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 4", - }); - - // 2. - signature = copyBuffer(signature); - - // 3. - data = copyBuffer(data); - - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); - - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Verifying algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } - - if (!ArrayPrototypeIncludes(key[_usages], "verify")) { - throw new DOMException( - "Key does not support the 'verify' operation.", - "InvalidAccessError", - ); - } - - switch (normalizedAlgorithm.name) { - case "RSASSA-PKCS1-v1_5": { - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "RSASSA-PKCS1-v1_5", - hash: hashAlgorithm, - signature, - }, data); - } - case "RSA-PSS": { - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "RSA-PSS", - hash: hashAlgorithm, - signature, - }, data); - } - case "HMAC": { - const hash = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "HMAC", - hash, - signature, - }, data); - } - case "ECDSA": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // 2. - const hash = normalizedAlgorithm.hash.name; - - // 3-8. - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "ECDSA", - hash, - signature, - namedCurve: key[_algorithm].namedCurve, - }, data); - } - case "Ed25519": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - return ops.op_verify_ed25519(keyData, data, signature); - } - } - - throw new TypeError("unreachable"); - } - - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async wrapKey(format, key, wrappingKey, wrapAlgorithm) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - wrappingKey = webidl.converters.CryptoKey(wrappingKey, { - prefix, - context: "Argument 3", - }); - wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { - prefix, - context: "Argument 4", - }); - - let normalizedAlgorithm; - - try { - // 2. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); - } catch (_) { - // 3. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); - } - - // 8. - if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { - throw new DOMException( - "Wrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } - - // 9. - if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { - throw new DOMException( - "Key does not support the 'wrapKey' operation.", - "InvalidAccessError", - ); - } - - // 10. NotSupportedError will be thrown in step 12. - // 11. - if (key[_extractable] === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); - } - - // 12. - const exportedKey = await this.exportKey(format, key); - - let bytes; - // 13. - if (format !== "jwk") { - bytes = new Uint8Array(exportedKey); - } else { - const jwk = JSONStringify(exportedKey); - const ret = new Uint8Array(jwk.length); - for (let i = 0; i < jwk.length; i++) { - ret[i] = StringPrototypeCharCodeAt(jwk, i); - } - bytes = ret; - } - - // 14-15. - if ( - supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined - ) { - const handle = wrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const cipherText = await ops.op_crypto_wrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, bytes); - - // 4. - return cipherText.buffer; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined - ) { - // must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"] - return await encrypt( - normalizedAlgorithm, - constructKey( - wrappingKey[_type], - wrappingKey[_extractable], - ["encrypt"], - wrappingKey[_algorithm], - wrappingKey[_handle], - ), - bytes, - ); - } else { - throw new DOMException( - "Algorithm not supported", - "NotSupportedError", - ); - } - } - /** - * @param {string} format - * @param {BufferSource} wrappedKey - * @param {CryptoKey} unwrappingKey - * @param {AlgorithmIdentifier} unwrapAlgorithm - * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async unwrapKey( - format, - wrappedKey, - unwrappingKey, - unwrapAlgorithm, - unwrappedKeyAlgorithm, - extractable, - keyUsages, - ) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 7, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - wrappedKey = webidl.converters.BufferSource(wrappedKey, { - prefix, - context: "Argument 2", - }); - unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { - prefix, - context: "Argument 3", - }); - unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { - prefix, - context: "Argument 4", - }); - unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( - unwrappedKeyAlgorithm, - { - prefix, - context: "Argument 5", - }, - ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: "Argument 6", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 7", - }); - - // 2. - wrappedKey = copyBuffer(wrappedKey); - - let normalizedAlgorithm; - - try { - // 3. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); - } catch (_) { - // 4. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); - } - - // 6. - const normalizedKeyAlgorithm = normalizeAlgorithm( - unwrappedKeyAlgorithm, - "importKey", - ); - - // 11. - if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { - throw new DOMException( - "Unwrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } - - // 12. - if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { - throw new DOMException( - "Key does not support the 'unwrapKey' operation.", - "InvalidAccessError", - ); - } - - // 13. - let key; - if ( - supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined - ) { - const handle = unwrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const plainText = await ops.op_crypto_unwrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, wrappedKey); - - // 4. - key = plainText.buffer; - break; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined - ) { - // must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"] - key = await this.decrypt( - normalizedAlgorithm, - constructKey( - unwrappingKey[_type], - unwrappingKey[_extractable], - ["decrypt"], - unwrappingKey[_algorithm], - unwrappingKey[_handle], - ), - wrappedKey, - ); - } else { - throw new DOMException( - "Algorithm not supported", - "NotSupportedError", - ); - } - - let bytes; - // 14. - if (format !== "jwk") { - bytes = key; - } else { - const k = new Uint8Array(key); - let str = ""; - for (let i = 0; i < k.length; i++) { - str += StringFromCharCode(k[i]); - } - bytes = JSONParse(str); - } - - // 15. - const result = await this.importKey( - format, - bytes, - normalizedKeyAlgorithm, - extractable, - keyUsages, - ); - // 16. - if ( - (result[_type] == "secret" || result[_type] == "private") && - keyUsages.length == 0 - ) { - throw new SyntaxError("Invalid key type."); - } - // 17. - result[_extractable] = extractable; - // 18. - result[_usages] = usageIntersection(keyUsages, recognisedUsages); - // 19. - return result; - } - - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async generateKey(algorithm, extractable, keyUsages) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - extractable = webidl.converters["boolean"](extractable, { - prefix, - context: "Argument 2", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 3", - }); - - const usages = keyUsages; - - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey"); - const result = await generateKey( - normalizedAlgorithm, - extractable, - usages, - ); - - if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) { - const type = result[_type]; - if ((type === "secret" || type === "private") && usages.length === 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if ( - ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey) - ) { - if (result.privateKey[_usages].length === 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } - - return result; - } - } - const SubtleCryptoPrototype = SubtleCrypto.prototype; - - async function generateKey(normalizedAlgorithm, extractable, usages) { - const algorithmName = normalizedAlgorithm.name; - - switch (algorithmName) { - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - const keyData = await core.opAsync( - "op_crypto_generate_key", - { - algorithm: "RSA", - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - }, - ); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - - // 4-8. - const algorithm = { - name: algorithmName, - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - hash: normalizedAlgorithm.hash, - }; - - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); - - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); - - // 19-22. - return { publicKey, privateKey }; - } - case "RSA-OAEP": { - if ( - ArrayPrototypeFind( - usages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - const keyData = await core.opAsync( - "op_crypto_generate_key", - { - algorithm: "RSA", - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - }, - ); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - - // 4-8. - const algorithm = { - name: algorithmName, - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - hash: normalizedAlgorithm.hash, - }; - - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["encrypt", "wrapKey"]), - algorithm, - handle, - ); - - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["decrypt", "unwrapKey"]), - algorithm, - handle, - ); - - // 19-22. - return { publicKey, privateKey }; - } - case "ECDSA": { - const namedCurve = normalizedAlgorithm.namedCurve; - - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-3. - const handle = {}; - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - namedCurve, - ) - ) { - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "EC", - namedCurve, - }); - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - } else { - throw new DOMException("Curve not supported", "NotSupportedError"); - } - - // 4-6. - const algorithm = { - name: algorithmName, - namedCurve, - }; - - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); - - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); - - // 17-20. - return { publicKey, privateKey }; - } - case "ECDH": { - const namedCurve = normalizedAlgorithm.namedCurve; - - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-3. - const handle = {}; - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - namedCurve, - ) - ) { - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "EC", - namedCurve, - }); - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - } else { - throw new DOMException("Curve not supported", "NotSupportedError"); - } - - // 4-6. - const algorithm = { - name: algorithmName, - namedCurve, - }; - - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - handle, - ); - - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); - - // 17-20. - return { publicKey, privateKey }; - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - return generateKeyAES(normalizedAlgorithm, extractable, usages); - } - case "AES-KW": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - return generateKeyAES(normalizedAlgorithm, extractable, usages); - } - case "X25519": { - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - const privateKeyData = new Uint8Array(32); - const publicKeyData = new Uint8Array(32); - ops.op_generate_x25519_keypair(privateKeyData, publicKeyData); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const publicHandle = {}; - WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); - - const algorithm = { - name: algorithmName, - }; - - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - publicHandle, - ); - - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); - - return { publicKey, privateKey }; - } - case "Ed25519": { - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const ED25519_SEED_LEN = 32; - const ED25519_PUBLIC_KEY_LEN = 32; - const privateKeyData = new Uint8Array(ED25519_SEED_LEN); - const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN); - if ( - !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData) - ) { - throw new DOMException("Failed to generate key", "OperationError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const publicHandle = {}; - WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); - - const algorithm = { - name: algorithmName, - }; - - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - publicHandle, - ); - - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); - - return { publicKey, privateKey }; - } - case "HMAC": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - let length; - if (normalizedAlgorithm.length === undefined) { - length = null; - } else if (normalizedAlgorithm.length !== 0) { - length = normalizedAlgorithm.length; - } else { - throw new DOMException("Invalid length", "OperationError"); - } - - // 3-4. - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "HMAC", - hash: normalizedAlgorithm.hash.name, - length, - }); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); - - // 6-10. - const algorithm = { - name: algorithmName, - hash: { - name: normalizedAlgorithm.hash.name, - }, - length: keyData.byteLength * 8, - }; - - // 5, 11-13. - const key = constructKey( - "secret", - extractable, - usages, - algorithm, - handle, - ); - - // 14. - return key; - } - } - } - - function importKeyEd25519( - format, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "raw": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, keyData); - - // 2-3. - const algorithm = { - name: "Ed25519", - }; - - // 4-6. - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - case "spki": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const publicKeyData = new Uint8Array(32); - if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const privateKeyData = new Uint8Array(32); - if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - case "jwk": { - // 1. - const jwk = keyData; - - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["sign"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["verify"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } - - // 3. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } - - // 4. - if (jwk.crv !== "Ed25519") { - throw new DOMException("Invalid curve", "DataError"); - } - - // 5. - if (jwk.alg !== undefined && jwk.alg !== "EdDSA") { - throw new DOMException("Invalid algorithm", "DataError"); - } - - // 6. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" - ) { - throw new DOMException("Invalid key usage", "DataError"); - } - - // 7. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 8. - if (jwk.ext !== undefined && jwk.ext === false && extractable) { - throw new DOMException("Invalid key extractability", "DataError"); - } - - // 9. - if (jwk.d !== undefined) { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } else { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function importKeyX25519( - format, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "raw": { - // 1. - if (keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, keyData); - - // 2-3. - const algorithm = { - name: "X25519", - }; - - // 4-6. - return constructKey( - "public", - extractable, - [], - algorithm, - handle, - ); - } - case "spki": { - // 1. - if (keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const publicKeyData = new Uint8Array(32); - if (!ops.op_import_spki_x25519(keyData, publicKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "public", - extractable, - [], - algorithm, - handle, - ); - } - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const privateKeyData = new Uint8Array(32); - if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - case "jwk": { - // 1. - const jwk = keyData; - - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["deriveKey", "deriveBits"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } - - // 3. - if (jwk.d === undefined && keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 4. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } - - // 5. - if (jwk.crv !== "X25519") { - throw new DOMException("Invalid curve", "DataError"); - } - - // 6. - if (keyUsages.length > 0 && jwk.use !== undefined) { - if (jwk.use !== "enc") { - throw new DOMException("Invalid key use", "DataError"); - } - } - - // 7. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 8. - if (jwk.ext !== undefined && jwk.ext === false && extractable) { - throw new DOMException("Invalid key extractability", "DataError"); - } - - // 9. - if (jwk.d !== undefined) { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); - } else { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "public", - extractable, - [], - algorithm, - handle, - ); - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function exportKeyAES( - format, - key, - innerKey, - ) { - switch (format) { - // 2. - case "raw": { - // 1. - const data = innerKey.data; - // 2. - return data.buffer; - } - case "jwk": { - // 1-2. - const jwk = { - kty: "oct", - }; - - // 3. - const data = ops.op_crypto_export_key({ - format: "jwksecret", - algorithm: "AES", - }, innerKey); - ObjectAssign(jwk, data); - - // 4. - const algorithm = key[_algorithm]; - switch (algorithm.length) { - case 128: - jwk.alg = aesJwkAlg[algorithm.name][128]; - break; - case 192: - jwk.alg = aesJwkAlg[algorithm.name][192]; - break; - case 256: - jwk.alg = aesJwkAlg[algorithm.name][256]; - break; - default: - throw new DOMException( - "Invalid key length", - "NotSupportedError", - ); - } - - // 5. - jwk.key_ops = key.usages; - - // 6. - jwk.ext = key[_extractable]; - - // 7. - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - supportedKeyUsages, - ) { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const algorithmName = normalizedAlgorithm.name; - - // 2. - let data = keyData; - - switch (format) { - case "raw": { - // 2. - if ( - !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8) - ) { - throw new DOMException("Invalid key length", "Datarror"); - } - - break; - } - case "jwk": { - // 1. - const jwk = keyData; - - // 2. - if (jwk.kty !== "oct") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'oct'", - "DataError", - ); - } - - // Section 6.4.1 of RFC7518 - if (jwk.k === undefined) { - throw new DOMException( - "'k' property of JsonWebKey must be present", - "DataError", - ); - } - - // 4. - const { rawData } = ops.op_crypto_import_key( - { algorithm: "AES" }, - { jwkSecret: jwk }, - ); - data = rawData.data; - - // 5. - switch (data.byteLength * 8) { - case 128: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][128] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - case 192: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][192] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - case 256: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][256] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - default: - throw new DOMException( - "Invalid key length", - "DataError", - ); - } - - // 6. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc" - ) { - throw new DOMException("Invalid key usages", "DataError"); - } - - // 7. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 8. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); - } - - break; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data, - }); - - // 4-7. - const algorithm = { - name: algorithmName, - length: data.byteLength * 8, - }; - - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - // 8. - return key; - } - - function importKeyHMAC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - let hash; - let data; - - // 4. https://w3c.github.io/webcrypto/#hmac-operations - switch (format) { - case "raw": { - data = keyData; - hash = normalizedAlgorithm.hash; - break; - } - case "jwk": { - const jwk = keyData; - - // 2. - if (jwk.kty !== "oct") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'oct'", - "DataError", - ); - } - - // Section 6.4.1 of RFC7518 - if (jwk.k === undefined) { - throw new DOMException( - "'k' property of JsonWebKey must be present", - "DataError", - ); - } - - // 4. - const { rawData } = ops.op_crypto_import_key( - { algorithm: "HMAC" }, - { jwkSecret: jwk }, - ); - data = rawData.data; - - // 5. - hash = normalizedAlgorithm.hash; - - // 6. - switch (hash.name) { - case "SHA-1": { - if (jwk.alg !== undefined && jwk.alg !== "HS1") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS1'", - "DataError", - ); - } - break; - } - case "SHA-256": { - if (jwk.alg !== undefined && jwk.alg !== "HS256") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS256'", - "DataError", - ); - } - break; - } - case "SHA-384": { - if (jwk.alg !== undefined && jwk.alg !== "HS384") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS384'", - "DataError", - ); - } - break; - } - case "SHA-512": { - if (jwk.alg !== undefined && jwk.alg !== "HS512") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS512'", - "DataError", - ); - } - break; - } - default: - throw new TypeError("unreachable"); - } - - // 7. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" - ) { - throw new DOMException( - "'use' property of JsonWebKey must be 'sig'", - "DataError", - ); - } - - // 8. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 9. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); - } - - break; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - - // 5. - let length = data.byteLength * 8; - // 6. - if (length === 0) { - throw new DOMException("Key length is zero", "DataError"); - } - // 7. - if (normalizedAlgorithm.length !== undefined) { - if ( - normalizedAlgorithm.length > length || - normalizedAlgorithm.length <= (length - 8) - ) { - throw new DOMException( - "Key length is invalid", - "DataError", - ); - } - length = normalizedAlgorithm.length; - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data, - }); - - const algorithm = { - name: "HMAC", - length, - hash, - }; - - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - - function importKeyEC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name]; - - switch (format) { - case "raw": { - // 1. - if ( - !ArrayPrototypeIncludes( - supportedNamedCurves, - normalizedAlgorithm.namedCurve, - ) - ) { - throw new DOMException( - "Invalid namedCurve", - "DataError", - ); - } - - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { raw: keyData }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - // 4-5. - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-9. - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { pkcs8: keyData }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - case "spki": { - // 1. - if (normalizedAlgorithm.name == "ECDSA") { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if (keyUsages.length != 0) { - throw new DOMException("Key usage must be empty", "SyntaxError"); - } - - // 2-12 - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { spki: keyData }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - case "jwk": { - const jwk = keyData; - - const keyType = (jwk.d !== undefined) ? "private" : "public"; - - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - if (jwk.kty !== "EC") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'EC'", - "DataError", - ); - } - - // 4. - if ( - keyUsages.length > 0 && jwk.use !== undefined && - jwk.use !== supportedUsages.jwkUse - ) { - throw new DOMException( - `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, - "DataError", - ); - } - - // 5. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' member of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' member of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 6. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); - } - - // 9. - if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") { - let algNamedCurve; - - switch (jwk.alg) { - case "ES256": { - algNamedCurve = "P-256"; - break; - } - case "ES384": { - algNamedCurve = "P-384"; - break; - } - case "ES512": { - algNamedCurve = "P-521"; - break; - } - default: - throw new DOMException( - "Curve algorithm not supported", - "DataError", - ); - } - - if (algNamedCurve) { - if (algNamedCurve !== normalizedAlgorithm.namedCurve) { - throw new DOMException( - "Mismatched curve algorithm", - "DataError", - ); - } - } - } - - // Validate that this is a valid public key. - if (jwk.x === undefined) { - throw new DOMException( - "'x' property of JsonWebKey is required for EC keys", - "DataError", - ); - } - if (jwk.y === undefined) { - throw new DOMException( - "'y' property of JsonWebKey is required for EC keys", - "DataError", - ); - } - - if (jwk.d !== undefined) { - // it's also a Private key - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { jwkPrivateEc: jwk }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } else { - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { jwkPublicEc: jwk }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - const SUPPORTED_KEY_USAGES = { - "RSASSA-PKCS1-v1_5": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "RSA-PSS": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "RSA-OAEP": { - public: ["encrypt", "wrapKey"], - private: ["decrypt", "unwrapKey"], - jwkUse: "enc", - }, - "ECDSA": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "ECDH": { - public: [], - private: ["deriveKey", "deriveBits"], - jwkUse: "enc", - }, - }; - - function importKeyRSA( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - { pkcs8: keyData }, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - case "spki": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - { spki: keyData }, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - case "jwk": { - // 1. - const jwk = keyData; - - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'RSA'", - "DataError", - ); - } - - // 4. - if ( - keyUsages.length > 0 && jwk.use !== undefined && - StringPrototypeToLowerCase(jwk.use) !== - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse - ) { - throw new DOMException( - `'use' property of JsonWebKey must be '${ - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse - }'`, - "DataError", - ); - } - - // 5. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); - } - - // 7. - let hash; - - // 8. - if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "RS1": - hash = "SHA-1"; - break; - case "RS256": - hash = "SHA-256"; - break; - case "RS384": - hash = "SHA-384"; - break; - case "RS512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, - "DataError", - ); - } - } else if (normalizedAlgorithm.name === "RSA-PSS") { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "PS1": - hash = "SHA-1"; - break; - case "PS256": - hash = "SHA-256"; - break; - case "PS384": - hash = "SHA-384"; - break; - case "PS512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`, - "DataError", - ); - } - } else { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "RSA-OAEP": - hash = "SHA-1"; - break; - case "RSA-OAEP-256": - hash = "SHA-256"; - break; - case "RSA-OAEP-384": - hash = "SHA-384"; - break; - case "RSA-OAEP-512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`, - "DataError", - ); - } - } - - // 9. - if (hash !== undefined) { - // 9.1. - const normalizedHash = normalizeAlgorithm(hash, "digest"); - - // 9.2. - if (normalizedHash.name !== normalizedAlgorithm.hash.name) { - throw new DOMException( - `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, - "DataError", - ); - } - } - - // 10. - if (jwk.d !== undefined) { - // Private key - const optimizationsPresent = jwk.p !== undefined || - jwk.q !== undefined || jwk.dp !== undefined || - jwk.dq !== undefined || jwk.qi !== undefined; - if (optimizationsPresent) { - if (jwk.q === undefined) { - throw new DOMException( - "'q' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.dp === undefined) { - throw new DOMException( - "'dp' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.dq === undefined) { - throw new DOMException( - "'dq' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.qi === undefined) { - throw new DOMException( - "'qi' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.oth !== undefined) { - throw new DOMException( - "'oth' property of JsonWebKey is not supported", - "NotSupportedError", - ); - } - } else { - throw new DOMException( - "only optimized private keys are supported", - "NotSupportedError", - ); - } - - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - hash: normalizedAlgorithm.hash.name, - }, - { jwkPrivateRsa: jwk }, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } else { - // Validate that this is a valid public key. - if (jwk.n === undefined) { - throw new DOMException( - "'n' property of JsonWebKey is required for public keys", - "DataError", - ); - } - if (jwk.e === undefined) { - throw new DOMException( - "'e' property of JsonWebKey is required for public keys", - "DataError", - ); - } - - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - hash: normalizedAlgorithm.hash.name, - }, - { jwkPublicRsa: jwk }, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function importKeyHKDF( - format, - keyData, - extractable, - keyUsages, - ) { - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } - - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } - - // 3. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); - - // 4-8. - const algorithm = { - name: "HKDF", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - // 9. - return key; - } - - function importKeyPBKDF2( - format, - keyData, - extractable, - keyUsages, - ) { - // 1. - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } - - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } - - // 4. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); - - // 5-9. - const algorithm = { - name: "PBKDF2", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - // 10. - return key; - } - - function exportKeyHMAC(format, key, innerKey) { - // 1. - if (innerKey == null) { - throw new DOMException("Key is not available", "OperationError"); - } - - switch (format) { - // 3. - case "raw": { - const bits = innerKey.data; - for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) { - bits.push(0); - } - // 4-5. - return bits.buffer; - } - case "jwk": { - // 1-2. - const jwk = { - kty: "oct", - }; - - // 3. - const data = ops.op_crypto_export_key({ - format: "jwksecret", - algorithm: key[_algorithm].name, - }, innerKey); - jwk.k = data.k; - - // 4. - const algorithm = key[_algorithm]; - // 5. - const hash = algorithm.hash; - // 6. - switch (hash.name) { + let length; + if (algorithm.length === undefined) { + switch (algorithm.hash.name) { case "SHA-1": - jwk.alg = "HS1"; + length = 512; break; case "SHA-256": - jwk.alg = "HS256"; + length = 512; break; case "SHA-384": - jwk.alg = "HS384"; + length = 1024; break; case "SHA-512": - jwk.alg = "HS512"; + length = 1024; break; default: throw new DOMException( - "Hash algorithm not supported", + "Unrecognized hash algorithm", "NotSupportedError", ); } - // 7. - jwk.key_ops = key.usages; - // 8. - jwk.ext = key[_extractable]; - // 9. - return jwk; + } else if (algorithm.length !== 0) { + length = algorithm.length; + } else { + throw new TypeError("Invalid length."); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + + // 2. + return length; } + case "HKDF": { + // 1. + return null; + } + case "PBKDF2": { + // 1. + return null; + } + default: + throw new TypeError("unreachable"); + } +} + +class SubtleCrypto { + constructor() { + webidl.illegalConstructor(); } - function exportKeyRSA(format, key, innerKey) { - switch (format) { - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a private key", - "InvalidAccessError", - ); - } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - format: "pkcs8", - }, innerKey); - - // 3. - return data.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - format: "spki", - }, innerKey); - - // 3. - return data.buffer; - } - case "jwk": { - // 1-2. - const jwk = { - kty: "RSA", - }; - - // 3. - const hash = key[_algorithm].hash.name; - - // 4. - if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") { - switch (hash) { - case "SHA-1": - jwk.alg = "RS1"; - break; - case "SHA-256": - jwk.alg = "RS256"; - break; - case "SHA-384": - jwk.alg = "RS384"; - break; - case "SHA-512": - jwk.alg = "RS512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } else if (key[_algorithm].name === "RSA-PSS") { - switch (hash) { - case "SHA-1": - jwk.alg = "PS1"; - break; - case "SHA-256": - jwk.alg = "PS256"; - break; - case "SHA-384": - jwk.alg = "PS384"; - break; - case "SHA-512": - jwk.alg = "PS512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } else { - switch (hash) { - case "SHA-1": - jwk.alg = "RSA-OAEP"; - break; - case "SHA-256": - jwk.alg = "RSA-OAEP-256"; - break; - case "SHA-384": - jwk.alg = "RSA-OAEP-384"; - break; - case "SHA-512": - jwk.alg = "RSA-OAEP-512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } - - // 5-6. - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - }, innerKey); - ObjectAssign(jwk, data); - - // 7. - jwk.key_ops = key.usages; - - // 8. - jwk.ext = key[_extractable]; - - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function exportKeyEd25519(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2-3. - return innerKey.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - const spkiDer = ops.op_export_spki_ed25519(innerKey); - return spkiDer.buffer; - } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - const pkcs8Der = ops.op_export_pkcs8_ed25519( - new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), - ); - pkcs8Der[15] = 0x20; - return pkcs8Der.buffer; - } - case "jwk": { - const x = key[_type] === "private" - ? ops.op_jwk_x_ed25519(innerKey) - : ops.op_crypto_base64url_encode(innerKey); - const jwk = { - kty: "OKP", - alg: "EdDSA", - crv: "Ed25519", - x, - "key_ops": key.usages, - ext: key[_extractable], - }; - if (key[_type] === "private") { - jwk.d = ops.op_crypto_base64url_encode(innerKey); - } - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function exportKeyX25519(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2-3. - return innerKey.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - const spkiDer = ops.op_export_spki_x25519(innerKey); - return spkiDer.buffer; - } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - const pkcs8Der = ops.op_export_pkcs8_x25519( - new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), - ); - pkcs8Der[15] = 0x20; - return pkcs8Der.buffer; - } - case "jwk": { - if (key[_type] === "private") { - throw new DOMException("Not implemented", "NotSupportedError"); - } - const x = ops.op_crypto_base64url_encode(innerKey); - const jwk = { - kty: "OKP", - crv: "X25519", - x, - "key_ops": key.usages, - ext: key[_extractable], - }; - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function exportKeyEC(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - format: "raw", - }, innerKey); - - return data.buffer; - } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a private key", - "InvalidAccessError", - ); - } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - format: "pkcs8", - }, innerKey); - - return data.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - format: "spki", - }, innerKey); - - return data.buffer; - } - case "jwk": { - if (key[_algorithm].name == "ECDSA") { - // 1-2. - const jwk = { - kty: "EC", - }; - - // 3.1 - jwk.crv = key[_algorithm].namedCurve; - - // Missing from spec - let algNamedCurve; - - switch (key[_algorithm].namedCurve) { - case "P-256": { - algNamedCurve = "ES256"; - break; - } - case "P-384": { - algNamedCurve = "ES384"; - break; - } - case "P-521": { - algNamedCurve = "ES512"; - break; - } - default: - throw new DOMException( - "Curve algorithm not supported", - "DataError", - ); - } - - jwk.alg = algNamedCurve; - - // 3.2 - 3.4. - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - }, innerKey); - ObjectAssign(jwk, data); - - // 4. - jwk.key_ops = key.usages; - - // 5. - jwk.ext = key[_extractable]; - - return jwk; - } else { // ECDH - // 1-2. - const jwk = { - kty: "EC", - }; - - // missing step from spec - jwk.alg = "ECDH"; - - // 3.1 - jwk.crv = key[_algorithm].namedCurve; - - // 3.2 - 3.4 - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - }, innerKey); - ObjectAssign(jwk, data); - - // 4. - jwk.key_ops = key.usages; - - // 5. - jwk.ext = key[_extractable]; - - return jwk; - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - async function generateKeyAES(normalizedAlgorithm, extractable, usages) { - const algorithmName = normalizedAlgorithm.name; - - // 2. - if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) { - throw new DOMException("Invalid key length", "OperationError"); - } - - // 3. - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "AES", - length: normalizedAlgorithm.length, + /** + * @param {string} algorithm + * @param {BufferSource} data + * @returns {Promise} + */ + async digest(algorithm, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", }); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", }); - // 6-8. - const algorithm = { - name: algorithmName, - length: normalizedAlgorithm.length, - }; + data = copyBuffer(data); - // 9-11. - const key = constructKey( - "secret", - extractable, - usages, - algorithm, - handle, + algorithm = normalizeAlgorithm(algorithm, "digest"); + + const result = await core.opAsync( + "op_crypto_subtle_digest", + algorithm.name, + data, ); - // 12. - return key; + return result.buffer; } - async function deriveBits(normalizedAlgorithm, baseKey, length) { - switch (normalizedAlgorithm.name) { - case "PBKDF2": { - // 1. - if (length == null || length == 0 || length % 8 !== 0) { - throw new DOMException("Invalid length", "OperationError"); - } + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async encrypt(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); - if (normalizedAlgorithm.iterations == 0) { - throw new DOMException( - "iterations must not be zero", - "OperationError", - ); - } + // 2. + data = copyBuffer(data); - const handle = baseKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); - normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - - const buf = await core.opAsync("op_crypto_derive_bits", { - key: keyData, - algorithm: "PBKDF2", - hash: normalizedAlgorithm.hash.name, - iterations: normalizedAlgorithm.iterations, - length, - }, normalizedAlgorithm.salt); - - return buf.buffer; - } - case "ECDH": { - // 1. - if (baseKey[_type] !== "private") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 2. - const publicKey = normalizedAlgorithm.public; - // 3. - if (publicKey[_type] !== "public") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 4. - if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { - throw new DOMException( - "Algorithm mismatch", - "InvalidAccessError", - ); - } - // 5. - if ( - publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve - ) { - throw new DOMException( - "namedCurve mismatch", - "InvalidAccessError", - ); - } - // 6. - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - publicKey[_algorithm].namedCurve, - ) - ) { - const baseKeyhandle = baseKey[_handle]; - const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle); - const publicKeyhandle = publicKey[_handle]; - const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle); - - const buf = await core.opAsync("op_crypto_derive_bits", { - key: baseKeyData, - publicKey: publicKeyData, - algorithm: "ECDH", - namedCurve: publicKey[_algorithm].namedCurve, - length, - }); - - // 8. - if (length === null) { - return buf.buffer; - } else if (buf.buffer.byteLength * 8 < length) { - throw new DOMException("Invalid length", "OperationError"); - } else { - return buf.buffer.slice(0, MathCeil(length / 8)); - } - } else { - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - case "HKDF": { - // 1. - if (length === null || length === 0 || length % 8 !== 0) { - throw new DOMException("Invalid length", "OperationError"); - } - - const handle = baseKey[_handle]; - const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle); - - normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - - normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info); - - const buf = await core.opAsync("op_crypto_derive_bits", { - key: keyDerivationKey, - algorithm: "HKDF", - hash: normalizedAlgorithm.hash.name, - info: normalizedAlgorithm.info, - length, - }, normalizedAlgorithm.salt); - - return buf.buffer; - } - case "X25519": { - // 1. - if (baseKey[_type] !== "private") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 2. - const publicKey = normalizedAlgorithm.public; - // 3. - if (publicKey[_type] !== "public") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 4. - if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { - throw new DOMException( - "Algorithm mismatch", - "InvalidAccessError", - ); - } - - // 5. - const kHandle = baseKey[_handle]; - const k = WeakMapPrototypeGet(KEY_STORE, kHandle); - - const uHandle = publicKey[_handle]; - const u = WeakMapPrototypeGet(KEY_STORE, uHandle); - - const secret = new Uint8Array(32); - const isIdentity = ops.op_derive_bits_x25519(k, u, secret); - - // 6. - if (isIdentity) { - throw new DOMException("Invalid key", "OperationError"); - } - - // 7. - if (length === null) { - return secret.buffer; - } else if ( - secret.buffer.byteLength * 8 < length - ) { - throw new DOMException("Invalid length", "OperationError"); - } else { - return secret.buffer.slice(0, MathCeil(length / 8)); - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Encryption algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); } + + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { + throw new DOMException( + "Key does not support the 'encrypt' operation.", + "InvalidAccessError", + ); + } + + return await encrypt(normalizedAlgorithm, key, data); } - async function encrypt(normalizedAlgorithm, key, data) { + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async decrypt(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); + + // 2. + data = copyBuffer(data); + + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); + + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Decryption algorithm doesn't match key algorithm.", + "OperationError", + ); + } + + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { + throw new DOMException( + "Key does not support the 'decrypt' operation.", + "InvalidAccessError", + ); + } + const handle = key[_handle]; const keyData = WeakMapPrototypeGet(KEY_STORE, handle); switch (normalizedAlgorithm.name) { case "RSA-OAEP": { // 1. - if (key[_type] !== "public") { + if (key[_type] !== "private") { throw new DOMException( "Key type not supported", "InvalidAccessError", @@ -4508,7 +588,7 @@ // 3-5. const hashAlgorithm = key[_algorithm].hash.name; - const cipherText = await core.opAsync("op_crypto_encrypt", { + const plainText = await core.opAsync("op_crypto_decrypt", { key: keyData, algorithm: "RSA-OAEP", hash: hashAlgorithm, @@ -4516,7 +596,7 @@ }, data); // 6. - return cipherText.buffer; + return plainText.buffer; } case "AES-CBC": { normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); @@ -4524,21 +604,20 @@ // 1. if (normalizedAlgorithm.iv.byteLength !== 16) { throw new DOMException( - "Initialization vector must be 16 bytes", + "Counter must be 16 bytes", "OperationError", ); } - // 2. - const cipherText = await core.opAsync("op_crypto_encrypt", { + const plainText = await core.opAsync("op_crypto_decrypt", { key: keyData, algorithm: "AES-CBC", - length: key[_algorithm].length, iv: normalizedAlgorithm.iv, + length: key[_algorithm].length, }, data); - // 4. - return cipherText.buffer; + // 6. + return plainText.buffer; } case "AES-CTR": { normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); @@ -4553,7 +632,7 @@ // 2. if ( - normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128 + normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 ) { throw new DOMException( "Counter length must not be 0 or greater than 128", @@ -4562,7 +641,7 @@ } // 3. - const cipherText = await core.opAsync("op_crypto_encrypt", { + const cipherText = await core.opAsync("op_crypto_decrypt", { key: keyData, algorithm: "AES-CTR", keyLength: key[_algorithm].length, @@ -4577,39 +656,7 @@ normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); // 1. - if (data.byteLength > (2 ** 39) - 256) { - throw new DOMException( - "Plaintext too large", - "OperationError", - ); - } - - // 2. - // We only support 96-bit and 128-bit nonce. - if ( - ArrayPrototypeIncludes( - [12, 16], - normalizedAlgorithm.iv.byteLength, - ) === undefined - ) { - throw new DOMException( - "Initialization vector length not supported", - "NotSupportedError", - ); - } - - // 3. - if (normalizedAlgorithm.additionalData !== undefined) { - if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { - throw new DOMException( - "Additional data too large", - "OperationError", - ); - } - } - - // 4. - if (normalizedAlgorithm.tagLength == undefined) { + if (normalizedAlgorithm.tagLength === undefined) { normalizedAlgorithm.tagLength = 128; } else if ( !ArrayPrototypeIncludes( @@ -4622,106 +669,4051 @@ "OperationError", ); } - // 5. - if (normalizedAlgorithm.additionalData) { + + // 2. + if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + throw new DOMException( + "Tag length overflows ciphertext", + "OperationError", + ); + } + + // 3. We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } + + // 4. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { + throw new DOMException( + "Additional data too large", + "OperationError", + ); + } normalizedAlgorithm.additionalData = copyBuffer( normalizedAlgorithm.additionalData, ); } - // 6-7. - const cipherText = await core.opAsync("op_crypto_encrypt", { + + // 5-8. + const plaintext = await core.opAsync("op_crypto_decrypt", { key: keyData, algorithm: "AES-GCM", length: key[_algorithm].length, iv: normalizedAlgorithm.iv, - additionalData: normalizedAlgorithm.additionalData || null, + additionalData: normalizedAlgorithm.additionalData || + null, tagLength: normalizedAlgorithm.tagLength, }, data); - // 8. - return cipherText.buffer; + // 9. + return plaintext.buffer; } default: throw new DOMException("Not implemented", "NotSupportedError"); } } - webidl.configurePrototype(SubtleCrypto); - const subtle = webidl.createBranded(SubtleCrypto); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async sign(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); - class Crypto { - constructor() { - webidl.illegalConstructor(); + // 1. + data = copyBuffer(data); + + // 2. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); + + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Signing algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); } - getRandomValues(arrayBufferView) { - webidl.assertBranded(this, CryptoPrototype); - const prefix = "Failed to execute 'getRandomValues' on 'Crypto'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // Fast path for Uint8Array - if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView)) { - ops.op_crypto_get_random_values(arrayBufferView); - return arrayBufferView; + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "sign")) { + throw new DOMException( + "Key does not support the 'sign' operation.", + "InvalidAccessError", + ); + } + + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + // 2. + const hashAlgorithm = key[_algorithm].hash.name; + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "RSASSA-PKCS1-v1_5", + hash: hashAlgorithm, + }, data); + + return signature.buffer; } - arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, { - prefix, - context: "Argument 1", - }); + case "RSA-PSS": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + // 2. + const hashAlgorithm = key[_algorithm].hash.name; + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "RSA-PSS", + hash: hashAlgorithm, + saltLength: normalizedAlgorithm.saltLength, + }, data); + + return signature.buffer; + } + case "ECDSA": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + // 2. + const hashAlgorithm = normalizedAlgorithm.hash.name; + const namedCurve = key[_algorithm].namedCurve; + if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) { + throw new DOMException("Curve not supported", "NotSupportedError"); + } + + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "ECDSA", + hash: hashAlgorithm, + namedCurve, + }, data); + + return signature.buffer; + } + case "HMAC": { + const hashAlgorithm = key[_algorithm].hash.name; + + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "HMAC", + hash: hashAlgorithm, + }, data); + + return signature.buffer; + } + case "Ed25519": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260 + const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN + const signature = new Uint8Array(SIGNATURE_LEN); + if (!ops.op_sign_ed25519(keyData, data, signature)) { + throw new DOMException( + "Failed to sign", + "OperationError", + ); + } + return signature.buffer; + } + } + + throw new TypeError("unreachable"); + } + + /** + * @param {string} format + * @param {BufferSource} keyData + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsages[]} keyUsages + * @returns {Promise} + */ + // deno-lint-ignore require-await + async importKey(format, keyData, algorithm, extractable, keyUsages) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, { + prefix, + context: "Argument 2", + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 3", + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: "Argument 4", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 5", + }); + + // 2. + if (format !== "jwk") { if ( - !( - ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf( - Uint8ClampedArrayPrototype, - arrayBufferView, - ) || - ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf( - BigInt64ArrayPrototype, - arrayBufferView, - ) || - ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView) - ) + ArrayBufferIsView(keyData) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) ) { - throw new DOMException( - "The provided ArrayBufferView is not an integer array type", - "TypeMismatchError", + keyData = copyBuffer(keyData); + } else { + throw new TypeError("keyData is a JsonWebKey"); + } + } else { + if ( + ArrayBufferIsView(keyData) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) + ) { + throw new TypeError("keyData is not a JsonWebKey"); + } + } + + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); + + const algorithmName = normalizedAlgorithm.name; + + switch (algorithmName) { + case "HMAC": { + return importKeyHMAC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, ); } - const ui8 = new Uint8Array( - arrayBufferView.buffer, - arrayBufferView.byteOffset, - arrayBufferView.byteLength, - ); - ops.op_crypto_get_random_values(ui8); - return arrayBufferView; - } - - randomUUID() { - webidl.assertBranded(this, CryptoPrototype); - return ops.op_crypto_random_uuid(); - } - - get subtle() { - webidl.assertBranded(this, CryptoPrototype); - return subtle; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; + case "ECDH": + case "ECDSA": { + return importKeyEC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); + } + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": + case "RSA-OAEP": { + return importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); + } + case "HKDF": { + return importKeyHKDF(format, keyData, extractable, keyUsages); + } + case "PBKDF2": { + return importKeyPBKDF2(format, keyData, extractable, keyUsages); + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": { + return importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + ); + } + case "AES-KW": { + return importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["wrapKey", "unwrapKey"], + ); + } + case "X25519": { + return importKeyX25519( + format, + keyData, + extractable, + keyUsages, + ); + } + case "Ed25519": { + return importKeyEd25519( + format, + keyData, + extractable, + keyUsages, + ); + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } } - webidl.configurePrototype(Crypto); - const CryptoPrototype = Crypto.prototype; + /** + * @param {string} format + * @param {CryptoKey} key + * @returns {Promise} + */ + // deno-lint-ignore require-await + async exportKey(format, key) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); - window.__bootstrap.crypto = { - SubtleCrypto, - crypto: webidl.createBranded(Crypto), - Crypto, - CryptoKey, + const handle = key[_handle]; + // 2. + const innerKey = WeakMapPrototypeGet(KEY_STORE, handle); + + const algorithmName = key[_algorithm].name; + + let result; + + switch (algorithmName) { + case "HMAC": { + result = exportKeyHMAC(format, key, innerKey); + break; + } + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": + case "RSA-OAEP": { + result = exportKeyRSA(format, key, innerKey); + break; + } + case "ECDH": + case "ECDSA": { + result = exportKeyEC(format, key, innerKey); + break; + } + case "Ed25519": { + result = exportKeyEd25519(format, key, innerKey); + break; + } + case "X25519": { + result = exportKeyX25519(format, key, innerKey); + break; + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": + case "AES-KW": { + result = exportKeyAES(format, key, innerKey); + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } + + if (key.extractable === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); + } + + return result; + } + + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number | null} length + * @returns {Promise} + */ + async deriveBits(algorithm, baseKey, length) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: "Argument 2", + }); + if (length !== null) { + length = webidl.converters["unsigned long"](length, { + prefix, + context: "Argument 3", + }); + } + + // 2. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + // 4-6. + const result = await deriveBits(normalizedAlgorithm, baseKey, length); + // 7. + if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { + throw new DOMException("Invalid algorithm name", "InvalidAccessError"); + } + // 8. + if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) { + throw new DOMException( + "baseKey usages does not contain `deriveBits`", + "InvalidAccessError", + ); + } + // 9-10. + return result; + } + + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number} length + * @returns {Promise} + */ + async deriveKey( + algorithm, + baseKey, + derivedKeyType, + extractable, + keyUsages, + ) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: "Argument 2", + }); + derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { + prefix, + context: "Argument 3", + }); + extractable = webidl.converters["boolean"](extractable, { + prefix, + context: "Argument 4", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 5", + }); + + // 2-3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + + // 4-5. + const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm( + derivedKeyType, + "importKey", + ); + + // 6-7. + const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm( + derivedKeyType, + "get key length", + ); + + // 8-10. + + // 11. + if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { + throw new DOMException( + "Invalid algorithm name", + "InvalidAccessError", + ); + } + + // 12. + if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) { + throw new DOMException( + "baseKey usages does not contain `deriveKey`", + "InvalidAccessError", + ); + } + + // 13. + const length = getKeyLength(normalizedDerivedKeyAlgorithmLength); + + // 14. + const secret = await this.deriveBits( + normalizedAlgorithm, + baseKey, + length, + ); + + // 15. + const result = await this.importKey( + "raw", + secret, + normalizedDerivedKeyAlgorithmImport, + extractable, + keyUsages, + ); + + // 16. + if ( + ArrayPrototypeIncludes(["private", "secret"], result[_type]) && + keyUsages.length == 0 + ) { + throw new SyntaxError("Invalid key usages"); + } + // 17. + return result; + } + + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} signature + * @param {BufferSource} data + * @returns {Promise} + */ + async verify(algorithm, key, signature, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + signature = webidl.converters.BufferSource(signature, { + prefix, + context: "Argument 3", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 4", + }); + + // 2. + signature = copyBuffer(signature); + + // 3. + data = copyBuffer(data); + + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); + + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Verifying algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + if (!ArrayPrototypeIncludes(key[_usages], "verify")) { + throw new DOMException( + "Key does not support the 'verify' operation.", + "InvalidAccessError", + ); + } + + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + const hashAlgorithm = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSASSA-PKCS1-v1_5", + hash: hashAlgorithm, + signature, + }, data); + } + case "RSA-PSS": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + const hashAlgorithm = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSA-PSS", + hash: hashAlgorithm, + signature, + }, data); + } + case "HMAC": { + const hash = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "HMAC", + hash, + signature, + }, data); + } + case "ECDSA": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + // 2. + const hash = normalizedAlgorithm.hash.name; + + // 3-8. + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "ECDSA", + hash, + signature, + namedCurve: key[_algorithm].namedCurve, + }, data); + } + case "Ed25519": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + return ops.op_verify_ed25519(keyData, data, signature); + } + } + + throw new TypeError("unreachable"); + } + + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async wrapKey(format, key, wrappingKey, wrapAlgorithm) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + wrappingKey = webidl.converters.CryptoKey(wrappingKey, { + prefix, + context: "Argument 3", + }); + wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { + prefix, + context: "Argument 4", + }); + + let normalizedAlgorithm; + + try { + // 2. + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); + } catch (_) { + // 3. + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); + } + + // 8. + if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { + throw new DOMException( + "Wrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + // 9. + if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { + throw new DOMException( + "Key does not support the 'wrapKey' operation.", + "InvalidAccessError", + ); + } + + // 10. NotSupportedError will be thrown in step 12. + // 11. + if (key[_extractable] === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); + } + + // 12. + const exportedKey = await this.exportKey(format, key); + + let bytes; + // 13. + if (format !== "jwk") { + bytes = new Uint8Array(exportedKey); + } else { + const jwk = JSONStringify(exportedKey); + const ret = new Uint8Array(jwk.length); + for (let i = 0; i < jwk.length; i++) { + ret[i] = StringPrototypeCharCodeAt(jwk, i); + } + bytes = ret; + } + + // 14-15. + if ( + supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = wrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + switch (normalizedAlgorithm.name) { + case "AES-KW": { + const cipherText = await ops.op_crypto_wrap_key({ + key: keyData, + algorithm: normalizedAlgorithm.name, + }, bytes); + + // 4. + return cipherText.buffer; + } + default: { + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } + } + } else if ( + supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined + ) { + // must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"] + return await encrypt( + normalizedAlgorithm, + constructKey( + wrappingKey[_type], + wrappingKey[_extractable], + ["encrypt"], + wrappingKey[_algorithm], + wrappingKey[_handle], + ), + bytes, + ); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } + } + /** + * @param {string} format + * @param {BufferSource} wrappedKey + * @param {CryptoKey} unwrappingKey + * @param {AlgorithmIdentifier} unwrapAlgorithm + * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async unwrapKey( + format, + wrappedKey, + unwrappingKey, + unwrapAlgorithm, + unwrappedKeyAlgorithm, + extractable, + keyUsages, + ) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 7, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + wrappedKey = webidl.converters.BufferSource(wrappedKey, { + prefix, + context: "Argument 2", + }); + unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { + prefix, + context: "Argument 3", + }); + unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { + prefix, + context: "Argument 4", + }); + unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + unwrappedKeyAlgorithm, + { + prefix, + context: "Argument 5", + }, + ); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: "Argument 6", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 7", + }); + + // 2. + wrappedKey = copyBuffer(wrappedKey); + + let normalizedAlgorithm; + + try { + // 3. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); + } catch (_) { + // 4. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); + } + + // 6. + const normalizedKeyAlgorithm = normalizeAlgorithm( + unwrappedKeyAlgorithm, + "importKey", + ); + + // 11. + if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { + throw new DOMException( + "Unwrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + // 12. + if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { + throw new DOMException( + "Key does not support the 'unwrapKey' operation.", + "InvalidAccessError", + ); + } + + // 13. + let key; + if ( + supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = unwrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + switch (normalizedAlgorithm.name) { + case "AES-KW": { + const plainText = await ops.op_crypto_unwrap_key({ + key: keyData, + algorithm: normalizedAlgorithm.name, + }, wrappedKey); + + // 4. + key = plainText.buffer; + break; + } + default: { + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } + } + } else if ( + supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined + ) { + // must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"] + key = await this.decrypt( + normalizedAlgorithm, + constructKey( + unwrappingKey[_type], + unwrappingKey[_extractable], + ["decrypt"], + unwrappingKey[_algorithm], + unwrappingKey[_handle], + ), + wrappedKey, + ); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } + + let bytes; + // 14. + if (format !== "jwk") { + bytes = key; + } else { + const k = new Uint8Array(key); + let str = ""; + for (let i = 0; i < k.length; i++) { + str += StringFromCharCode(k[i]); + } + bytes = JSONParse(str); + } + + // 15. + const result = await this.importKey( + format, + bytes, + normalizedKeyAlgorithm, + extractable, + keyUsages, + ); + // 16. + if ( + (result[_type] == "secret" || result[_type] == "private") && + keyUsages.length == 0 + ) { + throw new SyntaxError("Invalid key type."); + } + // 17. + result[_extractable] = extractable; + // 18. + result[_usages] = usageIntersection(keyUsages, recognisedUsages); + // 19. + return result; + } + + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async generateKey(algorithm, extractable, keyUsages) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + extractable = webidl.converters["boolean"](extractable, { + prefix, + context: "Argument 2", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 3", + }); + + const usages = keyUsages; + + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey"); + const result = await generateKey( + normalizedAlgorithm, + extractable, + usages, + ); + + if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) { + const type = result[_type]; + if ((type === "secret" || type === "private") && usages.length === 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else if ( + ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey) + ) { + if (result.privateKey[_usages].length === 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } + + return result; + } +} +const SubtleCryptoPrototype = SubtleCrypto.prototype; + +async function generateKey(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; + + switch (algorithmName) { + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2. + const keyData = await core.opAsync( + "op_crypto_generate_key", + { + algorithm: "RSA", + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + }, + ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); + + // 4-8. + const algorithm = { + name: algorithmName, + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + hash: normalizedAlgorithm.hash, + }; + + // 9-13. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + handle, + ); + + // 14-18. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); + + // 19-22. + return { publicKey, privateKey }; + } + case "RSA-OAEP": { + if ( + ArrayPrototypeFind( + usages, + (u) => + !ArrayPrototypeIncludes([ + "encrypt", + "decrypt", + "wrapKey", + "unwrapKey", + ], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2. + const keyData = await core.opAsync( + "op_crypto_generate_key", + { + algorithm: "RSA", + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + }, + ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); + + // 4-8. + const algorithm = { + name: algorithmName, + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + hash: normalizedAlgorithm.hash, + }; + + // 9-13. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["encrypt", "wrapKey"]), + algorithm, + handle, + ); + + // 14-18. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["decrypt", "unwrapKey"]), + algorithm, + handle, + ); + + // 19-22. + return { publicKey, privateKey }; + } + case "ECDSA": { + const namedCurve = normalizedAlgorithm.namedCurve; + + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-3. + const handle = {}; + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + namedCurve, + ) + ) { + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "EC", + namedCurve, + }); + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); + } else { + throw new DOMException("Curve not supported", "NotSupportedError"); + } + + // 4-6. + const algorithm = { + name: algorithmName, + namedCurve, + }; + + // 7-11. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + handle, + ); + + // 12-16. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); + + // 17-20. + return { publicKey, privateKey }; + } + case "ECDH": { + const namedCurve = normalizedAlgorithm.namedCurve; + + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-3. + const handle = {}; + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + namedCurve, + ) + ) { + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "EC", + namedCurve, + }); + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); + } else { + throw new DOMException("Curve not supported", "NotSupportedError"); + } + + // 4-6. + const algorithm = { + name: algorithmName, + namedCurve, + }; + + // 7-11. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, []), + algorithm, + handle, + ); + + // 12-16. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); + + // 17-20. + return { publicKey, privateKey }; + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => + !ArrayPrototypeIncludes([ + "encrypt", + "decrypt", + "wrapKey", + "unwrapKey", + ], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + return generateKeyAES(normalizedAlgorithm, extractable, usages); + } + case "AES-KW": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + return generateKeyAES(normalizedAlgorithm, extractable, usages); + } + case "X25519": { + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + const privateKeyData = new Uint8Array(32); + const publicKeyData = new Uint8Array(32); + ops.op_generate_x25519_keypair(privateKeyData, publicKeyData); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const publicHandle = {}; + WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); + + const algorithm = { + name: algorithmName, + }; + + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, []), + algorithm, + publicHandle, + ); + + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); + + return { publicKey, privateKey }; + } + case "Ed25519": { + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const ED25519_SEED_LEN = 32; + const ED25519_PUBLIC_KEY_LEN = 32; + const privateKeyData = new Uint8Array(ED25519_SEED_LEN); + const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN); + if ( + !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData) + ) { + throw new DOMException("Failed to generate key", "OperationError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const publicHandle = {}; + WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); + + const algorithm = { + name: algorithmName, + }; + + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + publicHandle, + ); + + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); + + return { publicKey, privateKey }; + } + case "HMAC": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2. + let length; + if (normalizedAlgorithm.length === undefined) { + length = null; + } else if (normalizedAlgorithm.length !== 0) { + length = normalizedAlgorithm.length; + } else { + throw new DOMException("Invalid length", "OperationError"); + } + + // 3-4. + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "HMAC", + hash: normalizedAlgorithm.hash.name, + length, + }); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); + + // 6-10. + const algorithm = { + name: algorithmName, + hash: { + name: normalizedAlgorithm.hash.name, + }, + length: keyData.byteLength * 8, + }; + + // 5, 11-13. + const key = constructKey( + "secret", + extractable, + usages, + algorithm, + handle, + ); + + // 14. + return key; + } + } +} + +function importKeyEd25519( + format, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "raw": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, keyData); + + // 2-3. + const algorithm = { + name: "Ed25519", + }; + + // 4-6. + return constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "spki": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + + const algorithm = { + name: "Ed25519", + }; + + return constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["sign"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const privateKeyData = new Uint8Array(32); + if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const algorithm = { + name: "Ed25519", + }; + + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.d !== undefined) { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + ["sign"], + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + ["verify"], + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } + + // 3. + if (jwk.kty !== "OKP") { + throw new DOMException("Invalid key type", "DataError"); + } + + // 4. + if (jwk.crv !== "Ed25519") { + throw new DOMException("Invalid curve", "DataError"); + } + + // 5. + if (jwk.alg !== undefined && jwk.alg !== "EdDSA") { + throw new DOMException("Invalid algorithm", "DataError"); + } + + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ) { + throw new DOMException("Invalid key usage", "DataError"); + } + + // 7. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 8. + if (jwk.ext !== undefined && jwk.ext === false && extractable) { + throw new DOMException("Invalid key extractability", "DataError"); + } + + // 9. + if (jwk.d !== undefined) { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const algorithm = { + name: "Ed25519", + }; + + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } else { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + + const algorithm = { + name: "Ed25519", + }; + + return constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyX25519( + format, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "raw": { + // 1. + if (keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, keyData); + + // 2-3. + const algorithm = { + name: "X25519", + }; + + // 4-6. + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + case "spki": { + // 1. + if (keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_x25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + + const algorithm = { + name: "X25519", + }; + + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const privateKeyData = new Uint8Array(32); + if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const algorithm = { + name: "X25519", + }; + + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.d !== undefined) { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + ["deriveKey", "deriveBits"], + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } + + // 3. + if (jwk.d === undefined && keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 4. + if (jwk.kty !== "OKP") { + throw new DOMException("Invalid key type", "DataError"); + } + + // 5. + if (jwk.crv !== "X25519") { + throw new DOMException("Invalid curve", "DataError"); + } + + // 6. + if (keyUsages.length > 0 && jwk.use !== undefined) { + if (jwk.use !== "enc") { + throw new DOMException("Invalid key use", "DataError"); + } + } + + // 7. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 8. + if (jwk.ext !== undefined && jwk.ext === false && extractable) { + throw new DOMException("Invalid key extractability", "DataError"); + } + + // 9. + if (jwk.d !== undefined) { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const algorithm = { + name: "X25519", + }; + + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); + } else { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + + const algorithm = { + name: "X25519", + }; + + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function exportKeyAES( + format, + key, + innerKey, +) { + switch (format) { + // 2. + case "raw": { + // 1. + const data = innerKey.data; + // 2. + return data.buffer; + } + case "jwk": { + // 1-2. + const jwk = { + kty: "oct", + }; + + // 3. + const data = ops.op_crypto_export_key({ + format: "jwksecret", + algorithm: "AES", + }, innerKey); + ObjectAssign(jwk, data); + + // 4. + const algorithm = key[_algorithm]; + switch (algorithm.length) { + case 128: + jwk.alg = aesJwkAlg[algorithm.name][128]; + break; + case 192: + jwk.alg = aesJwkAlg[algorithm.name][192]; + break; + case 256: + jwk.alg = aesJwkAlg[algorithm.name][256]; + break; + default: + throw new DOMException( + "Invalid key length", + "NotSupportedError", + ); + } + + // 5. + jwk.key_ops = key.usages; + + // 6. + jwk.ext = key[_extractable]; + + // 7. + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + supportedKeyUsages, +) { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + const algorithmName = normalizedAlgorithm.name; + + // 2. + let data = keyData; + + switch (format) { + case "raw": { + // 2. + if ( + !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8) + ) { + throw new DOMException("Invalid key length", "Datarror"); + } + + break; + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", + ); + } + + // Section 6.4.1 of RFC7518 + if (jwk.k === undefined) { + throw new DOMException( + "'k' property of JsonWebKey must be present", + "DataError", + ); + } + + // 4. + const { rawData } = ops.op_crypto_import_key( + { algorithm: "AES" }, + { jwkSecret: jwk }, + ); + data = rawData.data; + + // 5. + switch (data.byteLength * 8) { + case 128: + if ( + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][128] + ) { + throw new DOMException("Invalid algorithm", "DataError"); + } + break; + case 192: + if ( + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][192] + ) { + throw new DOMException("Invalid algorithm", "DataError"); + } + break; + case 256: + if ( + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][256] + ) { + throw new DOMException("Invalid algorithm", "DataError"); + } + break; + default: + throw new DOMException( + "Invalid key length", + "DataError", + ); + } + + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc" + ) { + throw new DOMException("Invalid key usages", "DataError"); + } + + // 7. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 8. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data, + }); + + // 4-7. + const algorithm = { + name: algorithmName, + length: data.byteLength * 8, }; -})(this); + + const key = constructKey( + "secret", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 8. + return key; +} + +function importKeyHMAC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + let hash; + let data; + + // 4. https://w3c.github.io/webcrypto/#hmac-operations + switch (format) { + case "raw": { + data = keyData; + hash = normalizedAlgorithm.hash; + break; + } + case "jwk": { + const jwk = keyData; + + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", + ); + } + + // Section 6.4.1 of RFC7518 + if (jwk.k === undefined) { + throw new DOMException( + "'k' property of JsonWebKey must be present", + "DataError", + ); + } + + // 4. + const { rawData } = ops.op_crypto_import_key( + { algorithm: "HMAC" }, + { jwkSecret: jwk }, + ); + data = rawData.data; + + // 5. + hash = normalizedAlgorithm.hash; + + // 6. + switch (hash.name) { + case "SHA-1": { + if (jwk.alg !== undefined && jwk.alg !== "HS1") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS1'", + "DataError", + ); + } + break; + } + case "SHA-256": { + if (jwk.alg !== undefined && jwk.alg !== "HS256") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS256'", + "DataError", + ); + } + break; + } + case "SHA-384": { + if (jwk.alg !== undefined && jwk.alg !== "HS384") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS384'", + "DataError", + ); + } + break; + } + case "SHA-512": { + if (jwk.alg !== undefined && jwk.alg !== "HS512") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS512'", + "DataError", + ); + } + break; + } + default: + throw new TypeError("unreachable"); + } + + // 7. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ) { + throw new DOMException( + "'use' property of JsonWebKey must be 'sig'", + "DataError", + ); + } + + // 8. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 9. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } + + // 5. + let length = data.byteLength * 8; + // 6. + if (length === 0) { + throw new DOMException("Key length is zero", "DataError"); + } + // 7. + if (normalizedAlgorithm.length !== undefined) { + if ( + normalizedAlgorithm.length > length || + normalizedAlgorithm.length <= (length - 8) + ) { + throw new DOMException( + "Key length is invalid", + "DataError", + ); + } + length = normalizedAlgorithm.length; + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data, + }); + + const algorithm = { + name: "HMAC", + length, + hash, + }; + + const key = constructKey( + "secret", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; +} + +function importKeyEC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name]; + + switch (format) { + case "raw": { + // 1. + if ( + !ArrayPrototypeIncludes( + supportedNamedCurves, + normalizedAlgorithm.namedCurve, + ) + ) { + throw new DOMException( + "Invalid namedCurve", + "DataError", + ); + } + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { raw: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + // 4-5. + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { pkcs8: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "spki": { + // 1. + if (normalizedAlgorithm.name == "ECDSA") { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else if (keyUsages.length != 0) { + throw new DOMException("Key usage must be empty", "SyntaxError"); + } + + // 2-12 + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { spki: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "jwk": { + const jwk = keyData; + + const keyType = (jwk.d !== undefined) ? "private" : "public"; + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + if (jwk.kty !== "EC") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'EC'", + "DataError", + ); + } + + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + jwk.use !== supportedUsages.jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, + "DataError", + ); + } + + // 5. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' member of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' member of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 6. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + // 9. + if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") { + let algNamedCurve; + + switch (jwk.alg) { + case "ES256": { + algNamedCurve = "P-256"; + break; + } + case "ES384": { + algNamedCurve = "P-384"; + break; + } + case "ES512": { + algNamedCurve = "P-521"; + break; + } + default: + throw new DOMException( + "Curve algorithm not supported", + "DataError", + ); + } + + if (algNamedCurve) { + if (algNamedCurve !== normalizedAlgorithm.namedCurve) { + throw new DOMException( + "Mismatched curve algorithm", + "DataError", + ); + } + } + } + + // Validate that this is a valid public key. + if (jwk.x === undefined) { + throw new DOMException( + "'x' property of JsonWebKey is required for EC keys", + "DataError", + ); + } + if (jwk.y === undefined) { + throw new DOMException( + "'y' property of JsonWebKey is required for EC keys", + "DataError", + ); + } + + if (jwk.d !== undefined) { + // it's also a Private key + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPrivateEc: jwk }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } else { + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPublicEc: jwk }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +const SUPPORTED_KEY_USAGES = { + "RSASSA-PKCS1-v1_5": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "RSA-PSS": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "RSA-OAEP": { + public: ["encrypt", "wrapKey"], + private: ["decrypt", "unwrapKey"], + jwkUse: "enc", + }, + "ECDSA": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "ECDH": { + public: [], + private: ["deriveKey", "deriveBits"], + jwkUse: "enc", + }, +}; + +function importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + // Needed to perform step 7 without normalization. + hash: normalizedAlgorithm.hash.name, + }, + { pkcs8: keyData }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "spki": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + // Needed to perform step 7 without normalization. + hash: normalizedAlgorithm.hash.name, + }, + { spki: keyData }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.d !== undefined) { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'RSA'", + "DataError", + ); + } + + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + StringPrototypeToLowerCase(jwk.use) !== + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${ + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse + }'`, + "DataError", + ); + } + + // 5. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + // 7. + let hash; + + // 8. + if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "RS1": + hash = "SHA-1"; + break; + case "RS256": + hash = "SHA-256"; + break; + case "RS384": + hash = "SHA-384"; + break; + case "RS512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, + "DataError", + ); + } + } else if (normalizedAlgorithm.name === "RSA-PSS") { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "PS1": + hash = "SHA-1"; + break; + case "PS256": + hash = "SHA-256"; + break; + case "PS384": + hash = "SHA-384"; + break; + case "PS512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`, + "DataError", + ); + } + } else { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "RSA-OAEP": + hash = "SHA-1"; + break; + case "RSA-OAEP-256": + hash = "SHA-256"; + break; + case "RSA-OAEP-384": + hash = "SHA-384"; + break; + case "RSA-OAEP-512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`, + "DataError", + ); + } + } + + // 9. + if (hash !== undefined) { + // 9.1. + const normalizedHash = normalizeAlgorithm(hash, "digest"); + + // 9.2. + if (normalizedHash.name !== normalizedAlgorithm.hash.name) { + throw new DOMException( + `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, + "DataError", + ); + } + } + + // 10. + if (jwk.d !== undefined) { + // Private key + const optimizationsPresent = jwk.p !== undefined || + jwk.q !== undefined || jwk.dp !== undefined || + jwk.dq !== undefined || jwk.qi !== undefined; + if (optimizationsPresent) { + if (jwk.q === undefined) { + throw new DOMException( + "'q' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dp === undefined) { + throw new DOMException( + "'dp' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dq === undefined) { + throw new DOMException( + "'dq' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.qi === undefined) { + throw new DOMException( + "'qi' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.oth !== undefined) { + throw new DOMException( + "'oth' property of JsonWebKey is not supported", + "NotSupportedError", + ); + } + } else { + throw new DOMException( + "only optimized private keys are supported", + "NotSupportedError", + ); + } + + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPrivateRsa: jwk }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } else { + // Validate that this is a valid public key. + if (jwk.n === undefined) { + throw new DOMException( + "'n' property of JsonWebKey is required for public keys", + "DataError", + ); + } + if (jwk.e === undefined) { + throw new DOMException( + "'e' property of JsonWebKey is required for public keys", + "DataError", + ); + } + + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPublicRsa: jwk }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyHKDF( + format, + keyData, + extractable, + keyUsages, +) { + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); + } + + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); + } + + // 3. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); + + // 4-8. + const algorithm = { + name: "HKDF", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 9. + return key; +} + +function importKeyPBKDF2( + format, + keyData, + extractable, + keyUsages, +) { + // 1. + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); + } + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); + } + + // 4. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); + + // 5-9. + const algorithm = { + name: "PBKDF2", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 10. + return key; +} + +function exportKeyHMAC(format, key, innerKey) { + // 1. + if (innerKey == null) { + throw new DOMException("Key is not available", "OperationError"); + } + + switch (format) { + // 3. + case "raw": { + const bits = innerKey.data; + for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) { + bits.push(0); + } + // 4-5. + return bits.buffer; + } + case "jwk": { + // 1-2. + const jwk = { + kty: "oct", + }; + + // 3. + const data = ops.op_crypto_export_key({ + format: "jwksecret", + algorithm: key[_algorithm].name, + }, innerKey); + jwk.k = data.k; + + // 4. + const algorithm = key[_algorithm]; + // 5. + const hash = algorithm.hash; + // 6. + switch (hash.name) { + case "SHA-1": + jwk.alg = "HS1"; + break; + case "SHA-256": + jwk.alg = "HS256"; + break; + case "SHA-384": + jwk.alg = "HS384"; + break; + case "SHA-512": + jwk.alg = "HS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + // 7. + jwk.key_ops = key.usages; + // 8. + jwk.ext = key[_extractable]; + // 9. + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function exportKeyRSA(format, key, innerKey) { + switch (format) { + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + format: "pkcs8", + }, innerKey); + + // 3. + return data.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + format: "spki", + }, innerKey); + + // 3. + return data.buffer; + } + case "jwk": { + // 1-2. + const jwk = { + kty: "RSA", + }; + + // 3. + const hash = key[_algorithm].hash.name; + + // 4. + if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") { + switch (hash) { + case "SHA-1": + jwk.alg = "RS1"; + break; + case "SHA-256": + jwk.alg = "RS256"; + break; + case "SHA-384": + jwk.alg = "RS384"; + break; + case "SHA-512": + jwk.alg = "RS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + } else if (key[_algorithm].name === "RSA-PSS") { + switch (hash) { + case "SHA-1": + jwk.alg = "PS1"; + break; + case "SHA-256": + jwk.alg = "PS256"; + break; + case "SHA-384": + jwk.alg = "PS384"; + break; + case "SHA-512": + jwk.alg = "PS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + } else { + switch (hash) { + case "SHA-1": + jwk.alg = "RSA-OAEP"; + break; + case "SHA-256": + jwk.alg = "RSA-OAEP-256"; + break; + case "SHA-384": + jwk.alg = "RSA-OAEP-384"; + break; + case "SHA-512": + jwk.alg = "RSA-OAEP-512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + } + + // 5-6. + const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", + algorithm: key[_algorithm].name, + }, innerKey); + ObjectAssign(jwk, data); + + // 7. + jwk.key_ops = key.usages; + + // 8. + jwk.ext = key[_extractable]; + + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function exportKeyEd25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + const spkiDer = ops.op_export_spki_ed25519(innerKey); + return spkiDer.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + const pkcs8Der = ops.op_export_pkcs8_ed25519( + new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + ); + pkcs8Der[15] = 0x20; + return pkcs8Der.buffer; + } + case "jwk": { + const x = key[_type] === "private" + ? ops.op_jwk_x_ed25519(innerKey) + : ops.op_crypto_base64url_encode(innerKey); + const jwk = { + kty: "OKP", + alg: "EdDSA", + crv: "Ed25519", + x, + "key_ops": key.usages, + ext: key[_extractable], + }; + if (key[_type] === "private") { + jwk.d = ops.op_crypto_base64url_encode(innerKey); + } + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function exportKeyX25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + const spkiDer = ops.op_export_spki_x25519(innerKey); + return spkiDer.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + const pkcs8Der = ops.op_export_pkcs8_x25519( + new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + ); + pkcs8Der[15] = 0x20; + return pkcs8Der.buffer; + } + case "jwk": { + if (key[_type] === "private") { + throw new DOMException("Not implemented", "NotSupportedError"); + } + const x = ops.op_crypto_base64url_encode(innerKey); + const jwk = { + kty: "OKP", + crv: "X25519", + x, + "key_ops": key.usages, + ext: key[_extractable], + }; + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function exportKeyEC(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "raw", + }, innerKey); + + return data.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "pkcs8", + }, innerKey); + + return data.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "spki", + }, innerKey); + + return data.buffer; + } + case "jwk": { + if (key[_algorithm].name == "ECDSA") { + // 1-2. + const jwk = { + kty: "EC", + }; + + // 3.1 + jwk.crv = key[_algorithm].namedCurve; + + // Missing from spec + let algNamedCurve; + + switch (key[_algorithm].namedCurve) { + case "P-256": { + algNamedCurve = "ES256"; + break; + } + case "P-384": { + algNamedCurve = "ES384"; + break; + } + case "P-521": { + algNamedCurve = "ES512"; + break; + } + default: + throw new DOMException( + "Curve algorithm not supported", + "DataError", + ); + } + + jwk.alg = algNamedCurve; + + // 3.2 - 3.4. + const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + }, innerKey); + ObjectAssign(jwk, data); + + // 4. + jwk.key_ops = key.usages; + + // 5. + jwk.ext = key[_extractable]; + + return jwk; + } else { // ECDH + // 1-2. + const jwk = { + kty: "EC", + }; + + // missing step from spec + jwk.alg = "ECDH"; + + // 3.1 + jwk.crv = key[_algorithm].namedCurve; + + // 3.2 - 3.4 + const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + }, innerKey); + ObjectAssign(jwk, data); + + // 4. + jwk.key_ops = key.usages; + + // 5. + jwk.ext = key[_extractable]; + + return jwk; + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +async function generateKeyAES(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; + + // 2. + if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) { + throw new DOMException("Invalid key length", "OperationError"); + } + + // 3. + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "AES", + length: normalizedAlgorithm.length, + }); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); + + // 6-8. + const algorithm = { + name: algorithmName, + length: normalizedAlgorithm.length, + }; + + // 9-11. + const key = constructKey( + "secret", + extractable, + usages, + algorithm, + handle, + ); + + // 12. + return key; +} + +async function deriveBits(normalizedAlgorithm, baseKey, length) { + switch (normalizedAlgorithm.name) { + case "PBKDF2": { + // 1. + if (length == null || length == 0 || length % 8 !== 0) { + throw new DOMException("Invalid length", "OperationError"); + } + + if (normalizedAlgorithm.iterations == 0) { + throw new DOMException( + "iterations must not be zero", + "OperationError", + ); + } + + const handle = baseKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); + + const buf = await core.opAsync("op_crypto_derive_bits", { + key: keyData, + algorithm: "PBKDF2", + hash: normalizedAlgorithm.hash.name, + iterations: normalizedAlgorithm.iterations, + length, + }, normalizedAlgorithm.salt); + + return buf.buffer; + } + case "ECDH": { + // 1. + if (baseKey[_type] !== "private") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 2. + const publicKey = normalizedAlgorithm.public; + // 3. + if (publicKey[_type] !== "public") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 4. + if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { + throw new DOMException( + "Algorithm mismatch", + "InvalidAccessError", + ); + } + // 5. + if ( + publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve + ) { + throw new DOMException( + "namedCurve mismatch", + "InvalidAccessError", + ); + } + // 6. + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + publicKey[_algorithm].namedCurve, + ) + ) { + const baseKeyhandle = baseKey[_handle]; + const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle); + const publicKeyhandle = publicKey[_handle]; + const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle); + + const buf = await core.opAsync("op_crypto_derive_bits", { + key: baseKeyData, + publicKey: publicKeyData, + algorithm: "ECDH", + namedCurve: publicKey[_algorithm].namedCurve, + length, + }); + + // 8. + if (length === null) { + return buf.buffer; + } else if (buf.buffer.byteLength * 8 < length) { + throw new DOMException("Invalid length", "OperationError"); + } else { + return buf.buffer.slice(0, MathCeil(length / 8)); + } + } else { + throw new DOMException("Not implemented", "NotSupportedError"); + } + } + case "HKDF": { + // 1. + if (length === null || length === 0 || length % 8 !== 0) { + throw new DOMException("Invalid length", "OperationError"); + } + + const handle = baseKey[_handle]; + const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle); + + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); + + normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info); + + const buf = await core.opAsync("op_crypto_derive_bits", { + key: keyDerivationKey, + algorithm: "HKDF", + hash: normalizedAlgorithm.hash.name, + info: normalizedAlgorithm.info, + length, + }, normalizedAlgorithm.salt); + + return buf.buffer; + } + case "X25519": { + // 1. + if (baseKey[_type] !== "private") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 2. + const publicKey = normalizedAlgorithm.public; + // 3. + if (publicKey[_type] !== "public") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 4. + if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { + throw new DOMException( + "Algorithm mismatch", + "InvalidAccessError", + ); + } + + // 5. + const kHandle = baseKey[_handle]; + const k = WeakMapPrototypeGet(KEY_STORE, kHandle); + + const uHandle = publicKey[_handle]; + const u = WeakMapPrototypeGet(KEY_STORE, uHandle); + + const secret = new Uint8Array(32); + const isIdentity = ops.op_derive_bits_x25519(k, u, secret); + + // 6. + if (isIdentity) { + throw new DOMException("Invalid key", "OperationError"); + } + + // 7. + if (length === null) { + return secret.buffer; + } else if ( + secret.buffer.byteLength * 8 < length + ) { + throw new DOMException("Invalid length", "OperationError"); + } else { + return secret.buffer.slice(0, MathCeil(length / 8)); + } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +async function encrypt(normalizedAlgorithm, key, data) { + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + switch (normalizedAlgorithm.name) { + case "RSA-OAEP": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + // 2. + if (normalizedAlgorithm.label) { + normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); + } else { + normalizedAlgorithm.label = new Uint8Array(); + } + + // 3-5. + const hashAlgorithm = key[_algorithm].hash.name; + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "RSA-OAEP", + hash: hashAlgorithm, + label: normalizedAlgorithm.label, + }, data); + + // 6. + return cipherText.buffer; + } + case "AES-CBC": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); + + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Initialization vector must be 16 bytes", + "OperationError", + ); + } + + // 2. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-CBC", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + }, data); + + // 4. + return cipherText.buffer; + } + case "AES-CTR": { + normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); + + // 1. + if (normalizedAlgorithm.counter.byteLength !== 16) { + throw new DOMException( + "Counter vector must be 16 bytes", + "OperationError", + ); + } + + // 2. + if ( + normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128 + ) { + throw new DOMException( + "Counter length must not be 0 or greater than 128", + "OperationError", + ); + } + + // 3. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-CTR", + keyLength: key[_algorithm].length, + counter: normalizedAlgorithm.counter, + ctrLength: normalizedAlgorithm.length, + }, data); + + // 4. + return cipherText.buffer; + } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); + + // 1. + if (data.byteLength > (2 ** 39) - 256) { + throw new DOMException( + "Plaintext too large", + "OperationError", + ); + } + + // 2. + // We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } + + // 3. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { + throw new DOMException( + "Additional data too large", + "OperationError", + ); + } + } + + // 4. + if (normalizedAlgorithm.tagLength == undefined) { + normalizedAlgorithm.tagLength = 128; + } else if ( + !ArrayPrototypeIncludes( + [32, 64, 96, 104, 112, 120, 128], + normalizedAlgorithm.tagLength, + ) + ) { + throw new DOMException( + "Invalid tag length", + "OperationError", + ); + } + // 5. + if (normalizedAlgorithm.additionalData) { + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } + // 6-7. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-GCM", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + additionalData: normalizedAlgorithm.additionalData || null, + tagLength: normalizedAlgorithm.tagLength, + }, data); + + // 8. + return cipherText.buffer; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +webidl.configurePrototype(SubtleCrypto); +const subtle = webidl.createBranded(SubtleCrypto); + +class Crypto { + constructor() { + webidl.illegalConstructor(); + } + + getRandomValues(arrayBufferView) { + webidl.assertBranded(this, CryptoPrototype); + const prefix = "Failed to execute 'getRandomValues' on 'Crypto'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // Fast path for Uint8Array + if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView)) { + ops.op_crypto_get_random_values(arrayBufferView); + return arrayBufferView; + } + arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, { + prefix, + context: "Argument 1", + }); + if ( + !( + ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf( + Uint8ClampedArrayPrototype, + arrayBufferView, + ) || + ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf( + BigInt64ArrayPrototype, + arrayBufferView, + ) || + ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView) + ) + ) { + throw new DOMException( + "The provided ArrayBufferView is not an integer array type", + "TypeMismatchError", + ); + } + const ui8 = new Uint8Array( + arrayBufferView.buffer, + arrayBufferView.byteOffset, + arrayBufferView.byteLength, + ); + ops.op_crypto_get_random_values(ui8); + return arrayBufferView; + } + + randomUUID() { + webidl.assertBranded(this, CryptoPrototype); + return ops.op_crypto_random_uuid(); + } + + get subtle() { + webidl.assertBranded(this, CryptoPrototype); + return subtle; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} + +webidl.configurePrototype(Crypto); +const CryptoPrototype = Crypto.prototype; + +const crypto = webidl.createBranded(Crypto); +export { Crypto, crypto, CryptoKey, SubtleCrypto }; diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index d381672fd1..86d50f8a55 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -4,484 +4,481 @@ /// /// -"use strict"; +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { CryptoKey } from "internal:ext/crypto/00_crypto.js"; +const { + ArrayBufferIsView, + ArrayBufferPrototype, + ObjectPrototypeIsPrototypeOf, + SafeArrayIterator, +} = primordials; -((window) => { - const webidl = window.__bootstrap.webidl; - const { CryptoKey } = window.__bootstrap.crypto; - const { - ArrayBufferIsView, - ArrayBufferPrototype, - ObjectPrototypeIsPrototypeOf, - SafeArrayIterator, - } = window.__bootstrap.primordials; +webidl.converters.AlgorithmIdentifier = (V, opts) => { + // Union for (object or DOMString) + if (webidl.type(V) == "Object") { + return webidl.converters.object(V, opts); + } + return webidl.converters.DOMString(V, opts); +}; - webidl.converters.AlgorithmIdentifier = (V, opts) => { - // Union for (object or DOMString) - if (webidl.type(V) == "Object") { - return webidl.converters.object(V, opts); - } - return webidl.converters.DOMString(V, opts); - }; +webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { + // Union for (BufferSource or JsonWebKey) + if ( + ArrayBufferIsView(V) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) + ) { + return webidl.converters.BufferSource(V, opts); + } + return webidl.converters.JsonWebKey(V, opts); +}; - webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { - // Union for (BufferSource or JsonWebKey) - if ( - ArrayBufferIsView(V) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) - ) { - return webidl.converters.BufferSource(V, opts); - } - return webidl.converters.JsonWebKey(V, opts); - }; +webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ + "public", + "private", + "secret", +]); - webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ - "public", - "private", - "secret", - ]); +webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [ + "raw", + "pkcs8", + "spki", + "jwk", +]); - webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [ - "raw", - "pkcs8", - "spki", - "jwk", - ]); +webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ + "encrypt", + "decrypt", + "sign", + "verify", + "deriveKey", + "deriveBits", + "wrapKey", + "unwrapKey", +]); - webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]); +webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters.KeyUsage, +); - webidl.converters["sequence"] = webidl.createSequenceConverter( - webidl.converters.KeyUsage, - ); +webidl.converters.HashAlgorithmIdentifier = + webidl.converters.AlgorithmIdentifier; - webidl.converters.HashAlgorithmIdentifier = - webidl.converters.AlgorithmIdentifier; +/** @type {webidl.Dictionary} */ +const dictAlgorithm = [{ + key: "name", + converter: webidl.converters.DOMString, + required: true, +}]; - /** @type {__bootstrap.webidl.Dictionary} */ - const dictAlgorithm = [{ - key: "name", - converter: webidl.converters.DOMString, +webidl.converters.Algorithm = webidl + .createDictionaryConverter("Algorithm", dictAlgorithm); + +webidl.converters.BigInteger = webidl.converters.Uint8Array; + +/** @type {webidl.Dictionary} */ +const dictRsaKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "modulusLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), required: true, - }]; + }, + { + key: "publicExponent", + converter: webidl.converters.BigInteger, + required: true, + }, +]; - webidl.converters.Algorithm = webidl - .createDictionaryConverter("Algorithm", dictAlgorithm); +webidl.converters.RsaKeyGenParams = webidl + .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); - webidl.converters.BigInteger = webidl.converters.Uint8Array; +const dictRsaHashedKeyGenParams = [ + ...new SafeArrayIterator(dictRsaKeyGenParams), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; - /** @type {__bootstrap.webidl.Dictionary} */ - const dictRsaKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "modulusLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - { - key: "publicExponent", - converter: webidl.converters.BigInteger, - required: true, - }, - ]; +webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( + "RsaHashedKeyGenParams", + dictRsaHashedKeyGenParams, +); - webidl.converters.RsaKeyGenParams = webidl - .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); +const dictRsaHashedImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; - const dictRsaHashedKeyGenParams = [ - ...new SafeArrayIterator(dictRsaKeyGenParams), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; +webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( + "RsaHashedImportParams", + dictRsaHashedImportParams, +); - webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( - "RsaHashedKeyGenParams", - dictRsaHashedKeyGenParams, +webidl.converters.NamedCurve = webidl.converters.DOMString; + +const dictEcKeyImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "namedCurve", + converter: webidl.converters.NamedCurve, + required: true, + }, +]; + +webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( + "EcKeyImportParams", + dictEcKeyImportParams, +); + +const dictEcKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "namedCurve", + converter: webidl.converters.NamedCurve, + required: true, + }, +]; + +webidl.converters.EcKeyGenParams = webidl + .createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams); + +const dictAesKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; + +webidl.converters.AesKeyGenParams = webidl + .createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); + +const dictHmacKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, +]; + +webidl.converters.HmacKeyGenParams = webidl + .createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams); + +const dictRsaPssParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "saltLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; + +webidl.converters.RsaPssParams = webidl + .createDictionaryConverter("RsaPssParams", dictRsaPssParams); + +const dictRsaOaepParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "label", + converter: webidl.converters["BufferSource"], + }, +]; + +webidl.converters.RsaOaepParams = webidl + .createDictionaryConverter("RsaOaepParams", dictRsaOaepParams); + +const dictEcdsaParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; + +webidl.converters["EcdsaParams"] = webidl + .createDictionaryConverter("EcdsaParams", dictEcdsaParams); + +const dictHmacImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, +]; + +webidl.converters.HmacImportParams = webidl + .createDictionaryConverter("HmacImportParams", dictHmacImportParams); + +const dictRsaOtherPrimesInfo = [ + { + key: "r", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "t", + converter: webidl.converters["DOMString"], + }, +]; + +webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter( + "RsaOtherPrimesInfo", + dictRsaOtherPrimesInfo, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter( + webidl.converters.RsaOtherPrimesInfo, ); - const dictRsaHashedImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; +const dictJsonWebKey = [ + // Sections 4.2 and 4.3 of RFC7517. + // https://datatracker.ietf.org/doc/html/rfc7517#section-4 + { + key: "kty", + converter: webidl.converters["DOMString"], + }, + { + key: "use", + converter: webidl.converters["DOMString"], + }, + { + key: "key_ops", + converter: webidl.converters["sequence"], + }, + { + key: "alg", + converter: webidl.converters["DOMString"], + }, + // JSON Web Key Parameters Registration + { + key: "ext", + converter: webidl.converters["boolean"], + }, + // Section 6 of RFC7518 JSON Web Algorithms + // https://datatracker.ietf.org/doc/html/rfc7518#section-6 + { + key: "crv", + converter: webidl.converters["DOMString"], + }, + { + key: "x", + converter: webidl.converters["DOMString"], + }, + { + key: "y", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "n", + converter: webidl.converters["DOMString"], + }, + { + key: "e", + converter: webidl.converters["DOMString"], + }, + { + key: "p", + converter: webidl.converters["DOMString"], + }, + { + key: "q", + converter: webidl.converters["DOMString"], + }, + { + key: "dp", + converter: webidl.converters["DOMString"], + }, + { + key: "dq", + converter: webidl.converters["DOMString"], + }, + { + key: "qi", + converter: webidl.converters["DOMString"], + }, + { + key: "oth", + converter: webidl.converters["sequence"], + }, + { + key: "k", + converter: webidl.converters["DOMString"], + }, +]; - webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( - "RsaHashedImportParams", - dictRsaHashedImportParams, - ); +webidl.converters.JsonWebKey = webidl.createDictionaryConverter( + "JsonWebKey", + dictJsonWebKey, +); - webidl.converters.NamedCurve = webidl.converters.DOMString; +const dictHkdfParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "salt", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "info", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; - const dictEcKeyImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "namedCurve", - converter: webidl.converters.NamedCurve, - required: true, - }, - ]; +webidl.converters.HkdfParams = webidl + .createDictionaryConverter("HkdfParams", dictHkdfParams); - webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( - "EcKeyImportParams", - dictEcKeyImportParams, - ); +const dictPbkdf2Params = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "iterations", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, + { + key: "salt", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; - const dictEcKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "namedCurve", - converter: webidl.converters.NamedCurve, - required: true, - }, - ]; +webidl.converters.Pbkdf2Params = webidl + .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params); - webidl.converters.EcKeyGenParams = webidl - .createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams); +const dictAesDerivedKeyParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; - const dictAesKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; +const dictAesCbcParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; - webidl.converters.AesKeyGenParams = webidl - .createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); +const dictAesGcmParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "tagLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, + { + key: "additionalData", + converter: webidl.converters["BufferSource"], + }, +]; - const dictHmacKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - ]; +const dictAesCtrParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "counter", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; - webidl.converters.HmacKeyGenParams = webidl - .createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams); +webidl.converters.AesDerivedKeyParams = webidl + .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); - const dictRsaPssParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "saltLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; +webidl.converters.AesCbcParams = webidl + .createDictionaryConverter("AesCbcParams", dictAesCbcParams); - webidl.converters.RsaPssParams = webidl - .createDictionaryConverter("RsaPssParams", dictRsaPssParams); +webidl.converters.AesGcmParams = webidl + .createDictionaryConverter("AesGcmParams", dictAesGcmParams); - const dictRsaOaepParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "label", - converter: webidl.converters["BufferSource"], - }, - ]; +webidl.converters.AesCtrParams = webidl + .createDictionaryConverter("AesCtrParams", dictAesCtrParams); - webidl.converters.RsaOaepParams = webidl - .createDictionaryConverter("RsaOaepParams", dictRsaOaepParams); +webidl.converters.CryptoKey = webidl.createInterfaceConverter( + "CryptoKey", + CryptoKey.prototype, +); - const dictEcdsaParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; +const dictCryptoKeyPair = [ + { + key: "publicKey", + converter: webidl.converters.CryptoKey, + }, + { + key: "privateKey", + converter: webidl.converters.CryptoKey, + }, +]; - webidl.converters["EcdsaParams"] = webidl - .createDictionaryConverter("EcdsaParams", dictEcdsaParams); +webidl.converters.CryptoKeyPair = webidl + .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair); - const dictHmacImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - ]; +const dictEcdhKeyDeriveParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "public", + converter: webidl.converters.CryptoKey, + required: true, + }, +]; - webidl.converters.HmacImportParams = webidl - .createDictionaryConverter("HmacImportParams", dictHmacImportParams); - - const dictRsaOtherPrimesInfo = [ - { - key: "r", - converter: webidl.converters["DOMString"], - }, - { - key: "d", - converter: webidl.converters["DOMString"], - }, - { - key: "t", - converter: webidl.converters["DOMString"], - }, - ]; - - webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter( - "RsaOtherPrimesInfo", - dictRsaOtherPrimesInfo, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter( - webidl.converters.RsaOtherPrimesInfo, - ); - - const dictJsonWebKey = [ - // Sections 4.2 and 4.3 of RFC7517. - // https://datatracker.ietf.org/doc/html/rfc7517#section-4 - { - key: "kty", - converter: webidl.converters["DOMString"], - }, - { - key: "use", - converter: webidl.converters["DOMString"], - }, - { - key: "key_ops", - converter: webidl.converters["sequence"], - }, - { - key: "alg", - converter: webidl.converters["DOMString"], - }, - // JSON Web Key Parameters Registration - { - key: "ext", - converter: webidl.converters["boolean"], - }, - // Section 6 of RFC7518 JSON Web Algorithms - // https://datatracker.ietf.org/doc/html/rfc7518#section-6 - { - key: "crv", - converter: webidl.converters["DOMString"], - }, - { - key: "x", - converter: webidl.converters["DOMString"], - }, - { - key: "y", - converter: webidl.converters["DOMString"], - }, - { - key: "d", - converter: webidl.converters["DOMString"], - }, - { - key: "n", - converter: webidl.converters["DOMString"], - }, - { - key: "e", - converter: webidl.converters["DOMString"], - }, - { - key: "p", - converter: webidl.converters["DOMString"], - }, - { - key: "q", - converter: webidl.converters["DOMString"], - }, - { - key: "dp", - converter: webidl.converters["DOMString"], - }, - { - key: "dq", - converter: webidl.converters["DOMString"], - }, - { - key: "qi", - converter: webidl.converters["DOMString"], - }, - { - key: "oth", - converter: webidl.converters["sequence"], - }, - { - key: "k", - converter: webidl.converters["DOMString"], - }, - ]; - - webidl.converters.JsonWebKey = webidl.createDictionaryConverter( - "JsonWebKey", - dictJsonWebKey, - ); - - const dictHkdfParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "salt", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "info", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - webidl.converters.HkdfParams = webidl - .createDictionaryConverter("HkdfParams", dictHkdfParams); - - const dictPbkdf2Params = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "iterations", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - { - key: "salt", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - webidl.converters.Pbkdf2Params = webidl - .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params); - - const dictAesDerivedKeyParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - const dictAesCbcParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "iv", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - const dictAesGcmParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "iv", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "tagLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - { - key: "additionalData", - converter: webidl.converters["BufferSource"], - }, - ]; - - const dictAesCtrParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "counter", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - webidl.converters.AesDerivedKeyParams = webidl - .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); - - webidl.converters.AesCbcParams = webidl - .createDictionaryConverter("AesCbcParams", dictAesCbcParams); - - webidl.converters.AesGcmParams = webidl - .createDictionaryConverter("AesGcmParams", dictAesGcmParams); - - webidl.converters.AesCtrParams = webidl - .createDictionaryConverter("AesCtrParams", dictAesCtrParams); - - webidl.converters.CryptoKey = webidl.createInterfaceConverter( - "CryptoKey", - CryptoKey.prototype, - ); - - const dictCryptoKeyPair = [ - { - key: "publicKey", - converter: webidl.converters.CryptoKey, - }, - { - key: "privateKey", - converter: webidl.converters.CryptoKey, - }, - ]; - - webidl.converters.CryptoKeyPair = webidl - .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair); - - const dictEcdhKeyDeriveParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "public", - converter: webidl.converters.CryptoKey, - required: true, - }, - ]; - - webidl.converters.EcdhKeyDeriveParams = webidl - .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams); -})(this); +webidl.converters.EcdhKeyDeriveParams = webidl + .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams); diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 906e7d06f7..de47e467bb 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -75,7 +75,7 @@ use crate::shared::RawKeyData; pub fn init(maybe_seed: Option) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/crypto", "00_crypto.js", "01_webidl.js", diff --git a/ext/fetch/01_fetch_util.js b/ext/fetch/01_fetch_util.js deleted file mode 100644 index 3ed554ecb1..0000000000 --- a/ext/fetch/01_fetch_util.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { TypeError } = window.__bootstrap.primordials; - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - window.__bootstrap.fetchUtil = { - requiredArguments, - }; -})(this); diff --git a/ext/fetch/20_headers.js b/ext/fetch/20_headers.js index 54e635522a..9790bb69fd 100644 --- a/ext/fetch/20_headers.js +++ b/ext/fetch/20_headers.js @@ -8,97 +8,369 @@ /// /// /// -"use strict"; -((window) => { - const webidl = window.__bootstrap.webidl; - const { - HTTP_TAB_OR_SPACE_PREFIX_RE, - HTTP_TAB_OR_SPACE_SUFFIX_RE, - HTTP_TOKEN_CODE_POINT_RE, - byteLowerCase, - collectSequenceOfCodepoints, - collectHttpQuotedString, - httpTrim, - } = window.__bootstrap.infra; - const { - ArrayIsArray, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSort, - ArrayPrototypeJoin, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ObjectPrototypeHasOwnProperty, - ObjectEntries, - RegExpPrototypeTest, - SafeArrayIterator, - Symbol, - SymbolFor, - SymbolIterator, - StringPrototypeReplaceAll, - TypeError, - } = window.__bootstrap.primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + byteLowerCase, + collectHttpQuotedString, + collectSequenceOfCodepoints, + HTTP_TAB_OR_SPACE_PREFIX_RE, + HTTP_TAB_OR_SPACE_SUFFIX_RE, + HTTP_TOKEN_CODE_POINT_RE, + httpTrim, +} from "internal:ext/web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSort, + ArrayPrototypeJoin, + ArrayPrototypeSplice, + ArrayPrototypeFilter, + ObjectPrototypeHasOwnProperty, + ObjectEntries, + RegExpPrototypeTest, + SafeArrayIterator, + Symbol, + SymbolFor, + SymbolIterator, + StringPrototypeReplaceAll, + TypeError, +} = primordials; - const _headerList = Symbol("header list"); - const _iterableHeaders = Symbol("iterable headers"); - const _guard = Symbol("guard"); +const _headerList = Symbol("header list"); +const _iterableHeaders = Symbol("iterable headers"); +const _guard = Symbol("guard"); - /** - * @typedef Header - * @type {[string, string]} - */ +/** + * @typedef Header + * @type {[string, string]} + */ - /** - * @typedef HeaderList - * @type {Header[]} - */ +/** + * @typedef HeaderList + * @type {Header[]} + */ - /** - * @param {string} potentialValue - * @returns {string} - */ - function normalizeHeaderValue(potentialValue) { - return httpTrim(potentialValue); +/** + * @param {string} potentialValue + * @returns {string} + */ +function normalizeHeaderValue(potentialValue) { + return httpTrim(potentialValue); +} + +/** + * @param {Headers} headers + * @param {HeadersInit} object + */ +function fillHeaders(headers, object) { + if (ArrayIsArray(object)) { + for (let i = 0; i < object.length; ++i) { + const header = object[i]; + if (header.length !== 2) { + throw new TypeError( + `Invalid header. Length must be 2, but is ${header.length}`, + ); + } + appendHeader(headers, header[0], header[1]); + } + } else { + for (const key in object) { + if (!ObjectPrototypeHasOwnProperty(object, key)) { + continue; + } + appendHeader(headers, key, object[key]); + } + } +} + +// Regex matching illegal chars in a header value +// deno-lint-ignore no-control-regex +const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; + +/** + * https://fetch.spec.whatwg.org/#concept-headers-append + * @param {Headers} headers + * @param {string} name + * @param {string} value + */ +function appendHeader(headers, name, value) { + // 1. + value = normalizeHeaderValue(value); + + // 2. + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); + } + if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { + throw new TypeError("Header value is not valid."); + } + + // 3. + if (headers[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); + } + + // 7. + const list = headers[_headerList]; + const lowercaseName = byteLowerCase(name); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + name = list[i][0]; + break; + } + } + ArrayPrototypePush(list, [name, value]); +} + +/** + * https://fetch.spec.whatwg.org/#concept-header-list-get + * @param {HeaderList} list + * @param {string} name + */ +function getHeader(list, name) { + const lowercaseName = byteLowerCase(name); + const entries = ArrayPrototypeMap( + ArrayPrototypeFilter( + list, + (entry) => byteLowerCase(entry[0]) === lowercaseName, + ), + (entry) => entry[1], + ); + if (entries.length === 0) { + return null; + } else { + return ArrayPrototypeJoin(entries, "\x2C\x20"); + } +} + +/** + * https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split + * @param {HeaderList} list + * @param {string} name + * @returns {string[] | null} + */ +function getDecodeSplitHeader(list, name) { + const initialValue = getHeader(list, name); + if (initialValue === null) return null; + const input = initialValue; + let position = 0; + const values = []; + let value = ""; + while (position < initialValue.length) { + // 7.1. collect up to " or , + const res = collectSequenceOfCodepoints( + initialValue, + position, + (c) => c !== "\u0022" && c !== "\u002C", + ); + value += res.result; + position = res.position; + + if (position < initialValue.length) { + if (input[position] === "\u0022") { + const res = collectHttpQuotedString(input, position, false); + value += res.result; + position = res.position; + if (position < initialValue.length) { + continue; + } + } else { + if (input[position] !== "\u002C") throw new TypeError("Unreachable"); + position += 1; + } + } + + value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, ""); + value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, ""); + + ArrayPrototypePush(values, value); + value = ""; + } + return values; +} + +class Headers { + /** @type {HeaderList} */ + [_headerList] = []; + /** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */ + [_guard]; + + get [_iterableHeaders]() { + const list = this[_headerList]; + + // The order of steps are not similar to the ones suggested by the + // spec but produce the same result. + const headers = {}; + const cookies = []; + for (let i = 0; i < list.length; ++i) { + const entry = list[i]; + const name = byteLowerCase(entry[0]); + const value = entry[1]; + if (value === null) throw new TypeError("Unreachable"); + // The following if statement is not spec compliant. + // `set-cookie` is the only header that can not be concatenated, + // so must be given to the user as multiple headers. + // The else block of the if statement is spec compliant again. + if (name === "set-cookie") { + ArrayPrototypePush(cookies, [name, value]); + } else { + // The following code has the same behaviour as getHeader() + // at the end of loop. But it avoids looping through the entire + // list to combine multiple values with same header name. It + // instead gradually combines them as they are found. + let header = headers[name]; + if (header && header.length > 0) { + header += "\x2C\x20" + value; + } else { + header = value; + } + headers[name] = header; + } + } + + return ArrayPrototypeSort( + [ + ...new SafeArrayIterator(ObjectEntries(headers)), + ...new SafeArrayIterator(cookies), + ], + (a, b) => { + const akey = a[0]; + const bkey = b[0]; + if (akey > bkey) return 1; + if (akey < bkey) return -1; + return 0; + }, + ); + } + + /** @param {HeadersInit} [init] */ + constructor(init = undefined) { + const prefix = "Failed to construct 'Headers'"; + if (init !== undefined) { + init = webidl.converters["HeadersInit"](init, { + prefix, + context: "Argument 1", + }); + } + + this[webidl.brand] = webidl.brand; + this[_guard] = "none"; + if (init !== undefined) { + fillHeaders(this, init); + } } /** - * @param {Headers} headers - * @param {HeadersInit} object + * @param {string} name + * @param {string} value */ - function fillHeaders(headers, object) { - if (ArrayIsArray(object)) { - for (let i = 0; i < object.length; ++i) { - const header = object[i]; - if (header.length !== 2) { - throw new TypeError( - `Invalid header. Length must be 2, but is ${header.length}`, - ); - } - appendHeader(headers, header[0], header[1]); - } - } else { - for (const key in object) { - if (!ObjectPrototypeHasOwnProperty(object, key)) { - continue; - } - appendHeader(headers, key, object[key]); + append(name, value) { + webidl.assertBranded(this, HeadersPrototype); + const prefix = "Failed to execute 'append' on 'Headers'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters["ByteString"](value, { + prefix, + context: "Argument 2", + }); + appendHeader(this, name, value); + } + + /** + * @param {string} name + */ + delete(name) { + const prefix = "Failed to execute 'delete' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); + } + if (this[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); + } + + const list = this[_headerList]; + const lowercaseName = byteLowerCase(name); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + ArrayPrototypeSplice(list, i, 1); + i--; } } } - // Regex matching illegal chars in a header value - // deno-lint-ignore no-control-regex - const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; + /** + * @param {string} name + */ + get(name) { + const prefix = "Failed to execute 'get' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); + } + + const list = this[_headerList]; + return getHeader(list, name); + } + + /** + * @param {string} name + */ + has(name) { + const prefix = "Failed to execute 'has' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); + } + + const list = this[_headerList]; + const lowercaseName = byteLowerCase(name); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + return true; + } + } + return false; + } /** - * https://fetch.spec.whatwg.org/#concept-headers-append - * @param {Headers} headers * @param {string} name * @param {string} value */ - function appendHeader(headers, name, value) { - // 1. + set(name, value) { + webidl.assertBranded(this, HeadersPrototype); + const prefix = "Failed to execute 'set' on 'Headers'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters["ByteString"](value, { + prefix, + context: "Argument 2", + }); + value = normalizeHeaderValue(value); // 2. @@ -109,371 +381,97 @@ throw new TypeError("Header value is not valid."); } - // 3. - if (headers[_guard] == "immutable") { + if (this[_guard] == "immutable") { throw new TypeError("Headers are immutable."); } - // 7. - const list = headers[_headerList]; + const list = this[_headerList]; const lowercaseName = byteLowerCase(name); + let added = false; for (let i = 0; i < list.length; i++) { if (byteLowerCase(list[i][0]) === lowercaseName) { - name = list[i][0]; - break; - } - } - ArrayPrototypePush(list, [name, value]); - } - - /** - * https://fetch.spec.whatwg.org/#concept-header-list-get - * @param {HeaderList} list - * @param {string} name - */ - function getHeader(list, name) { - const lowercaseName = byteLowerCase(name); - const entries = ArrayPrototypeMap( - ArrayPrototypeFilter( - list, - (entry) => byteLowerCase(entry[0]) === lowercaseName, - ), - (entry) => entry[1], - ); - if (entries.length === 0) { - return null; - } else { - return ArrayPrototypeJoin(entries, "\x2C\x20"); - } - } - - /** - * https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split - * @param {HeaderList} list - * @param {string} name - * @returns {string[] | null} - */ - function getDecodeSplitHeader(list, name) { - const initialValue = getHeader(list, name); - if (initialValue === null) return null; - const input = initialValue; - let position = 0; - const values = []; - let value = ""; - while (position < initialValue.length) { - // 7.1. collect up to " or , - const res = collectSequenceOfCodepoints( - initialValue, - position, - (c) => c !== "\u0022" && c !== "\u002C", - ); - value += res.result; - position = res.position; - - if (position < initialValue.length) { - if (input[position] === "\u0022") { - const res = collectHttpQuotedString(input, position, false); - value += res.result; - position = res.position; - if (position < initialValue.length) { - continue; - } + if (!added) { + list[i][1] = value; + added = true; } else { - if (input[position] !== "\u002C") throw new TypeError("Unreachable"); - position += 1; - } - } - - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, ""); - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, ""); - - ArrayPrototypePush(values, value); - value = ""; - } - return values; - } - - class Headers { - /** @type {HeaderList} */ - [_headerList] = []; - /** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */ - [_guard]; - - get [_iterableHeaders]() { - const list = this[_headerList]; - - // The order of steps are not similar to the ones suggested by the - // spec but produce the same result. - const headers = {}; - const cookies = []; - for (let i = 0; i < list.length; ++i) { - const entry = list[i]; - const name = byteLowerCase(entry[0]); - const value = entry[1]; - if (value === null) throw new TypeError("Unreachable"); - // The following if statement is not spec compliant. - // `set-cookie` is the only header that can not be concatenated, - // so must be given to the user as multiple headers. - // The else block of the if statement is spec compliant again. - if (name === "set-cookie") { - ArrayPrototypePush(cookies, [name, value]); - } else { - // The following code has the same behaviour as getHeader() - // at the end of loop. But it avoids looping through the entire - // list to combine multiple values with same header name. It - // instead gradually combines them as they are found. - let header = headers[name]; - if (header && header.length > 0) { - header += "\x2C\x20" + value; - } else { - header = value; - } - headers[name] = header; - } - } - - return ArrayPrototypeSort( - [ - ...new SafeArrayIterator(ObjectEntries(headers)), - ...new SafeArrayIterator(cookies), - ], - (a, b) => { - const akey = a[0]; - const bkey = b[0]; - if (akey > bkey) return 1; - if (akey < bkey) return -1; - return 0; - }, - ); - } - - /** @param {HeadersInit} [init] */ - constructor(init = undefined) { - const prefix = "Failed to construct 'Headers'"; - if (init !== undefined) { - init = webidl.converters["HeadersInit"](init, { - prefix, - context: "Argument 1", - }); - } - - this[webidl.brand] = webidl.brand; - this[_guard] = "none"; - if (init !== undefined) { - fillHeaders(this, init); - } - } - - /** - * @param {string} name - * @param {string} value - */ - append(name, value) { - webidl.assertBranded(this, HeadersPrototype); - const prefix = "Failed to execute 'append' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - appendHeader(this, name, value); - } - - /** - * @param {string} name - */ - delete(name) { - const prefix = "Failed to execute 'delete' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { ArrayPrototypeSplice(list, i, 1); i--; } } } - - /** - * @param {string} name - */ - get(name) { - const prefix = "Failed to execute 'get' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - return getHeader(list, name); - } - - /** - * @param {string} name - */ - has(name) { - const prefix = "Failed to execute 'has' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { - return true; - } - } - return false; - } - - /** - * @param {string} name - * @param {string} value - */ - set(name, value) { - webidl.assertBranded(this, HeadersPrototype); - const prefix = "Failed to execute 'set' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - - value = normalizeHeaderValue(value); - - // 2. - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { - throw new TypeError("Header value is not valid."); - } - - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - let added = false; - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { - if (!added) { - list[i][1] = value; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - if (!added) { - ArrayPrototypePush(list, [name, value]); - } - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - const headers = {}; - // deno-lint-ignore prefer-primordials - for (const header of this) { - headers[header[0]] = header[1]; - } - return `Headers ${inspect(headers)}`; + if (!added) { + ArrayPrototypePush(list, [name, value]); } } - webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); - - webidl.configurePrototype(Headers); - const HeadersPrototype = Headers.prototype; - - webidl.converters["HeadersInit"] = (V, opts) => { - // Union for (sequence> or record) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence>"](V, opts); - } - return webidl.converters["record"](V, opts); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + const headers = {}; + // deno-lint-ignore prefer-primordials + for (const header of this) { + headers[header[0]] = header[1]; } - throw webidl.makeException( - TypeError, - "The provided value is not of type '(sequence> or record)'", - opts, - ); - }; - webidl.converters["Headers"] = webidl.createInterfaceConverter( - "Headers", - Headers.prototype, + return `Headers ${inspect(headers)}`; + } +} + +webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); + +webidl.configurePrototype(Headers); +const HeadersPrototype = Headers.prototype; + +webidl.converters["HeadersInit"] = (V, opts) => { + // Union for (sequence> or record) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence>"](V, opts); + } + return webidl.converters["record"](V, opts); + } + throw webidl.makeException( + TypeError, + "The provided value is not of type '(sequence> or record)'", + opts, ); +}; +webidl.converters["Headers"] = webidl.createInterfaceConverter( + "Headers", + Headers.prototype, +); - /** - * @param {HeaderList} list - * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard - * @returns {Headers} - */ - function headersFromHeaderList(list, guard) { - const headers = webidl.createBranded(Headers); - headers[_headerList] = list; - headers[_guard] = guard; - return headers; - } +/** + * @param {HeaderList} list + * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard + * @returns {Headers} + */ +function headersFromHeaderList(list, guard) { + const headers = webidl.createBranded(Headers); + headers[_headerList] = list; + headers[_guard] = guard; + return headers; +} - /** - * @param {Headers} - * @returns {HeaderList} - */ - function headerListFromHeaders(headers) { - return headers[_headerList]; - } +/** + * @param {Headers} headers + * @returns {HeaderList} + */ +function headerListFromHeaders(headers) { + return headers[_headerList]; +} - /** - * @param {Headers} - * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} - */ - function guardFromHeaders(headers) { - return headers[_guard]; - } +/** + * @param {Headers} headers + * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} + */ +function guardFromHeaders(headers) { + return headers[_guard]; +} - window.__bootstrap.headers = { - headersFromHeaderList, - headerListFromHeaders, - getDecodeSplitHeader, - guardFromHeaders, - fillHeaders, - getHeader, - Headers, - }; -})(this); +export { + fillHeaders, + getDecodeSplitHeader, + getHeader, + guardFromHeaders, + headerListFromHeaders, + Headers, + headersFromHeaderList, +}; diff --git a/ext/fetch/21_formdata.js b/ext/fetch/21_formdata.js index d253976ef1..1639646e03 100644 --- a/ext/fetch/21_formdata.js +++ b/ext/fetch/21_formdata.js @@ -8,516 +8,518 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { Blob, BlobPrototype, File, FilePrototype } = - globalThis.__bootstrap.file; - const { - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - Map, - MapPrototypeGet, - MapPrototypeSet, - MathRandom, - ObjectPrototypeIsPrototypeOf, - Symbol, - StringFromCharCode, - StringPrototypeTrim, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeReplace, - StringPrototypeIndexOf, - StringPrototypePadStart, - StringPrototypeCodePointAt, - StringPrototypeReplaceAll, - TypeError, - TypedArrayPrototypeSubarray, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + Blob, + BlobPrototype, + File, + FilePrototype, +} from "internal:ext/web/09_file.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + Map, + MapPrototypeGet, + MapPrototypeSet, + MathRandom, + ObjectPrototypeIsPrototypeOf, + Symbol, + StringFromCharCode, + StringPrototypeTrim, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeReplace, + StringPrototypeIndexOf, + StringPrototypePadStart, + StringPrototypeCodePointAt, + StringPrototypeReplaceAll, + TypeError, + TypedArrayPrototypeSubarray, +} = primordials; - const entryList = Symbol("entry list"); +const entryList = Symbol("entry list"); - /** - * @param {string} name - * @param {string | Blob} value - * @param {string | undefined} filename - * @returns {FormDataEntry} - */ - function createEntry(name, value, filename) { - if ( - ObjectPrototypeIsPrototypeOf(BlobPrototype, value) && - !ObjectPrototypeIsPrototypeOf(FilePrototype, value) - ) { - value = new File([value], "blob", { type: value.type }); +/** + * @param {string} name + * @param {string | Blob} value + * @param {string | undefined} filename + * @returns {FormDataEntry} + */ +function createEntry(name, value, filename) { + if ( + ObjectPrototypeIsPrototypeOf(BlobPrototype, value) && + !ObjectPrototypeIsPrototypeOf(FilePrototype, value) + ) { + value = new File([value], "blob", { type: value.type }); + } + if ( + ObjectPrototypeIsPrototypeOf(FilePrototype, value) && + filename !== undefined + ) { + value = new File([value], filename, { + type: value.type, + lastModified: value.lastModified, + }); + } + return { + name, + // @ts-expect-error because TS is not smart enough + value, + }; +} + +/** + * @typedef FormDataEntry + * @property {string} name + * @property {FormDataEntryValue} value + */ + +class FormData { + /** @type {FormDataEntry[]} */ + [entryList] = []; + + /** @param {void} form */ + constructor(form) { + if (form !== undefined) { + webidl.illegalConstructor(); } - if ( - ObjectPrototypeIsPrototypeOf(FilePrototype, value) && - filename !== undefined - ) { - value = new File([value], filename, { - type: value.type, - lastModified: value.lastModified, - }); - } - return { - name, - // @ts-expect-error because TS is not smart enough - value, - }; + this[webidl.brand] = webidl.brand; } /** - * @typedef FormDataEntry - * @property {string} name - * @property {FormDataEntryValue} value + * @param {string} name + * @param {string | Blob} valueOrBlobValue + * @param {string} [filename] + * @returns {void} */ + append(name, valueOrBlobValue, filename) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'append' on 'FormData'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); - class FormData { - /** @type {FormDataEntry[]} */ - [entryList] = []; - - /** @param {void} form */ - constructor(form) { - if (form !== undefined) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; - } - - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - append(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'append' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { + valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { - prefix, - context: "Argument 2", + context: "Argument 3", }); } - - const entry = createEntry(name, valueOrBlobValue, filename); - - ArrayPrototypePush(this[entryList], entry); + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + prefix, + context: "Argument 2", + }); } - /** - * @param {string} name - * @returns {void} - */ - delete(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'name' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + const entry = createEntry(name, valueOrBlobValue, filename); - name = webidl.converters["USVString"](name, { + ArrayPrototypePush(this[entryList], entry); + } + + /** + * @param {string} name + * @returns {void} + */ + delete(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'name' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const list = this[entryList]; + for (let i = 0; i < list.length; i++) { + if (list[i].name === name) { + ArrayPrototypeSplice(list, i, 1); + i--; + } + } + } + + /** + * @param {string} name + * @returns {FormDataEntryValue | null} + */ + get(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'get' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) return entry.value; + } + return null; + } + + /** + * @param {string} name + * @returns {FormDataEntryValue[]} + */ + getAll(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'getAll' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const returnList = []; + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) ArrayPrototypePush(returnList, entry.value); + } + return returnList; + } + + /** + * @param {string} name + * @returns {boolean} + */ + has(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'has' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) return true; + } + return false; + } + + /** + * @param {string} name + * @param {string | Blob} valueOrBlobValue + * @param {string} [filename] + * @returns {void} + */ + set(name, valueOrBlobValue, filename) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'set' on 'FormData'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { + valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { + prefix, + context: "Argument 3", + }); + } + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + prefix, + context: "Argument 2", + }); + } - const list = this[entryList]; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { + const entry = createEntry(name, valueOrBlobValue, filename); + + const list = this[entryList]; + let added = false; + for (let i = 0; i < list.length; i++) { + if (list[i].name === name) { + if (!added) { + list[i] = entry; + added = true; + } else { ArrayPrototypeSplice(list, i, 1); i--; } } } - - /** - * @param {string} name - * @returns {FormDataEntryValue | null} - */ - get(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'get' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) return entry.value; - } - return null; - } - - /** - * @param {string} name - * @returns {FormDataEntryValue[]} - */ - getAll(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'getAll' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - const returnList = []; - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) ArrayPrototypePush(returnList, entry.value); - } - return returnList; - } - - /** - * @param {string} name - * @returns {boolean} - */ - has(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'has' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) return true; - } - return false; - } - - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - set(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'set' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - } - - const entry = createEntry(name, valueOrBlobValue, filename); - - const list = this[entryList]; - let added = false; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { - if (!added) { - list[i] = entry; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - if (!added) { - ArrayPrototypePush(list, entry); - } + if (!added) { + ArrayPrototypePush(list, entry); } } +} - webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); +webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); - webidl.configurePrototype(FormData); - const FormDataPrototype = FormData.prototype; +webidl.configurePrototype(FormData); +const FormDataPrototype = FormData.prototype; - const escape = (str, isFilename) => { - const escapeMap = { - "\n": "%0A", - "\r": "%0D", - '"': "%22", - }; - - return StringPrototypeReplace( - isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"), - /([\n\r"])/g, - (c) => escapeMap[c], - ); +const escape = (str, isFilename) => { + const escapeMap = { + "\n": "%0A", + "\r": "%0D", + '"': "%22", }; - /** - * convert FormData to a Blob synchronous without reading all of the files - * @param {globalThis.FormData} formData - */ - function formDataToBlob(formData) { - const boundary = StringPrototypePadStart( - StringPrototypeSlice( - StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""), - -28, - ), - 32, - "-", - ); - const chunks = []; - const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`; + return StringPrototypeReplace( + isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"), + /([\n\r"])/g, + (c) => escapeMap[c], + ); +}; - // deno-lint-ignore prefer-primordials - for (const { 0: name, 1: value } of formData) { - if (typeof value === "string") { - ArrayPrototypePush( - chunks, - prefix + escape(name) + '"' + CRLF + CRLF + - StringPrototypeReplace(value, /\r(?!\n)|(?} - */ - function parseContentDisposition(value) { - /** @type {Map} */ - const params = new Map(); - // Forced to do so for some Map constructor param mismatch - const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); - for (let i = 0; i < values.length; i++) { - const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "="); - if (entries.length > 1) { - MapPrototypeSet( - params, - entries[0], - StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"), - ); - } - } - return params; - } - - const CRLF = "\r\n"; - const LF = StringPrototypeCodePointAt(CRLF, 1); - const CR = StringPrototypeCodePointAt(CRLF, 0); - - class MultipartParser { - /** - * @param {Uint8Array} body - * @param {string | undefined} boundary - */ - constructor(body, boundary) { - if (!boundary) { - throw new TypeError("multipart/form-data must provide a boundary"); - } - - this.boundary = `--${boundary}`; - this.body = body; - this.boundaryChars = core.encode(this.boundary); - } - - /** - * @param {string} headersText - * @returns {{ headers: Headers, disposition: Map }} - */ - #parseHeaders(headersText) { - const headers = new Headers(); - const rawHeaders = StringPrototypeSplit(headersText, "\r\n"); - for (let i = 0; i < rawHeaders.length; ++i) { - const rawHeader = rawHeaders[i]; - const sepIndex = StringPrototypeIndexOf(rawHeader, ":"); - if (sepIndex < 0) { - continue; // Skip this header - } - const key = StringPrototypeSlice(rawHeader, 0, sepIndex); - const value = StringPrototypeSlice(rawHeader, sepIndex + 1); - headers.set(key, value); - } - - const disposition = parseContentDisposition( - headers.get("Content-Disposition") ?? "", + // deno-lint-ignore prefer-primordials + for (const { 0: name, 1: value } of formData) { + if (typeof value === "string") { + ArrayPrototypePush( + chunks, + prefix + escape(name) + '"' + CRLF + CRLF + + StringPrototypeReplace(value, /\r(?!\n)|(?= this.boundary.length) { - const { headers, disposition } = this.#parseHeaders(headerText); - const content = TypedArrayPrototypeSubarray( - this.body, - fileStart, - i - boundaryIndex - 1, - ); - // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata - const filename = MapPrototypeGet(disposition, "filename"); - const name = MapPrototypeGet(disposition, "name"); - - state = 5; - // Reset - boundaryIndex = 0; - headerText = ""; - - if (!name) { - continue; // Skip, unknown name - } - - if (filename) { - const blob = new Blob([content], { - type: headers.get("Content-Type") || "application/octet-stream", - }); - formData.append(name, blob, filename); - } else { - formData.append(name, core.decode(content)); - } - } - } else if (state === 5 && isNewLine) { - state = 1; - } - } - - return formData; } } + ArrayPrototypePush(chunks, `--${boundary}--`); + + return new Blob(chunks, { + type: "multipart/form-data; boundary=" + boundary, + }); +} + +/** + * @param {string} value + * @returns {Map} + */ +function parseContentDisposition(value) { + /** @type {Map} */ + const params = new Map(); + // Forced to do so for some Map constructor param mismatch + const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); + for (let i = 0; i < values.length; i++) { + const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "="); + if (entries.length > 1) { + MapPrototypeSet( + params, + entries[0], + StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"), + ); + } + } + return params; +} + +const CRLF = "\r\n"; +const LF = StringPrototypeCodePointAt(CRLF, 1); +const CR = StringPrototypeCodePointAt(CRLF, 0); + +class MultipartParser { /** * @param {Uint8Array} body * @param {string | undefined} boundary - * @returns {FormData} */ - function parseFormData(body, boundary) { - const parser = new MultipartParser(body, boundary); - return parser.parse(); + constructor(body, boundary) { + if (!boundary) { + throw new TypeError("multipart/form-data must provide a boundary"); + } + + this.boundary = `--${boundary}`; + this.body = body; + this.boundaryChars = core.encode(this.boundary); } /** - * @param {FormDataEntry[]} entries - * @returns {FormData} + * @param {string} headersText + * @returns {{ headers: Headers, disposition: Map }} */ - function formDataFromEntries(entries) { - const fd = new FormData(); - fd[entryList] = entries; - return fd; + #parseHeaders(headersText) { + const headers = new Headers(); + const rawHeaders = StringPrototypeSplit(headersText, "\r\n"); + for (let i = 0; i < rawHeaders.length; ++i) { + const rawHeader = rawHeaders[i]; + const sepIndex = StringPrototypeIndexOf(rawHeader, ":"); + if (sepIndex < 0) { + continue; // Skip this header + } + const key = StringPrototypeSlice(rawHeader, 0, sepIndex); + const value = StringPrototypeSlice(rawHeader, sepIndex + 1); + headers.set(key, value); + } + + const disposition = parseContentDisposition( + headers.get("Content-Disposition") ?? "", + ); + + return { headers, disposition }; } - webidl.converters["FormData"] = webidl - .createInterfaceConverter("FormData", FormDataPrototype); + /** + * @returns {FormData} + */ + parse() { + // To have fields body must be at least 2 boundaries + \r\n + -- + // on the last boundary. + if (this.body.length < (this.boundary.length * 2) + 4) { + const decodedBody = core.decode(this.body); + const lastBoundary = this.boundary + "--"; + // check if it's an empty valid form data + if ( + decodedBody === lastBoundary || + decodedBody === lastBoundary + "\r\n" + ) { + return new FormData(); + } + throw new TypeError("Unable to parse body as form data."); + } - globalThis.__bootstrap.formData = { - FormData, - FormDataPrototype, - formDataToBlob, - parseFormData, - formDataFromEntries, - }; -})(globalThis); + const formData = new FormData(); + let headerText = ""; + let boundaryIndex = 0; + let state = 0; + let fileStart = 0; + + for (let i = 0; i < this.body.length; i++) { + const byte = this.body[i]; + const prevByte = this.body[i - 1]; + const isNewLine = byte === LF && prevByte === CR; + + if (state === 1 || state === 2 || state == 3) { + headerText += StringFromCharCode(byte); + } + if (state === 0 && isNewLine) { + state = 1; + } else if (state === 1 && isNewLine) { + state = 2; + const headersDone = this.body[i + 1] === CR && + this.body[i + 2] === LF; + + if (headersDone) { + state = 3; + } + } else if (state === 2 && isNewLine) { + state = 3; + } else if (state === 3 && isNewLine) { + state = 4; + fileStart = i + 1; + } else if (state === 4) { + if (this.boundaryChars[boundaryIndex] !== byte) { + boundaryIndex = 0; + } else { + boundaryIndex++; + } + + if (boundaryIndex >= this.boundary.length) { + const { headers, disposition } = this.#parseHeaders(headerText); + const content = TypedArrayPrototypeSubarray( + this.body, + fileStart, + i - boundaryIndex - 1, + ); + // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata + const filename = MapPrototypeGet(disposition, "filename"); + const name = MapPrototypeGet(disposition, "name"); + + state = 5; + // Reset + boundaryIndex = 0; + headerText = ""; + + if (!name) { + continue; // Skip, unknown name + } + + if (filename) { + const blob = new Blob([content], { + type: headers.get("Content-Type") || "application/octet-stream", + }); + formData.append(name, blob, filename); + } else { + formData.append(name, core.decode(content)); + } + } + } else if (state === 5 && isNewLine) { + state = 1; + } + } + + return formData; + } +} + +/** + * @param {Uint8Array} body + * @param {string | undefined} boundary + * @returns {FormData} + */ +function parseFormData(body, boundary) { + const parser = new MultipartParser(body, boundary); + return parser.parse(); +} + +/** + * @param {FormDataEntry[]} entries + * @returns {FormData} + */ +function formDataFromEntries(entries) { + const fd = new FormData(); + fd[entryList] = entries; + return fd; +} + +webidl.converters["FormData"] = webidl + .createInterfaceConverter("FormData", FormDataPrototype); + +export { + FormData, + formDataFromEntries, + FormDataPrototype, + formDataToBlob, + parseFormData, +}; diff --git a/ext/fetch/22_body.js b/ext/fetch/22_body.js index bea1abce2a..48819650a1 100644 --- a/ext/fetch/22_body.js +++ b/ext/fetch/22_body.js @@ -10,462 +10,462 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { parseUrlEncoded } = globalThis.__bootstrap.url; - const { URLSearchParamsPrototype } = globalThis.__bootstrap.url; - const { - parseFormData, - formDataFromEntries, - formDataToBlob, - FormDataPrototype, - } = globalThis.__bootstrap.formData; - const mimesniff = globalThis.__bootstrap.mimesniff; - const { BlobPrototype } = globalThis.__bootstrap.file; - const { - isReadableStreamDisturbed, - errorReadableStream, - readableStreamClose, - readableStreamDisturb, - readableStreamCollectIntoUint8Array, - readableStreamThrowIfErrored, - createProxy, - ReadableStreamPrototype, - } = globalThis.__bootstrap.streams; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeMap, - JSONParse, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - TypedArrayPrototypeSlice, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + parseUrlEncoded, + URLSearchParamsPrototype, +} from "internal:ext/url/00_url.js"; +import { + formDataFromEntries, + FormDataPrototype, + formDataToBlob, + parseFormData, +} from "internal:ext/fetch/21_formdata.js"; +import * as mimesniff from "internal:ext/web/01_mimesniff.js"; +import { BlobPrototype } from "internal:ext/web/09_file.js"; +import { + createProxy, + errorReadableStream, + isReadableStreamDisturbed, + readableStreamClose, + readableStreamCollectIntoUint8Array, + readableStreamDisturb, + ReadableStreamPrototype, + readableStreamThrowIfErrored, +} from "internal:ext/web/06_streams.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeMap, + JSONParse, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + TypedArrayPrototypeSlice, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; +/** + * @param {Uint8Array | string} chunk + * @returns {Uint8Array} + */ +function chunkToU8(chunk) { + return typeof chunk === "string" ? core.encode(chunk) : chunk; +} + +/** + * @param {Uint8Array | string} chunk + * @returns {string} + */ +function chunkToString(chunk) { + return typeof chunk === "string" ? chunk : core.decode(chunk); +} + +class InnerBody { /** - * @param {Uint8Array | string} chunk - * @returns {Uint8Array} + * @param {ReadableStream | { body: Uint8Array | string, consumed: boolean }} stream */ - function chunkToU8(chunk) { - return typeof chunk === "string" ? core.encode(chunk) : chunk; + constructor(stream) { + /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ + this.streamOrStatic = stream ?? + { body: new Uint8Array(), consumed: false }; + /** @type {null | Uint8Array | string | Blob | FormData} */ + this.source = null; + /** @type {null | number} */ + this.length = null; } - /** - * @param {Uint8Array | string} chunk - * @returns {string} - */ - function chunkToString(chunk) { - return typeof chunk === "string" ? chunk : core.decode(chunk); - } - - class InnerBody { - /** - * @param {ReadableStream | { body: Uint8Array | string, consumed: boolean }} stream - */ - constructor(stream) { - /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ - this.streamOrStatic = stream ?? - { body: new Uint8Array(), consumed: false }; - /** @type {null | Uint8Array | string | Blob | FormData} */ - this.source = null; - /** @type {null | number} */ - this.length = null; - } - - get stream() { - if ( - !ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - const { body, consumed } = this.streamOrStatic; - if (consumed) { - this.streamOrStatic = new ReadableStream(); - this.streamOrStatic.getReader(); - readableStreamDisturb(this.streamOrStatic); - readableStreamClose(this.streamOrStatic); - } else { - this.streamOrStatic = new ReadableStream({ - start(controller) { - controller.enqueue(chunkToU8(body)); - controller.close(); - }, - }); - } - } - return this.streamOrStatic; - } - - /** - * https://fetch.spec.whatwg.org/#body-unusable - * @returns {boolean} - */ - unusable() { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - return this.streamOrStatic.locked || - isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; - } - - /** - * @returns {boolean} - */ - consumed() { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - return isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; - } - - /** - * https://fetch.spec.whatwg.org/#concept-body-consume-body - * @returns {Promise} - */ - consume() { - if (this.unusable()) throw new TypeError("Body already consumed."); - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - readableStreamThrowIfErrored(this.stream); - return readableStreamCollectIntoUint8Array(this.stream); + get stream() { + if ( + !ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + const { body, consumed } = this.streamOrStatic; + if (consumed) { + this.streamOrStatic = new ReadableStream(); + this.streamOrStatic.getReader(); + readableStreamDisturb(this.streamOrStatic); + readableStreamClose(this.streamOrStatic); } else { - this.streamOrStatic.consumed = true; - return this.streamOrStatic.body; - } - } - - cancel(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - this.streamOrStatic.cancel(error); - } else { - this.streamOrStatic.consumed = true; - } - } - - error(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - errorReadableStream(this.streamOrStatic, error); - } else { - this.streamOrStatic.consumed = true; - } - } - - /** - * @returns {InnerBody} - */ - clone() { - const { 0: out1, 1: out2 } = this.stream.tee(); - this.streamOrStatic = out1; - const second = new InnerBody(out2); - second.source = core.deserialize(core.serialize(this.source)); - second.length = this.length; - return second; - } - - /** - * @returns {InnerBody} - */ - createProxy() { - let proxyStreamOrStatic; - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - proxyStreamOrStatic = createProxy(this.streamOrStatic); - } else { - proxyStreamOrStatic = { ...this.streamOrStatic }; - this.streamOrStatic.consumed = true; - } - const proxy = new InnerBody(proxyStreamOrStatic); - proxy.source = this.source; - proxy.length = this.length; - return proxy; - } - } - - /** - * @param {any} prototype - * @param {symbol} bodySymbol - * @param {symbol} mimeTypeSymbol - * @returns {void} - */ - function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { - async function consumeBody(object, type) { - webidl.assertBranded(object, prototype); - - const body = object[bodySymbol] !== null - ? await object[bodySymbol].consume() - : new Uint8Array(); - - const mimeType = type === "Blob" || type === "FormData" - ? object[mimeTypeSymbol] - : null; - return packageData(body, type, mimeType); - } - - /** @type {PropertyDescriptorMap} */ - const mixin = { - body: { - /** - * @returns {ReadableStream | null} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] === null) { - return null; - } else { - return this[bodySymbol].stream; - } - }, - configurable: true, - enumerable: true, - }, - bodyUsed: { - /** - * @returns {boolean} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] !== null) { - return this[bodySymbol].consumed(); - } - return false; - }, - configurable: true, - enumerable: true, - }, - arrayBuffer: { - /** @returns {Promise} */ - value: function arrayBuffer() { - return consumeBody(this, "ArrayBuffer"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - blob: { - /** @returns {Promise} */ - value: function blob() { - return consumeBody(this, "Blob"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - formData: { - /** @returns {Promise} */ - value: function formData() { - return consumeBody(this, "FormData"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - json: { - /** @returns {Promise} */ - value: function json() { - return consumeBody(this, "JSON"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - text: { - /** @returns {Promise} */ - value: function text() { - return consumeBody(this, "text"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - }; - return ObjectDefineProperties(prototype, mixin); - } - - /** - * https://fetch.spec.whatwg.org/#concept-body-package-data - * @param {Uint8Array | string} bytes - * @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type - * @param {MimeType | null} [mimeType] - */ - function packageData(bytes, type, mimeType) { - switch (type) { - case "ArrayBuffer": - return chunkToU8(bytes).buffer; - case "Blob": - return new Blob([bytes], { - type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "", + this.streamOrStatic = new ReadableStream({ + start(controller) { + controller.enqueue(chunkToU8(body)); + controller.close(); + }, }); - case "FormData": { - if (mimeType !== null) { - const essence = mimesniff.essence(mimeType); - if (essence === "multipart/form-data") { - const boundary = mimeType.parameters.get("boundary"); - if (boundary === null) { - throw new TypeError( - "Missing boundary parameter in mime type of multipart formdata.", - ); - } - return parseFormData(chunkToU8(bytes), boundary); - } else if (essence === "application/x-www-form-urlencoded") { - // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer - const entries = parseUrlEncoded(chunkToU8(bytes)); - return formDataFromEntries( - ArrayPrototypeMap( - entries, - (x) => ({ name: x[0], value: x[1] }), - ), + } + } + return this.streamOrStatic; + } + + /** + * https://fetch.spec.whatwg.org/#body-unusable + * @returns {boolean} + */ + unusable() { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + return this.streamOrStatic.locked || + isReadableStreamDisturbed(this.streamOrStatic); + } + return this.streamOrStatic.consumed; + } + + /** + * @returns {boolean} + */ + consumed() { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + return isReadableStreamDisturbed(this.streamOrStatic); + } + return this.streamOrStatic.consumed; + } + + /** + * https://fetch.spec.whatwg.org/#concept-body-consume-body + * @returns {Promise} + */ + consume() { + if (this.unusable()) throw new TypeError("Body already consumed."); + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + readableStreamThrowIfErrored(this.stream); + return readableStreamCollectIntoUint8Array(this.stream); + } else { + this.streamOrStatic.consumed = true; + return this.streamOrStatic.body; + } + } + + cancel(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + this.streamOrStatic.cancel(error); + } else { + this.streamOrStatic.consumed = true; + } + } + + error(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + errorReadableStream(this.streamOrStatic, error); + } else { + this.streamOrStatic.consumed = true; + } + } + + /** + * @returns {InnerBody} + */ + clone() { + const { 0: out1, 1: out2 } = this.stream.tee(); + this.streamOrStatic = out1; + const second = new InnerBody(out2); + second.source = core.deserialize(core.serialize(this.source)); + second.length = this.length; + return second; + } + + /** + * @returns {InnerBody} + */ + createProxy() { + let proxyStreamOrStatic; + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + proxyStreamOrStatic = createProxy(this.streamOrStatic); + } else { + proxyStreamOrStatic = { ...this.streamOrStatic }; + this.streamOrStatic.consumed = true; + } + const proxy = new InnerBody(proxyStreamOrStatic); + proxy.source = this.source; + proxy.length = this.length; + return proxy; + } +} + +/** + * @param {any} prototype + * @param {symbol} bodySymbol + * @param {symbol} mimeTypeSymbol + * @returns {void} + */ +function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { + async function consumeBody(object, type) { + webidl.assertBranded(object, prototype); + + const body = object[bodySymbol] !== null + ? await object[bodySymbol].consume() + : new Uint8Array(); + + const mimeType = type === "Blob" || type === "FormData" + ? object[mimeTypeSymbol] + : null; + return packageData(body, type, mimeType); + } + + /** @type {PropertyDescriptorMap} */ + const mixin = { + body: { + /** + * @returns {ReadableStream | null} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] === null) { + return null; + } else { + return this[bodySymbol].stream; + } + }, + configurable: true, + enumerable: true, + }, + bodyUsed: { + /** + * @returns {boolean} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] !== null) { + return this[bodySymbol].consumed(); + } + return false; + }, + configurable: true, + enumerable: true, + }, + arrayBuffer: { + /** @returns {Promise} */ + value: function arrayBuffer() { + return consumeBody(this, "ArrayBuffer"); + }, + writable: true, + configurable: true, + enumerable: true, + }, + blob: { + /** @returns {Promise} */ + value: function blob() { + return consumeBody(this, "Blob"); + }, + writable: true, + configurable: true, + enumerable: true, + }, + formData: { + /** @returns {Promise} */ + value: function formData() { + return consumeBody(this, "FormData"); + }, + writable: true, + configurable: true, + enumerable: true, + }, + json: { + /** @returns {Promise} */ + value: function json() { + return consumeBody(this, "JSON"); + }, + writable: true, + configurable: true, + enumerable: true, + }, + text: { + /** @returns {Promise} */ + value: function text() { + return consumeBody(this, "text"); + }, + writable: true, + configurable: true, + enumerable: true, + }, + }; + return ObjectDefineProperties(prototype, mixin); +} + +/** + * https://fetch.spec.whatwg.org/#concept-body-package-data + * @param {Uint8Array | string} bytes + * @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type + * @param {MimeType | null} [mimeType] + */ +function packageData(bytes, type, mimeType) { + switch (type) { + case "ArrayBuffer": + return chunkToU8(bytes).buffer; + case "Blob": + return new Blob([bytes], { + type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "", + }); + case "FormData": { + if (mimeType !== null) { + const essence = mimesniff.essence(mimeType); + if (essence === "multipart/form-data") { + const boundary = mimeType.parameters.get("boundary"); + if (boundary === null) { + throw new TypeError( + "Missing boundary parameter in mime type of multipart formdata.", ); } - throw new TypeError("Body can not be decoded as form data"); + return parseFormData(chunkToU8(bytes), boundary); + } else if (essence === "application/x-www-form-urlencoded") { + // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer + const entries = parseUrlEncoded(chunkToU8(bytes)); + return formDataFromEntries( + ArrayPrototypeMap( + entries, + (x) => ({ name: x[0], value: x[1] }), + ), + ); } - throw new TypeError("Missing content type"); + throw new TypeError("Body can not be decoded as form data"); } - case "JSON": - return JSONParse(chunkToString(bytes)); - case "text": - return chunkToString(bytes); + throw new TypeError("Missing content type"); + } + case "JSON": + return JSONParse(chunkToString(bytes)); + case "text": + return chunkToString(bytes); + } +} + +/** + * @param {BodyInit} object + * @returns {{body: InnerBody, contentType: string | null}} + */ +function extractBody(object) { + /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ + let stream; + let source = null; + let length = null; + let contentType = null; + if (typeof object === "string") { + source = object; + contentType = "text/plain;charset=UTF-8"; + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) { + stream = object.stream(); + source = object; + length = object.size; + if (object.type.length !== 0) { + contentType = object.type; + } + } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) { + // Fast(er) path for common case of Uint8Array + const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength); + source = copy; + } else if ( + ArrayBufferIsView(object) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object) + ) { + const u8 = ArrayBufferIsView(object) + ? new Uint8Array( + object.buffer, + object.byteOffset, + object.byteLength, + ) + : new Uint8Array(object); + const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength); + source = copy; + } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) { + const res = formDataToBlob(object); + stream = res.stream(); + source = res; + length = res.size; + contentType = res.type; + } else if ( + ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object) + ) { + // TODO(@satyarohith): not sure what primordial here. + source = object.toString(); + contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) { + stream = object; + if (object.locked || isReadableStreamDisturbed(object)) { + throw new TypeError("ReadableStream is locked or disturbed"); } } - - /** - * @param {BodyInit} object - * @returns {{body: InnerBody, contentType: string | null}} - */ - function extractBody(object) { - /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ - let stream; - let source = null; - let length = null; - let contentType = null; - if (typeof object === "string") { - source = object; - contentType = "text/plain;charset=UTF-8"; - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) { - stream = object.stream(); - source = object; - length = object.size; - if (object.type.length !== 0) { - contentType = object.type; - } - } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) { - // Fast(er) path for common case of Uint8Array - const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength); - source = copy; - } else if ( - ArrayBufferIsView(object) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object) - ) { - const u8 = ArrayBufferIsView(object) - ? new Uint8Array( - object.buffer, - object.byteOffset, - object.byteLength, - ) - : new Uint8Array(object); - const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength); - source = copy; - } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) { - const res = formDataToBlob(object); - stream = res.stream(); - source = res; - length = res.size; - contentType = res.type; - } else if ( - ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object) - ) { - // TODO(@satyarohith): not sure what primordial here. - source = object.toString(); - contentType = "application/x-www-form-urlencoded;charset=UTF-8"; - } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) { - stream = object; - if (object.locked || isReadableStreamDisturbed(object)) { - throw new TypeError("ReadableStream is locked or disturbed"); - } - } - if (typeof source === "string") { - // WARNING: this deviates from spec (expects length to be set) - // https://fetch.spec.whatwg.org/#bodyinit > 7. - // no observable side-effect for users so far, but could change - stream = { body: source, consumed: false }; - length = null; // NOTE: string length != byte length - } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) { - stream = { body: source, consumed: false }; - length = source.byteLength; - } - const body = new InnerBody(stream); - body.source = source; - body.length = length; - return { body, contentType }; + if (typeof source === "string") { + // WARNING: this deviates from spec (expects length to be set) + // https://fetch.spec.whatwg.org/#bodyinit > 7. + // no observable side-effect for users so far, but could change + stream = { body: source, consumed: false }; + length = null; // NOTE: string length != byte length + } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) { + stream = { body: source, consumed: false }; + length = source.byteLength; } + const body = new InnerBody(stream); + body.source = source; + body.length = length; + return { body, contentType }; +} - webidl.converters["BodyInit_DOMString"] = (V, opts) => { - // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) { - return webidl.converters["ReadableStream"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) { - return webidl.converters["FormData"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) { - return webidl.converters["URLSearchParams"](V, opts); +webidl.converters["BodyInit_DOMString"] = (V, opts) => { + // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) { + return webidl.converters["ReadableStream"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) { + return webidl.converters["FormData"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) { + return webidl.converters["URLSearchParams"](V, opts); + } + if (typeof V === "object") { + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - if (typeof V === "object") { - if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) - ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } - // BodyInit conversion is passed to extractBody(), which calls core.encode(). - // core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization. - // Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion. - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter( - webidl.converters["BodyInit_DOMString"], - ); + } + // BodyInit conversion is passed to extractBody(), which calls core.encode(). + // core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization. + // Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion. + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter( + webidl.converters["BodyInit_DOMString"], +); - window.__bootstrap.fetchBody = { mixinBody, InnerBody, extractBody }; -})(globalThis); +export { extractBody, InnerBody, mixinBody }; diff --git a/ext/fetch/22_http_client.js b/ext/fetch/22_http_client.js index 7b9f5c446d..9d37f1b7fb 100644 --- a/ext/fetch/22_http_client.js +++ b/ext/fetch/22_http_client.js @@ -9,40 +9,34 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; +const core = globalThis.Deno.core; +const ops = core.ops; +/** + * @param {Deno.CreateHttpClientOptions} options + * @returns {HttpClient} + */ +function createHttpClient(options) { + options.caCerts ??= []; + return new HttpClient( + ops.op_fetch_custom_client( + options, + ), + ); +} + +class HttpClient { /** - * @param {Deno.CreateHttpClientOptions} options - * @returns {HttpClient} + * @param {number} rid */ - function createHttpClient(options) { - options.caCerts ??= []; - return new HttpClient( - ops.op_fetch_custom_client( - options, - ), - ); + constructor(rid) { + this.rid = rid; } - - class HttpClient { - /** - * @param {number} rid - */ - constructor(rid) { - this.rid = rid; - } - close() { - core.close(this.rid); - } + close() { + core.close(this.rid); } - const HttpClientPrototype = HttpClient.prototype; +} +const HttpClientPrototype = HttpClient.prototype; - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.createHttpClient = createHttpClient; - window.__bootstrap.fetch.HttpClient = HttpClient; - window.__bootstrap.fetch.HttpClientPrototype = HttpClientPrototype; -})(globalThis); +export { createHttpClient, HttpClient, HttpClientPrototype }; diff --git a/ext/fetch/23_request.js b/ext/fetch/23_request.js index e266a7e44a..1e8d5c1ec0 100644 --- a/ext/fetch/23_request.js +++ b/ext/fetch/23_request.js @@ -8,657 +8,664 @@ /// /// /// -"use strict"; -((window) => { - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - const { HTTP_TOKEN_CODE_POINT_RE, byteUpperCase } = window.__bootstrap.infra; - const { URL } = window.__bootstrap.url; - const { guardFromHeaders } = window.__bootstrap.headers; - const { mixinBody, extractBody, InnerBody } = window.__bootstrap.fetchBody; - const { getLocationHref } = window.__bootstrap.location; - const { extractMimeType } = window.__bootstrap.mimesniff; - const { blobFromObjectUrl } = window.__bootstrap.file; - const { - headersFromHeaderList, - headerListFromHeaders, - fillHeaders, - getDecodeSplitHeader, - } = window.__bootstrap.headers; - const { HttpClientPrototype } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { - ArrayPrototypeMap, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ObjectKeys, - ObjectPrototypeIsPrototypeOf, - RegExpPrototypeTest, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +import { + byteUpperCase, + HTTP_TOKEN_CODE_POINT_RE, +} from "internal:ext/web/00_infra.js"; +import { URL } from "internal:ext/url/00_url.js"; +import { + extractBody, + InnerBody, + mixinBody, +} from "internal:ext/fetch/22_body.js"; +import { getLocationHref } from "internal:ext/web/12_location.js"; +import { extractMimeType } from "internal:ext/web/01_mimesniff.js"; +import { blobFromObjectUrl } from "internal:ext/web/09_file.js"; +import { + fillHeaders, + getDecodeSplitHeader, + guardFromHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:ext/fetch/20_headers.js"; +import { HttpClientPrototype } from "internal:ext/fetch/22_http_client.js"; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + RegExpPrototypeTest, + Symbol, + SymbolFor, + TypeError, +} = primordials; - const _request = Symbol("request"); - const _headers = Symbol("headers"); - const _getHeaders = Symbol("get headers"); - const _headersCache = Symbol("headers cache"); - const _signal = Symbol("signal"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); - const _flash = Symbol("flash"); - const _url = Symbol("url"); - const _method = Symbol("method"); +const _request = Symbol("request"); +const _headers = Symbol("headers"); +const _getHeaders = Symbol("get headers"); +const _headersCache = Symbol("headers cache"); +const _signal = Symbol("signal"); +const _mimeType = Symbol("mime type"); +const _body = Symbol("body"); +const _flash = Symbol("flash"); +const _url = Symbol("url"); +const _method = Symbol("method"); - /** - * @param {(() => string)[]} urlList - * @param {string[]} urlListProcessed - */ - function processUrlList(urlList, urlListProcessed) { - for (let i = 0; i < urlList.length; i++) { - if (urlListProcessed[i] === undefined) { - urlListProcessed[i] = urlList[i](); - } +/** + * @param {(() => string)[]} urlList + * @param {string[]} urlListProcessed + */ +function processUrlList(urlList, urlListProcessed) { + for (let i = 0; i < urlList.length; i++) { + if (urlListProcessed[i] === undefined) { + urlListProcessed[i] = urlList[i](); } - return urlListProcessed; + } + return urlListProcessed; +} + +/** + * @typedef InnerRequest + * @property {() => string} method + * @property {() => string} url + * @property {() => string} currentUrl + * @property {() => [string, string][]} headerList + * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body + * @property {"follow" | "error" | "manual"} redirectMode + * @property {number} redirectCount + * @property {(() => string)[]} urlList + * @property {string[]} urlListProcessed + * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. + * @property {Blob | null} blobUrlEntry + */ + +/** + * @param {() => string} method + * @param {string | () => string} url + * @param {() => [string, string][]} headerList + * @param {typeof __window.bootstrap.fetchBody.InnerBody} body + * @param {boolean} maybeBlob + * @returns {InnerRequest} + */ +function newInnerRequest(method, url, headerList, body, maybeBlob) { + let blobUrlEntry = null; + if (maybeBlob && typeof url === "string" && url.startsWith("blob:")) { + blobUrlEntry = blobFromObjectUrl(url); + } + return { + methodInner: null, + get method() { + if (this.methodInner === null) { + try { + this.methodInner = method(); + } catch { + throw new TypeError("cannot read method: request closed"); + } + } + return this.methodInner; + }, + set method(value) { + this.methodInner = value; + }, + headerListInner: null, + get headerList() { + if (this.headerListInner === null) { + try { + this.headerListInner = headerList(); + } catch { + throw new TypeError("cannot read headers: request closed"); + } + } + return this.headerListInner; + }, + set headerList(value) { + this.headerListInner = value; + }, + body, + redirectMode: "follow", + redirectCount: 0, + urlList: [typeof url === "string" ? () => url : url], + urlListProcessed: [], + clientRid: null, + blobUrlEntry, + url() { + if (this.urlListProcessed[0] === undefined) { + try { + this.urlListProcessed[0] = this.urlList[0](); + } catch { + throw new TypeError("cannot read url: request closed"); + } + } + return this.urlListProcessed[0]; + }, + currentUrl() { + const currentIndex = this.urlList.length - 1; + if (this.urlListProcessed[currentIndex] === undefined) { + try { + this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); + } catch { + throw new TypeError("cannot read url: request closed"); + } + } + return this.urlListProcessed[currentIndex]; + }, + }; +} + +/** + * https://fetch.spec.whatwg.org/#concept-request-clone + * @param {InnerRequest} request + * @param {boolean} skipBody + * @param {boolean} flash + * @returns {InnerRequest} + */ +function cloneInnerRequest(request, skipBody = false, flash = false) { + const headerList = ArrayPrototypeMap( + request.headerList, + (x) => [x[0], x[1]], + ); + + let body = null; + if (request.body !== null && !skipBody) { + body = request.body.clone(); } - /** - * @typedef InnerRequest - * @property {() => string} method - * @property {() => string} url - * @property {() => string} currentUrl - * @property {() => [string, string][]} headerList - * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body - * @property {"follow" | "error" | "manual"} redirectMode - * @property {number} redirectCount - * @property {(() => string)[]} urlList - * @property {string[]} urlListProcessed - * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. - * @property {Blob | null} blobUrlEntry - */ - - /** - * @param {() => string} method - * @param {string | () => string} url - * @param {() => [string, string][]} headerList - * @param {typeof __window.bootstrap.fetchBody.InnerBody} body - * @param {boolean} maybeBlob - * @returns {InnerRequest} - */ - function newInnerRequest(method, url, headerList, body, maybeBlob) { - let blobUrlEntry = null; - if (maybeBlob && typeof url === "string" && url.startsWith("blob:")) { - blobUrlEntry = blobFromObjectUrl(url); - } + if (flash) { return { - methodInner: null, - get method() { - if (this.methodInner === null) { - try { - this.methodInner = method(); - } catch { - throw new TypeError("cannot read method: request closed"); - } - } - return this.methodInner; - }, - set method(value) { - this.methodInner = value; - }, - headerListInner: null, - get headerList() { - if (this.headerListInner === null) { - try { - this.headerListInner = headerList(); - } catch { - throw new TypeError("cannot read headers: request closed"); - } - } - return this.headerListInner; - }, - set headerList(value) { - this.headerListInner = value; - }, body, + methodCb: request.methodCb, + urlCb: request.urlCb, + headerList: request.headerList, + streamRid: request.streamRid, + serverId: request.serverId, redirectMode: "follow", redirectCount: 0, - urlList: [typeof url === "string" ? () => url : url], - urlListProcessed: [], - clientRid: null, - blobUrlEntry, - url() { - if (this.urlListProcessed[0] === undefined) { - try { - this.urlListProcessed[0] = this.urlList[0](); - } catch { - throw new TypeError("cannot read url: request closed"); - } - } - return this.urlListProcessed[0]; - }, - currentUrl() { - const currentIndex = this.urlList.length - 1; - if (this.urlListProcessed[currentIndex] === undefined) { - try { - this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); - } catch { - throw new TypeError("cannot read url: request closed"); - } - } - return this.urlListProcessed[currentIndex]; - }, }; } - /** - * https://fetch.spec.whatwg.org/#concept-request-clone - * @param {InnerRequest} request - * @param {boolean} skipBody - * @param {boolean} flash - * @returns {InnerRequest} - */ - function cloneInnerRequest(request, skipBody = false, flash = false) { - const headerList = ArrayPrototypeMap( - request.headerList, - (x) => [x[0], x[1]], - ); - - let body = null; - if (request.body !== null && !skipBody) { - body = request.body.clone(); - } - - if (flash) { - return { - body, - methodCb: request.methodCb, - urlCb: request.urlCb, - headerList: request.headerList, - streamRid: request.streamRid, - serverId: request.serverId, - redirectMode: "follow", - redirectCount: 0, - }; - } - - return { - method: request.method, - headerList, - body, - redirectMode: request.redirectMode, - redirectCount: request.redirectCount, - urlList: request.urlList, - urlListProcessed: request.urlListProcessed, - clientRid: request.clientRid, - blobUrlEntry: request.blobUrlEntry, - url() { - if (this.urlListProcessed[0] === undefined) { - try { - this.urlListProcessed[0] = this.urlList[0](); - } catch { - throw new TypeError("cannot read url: request closed"); - } + return { + method: request.method, + headerList, + body, + redirectMode: request.redirectMode, + redirectCount: request.redirectCount, + urlList: request.urlList, + urlListProcessed: request.urlListProcessed, + clientRid: request.clientRid, + blobUrlEntry: request.blobUrlEntry, + url() { + if (this.urlListProcessed[0] === undefined) { + try { + this.urlListProcessed[0] = this.urlList[0](); + } catch { + throw new TypeError("cannot read url: request closed"); } - return this.urlListProcessed[0]; - }, - currentUrl() { - const currentIndex = this.urlList.length - 1; - if (this.urlListProcessed[currentIndex] === undefined) { - try { - this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); - } catch { - throw new TypeError("cannot read url: request closed"); - } + } + return this.urlListProcessed[0]; + }, + currentUrl() { + const currentIndex = this.urlList.length - 1; + if (this.urlListProcessed[currentIndex] === undefined) { + try { + this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); + } catch { + throw new TypeError("cannot read url: request closed"); } - return this.urlListProcessed[currentIndex]; - }, - }; + } + return this.urlListProcessed[currentIndex]; + }, + }; +} + +/** + * @param {string} m + * @returns {boolean} + */ +function isKnownMethod(m) { + return ( + m === "DELETE" || + m === "GET" || + m === "HEAD" || + m === "OPTIONS" || + m === "POST" || + m === "PUT" + ); +} +/** + * @param {string} m + * @returns {string} + */ +function validateAndNormalizeMethod(m) { + // Fast path for well-known methods + if (isKnownMethod(m)) { + return m; } - /** - * @param {string} m - * @returns {boolean} - */ - function isKnownMethod(m) { - return ( - m === "DELETE" || - m === "GET" || - m === "HEAD" || - m === "OPTIONS" || - m === "POST" || - m === "PUT" + // Regular path + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, m)) { + throw new TypeError("Method is not valid."); + } + const upperCase = byteUpperCase(m); + if ( + upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK" + ) { + throw new TypeError("Method is forbidden."); + } + return upperCase; +} + +class Request { + /** @type {InnerRequest} */ + [_request]; + /** @type {Headers} */ + [_headersCache]; + [_getHeaders]; + + /** @type {Headers} */ + get [_headers]() { + if (this[_headersCache] === undefined) { + this[_headersCache] = this[_getHeaders](); + } + return this[_headersCache]; + } + + set [_headers](value) { + this[_headersCache] = value; + } + + /** @type {AbortSignal} */ + [_signal]; + get [_mimeType]() { + const values = getDecodeSplitHeader( + headerListFromHeaders(this[_headers]), + "Content-Type", ); + return extractMimeType(values); } + get [_body]() { + if (this[_flash]) { + return this[_flash].body; + } else { + return this[_request].body; + } + } + /** - * @param {string} m - * @returns {string} + * https://fetch.spec.whatwg.org/#dom-request + * @param {RequestInfo} input + * @param {RequestInit} init */ - function validateAndNormalizeMethod(m) { - // Fast path for well-known methods - if (isKnownMethod(m)) { - return m; - } + constructor(input, init = {}) { + const prefix = "Failed to construct 'Request'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters["RequestInfo_DOMString"](input, { + prefix, + context: "Argument 1", + }); + init = webidl.converters["RequestInit"](init, { + prefix, + context: "Argument 2", + }); - // Regular path - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, m)) { - throw new TypeError("Method is not valid."); - } - const upperCase = byteUpperCase(m); - if ( - upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK" - ) { - throw new TypeError("Method is forbidden."); - } - return upperCase; - } + this[webidl.brand] = webidl.brand; - class Request { /** @type {InnerRequest} */ - [_request]; - /** @type {Headers} */ - [_headersCache]; - [_getHeaders]; + let request; + const baseURL = getLocationHref(); - /** @type {Headers} */ - get [_headers]() { - if (this[_headersCache] === undefined) { - this[_headersCache] = this[_getHeaders](); - } - return this[_headersCache]; - } + // 4. + let signal = null; - set [_headers](value) { - this[_headersCache] = value; - } - - /** @type {AbortSignal} */ - [_signal]; - get [_mimeType]() { - const values = getDecodeSplitHeader( - headerListFromHeaders(this[_headers]), - "Content-Type", + // 5. + if (typeof input === "string") { + const parsedURL = new URL(input, baseURL); + request = newInnerRequest( + () => "GET", + parsedURL.href, + () => [], + null, + true, ); - return extractMimeType(values); - } - get [_body]() { - if (this[_flash]) { - return this[_flash].body; - } else { - return this[_request].body; + } else { // 6. + if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { + throw new TypeError("Unreachable"); } + const originalReq = input[_request]; + // fold in of step 12 from below + request = cloneInnerRequest(originalReq, true); + request.redirectCount = 0; // reset to 0 - cloneInnerRequest copies the value + signal = input[_signal]; } - /** - * https://fetch.spec.whatwg.org/#dom-request - * @param {RequestInfo} input - * @param {RequestInit} init - */ - constructor(input, init = {}) { - const prefix = "Failed to construct 'Request'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters["RequestInfo_DOMString"](input, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["RequestInit"](init, { - prefix, - context: "Argument 2", - }); + // 12. is folded into the else statement of step 6 above. - this[webidl.brand] = webidl.brand; + // 22. + if (init.redirect !== undefined) { + request.redirectMode = init.redirect; + } - /** @type {InnerRequest} */ - let request; - const baseURL = getLocationHref(); + // 25. + if (init.method !== undefined) { + let method = init.method; + method = validateAndNormalizeMethod(method); + request.method = method; + } - // 4. - let signal = null; + // 26. + if (init.signal !== undefined) { + signal = init.signal; + } - // 5. - if (typeof input === "string") { - const parsedURL = new URL(input, baseURL); - request = newInnerRequest( - () => "GET", - parsedURL.href, - () => [], - null, - true, - ); - } else { // 6. - if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { - throw new TypeError("Unreachable"); - } - const originalReq = input[_request]; - // fold in of step 12 from below - request = cloneInnerRequest(originalReq, true); - request.redirectCount = 0; // reset to 0 - cloneInnerRequest copies the value - signal = input[_signal]; - } - - // 12. is folded into the else statement of step 6 above. - - // 22. - if (init.redirect !== undefined) { - request.redirectMode = init.redirect; - } - - // 25. - if (init.method !== undefined) { - let method = init.method; - method = validateAndNormalizeMethod(method); - request.method = method; - } - - // 26. - if (init.signal !== undefined) { - signal = init.signal; - } - - // NOTE: non standard extension. This handles Deno.HttpClient parameter - if (init.client !== undefined) { - if ( - init.client !== null && - !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client) - ) { - throw webidl.makeException( - TypeError, - "`client` must be a Deno.HttpClient", - { prefix, context: "Argument 2" }, - ); - } - request.clientRid = init.client?.rid ?? null; - } - - // 27. - this[_request] = request; - - // 28. - this[_signal] = abortSignal.newSignal(); - - // 29. - if (signal !== null) { - abortSignal.follow(this[_signal], signal); - } - - // 30. - this[_headers] = headersFromHeaderList(request.headerList, "request"); - - // 32. - if (ObjectKeys(init).length > 0) { - let headers = ArrayPrototypeSlice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - if (init.headers !== undefined) { - headers = init.headers; - } - ArrayPrototypeSplice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - fillHeaders(this[_headers], headers); - } - - // 33. - let inputBody = null; - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { - inputBody = input[_body]; - } - - // 34. + // NOTE: non standard extension. This handles Deno.HttpClient parameter + if (init.client !== undefined) { if ( - (request.method === "GET" || request.method === "HEAD") && - ((init.body !== undefined && init.body !== null) || - inputBody !== null) + init.client !== null && + !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client) ) { - throw new TypeError("Request with GET/HEAD method cannot have body."); - } - - // 35. - let initBody = null; - - // 36. - if (init.body !== undefined && init.body !== null) { - const res = extractBody(init.body); - initBody = res.body; - if (res.contentType !== null && !this[_headers].has("content-type")) { - this[_headers].append("Content-Type", res.contentType); - } - } - - // 37. - const inputOrInitBody = initBody ?? inputBody; - - // 39. - let finalBody = inputOrInitBody; - - // 40. - if (initBody === null && inputBody !== null) { - if (input[_body] && input[_body].unusable()) { - throw new TypeError("Input request's body is unusable."); - } - finalBody = inputBody.createProxy(); - } - - // 41. - request.body = finalBody; - } - - get method() { - webidl.assertBranded(this, RequestPrototype); - if (this[_method]) { - return this[_method]; - } - if (this[_flash]) { - this[_method] = this[_flash].methodCb(); - return this[_method]; - } else { - this[_method] = this[_request].method; - return this[_method]; - } - } - - get url() { - webidl.assertBranded(this, RequestPrototype); - if (this[_url]) { - return this[_url]; - } - - if (this[_flash]) { - this[_url] = this[_flash].urlCb(); - return this[_url]; - } else { - this[_url] = this[_request].url(); - return this[_url]; - } - } - - get headers() { - webidl.assertBranded(this, RequestPrototype); - return this[_headers]; - } - - get redirect() { - webidl.assertBranded(this, RequestPrototype); - if (this[_flash]) { - return this[_flash].redirectMode; - } - return this[_request].redirectMode; - } - - get signal() { - webidl.assertBranded(this, RequestPrototype); - return this[_signal]; - } - - clone() { - webidl.assertBranded(this, RequestPrototype); - if (this[_body] && this[_body].unusable()) { - throw new TypeError("Body is unusable."); - } - let newReq; - if (this[_flash]) { - newReq = cloneInnerRequest(this[_flash], false, true); - } else { - newReq = cloneInnerRequest(this[_request]); - } - const newSignal = abortSignal.newSignal(); - - if (this[_signal]) { - abortSignal.follow(newSignal, this[_signal]); - } - - if (this[_flash]) { - return fromInnerRequest( - newReq, - newSignal, - guardFromHeaders(this[_headers]), - true, + throw webidl.makeException( + TypeError, + "`client` must be a Deno.HttpClient", + { prefix, context: "Argument 2" }, ); } + request.clientRid = init.client?.rid ?? null; + } + // 27. + this[_request] = request; + + // 28. + this[_signal] = abortSignal.newSignal(); + + // 29. + if (signal !== null) { + abortSignal.follow(this[_signal], signal); + } + + // 30. + this[_headers] = headersFromHeaderList(request.headerList, "request"); + + // 32. + if (ObjectKeys(init).length > 0) { + let headers = ArrayPrototypeSlice( + headerListFromHeaders(this[_headers]), + 0, + headerListFromHeaders(this[_headers]).length, + ); + if (init.headers !== undefined) { + headers = init.headers; + } + ArrayPrototypeSplice( + headerListFromHeaders(this[_headers]), + 0, + headerListFromHeaders(this[_headers]).length, + ); + fillHeaders(this[_headers], headers); + } + + // 33. + let inputBody = null; + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { + inputBody = input[_body]; + } + + // 34. + if ( + (request.method === "GET" || request.method === "HEAD") && + ((init.body !== undefined && init.body !== null) || + inputBody !== null) + ) { + throw new TypeError("Request with GET/HEAD method cannot have body."); + } + + // 35. + let initBody = null; + + // 36. + if (init.body !== undefined && init.body !== null) { + const res = extractBody(init.body); + initBody = res.body; + if (res.contentType !== null && !this[_headers].has("content-type")) { + this[_headers].append("Content-Type", res.contentType); + } + } + + // 37. + const inputOrInitBody = initBody ?? inputBody; + + // 39. + let finalBody = inputOrInitBody; + + // 40. + if (initBody === null && inputBody !== null) { + if (input[_body] && input[_body].unusable()) { + throw new TypeError("Input request's body is unusable."); + } + finalBody = inputBody.createProxy(); + } + + // 41. + request.body = finalBody; + } + + get method() { + webidl.assertBranded(this, RequestPrototype); + if (this[_method]) { + return this[_method]; + } + if (this[_flash]) { + this[_method] = this[_flash].methodCb(); + return this[_method]; + } else { + this[_method] = this[_request].method; + return this[_method]; + } + } + + get url() { + webidl.assertBranded(this, RequestPrototype); + if (this[_url]) { + return this[_url]; + } + + if (this[_flash]) { + this[_url] = this[_flash].urlCb(); + return this[_url]; + } else { + this[_url] = this[_request].url(); + return this[_url]; + } + } + + get headers() { + webidl.assertBranded(this, RequestPrototype); + return this[_headers]; + } + + get redirect() { + webidl.assertBranded(this, RequestPrototype); + if (this[_flash]) { + return this[_flash].redirectMode; + } + return this[_request].redirectMode; + } + + get signal() { + webidl.assertBranded(this, RequestPrototype); + return this[_signal]; + } + + clone() { + webidl.assertBranded(this, RequestPrototype); + if (this[_body] && this[_body].unusable()) { + throw new TypeError("Body is unusable."); + } + let newReq; + if (this[_flash]) { + newReq = cloneInnerRequest(this[_flash], false, true); + } else { + newReq = cloneInnerRequest(this[_request]); + } + const newSignal = abortSignal.newSignal(); + + if (this[_signal]) { + abortSignal.follow(newSignal, this[_signal]); + } + + if (this[_flash]) { return fromInnerRequest( newReq, newSignal, guardFromHeaders(this[_headers]), - false, + true, ); } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this), - keys: [ - "bodyUsed", - "headers", - "method", - "redirect", - "url", - ], - })); - } + return fromInnerRequest( + newReq, + newSignal, + guardFromHeaders(this[_headers]), + false, + ); } - webidl.configurePrototype(Request); - const RequestPrototype = Request.prototype; - mixinBody(RequestPrototype, _body, _mimeType); - - webidl.converters["Request"] = webidl.createInterfaceConverter( - "Request", - RequestPrototype, - ); - webidl.converters["RequestInfo_DOMString"] = (V, opts) => { - // Union for (Request or USVString) - if (typeof V == "object") { - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) { - return webidl.converters["Request"](V, opts); - } - } - // Passed to new URL(...) which implicitly converts DOMString -> USVString - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["RequestRedirect"] = webidl.createEnumConverter( - "RequestRedirect", - [ - "follow", - "error", - "manual", - ], - ); - webidl.converters["RequestInit"] = webidl.createDictionaryConverter( - "RequestInit", - [ - { key: "method", converter: webidl.converters["ByteString"] }, - { key: "headers", converter: webidl.converters["HeadersInit"] }, - { - key: "body", - converter: webidl.createNullableConverter( - webidl.converters["BodyInit_DOMString"], - ), - }, - { key: "redirect", converter: webidl.converters["RequestRedirect"] }, - { - key: "signal", - converter: webidl.createNullableConverter( - webidl.converters["AbortSignal"], - ), - }, - { key: "client", converter: webidl.converters.any }, - ], - ); - - /** - * @param {Request} request - * @returns {InnerRequest} - */ - function toInnerRequest(request) { - return request[_request]; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this), + keys: [ + "bodyUsed", + "headers", + "method", + "redirect", + "url", + ], + })); } +} - /** - * @param {InnerRequest} inner - * @param {AbortSignal} signal - * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard - * @param {boolean} flash - * @returns {Request} - */ - function fromInnerRequest(inner, signal, guard, flash) { - const request = webidl.createBranded(Request); - if (flash) { - request[_flash] = inner; - } else { - request[_request] = inner; +webidl.configurePrototype(Request); +const RequestPrototype = Request.prototype; +mixinBody(RequestPrototype, _body, _mimeType); + +webidl.converters["Request"] = webidl.createInterfaceConverter( + "Request", + RequestPrototype, +); +webidl.converters["RequestInfo_DOMString"] = (V, opts) => { + // Union for (Request or USVString) + if (typeof V == "object") { + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) { + return webidl.converters["Request"](V, opts); } - request[_signal] = signal; - request[_getHeaders] = flash - ? () => headersFromHeaderList(inner.headerList(), guard) - : () => headersFromHeaderList(inner.headerList, guard); - return request; } + // Passed to new URL(...) which implicitly converts DOMString -> USVString + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["RequestRedirect"] = webidl.createEnumConverter( + "RequestRedirect", + [ + "follow", + "error", + "manual", + ], +); +webidl.converters["RequestInit"] = webidl.createDictionaryConverter( + "RequestInit", + [ + { key: "method", converter: webidl.converters["ByteString"] }, + { key: "headers", converter: webidl.converters["HeadersInit"] }, + { + key: "body", + converter: webidl.createNullableConverter( + webidl.converters["BodyInit_DOMString"], + ), + }, + { key: "redirect", converter: webidl.converters["RequestRedirect"] }, + { + key: "signal", + converter: webidl.createNullableConverter( + webidl.converters["AbortSignal"], + ), + }, + { key: "client", converter: webidl.converters.any }, + ], +); - /** - * @param {number} serverId - * @param {number} streamRid - * @param {ReadableStream} body - * @param {() => string} methodCb - * @param {() => string} urlCb - * @param {() => [string, string][]} headersCb - * @returns {Request} - */ - function fromFlashRequest( - serverId, - streamRid, - body, +/** + * @param {Request} request + * @returns {InnerRequest} + */ +function toInnerRequest(request) { + return request[_request]; +} + +/** + * @param {InnerRequest} inner + * @param {AbortSignal} signal + * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard + * @param {boolean} flash + * @returns {Request} + */ +function fromInnerRequest(inner, signal, guard, flash) { + const request = webidl.createBranded(Request); + if (flash) { + request[_flash] = inner; + } else { + request[_request] = inner; + } + request[_signal] = signal; + request[_getHeaders] = flash + ? () => headersFromHeaderList(inner.headerList(), guard) + : () => headersFromHeaderList(inner.headerList, guard); + return request; +} + +/** + * @param {number} serverId + * @param {number} streamRid + * @param {ReadableStream} body + * @param {() => string} methodCb + * @param {() => string} urlCb + * @param {() => [string, string][]} headersCb + * @returns {Request} + */ +function fromFlashRequest( + serverId, + streamRid, + body, + methodCb, + urlCb, + headersCb, +) { + const request = webidl.createBranded(Request); + request[_flash] = { + body: body !== null ? new InnerBody(body) : null, methodCb, urlCb, - headersCb, - ) { - const request = webidl.createBranded(Request); - request[_flash] = { - body: body !== null ? new InnerBody(body) : null, - methodCb, - urlCb, - headerList: headersCb, - streamRid, - serverId, - redirectMode: "follow", - redirectCount: 0, - }; - request[_getHeaders] = () => headersFromHeaderList(headersCb(), "request"); - return request; - } + headerList: headersCb, + streamRid, + serverId, + redirectMode: "follow", + redirectCount: 0, + }; + request[_getHeaders] = () => headersFromHeaderList(headersCb(), "request"); + return request; +} - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.Request = Request; - window.__bootstrap.fetch.toInnerRequest = toInnerRequest; - window.__bootstrap.fetch.fromFlashRequest = fromFlashRequest; - window.__bootstrap.fetch.fromInnerRequest = fromInnerRequest; - window.__bootstrap.fetch.newInnerRequest = newInnerRequest; - window.__bootstrap.fetch.processUrlList = processUrlList; - window.__bootstrap.fetch._flash = _flash; -})(globalThis); +export { + _flash, + fromFlashRequest, + fromInnerRequest, + newInnerRequest, + processUrlList, + Request, + RequestPrototype, + toInnerRequest, +}; diff --git a/ext/fetch/23_response.js b/ext/fetch/23_response.js index 070068d28a..46912135ab 100644 --- a/ext/fetch/23_response.js +++ b/ext/fetch/23_response.js @@ -9,510 +9,510 @@ /// /// /// -"use strict"; -((window) => { - const { isProxy } = Deno.core; - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - const { - byteLowerCase, - } = window.__bootstrap.infra; - const { HTTP_TAB_OR_SPACE, regexMatcher, serializeJSValueToJSONString } = - window.__bootstrap.infra; - const { extractBody, mixinBody } = window.__bootstrap.fetchBody; - const { getLocationHref } = window.__bootstrap.location; - const { extractMimeType } = window.__bootstrap.mimesniff; - const { URL } = window.__bootstrap.url; - const { - getDecodeSplitHeader, - headerListFromHeaders, - headersFromHeaderList, - guardFromHeaders, - fillHeaders, - } = window.__bootstrap.headers; - const { - ArrayPrototypeMap, - ArrayPrototypePush, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - RangeError, - RegExp, - RegExpPrototypeTest, - SafeArrayIterator, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +import { + byteLowerCase, + HTTP_TAB_OR_SPACE, + regexMatcher, + serializeJSValueToJSONString, +} from "internal:ext/web/00_infra.js"; +import { extractBody, mixinBody } from "internal:ext/fetch/22_body.js"; +import { getLocationHref } from "internal:ext/web/12_location.js"; +import { extractMimeType } from "internal:ext/web/01_mimesniff.js"; +import { URL } from "internal:ext/url/00_url.js"; +import { + fillHeaders, + getDecodeSplitHeader, + guardFromHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:ext/fetch/20_headers.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypePush, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + RangeError, + RegExp, + RegExpPrototypeTest, + SafeArrayIterator, + Symbol, + SymbolFor, + TypeError, +} = primordials; - const VCHAR = ["\x21-\x7E"]; - const OBS_TEXT = ["\x80-\xFF"]; +const VCHAR = ["\x21-\x7E"]; +const OBS_TEXT = ["\x80-\xFF"]; - const REASON_PHRASE = [ - ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), - ...new SafeArrayIterator(VCHAR), - ...new SafeArrayIterator(OBS_TEXT), - ]; - const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); - const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); +const REASON_PHRASE = [ + ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), + ...new SafeArrayIterator(VCHAR), + ...new SafeArrayIterator(OBS_TEXT), +]; +const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); +const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); - const _response = Symbol("response"); - const _headers = Symbol("headers"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); +const _response = Symbol("response"); +const _headers = Symbol("headers"); +const _mimeType = Symbol("mime type"); +const _body = Symbol("body"); - /** - * @typedef InnerResponse - * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type - * @property {() => string | null} url - * @property {string[]} urlList - * @property {number} status - * @property {string} statusMessage - * @property {[string, string][]} headerList - * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body - * @property {boolean} aborted - * @property {string} [error] - */ +/** + * @typedef InnerResponse + * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type + * @property {() => string | null} url + * @property {string[]} urlList + * @property {number} status + * @property {string} statusMessage + * @property {[string, string][]} headerList + * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body + * @property {boolean} aborted + * @property {string} [error] + */ - /** - * @param {number} status - * @returns {boolean} - */ - function nullBodyStatus(status) { - return status === 101 || status === 204 || status === 205 || status === 304; - } +/** + * @param {number} status + * @returns {boolean} + */ +function nullBodyStatus(status) { + return status === 101 || status === 204 || status === 205 || status === 304; +} - /** - * @param {number} status - * @returns {boolean} - */ - function redirectStatus(status) { - return status === 301 || status === 302 || status === 303 || - status === 307 || status === 308; - } +/** + * @param {number} status + * @returns {boolean} + */ +function redirectStatus(status) { + return status === 301 || status === 302 || status === 303 || + status === 307 || status === 308; +} - /** - * https://fetch.spec.whatwg.org/#concept-response-clone - * @param {InnerResponse} response - * @returns {InnerResponse} - */ - function cloneInnerResponse(response) { - const urlList = [...new SafeArrayIterator(response.urlList)]; - const headerList = ArrayPrototypeMap( - response.headerList, - (x) => [x[0], x[1]], - ); - - let body = null; - if (response.body !== null) { - body = response.body.clone(); - } - - return { - type: response.type, - body, - headerList, - urlList, - status: response.status, - statusMessage: response.statusMessage, - aborted: response.aborted, - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - }; - } - - /** - * @returns {InnerResponse} - */ - function newInnerResponse(status = 200, statusMessage = "") { - return { - type: "default", - body: null, - headerList: [], - urlList: [], - status, - statusMessage, - aborted: false, - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - }; - } - - /** - * @param {string} error - * @returns {InnerResponse} - */ - function networkError(error) { - const resp = newInnerResponse(0); - resp.type = "error"; - resp.error = error; - return resp; - } - - /** - * @returns {InnerResponse} - */ - function abortedNetworkError() { - const resp = networkError("aborted"); - resp.aborted = true; - return resp; - } - - /** - * https://fetch.spec.whatwg.org#initialize-a-response - * @param {Response} response - * @param {ResponseInit} init - * @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType - */ - function initializeAResponse(response, init, bodyWithType) { - // 1. - if ((init.status < 200 || init.status > 599) && init.status != 101) { - throw new RangeError( - `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, - ); - } - - // 2. - if ( - init.statusText && - !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) - ) { - throw new TypeError("Status text is not valid."); - } - - // 3. - response[_response].status = init.status; - - // 4. - response[_response].statusMessage = init.statusText; - // 5. - /** @type {__bootstrap.headers.Headers} */ - const headers = response[_headers]; - if (init.headers) { - fillHeaders(headers, init.headers); - } - - // 6. - if (bodyWithType !== null) { - if (nullBodyStatus(response[_response].status)) { - throw new TypeError( - "Response with null body status cannot have body", - ); - } - - const { body, contentType } = bodyWithType; - response[_response].body = body; - - if (contentType !== null) { - let hasContentType = false; - const list = headerListFromHeaders(headers); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === "content-type") { - hasContentType = true; - break; - } - } - if (!hasContentType) { - ArrayPrototypePush(list, ["Content-Type", contentType]); - } - } - } - } - - class Response { - get [_mimeType]() { - const values = getDecodeSplitHeader( - headerListFromHeaders(this[_headers]), - "Content-Type", - ); - return extractMimeType(values); - } - get [_body]() { - return this[_response].body; - } - - /** - * @returns {Response} - */ - static error() { - const inner = newInnerResponse(0); - inner.type = "error"; - const response = webidl.createBranded(Response); - response[_response] = inner; - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "immutable", - ); - return response; - } - - /** - * @param {string} url - * @param {number} status - * @returns {Response} - */ - static redirect(url, status = 302) { - const prefix = "Failed to call 'Response.redirect'"; - url = webidl.converters["USVString"](url, { - prefix, - context: "Argument 1", - }); - status = webidl.converters["unsigned short"](status, { - prefix, - context: "Argument 2", - }); - - const baseURL = getLocationHref(); - const parsedURL = new URL(url, baseURL); - if (!redirectStatus(status)) { - throw new RangeError("Invalid redirect status code."); - } - const inner = newInnerResponse(status); - inner.type = "default"; - ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]); - const response = webidl.createBranded(Response); - response[_response] = inner; - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "immutable", - ); - return response; - } - - /** - * @param {any} data - * @param {ResponseInit} init - * @returns {Response} - */ - static json(data = undefined, init = {}) { - const prefix = "Failed to call 'Response.json'"; - data = webidl.converters.any(data); - init = webidl.converters["ResponseInit_fast"](init, { - prefix, - context: "Argument 2", - }); - - const str = serializeJSValueToJSONString(data); - const res = extractBody(str); - res.contentType = "application/json"; - const response = webidl.createBranded(Response); - response[_response] = newInnerResponse(); - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "response", - ); - initializeAResponse(response, init, res); - return response; - } - - /** - * @param {BodyInit | null} body - * @param {ResponseInit} init - */ - constructor(body = null, init = undefined) { - const prefix = "Failed to construct 'Response'"; - body = webidl.converters["BodyInit_DOMString?"](body, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["ResponseInit_fast"](init, { - prefix, - context: "Argument 2", - }); - - this[_response] = newInnerResponse(); - this[_headers] = headersFromHeaderList( - this[_response].headerList, - "response", - ); - - let bodyWithType = null; - if (body !== null) { - bodyWithType = extractBody(body); - } - initializeAResponse(this, init, bodyWithType); - this[webidl.brand] = webidl.brand; - } - - /** - * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} - */ - get type() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].type; - } - - /** - * @returns {string} - */ - get url() { - webidl.assertBranded(this, ResponsePrototype); - const url = this[_response].url(); - if (url === null) return ""; - const newUrl = new URL(url); - newUrl.hash = ""; - return newUrl.href; - } - - /** - * @returns {boolean} - */ - get redirected() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].urlList.length > 1; - } - - /** - * @returns {number} - */ - get status() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].status; - } - - /** - * @returns {boolean} - */ - get ok() { - webidl.assertBranded(this, ResponsePrototype); - const status = this[_response].status; - return status >= 200 && status <= 299; - } - - /** - * @returns {string} - */ - get statusText() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].statusMessage; - } - - /** - * @returns {Headers} - */ - get headers() { - webidl.assertBranded(this, ResponsePrototype); - return this[_headers]; - } - - /** - * @returns {Response} - */ - clone() { - webidl.assertBranded(this, ResponsePrototype); - if (this[_body] && this[_body].unusable()) { - throw new TypeError("Body is unusable."); - } - const second = webidl.createBranded(Response); - const newRes = cloneInnerResponse(this[_response]); - second[_response] = newRes; - second[_headers] = headersFromHeaderList( - newRes.headerList, - guardFromHeaders(this[_headers]), - ); - return second; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this), - keys: [ - "body", - "bodyUsed", - "headers", - "ok", - "redirected", - "status", - "statusText", - "url", - ], - })); - } - } - - webidl.configurePrototype(Response); - ObjectDefineProperties(Response, { - json: { enumerable: true }, - redirect: { enumerable: true }, - error: { enumerable: true }, - }); - const ResponsePrototype = Response.prototype; - mixinBody(ResponsePrototype, _body, _mimeType); - - webidl.converters["Response"] = webidl.createInterfaceConverter( - "Response", - ResponsePrototype, +/** + * https://fetch.spec.whatwg.org/#concept-response-clone + * @param {InnerResponse} response + * @returns {InnerResponse} + */ +function cloneInnerResponse(response) { + const urlList = [...new SafeArrayIterator(response.urlList)]; + const headerList = ArrayPrototypeMap( + response.headerList, + (x) => [x[0], x[1]], ); - webidl.converters["ResponseInit"] = webidl.createDictionaryConverter( - "ResponseInit", - [{ - key: "status", - defaultValue: 200, - converter: webidl.converters["unsigned short"], - }, { - key: "statusText", - defaultValue: "", - converter: webidl.converters["ByteString"], - }, { - key: "headers", - converter: webidl.converters["HeadersInit"], - }], - ); - webidl.converters["ResponseInit_fast"] = function (init, opts) { - if (init === undefined || init === null) { - return { status: 200, statusText: "", headers: undefined }; - } - // Fast path, if not a proxy - if (typeof init === "object" && !isProxy(init)) { - // Not a proxy fast path - const status = init.status !== undefined - ? webidl.converters["unsigned short"](init.status) - : 200; - const statusText = init.statusText !== undefined - ? webidl.converters["ByteString"](init.statusText) - : ""; - const headers = init.headers !== undefined - ? webidl.converters["HeadersInit"](init.headers) - : undefined; - return { status, statusText, headers }; - } - // Slow default path - return webidl.converters["ResponseInit"](init, opts); + + let body = null; + if (response.body !== null) { + body = response.body.clone(); + } + + return { + type: response.type, + body, + headerList, + urlList, + status: response.status, + statusMessage: response.statusMessage, + aborted: response.aborted, + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, }; +} - /** - * @param {Response} response - * @returns {InnerResponse} - */ - function toInnerResponse(response) { - return response[_response]; +/** + * @returns {InnerResponse} + */ +function newInnerResponse(status = 200, statusMessage = "") { + return { + type: "default", + body: null, + headerList: [], + urlList: [], + status, + statusMessage, + aborted: false, + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + }; +} + +/** + * @param {string} error + * @returns {InnerResponse} + */ +function networkError(error) { + const resp = newInnerResponse(0); + resp.type = "error"; + resp.error = error; + return resp; +} + +/** + * @returns {InnerResponse} + */ +function abortedNetworkError() { + const resp = networkError("aborted"); + resp.aborted = true; + return resp; +} + +/** + * https://fetch.spec.whatwg.org#initialize-a-response + * @param {Response} response + * @param {ResponseInit} init + * @param {{ body: fetchBody.InnerBody, contentType: string | null } | null} bodyWithType + */ +function initializeAResponse(response, init, bodyWithType) { + // 1. + if ((init.status < 200 || init.status > 599) && init.status != 101) { + throw new RangeError( + `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, + ); + } + + // 2. + if ( + init.statusText && + !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) + ) { + throw new TypeError("Status text is not valid."); + } + + // 3. + response[_response].status = init.status; + + // 4. + response[_response].statusMessage = init.statusText; + // 5. + /** @type {headers.Headers} */ + const headers = response[_headers]; + if (init.headers) { + fillHeaders(headers, init.headers); + } + + // 6. + if (bodyWithType !== null) { + if (nullBodyStatus(response[_response].status)) { + throw new TypeError( + "Response with null body status cannot have body", + ); + } + + const { body, contentType } = bodyWithType; + response[_response].body = body; + + if (contentType !== null) { + let hasContentType = false; + const list = headerListFromHeaders(headers); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === "content-type") { + hasContentType = true; + break; + } + } + if (!hasContentType) { + ArrayPrototypePush(list, ["Content-Type", contentType]); + } + } + } +} + +class Response { + get [_mimeType]() { + const values = getDecodeSplitHeader( + headerListFromHeaders(this[_headers]), + "Content-Type", + ); + return extractMimeType(values); + } + get [_body]() { + return this[_response].body; } /** - * @param {InnerResponse} inner - * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard * @returns {Response} */ - function fromInnerResponse(inner, guard) { + static error() { + const inner = newInnerResponse(0); + inner.type = "error"; const response = webidl.createBranded(Response); response[_response] = inner; - response[_headers] = headersFromHeaderList(inner.headerList, guard); + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "immutable", + ); return response; } - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.Response = Response; - window.__bootstrap.fetch.ResponsePrototype = ResponsePrototype; - window.__bootstrap.fetch.newInnerResponse = newInnerResponse; - window.__bootstrap.fetch.toInnerResponse = toInnerResponse; - window.__bootstrap.fetch.fromInnerResponse = fromInnerResponse; - window.__bootstrap.fetch.redirectStatus = redirectStatus; - window.__bootstrap.fetch.nullBodyStatus = nullBodyStatus; - window.__bootstrap.fetch.networkError = networkError; - window.__bootstrap.fetch.abortedNetworkError = abortedNetworkError; -})(globalThis); + /** + * @param {string} url + * @param {number} status + * @returns {Response} + */ + static redirect(url, status = 302) { + const prefix = "Failed to call 'Response.redirect'"; + url = webidl.converters["USVString"](url, { + prefix, + context: "Argument 1", + }); + status = webidl.converters["unsigned short"](status, { + prefix, + context: "Argument 2", + }); + + const baseURL = getLocationHref(); + const parsedURL = new URL(url, baseURL); + if (!redirectStatus(status)) { + throw new RangeError("Invalid redirect status code."); + } + const inner = newInnerResponse(status); + inner.type = "default"; + ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]); + const response = webidl.createBranded(Response); + response[_response] = inner; + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "immutable", + ); + return response; + } + + /** + * @param {any} data + * @param {ResponseInit} init + * @returns {Response} + */ + static json(data = undefined, init = {}) { + const prefix = "Failed to call 'Response.json'"; + data = webidl.converters.any(data); + init = webidl.converters["ResponseInit_fast"](init, { + prefix, + context: "Argument 2", + }); + + const str = serializeJSValueToJSONString(data); + const res = extractBody(str); + res.contentType = "application/json"; + const response = webidl.createBranded(Response); + response[_response] = newInnerResponse(); + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "response", + ); + initializeAResponse(response, init, res); + return response; + } + + /** + * @param {BodyInit | null} body + * @param {ResponseInit} init + */ + constructor(body = null, init = undefined) { + const prefix = "Failed to construct 'Response'"; + body = webidl.converters["BodyInit_DOMString?"](body, { + prefix, + context: "Argument 1", + }); + init = webidl.converters["ResponseInit_fast"](init, { + prefix, + context: "Argument 2", + }); + + this[_response] = newInnerResponse(); + this[_headers] = headersFromHeaderList( + this[_response].headerList, + "response", + ); + + let bodyWithType = null; + if (body !== null) { + bodyWithType = extractBody(body); + } + initializeAResponse(this, init, bodyWithType); + this[webidl.brand] = webidl.brand; + } + + /** + * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} + */ + get type() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].type; + } + + /** + * @returns {string} + */ + get url() { + webidl.assertBranded(this, ResponsePrototype); + const url = this[_response].url(); + if (url === null) return ""; + const newUrl = new URL(url); + newUrl.hash = ""; + return newUrl.href; + } + + /** + * @returns {boolean} + */ + get redirected() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].urlList.length > 1; + } + + /** + * @returns {number} + */ + get status() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].status; + } + + /** + * @returns {boolean} + */ + get ok() { + webidl.assertBranded(this, ResponsePrototype); + const status = this[_response].status; + return status >= 200 && status <= 299; + } + + /** + * @returns {string} + */ + get statusText() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].statusMessage; + } + + /** + * @returns {Headers} + */ + get headers() { + webidl.assertBranded(this, ResponsePrototype); + return this[_headers]; + } + + /** + * @returns {Response} + */ + clone() { + webidl.assertBranded(this, ResponsePrototype); + if (this[_body] && this[_body].unusable()) { + throw new TypeError("Body is unusable."); + } + const second = webidl.createBranded(Response); + const newRes = cloneInnerResponse(this[_response]); + second[_response] = newRes; + second[_headers] = headersFromHeaderList( + newRes.headerList, + guardFromHeaders(this[_headers]), + ); + return second; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this), + keys: [ + "body", + "bodyUsed", + "headers", + "ok", + "redirected", + "status", + "statusText", + "url", + ], + })); + } +} + +webidl.configurePrototype(Response); +ObjectDefineProperties(Response, { + json: { enumerable: true }, + redirect: { enumerable: true }, + error: { enumerable: true }, +}); +const ResponsePrototype = Response.prototype; +mixinBody(ResponsePrototype, _body, _mimeType); + +webidl.converters["Response"] = webidl.createInterfaceConverter( + "Response", + ResponsePrototype, +); +webidl.converters["ResponseInit"] = webidl.createDictionaryConverter( + "ResponseInit", + [{ + key: "status", + defaultValue: 200, + converter: webidl.converters["unsigned short"], + }, { + key: "statusText", + defaultValue: "", + converter: webidl.converters["ByteString"], + }, { + key: "headers", + converter: webidl.converters["HeadersInit"], + }], +); +webidl.converters["ResponseInit_fast"] = function (init, opts) { + if (init === undefined || init === null) { + return { status: 200, statusText: "", headers: undefined }; + } + // Fast path, if not a proxy + if (typeof init === "object" && !core.isProxy(init)) { + // Not a proxy fast path + const status = init.status !== undefined + ? webidl.converters["unsigned short"](init.status) + : 200; + const statusText = init.statusText !== undefined + ? webidl.converters["ByteString"](init.statusText) + : ""; + const headers = init.headers !== undefined + ? webidl.converters["HeadersInit"](init.headers) + : undefined; + return { status, statusText, headers }; + } + // Slow default path + return webidl.converters["ResponseInit"](init, opts); +}; + +/** + * @param {Response} response + * @returns {InnerResponse} + */ +function toInnerResponse(response) { + return response[_response]; +} + +/** + * @param {InnerResponse} inner + * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard + * @returns {Response} + */ +function fromInnerResponse(inner, guard) { + const response = webidl.createBranded(Response); + response[_response] = inner; + response[_headers] = headersFromHeaderList(inner.headerList, guard); + return response; +} + +export { + abortedNetworkError, + fromInnerResponse, + networkError, + newInnerResponse, + nullBodyStatus, + redirectStatus, + Response, + ResponsePrototype, + toInnerResponse, +}; diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js index ddb023a377..9c136f242e 100644 --- a/ext/fetch/26_fetch.js +++ b/ext/fetch/26_fetch.js @@ -9,578 +9,577 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { byteLowerCase } = window.__bootstrap.infra; - const { BlobPrototype } = window.__bootstrap.file; - const { errorReadableStream, ReadableStreamPrototype, readableStreamForRid } = - window.__bootstrap.streams; - const { InnerBody, extractBody } = window.__bootstrap.fetchBody; - const { - toInnerRequest, - toInnerResponse, - fromInnerResponse, - redirectStatus, - nullBodyStatus, - networkError, - abortedNetworkError, - processUrlList, - } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { - ArrayPrototypePush, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ObjectPrototypeIsPrototypeOf, - Promise, - PromisePrototypeThen, - PromisePrototypeCatch, - SafeArrayIterator, - String, - StringPrototypeStartsWith, - StringPrototypeToLowerCase, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - WeakMap, - WeakMapPrototypeDelete, - WeakMapPrototypeGet, - WeakMapPrototypeHas, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { byteLowerCase } from "internal:ext/web/00_infra.js"; +import { BlobPrototype } from "internal:ext/web/09_file.js"; +import { + errorReadableStream, + readableStreamForRid, + ReadableStreamPrototype, +} from "internal:ext/web/06_streams.js"; +import { extractBody, InnerBody } from "internal:ext/fetch/22_body.js"; +import { + processUrlList, + toInnerRequest, +} from "internal:ext/fetch/23_request.js"; +import { + abortedNetworkError, + fromInnerResponse, + networkError, + nullBodyStatus, + redirectStatus, + toInnerResponse, +} from "internal:ext/fetch/23_response.js"; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeSplice, + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ObjectPrototypeIsPrototypeOf, + Promise, + PromisePrototypeThen, + PromisePrototypeCatch, + SafeArrayIterator, + String, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, + TypeError, + Uint8Array, + Uint8ArrayPrototype, + WeakMap, + WeakMapPrototypeDelete, + WeakMapPrototypeGet, + WeakMapPrototypeHas, + WeakMapPrototypeSet, +} = primordials; - const REQUEST_BODY_HEADER_NAMES = [ - "content-encoding", - "content-language", - "content-location", - "content-type", - ]; +const REQUEST_BODY_HEADER_NAMES = [ + "content-encoding", + "content-language", + "content-location", + "content-type", +]; - const requestBodyReaders = new WeakMap(); +const requestBodyReaders = new WeakMap(); - /** - * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args - * @param {Uint8Array | null} body - * @returns {{ requestRid: number, requestBodyRid: number | null }} - */ - function opFetch(method, url, headers, clientRid, hasBody, bodyLength, body) { - return ops.op_fetch( - method, - url, - headers, - clientRid, - hasBody, - bodyLength, - body, - ); +/** + * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args + * @param {Uint8Array | null} body + * @returns {{ requestRid: number, requestBodyRid: number | null }} + */ +function opFetch(method, url, headers, clientRid, hasBody, bodyLength, body) { + return ops.op_fetch( + method, + url, + headers, + clientRid, + hasBody, + bodyLength, + body, + ); +} + +/** + * @param {number} rid + * @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>} + */ +function opFetchSend(rid) { + return core.opAsync("op_fetch_send", rid); +} + +/** + * @param {number} responseBodyRid + * @param {AbortSignal} [terminator] + * @returns {ReadableStream} + */ +function createResponseBodyStream(responseBodyRid, terminator) { + const readable = readableStreamForRid(responseBodyRid); + + function onAbort() { + errorReadableStream(readable, terminator.reason); + core.tryClose(responseBodyRid); } - /** - * @param {number} rid - * @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>} - */ - function opFetchSend(rid) { - return core.opAsync("op_fetch_send", rid); - } + // TODO(lucacasonato): clean up registration + terminator[abortSignal.add](onAbort); - /** - * @param {number} responseBodyRid - * @param {AbortSignal} [terminator] - * @returns {ReadableStream} - */ - function createResponseBodyStream(responseBodyRid, terminator) { - const readable = readableStreamForRid(responseBodyRid); + return readable; +} - function onAbort() { - errorReadableStream(readable, terminator.reason); - core.tryClose(responseBodyRid); +/** + * @param {InnerRequest} req + * @param {boolean} recursive + * @param {AbortSignal} terminator + * @returns {Promise} + */ +async function mainFetch(req, recursive, terminator) { + if (req.blobUrlEntry !== null) { + if (req.method !== "GET") { + throw new TypeError("Blob URL fetch only supports GET method."); } - // TODO(lucacasonato): clean up registration - terminator[abortSignal.add](onAbort); - - return readable; - } - - /** - * @param {InnerRequest} req - * @param {boolean} recursive - * @param {AbortSignal} terminator - * @returns {Promise} - */ - async function mainFetch(req, recursive, terminator) { - if (req.blobUrlEntry !== null) { - if (req.method !== "GET") { - throw new TypeError("Blob URL fetch only supports GET method."); - } - - const body = new InnerBody(req.blobUrlEntry.stream()); - terminator[abortSignal.add](() => body.error(terminator.reason)); - processUrlList(req.urlList, req.urlListProcessed); - - return { - headerList: [ - ["content-length", String(req.blobUrlEntry.size)], - ["content-type", req.blobUrlEntry.type], - ], - status: 200, - statusMessage: "OK", - body, - type: "basic", - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - urlList: recursive - ? [] - : [...new SafeArrayIterator(req.urlListProcessed)], - }; - } - - /** @type {ReadableStream | Uint8Array | null} */ - let reqBody = null; - - if (req.body !== null) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - req.body.streamOrStatic, - ) - ) { - if ( - req.body.length === null || - ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) - ) { - reqBody = req.body.stream; - } else { - const reader = req.body.stream.getReader(); - WeakMapPrototypeSet(requestBodyReaders, req, reader); - const r1 = await reader.read(); - if (r1.done) { - reqBody = new Uint8Array(0); - } else { - reqBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - WeakMapPrototypeDelete(requestBodyReaders, req); - } - } else { - req.body.streamOrStatic.consumed = true; - reqBody = req.body.streamOrStatic.body; - // TODO(@AaronO): plumb support for StringOrBuffer all the way - reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody; - } - } - - const { requestRid, requestBodyRid, cancelHandleRid } = opFetch( - req.method, - req.currentUrl(), - req.headerList, - req.clientRid, - reqBody !== null, - req.body?.length, - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody) - ? reqBody - : null, - ); - - function onAbort() { - if (cancelHandleRid !== null) { - core.tryClose(cancelHandleRid); - } - if (requestBodyRid !== null) { - core.tryClose(requestBodyRid); - } - } - terminator[abortSignal.add](onAbort); - - let requestSendError; - let requestSendErrorSet = false; - if (requestBodyRid !== null) { - if ( - reqBody === null || - !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody) - ) { - throw new TypeError("Unreachable"); - } - const reader = reqBody.getReader(); - WeakMapPrototypeSet(requestBodyReaders, req, reader); - (async () => { - let done = false; - while (!done) { - let val; - try { - const res = await reader.read(); - done = res.done; - val = res.value; - } catch (err) { - if (terminator.aborted) break; - // TODO(lucacasonato): propagate error into response body stream - requestSendError = err; - requestSendErrorSet = true; - break; - } - if (done) break; - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, val)) { - const error = new TypeError( - "Item in request body ReadableStream is not a Uint8Array", - ); - await reader.cancel(error); - // TODO(lucacasonato): propagate error into response body stream - requestSendError = error; - requestSendErrorSet = true; - break; - } - try { - await core.writeAll(requestBodyRid, val); - } catch (err) { - if (terminator.aborted) break; - await reader.cancel(err); - // TODO(lucacasonato): propagate error into response body stream - requestSendError = err; - requestSendErrorSet = true; - break; - } - } - if (done && !terminator.aborted) { - try { - await core.shutdown(requestBodyRid); - } catch (err) { - if (!terminator.aborted) { - requestSendError = err; - requestSendErrorSet = true; - } - } - } - WeakMapPrototypeDelete(requestBodyReaders, req); - core.tryClose(requestBodyRid); - })(); - } - let resp; - try { - resp = await opFetchSend(requestRid); - } catch (err) { - if (terminator.aborted) return; - if (requestSendErrorSet) { - // if the request body stream errored, we want to propagate that error - // instead of the original error from opFetchSend - throw new TypeError("Failed to fetch: request body stream errored", { - cause: requestSendError, - }); - } - throw err; - } finally { - if (cancelHandleRid !== null) { - core.tryClose(cancelHandleRid); - } - } - if (terminator.aborted) return abortedNetworkError(); - + const body = new InnerBody(req.blobUrlEntry.stream()); + terminator[abortSignal.add](() => body.error(terminator.reason)); processUrlList(req.urlList, req.urlListProcessed); - /** @type {InnerResponse} */ - const response = { - headerList: resp.headers, - status: resp.status, - body: null, - statusMessage: resp.statusText, + return { + headerList: [ + ["content-length", String(req.blobUrlEntry.size)], + ["content-type", req.blobUrlEntry.type], + ], + status: 200, + statusMessage: "OK", + body, type: "basic", url() { if (this.urlList.length == 0) return null; return this.urlList[this.urlList.length - 1]; }, - urlList: req.urlListProcessed, + urlList: recursive + ? [] + : [...new SafeArrayIterator(req.urlListProcessed)], }; - if (redirectStatus(resp.status)) { - switch (req.redirectMode) { - case "error": - core.close(resp.responseRid); - return networkError( - "Encountered redirect while redirect mode is set to 'error'", - ); - case "follow": - core.close(resp.responseRid); - return httpRedirectFetch(req, response, terminator); - case "manual": - break; - } - } + } - if (nullBodyStatus(response.status)) { + /** @type {ReadableStream | Uint8Array | null} */ + let reqBody = null; + + if (req.body !== null) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + req.body.streamOrStatic, + ) + ) { + if ( + req.body.length === null || + ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) + ) { + reqBody = req.body.stream; + } else { + const reader = req.body.stream.getReader(); + WeakMapPrototypeSet(requestBodyReaders, req, reader); + const r1 = await reader.read(); + if (r1.done) { + reqBody = new Uint8Array(0); + } else { + reqBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); + } + WeakMapPrototypeDelete(requestBodyReaders, req); + } + } else { + req.body.streamOrStatic.consumed = true; + reqBody = req.body.streamOrStatic.body; + // TODO(@AaronO): plumb support for StringOrBuffer all the way + reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody; + } + } + + const { requestRid, requestBodyRid, cancelHandleRid } = opFetch( + req.method, + req.currentUrl(), + req.headerList, + req.clientRid, + reqBody !== null, + req.body?.length, + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody) ? reqBody : null, + ); + + function onAbort() { + if (cancelHandleRid !== null) { + core.tryClose(cancelHandleRid); + } + if (requestBodyRid !== null) { + core.tryClose(requestBodyRid); + } + } + terminator[abortSignal.add](onAbort); + + let requestSendError; + let requestSendErrorSet = false; + if (requestBodyRid !== null) { + if ( + reqBody === null || + !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody) + ) { + throw new TypeError("Unreachable"); + } + const reader = reqBody.getReader(); + WeakMapPrototypeSet(requestBodyReaders, req, reader); + (async () => { + let done = false; + while (!done) { + let val; + try { + const res = await reader.read(); + done = res.done; + val = res.value; + } catch (err) { + if (terminator.aborted) break; + // TODO(lucacasonato): propagate error into response body stream + requestSendError = err; + requestSendErrorSet = true; + break; + } + if (done) break; + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, val)) { + const error = new TypeError( + "Item in request body ReadableStream is not a Uint8Array", + ); + await reader.cancel(error); + // TODO(lucacasonato): propagate error into response body stream + requestSendError = error; + requestSendErrorSet = true; + break; + } + try { + await core.writeAll(requestBodyRid, val); + } catch (err) { + if (terminator.aborted) break; + await reader.cancel(err); + // TODO(lucacasonato): propagate error into response body stream + requestSendError = err; + requestSendErrorSet = true; + break; + } + } + if (done && !terminator.aborted) { + try { + await core.shutdown(requestBodyRid); + } catch (err) { + if (!terminator.aborted) { + requestSendError = err; + requestSendErrorSet = true; + } + } + } + WeakMapPrototypeDelete(requestBodyReaders, req); + core.tryClose(requestBodyRid); + })(); + } + let resp; + try { + resp = await opFetchSend(requestRid); + } catch (err) { + if (terminator.aborted) return; + if (requestSendErrorSet) { + // if the request body stream errored, we want to propagate that error + // instead of the original error from opFetchSend + throw new TypeError("Failed to fetch: request body stream errored", { + cause: requestSendError, + }); + } + throw err; + } finally { + if (cancelHandleRid !== null) { + core.tryClose(cancelHandleRid); + } + } + if (terminator.aborted) return abortedNetworkError(); + + processUrlList(req.urlList, req.urlListProcessed); + + /** @type {InnerResponse} */ + const response = { + headerList: resp.headers, + status: resp.status, + body: null, + statusMessage: resp.statusText, + type: "basic", + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + urlList: req.urlListProcessed, + }; + if (redirectStatus(resp.status)) { + switch (req.redirectMode) { + case "error": + core.close(resp.responseRid); + return networkError( + "Encountered redirect while redirect mode is set to 'error'", + ); + case "follow": + core.close(resp.responseRid); + return httpRedirectFetch(req, response, terminator); + case "manual": + break; + } + } + + if (nullBodyStatus(response.status)) { + core.close(resp.responseRid); + } else { + if (req.method === "HEAD" || req.method === "CONNECT") { + response.body = null; core.close(resp.responseRid); } else { - if (req.method === "HEAD" || req.method === "CONNECT") { - response.body = null; - core.close(resp.responseRid); - } else { - response.body = new InnerBody( - createResponseBodyStream(resp.responseRid, terminator), - ); - } + response.body = new InnerBody( + createResponseBodyStream(resp.responseRid, terminator), + ); } + } - if (recursive) return response; + if (recursive) return response; - if (response.urlList.length === 0) { - processUrlList(req.urlList, req.urlListProcessed); - response.urlList = [...new SafeArrayIterator(req.urlListProcessed)]; - } + if (response.urlList.length === 0) { + processUrlList(req.urlList, req.urlListProcessed); + response.urlList = [...new SafeArrayIterator(req.urlListProcessed)]; + } + return response; +} + +/** + * @param {InnerRequest} request + * @param {InnerResponse} response + * @param {AbortSignal} terminator + * @returns {Promise} + */ +function httpRedirectFetch(request, response, terminator) { + const locationHeaders = ArrayPrototypeFilter( + response.headerList, + (entry) => byteLowerCase(entry[0]) === "location", + ); + if (locationHeaders.length === 0) { return response; } + const locationURL = new URL( + locationHeaders[0][1], + response.url() ?? undefined, + ); + if (locationURL.hash === "") { + locationURL.hash = request.currentUrl().hash; + } + if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") { + return networkError("Can not redirect to a non HTTP(s) url"); + } + if (request.redirectCount === 20) { + return networkError("Maximum number of redirects (20) reached"); + } + request.redirectCount++; + if ( + response.status !== 303 && + request.body !== null && + request.body.source === null + ) { + return networkError( + "Can not redeliver a streaming request body after a redirect", + ); + } + if ( + ((response.status === 301 || response.status === 302) && + request.method === "POST") || + (response.status === 303 && + request.method !== "GET" && + request.method !== "HEAD") + ) { + request.method = "GET"; + request.body = null; + for (let i = 0; i < request.headerList.length; i++) { + if ( + ArrayPrototypeIncludes( + REQUEST_BODY_HEADER_NAMES, + byteLowerCase(request.headerList[i][0]), + ) + ) { + ArrayPrototypeSplice(request.headerList, i, 1); + i--; + } + } + } + if (request.body !== null) { + const res = extractBody(request.body.source); + request.body = res.body; + } + ArrayPrototypePush(request.urlList, () => locationURL.href); + return mainFetch(request, true, terminator); +} - /** - * @param {InnerRequest} request - * @param {InnerResponse} response - * @param {AbortSignal} terminator - * @returns {Promise} - */ - function httpRedirectFetch(request, response, terminator) { - const locationHeaders = ArrayPrototypeFilter( - response.headerList, - (entry) => byteLowerCase(entry[0]) === "location", - ); - if (locationHeaders.length === 0) { - return response; +/** + * @param {RequestInfo} input + * @param {RequestInit} init + */ +function fetch(input, init = {}) { + // There is an async dispatch later that causes a stack trace disconnect. + // We reconnect it by assigning the result of that dispatch to `opPromise`, + // awaiting `opPromise` in an inner function also named `fetch()` and + // returning the result from that. + let opPromise = undefined; + // 1. + const result = new Promise((resolve, reject) => { + const prefix = "Failed to call 'fetch'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // 2. + const requestObject = new Request(input, init); + // 3. + const request = toInnerRequest(requestObject); + // 4. + if (requestObject.signal.aborted) { + reject(abortFetch(request, null, requestObject.signal.reason)); + return; } - const locationURL = new URL( - locationHeaders[0][1], - response.url() ?? undefined, - ); - if (locationURL.hash === "") { - locationURL.hash = request.currentUrl().hash; - } - if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") { - return networkError("Can not redirect to a non HTTP(s) url"); - } - if (request.redirectCount === 20) { - return networkError("Maximum number of redirects (20) reached"); - } - request.redirectCount++; - if ( - response.status !== 303 && - request.body !== null && - request.body.source === null - ) { - return networkError( - "Can not redeliver a streaming request body after a redirect", + + // 7. + let responseObject = null; + // 9. + let locallyAborted = false; + // 10. + function onabort() { + locallyAborted = true; + reject( + abortFetch(request, responseObject, requestObject.signal.reason), ); } - if ( - ((response.status === 301 || response.status === 302) && - request.method === "POST") || - (response.status === 303 && - request.method !== "GET" && - request.method !== "HEAD") - ) { - request.method = "GET"; - request.body = null; - for (let i = 0; i < request.headerList.length; i++) { - if ( - ArrayPrototypeIncludes( - REQUEST_BODY_HEADER_NAMES, - byteLowerCase(request.headerList[i][0]), - ) - ) { - ArrayPrototypeSplice(request.headerList, i, 1); - i--; - } - } + requestObject.signal[abortSignal.add](onabort); + + if (!requestObject.headers.has("Accept")) { + ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); } - if (request.body !== null) { - const res = extractBody(request.body.source); - request.body = res.body; + + if (!requestObject.headers.has("Accept-Language")) { + ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]); } - ArrayPrototypePush(request.urlList, () => locationURL.href); - return mainFetch(request, true, terminator); - } - /** - * @param {RequestInfo} input - * @param {RequestInit} init - */ - function fetch(input, init = {}) { - // There is an async dispatch later that causes a stack trace disconnect. - // We reconnect it by assigning the result of that dispatch to `opPromise`, - // awaiting `opPromise` in an inner function also named `fetch()` and - // returning the result from that. - let opPromise = undefined; - // 1. - const result = new Promise((resolve, reject) => { - const prefix = "Failed to call 'fetch'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // 2. - const requestObject = new Request(input, init); - // 3. - const request = toInnerRequest(requestObject); - // 4. - if (requestObject.signal.aborted) { - reject(abortFetch(request, null, requestObject.signal.reason)); - return; - } - - // 7. - let responseObject = null; - // 9. - let locallyAborted = false; - // 10. - function onabort() { - locallyAborted = true; - reject( - abortFetch(request, responseObject, requestObject.signal.reason), - ); - } - requestObject.signal[abortSignal.add](onabort); - - if (!requestObject.headers.has("Accept")) { - ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); - } - - if (!requestObject.headers.has("Accept-Language")) { - ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]); - } - - // 12. - opPromise = PromisePrototypeCatch( - PromisePrototypeThen( - mainFetch(request, false, requestObject.signal), - (response) => { - // 12.1. - if (locallyAborted) return; - // 12.2. - if (response.aborted) { - reject( - abortFetch( - request, - responseObject, - requestObject.signal.reason, - ), - ); - requestObject.signal[abortSignal.remove](onabort); - return; - } - // 12.3. - if (response.type === "error") { - const err = new TypeError( - "Fetch failed: " + (response.error ?? "unknown error"), - ); - reject(err); - requestObject.signal[abortSignal.remove](onabort); - return; - } - responseObject = fromInnerResponse(response, "immutable"); - resolve(responseObject); + // 12. + opPromise = PromisePrototypeCatch( + PromisePrototypeThen( + mainFetch(request, false, requestObject.signal), + (response) => { + // 12.1. + if (locallyAborted) return; + // 12.2. + if (response.aborted) { + reject( + abortFetch( + request, + responseObject, + requestObject.signal.reason, + ), + ); requestObject.signal[abortSignal.remove](onabort); - }, - ), - (err) => { - reject(err); + return; + } + // 12.3. + if (response.type === "error") { + const err = new TypeError( + "Fetch failed: " + (response.error ?? "unknown error"), + ); + reject(err); + requestObject.signal[abortSignal.remove](onabort); + return; + } + responseObject = fromInnerResponse(response, "immutable"); + resolve(responseObject); requestObject.signal[abortSignal.remove](onabort); }, - ); + ), + (err) => { + reject(err); + requestObject.signal[abortSignal.remove](onabort); + }, + ); + }); + if (opPromise) { + PromisePrototypeCatch(result, () => {}); + return (async function fetch() { + await opPromise; + return result; + })(); + } + return result; +} + +function abortFetch(request, responseObject, error) { + if (request.body !== null) { + if (WeakMapPrototypeHas(requestBodyReaders, request)) { + WeakMapPrototypeGet(requestBodyReaders, request).cancel(error); + } else { + request.body.cancel(error); + } + } + if (responseObject !== null) { + const response = toInnerResponse(responseObject); + if (response.body !== null) response.body.error(error); + } + return error; +} + +/** + * Handle the Response argument to the WebAssembly streaming APIs, after + * resolving if it was passed as a promise. This function should be registered + * through `Deno.core.setWasmStreamingCallback`. + * + * @param {any} source The source parameter that the WebAssembly streaming API + * was called with. If it was called with a Promise, `source` is the resolved + * value of that promise. + * @param {number} rid An rid that represents the wasm streaming resource. + */ +function handleWasmStreaming(source, rid) { + // This implements part of + // https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response + try { + const res = webidl.converters["Response"](source, { + prefix: "Failed to call 'WebAssembly.compileStreaming'", + context: "Argument 1", }); - if (opPromise) { - PromisePrototypeCatch(result, () => {}); - return (async function fetch() { - await opPromise; - return result; - })(); - } - return result; - } - function abortFetch(request, responseObject, error) { - if (request.body !== null) { - if (WeakMapPrototypeHas(requestBodyReaders, request)) { - WeakMapPrototypeGet(requestBodyReaders, request).cancel(error); - } else { - request.body.cancel(error); + // 2.3. + // The spec is ambiguous here, see + // https://github.com/WebAssembly/spec/issues/1138. The WPT tests expect + // the raw value of the Content-Type attribute lowercased. We ignore this + // for file:// because file fetches don't have a Content-Type. + if (!StringPrototypeStartsWith(res.url, "file://")) { + const contentType = res.headers.get("Content-Type"); + if ( + typeof contentType !== "string" || + StringPrototypeToLowerCase(contentType) !== "application/wasm" + ) { + throw new TypeError("Invalid WebAssembly content type."); } } - if (responseObject !== null) { - const response = toInnerResponse(responseObject); - if (response.body !== null) response.body.error(error); + + // 2.5. + if (!res.ok) { + throw new TypeError(`HTTP status code ${res.status}`); } - return error; - } - /** - * Handle the Response argument to the WebAssembly streaming APIs, after - * resolving if it was passed as a promise. This function should be registered - * through `Deno.core.setWasmStreamingCallback`. - * - * @param {any} source The source parameter that the WebAssembly streaming API - * was called with. If it was called with a Promise, `source` is the resolved - * value of that promise. - * @param {number} rid An rid that represents the wasm streaming resource. - */ - function handleWasmStreaming(source, rid) { - // This implements part of - // https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response - try { - const res = webidl.converters["Response"](source, { - prefix: "Failed to call 'WebAssembly.compileStreaming'", - context: "Argument 1", - }); + // Pass the resolved URL to v8. + ops.op_wasm_streaming_set_url(rid, res.url); - // 2.3. - // The spec is ambiguous here, see - // https://github.com/WebAssembly/spec/issues/1138. The WPT tests expect - // the raw value of the Content-Type attribute lowercased. We ignore this - // for file:// because file fetches don't have a Content-Type. - if (!StringPrototypeStartsWith(res.url, "file://")) { - const contentType = res.headers.get("Content-Type"); - if ( - typeof contentType !== "string" || - StringPrototypeToLowerCase(contentType) !== "application/wasm" - ) { - throw new TypeError("Invalid WebAssembly content type."); - } - } - - // 2.5. - if (!res.ok) { - throw new TypeError(`HTTP status code ${res.status}`); - } - - // Pass the resolved URL to v8. - ops.op_wasm_streaming_set_url(rid, res.url); - - if (res.body !== null) { - // 2.6. - // Rather than consuming the body as an ArrayBuffer, this passes each - // chunk to the feed as soon as it's available. - PromisePrototypeThen( - (async () => { - const reader = res.body.getReader(); - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) break; - ops.op_wasm_streaming_feed(rid, chunk); - } - })(), - // 2.7 - () => core.close(rid), - // 2.8 - (err) => core.abortWasmStreaming(rid, err), - ); - } else { + if (res.body !== null) { + // 2.6. + // Rather than consuming the body as an ArrayBuffer, this passes each + // chunk to the feed as soon as it's available. + PromisePrototypeThen( + (async () => { + const reader = res.body.getReader(); + while (true) { + const { value: chunk, done } = await reader.read(); + if (done) break; + ops.op_wasm_streaming_feed(rid, chunk); + } + })(), // 2.7 - core.close(rid); - } - } catch (err) { - // 2.8 - core.abortWasmStreaming(rid, err); + () => core.close(rid), + // 2.8 + (err) => core.abortWasmStreaming(rid, err), + ); + } else { + // 2.7 + core.close(rid); } + } catch (err) { + // 2.8 + core.abortWasmStreaming(rid, err); } +} - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.fetch = fetch; - window.__bootstrap.fetch.handleWasmStreaming = handleWasmStreaming; -})(this); +export { fetch, handleWasmStreaming }; diff --git a/ext/fetch/internal.d.ts b/ext/fetch/internal.d.ts index 13a91d2d0e..596e3ffcb5 100644 --- a/ext/fetch/internal.d.ts +++ b/ext/fetch/internal.d.ts @@ -5,106 +5,98 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var fetchUtil: { - requiredArguments(name: string, length: number, required: number): void; - }; +declare var domIterable: { + DomIterableMixin(base: any, dataSymbol: symbol): any; +}; - declare var domIterable: { - DomIterableMixin(base: any, dataSymbol: symbol): any; - }; - - declare namespace headers { - class Headers { - } - type HeaderList = [string, string][]; - function headersFromHeaderList( - list: HeaderList, - guard: - | "immutable" - | "request" - | "request-no-cors" - | "response" - | "none", - ): Headers; - function headerListFromHeaders(headers: Headers): HeaderList; - function fillHeaders(headers: Headers, object: HeadersInit): void; - function getDecodeSplitHeader( - list: HeaderList, - name: string, - ): string[] | null; - function guardFromHeaders( - headers: Headers, - ): "immutable" | "request" | "request-no-cors" | "response" | "none"; - } - - declare namespace formData { - declare type FormData = typeof FormData; - declare function formDataToBlob( - formData: globalThis.FormData, - ): Blob; - declare function parseFormData( - body: Uint8Array, - boundary: string | undefined, - ): FormData; - declare function formDataFromEntries(entries: FormDataEntry[]): FormData; - } - - declare namespace fetchBody { - function mixinBody( - prototype: any, - bodySymbol: symbol, - mimeTypeSymbol: symbol, - ): void; - class InnerBody { - constructor(stream?: ReadableStream); - stream: ReadableStream; - source: null | Uint8Array | Blob | FormData; - length: null | number; - unusable(): boolean; - consume(): Promise; - clone(): InnerBody; - } - function extractBody(object: BodyInit): { - body: InnerBody; - contentType: string | null; - }; - } - - declare namespace fetch { - function toInnerRequest(request: Request): InnerRequest; - function fromInnerRequest( - inner: InnerRequest, - signal: AbortSignal | null, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - skipBody: boolean, - flash: boolean, - ): Request; - function redirectStatus(status: number): boolean; - function nullBodyStatus(status: number): boolean; - function newInnerRequest( - method: string, - url: any, - headerList?: [string, string][], - body?: globalThis.__bootstrap.fetchBody.InnerBody, - ): InnerResponse; - function toInnerResponse(response: Response): InnerResponse; - function fromInnerResponse( - inner: InnerResponse, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - ): Response; - function networkError(error: string): InnerResponse; - } +declare module "internal:ext/fetch/20_headers.js" { + class Headers { } + type HeaderList = [string, string][]; + function headersFromHeaderList( + list: HeaderList, + guard: + | "immutable" + | "request" + | "request-no-cors" + | "response" + | "none", + ): Headers; + function headerListFromHeaders(headers: Headers): HeaderList; + function fillHeaders(headers: Headers, object: HeadersInit): void; + function getDecodeSplitHeader( + list: HeaderList, + name: string, + ): string[] | null; + function guardFromHeaders( + headers: Headers, + ): "immutable" | "request" | "request-no-cors" | "response" | "none"; +} + +declare module "internal:ext/fetch/21_formdata.js" { + type FormData = typeof FormData; + function formDataToBlob( + formData: FormData, + ): Blob; + function parseFormData( + body: Uint8Array, + boundary: string | undefined, + ): FormData; + function formDataFromEntries(entries: FormDataEntry[]): FormData; +} + +declare module "internal:ext/fetch/22_body.js" { + function mixinBody( + prototype: any, + bodySymbol: symbol, + mimeTypeSymbol: symbol, + ): void; + class InnerBody { + constructor(stream?: ReadableStream); + stream: ReadableStream; + source: null | Uint8Array | Blob | FormData; + length: null | number; + unusable(): boolean; + consume(): Promise; + clone(): InnerBody; + } + function extractBody(object: BodyInit): { + body: InnerBody; + contentType: string | null; + }; +} + +declare module "internal:ext/fetch/26_fetch.js" { + function toInnerRequest(request: Request): InnerRequest; + function fromInnerRequest( + inner: InnerRequest, + signal: AbortSignal | null, + guard: + | "request" + | "immutable" + | "request-no-cors" + | "response" + | "none", + skipBody: boolean, + flash: boolean, + ): Request; + function redirectStatus(status: number): boolean; + function nullBodyStatus(status: number): boolean; + function newInnerRequest( + method: string, + url: any, + headerList?: [string, string][], + body?: fetchBody.InnerBody, + ): InnerResponse; + function toInnerResponse(response: Response): InnerResponse; + function fromInnerResponse( + inner: InnerResponse, + guard: + | "request" + | "immutable" + | "request-no-cors" + | "response" + | "none", + ): Response; + function networkError(error: string): InnerResponse; } diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 78a42cd848..93c624dd65 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -97,9 +97,8 @@ where { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_console"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/fetch", - "01_fetch_util.js", "20_headers.js", "21_formdata.js", "22_body.js", diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index ce2e723700..6864fd6385 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -1,510 +1,509 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const __bootstrap = window.__bootstrap; - const { - ArrayPrototypeMap, - ArrayPrototypeJoin, - ObjectDefineProperty, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - Number, - NumberIsSafeInteger, - TypeError, - Uint8Array, - Int32Array, - Uint32Array, - BigInt64Array, - BigUint64Array, - Function, - ReflectHas, - PromisePrototypeThen, - MathMax, - MathCeil, - SafeMap, - SafeArrayIterator, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeJoin, + ObjectDefineProperty, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + Number, + NumberIsSafeInteger, + TypeError, + Uint8Array, + Int32Array, + Uint32Array, + BigInt64Array, + BigUint64Array, + Function, + ReflectHas, + PromisePrototypeThen, + MathMax, + MathCeil, + SafeMap, + SafeArrayIterator, +} = primordials; - const U32_BUFFER = new Uint32Array(2); - const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer); - const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer); - class UnsafePointerView { - pointer; +const U32_BUFFER = new Uint32Array(2); +const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer); +const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer); +class UnsafePointerView { + pointer; - constructor(pointer) { - this.pointer = pointer; - } - - getBool(offset = 0) { - return ops.op_ffi_read_bool( - this.pointer, - offset, - ); - } - - getUint8(offset = 0) { - return ops.op_ffi_read_u8( - this.pointer, - offset, - ); - } - - getInt8(offset = 0) { - return ops.op_ffi_read_i8( - this.pointer, - offset, - ); - } - - getUint16(offset = 0) { - return ops.op_ffi_read_u16( - this.pointer, - offset, - ); - } - - getInt16(offset = 0) { - return ops.op_ffi_read_i16( - this.pointer, - offset, - ); - } - - getUint32(offset = 0) { - return ops.op_ffi_read_u32( - this.pointer, - offset, - ); - } - - getInt32(offset = 0) { - return ops.op_ffi_read_i32( - this.pointer, - offset, - ); - } - - getBigUint64(offset = 0) { - ops.op_ffi_read_u64( - this.pointer, - offset, - U32_BUFFER, - ); - return U64_BUFFER[0]; - } - - getBigInt64(offset = 0) { - ops.op_ffi_read_i64( - this.pointer, - offset, - U32_BUFFER, - ); - return I64_BUFFER[0]; - } - - getFloat32(offset = 0) { - return ops.op_ffi_read_f32( - this.pointer, - offset, - ); - } - - getFloat64(offset = 0) { - return ops.op_ffi_read_f64( - this.pointer, - offset, - ); - } - - getCString(offset = 0) { - return ops.op_ffi_cstr_read( - this.pointer, - offset, - ); - } - - static getCString(pointer, offset = 0) { - return ops.op_ffi_cstr_read( - pointer, - offset, - ); - } - - getArrayBuffer(byteLength, offset = 0) { - return ops.op_ffi_get_buf( - this.pointer, - offset, - byteLength, - ); - } - - static getArrayBuffer(pointer, byteLength, offset = 0) { - return ops.op_ffi_get_buf( - pointer, - offset, - byteLength, - ); - } - - copyInto(destination, offset = 0) { - ops.op_ffi_buf_copy_into( - this.pointer, - offset, - destination, - destination.byteLength, - ); - } - - static copyInto(pointer, destination, offset = 0) { - ops.op_ffi_buf_copy_into( - pointer, - offset, - destination, - destination.byteLength, - ); - } + constructor(pointer) { + this.pointer = pointer; } - const OUT_BUFFER = new Uint32Array(2); - const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); - class UnsafePointer { - static of(value) { - if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { - return value.pointer; - } - ops.op_ffi_ptr_of(value, OUT_BUFFER); - const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1]; - if (NumberIsSafeInteger(result)) { - return result; - } - return OUT_BUFFER_64[0]; - } + getBool(offset = 0) { + return ops.op_ffi_read_bool( + this.pointer, + offset, + ); } - class UnsafeFnPointer { - pointer; - definition; - #structSize; + getUint8(offset = 0) { + return ops.op_ffi_read_u8( + this.pointer, + offset, + ); + } - constructor(pointer, definition) { - this.pointer = pointer; - this.definition = definition; - this.#structSize = isStruct(definition.result) - ? getTypeSizeAndAlignment(definition.result)[0] - : null; + getInt8(offset = 0) { + return ops.op_ffi_read_i8( + this.pointer, + offset, + ); + } + + getUint16(offset = 0) { + return ops.op_ffi_read_u16( + this.pointer, + offset, + ); + } + + getInt16(offset = 0) { + return ops.op_ffi_read_i16( + this.pointer, + offset, + ); + } + + getUint32(offset = 0) { + return ops.op_ffi_read_u32( + this.pointer, + offset, + ); + } + + getInt32(offset = 0) { + return ops.op_ffi_read_i32( + this.pointer, + offset, + ); + } + + getBigUint64(offset = 0) { + ops.op_ffi_read_u64( + this.pointer, + offset, + U32_BUFFER, + ); + return U64_BUFFER[0]; + } + + getBigInt64(offset = 0) { + ops.op_ffi_read_i64( + this.pointer, + offset, + U32_BUFFER, + ); + return I64_BUFFER[0]; + } + + getFloat32(offset = 0) { + return ops.op_ffi_read_f32( + this.pointer, + offset, + ); + } + + getFloat64(offset = 0) { + return ops.op_ffi_read_f64( + this.pointer, + offset, + ); + } + + getCString(offset = 0) { + return ops.op_ffi_cstr_read( + this.pointer, + offset, + ); + } + + static getCString(pointer, offset = 0) { + return ops.op_ffi_cstr_read( + pointer, + offset, + ); + } + + getArrayBuffer(byteLength, offset = 0) { + return ops.op_ffi_get_buf( + this.pointer, + offset, + byteLength, + ); + } + + static getArrayBuffer(pointer, byteLength, offset = 0) { + return ops.op_ffi_get_buf( + pointer, + offset, + byteLength, + ); + } + + copyInto(destination, offset = 0) { + ops.op_ffi_buf_copy_into( + this.pointer, + offset, + destination, + destination.byteLength, + ); + } + + static copyInto(pointer, destination, offset = 0) { + ops.op_ffi_buf_copy_into( + pointer, + offset, + destination, + destination.byteLength, + ); + } +} + +const OUT_BUFFER = new Uint32Array(2); +const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); +class UnsafePointer { + static of(value) { + if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { + return value.pointer; } + ops.op_ffi_ptr_of(value, OUT_BUFFER); + const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1]; + if (NumberIsSafeInteger(result)) { + return result; + } + return OUT_BUFFER_64[0]; + } +} - call(...parameters) { - if (this.definition.nonblocking) { - if (this.#structSize === null) { - return core.opAsync( +class UnsafeFnPointer { + pointer; + definition; + #structSize; + + constructor(pointer, definition) { + this.pointer = pointer; + this.definition = definition; + this.#structSize = isStruct(definition.result) + ? getTypeSizeAndAlignment(definition.result)[0] + : null; + } + + call(...parameters) { + if (this.definition.nonblocking) { + if (this.#structSize === null) { + return core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + ); + } else { + const buffer = new Uint8Array(this.#structSize); + return PromisePrototypeThen( + core.opAsync( "op_ffi_call_ptr_nonblocking", this.pointer, this.definition, parameters, - ); - } else { - const buffer = new Uint8Array(this.#structSize); - return PromisePrototypeThen( - core.opAsync( - "op_ffi_call_ptr_nonblocking", - this.pointer, - this.definition, - parameters, - buffer, - ), - () => buffer, - ); - } - } else { - if (this.#structSize === null) { - return ops.op_ffi_call_ptr( - this.pointer, - this.definition, - parameters, - ); - } else { - const buffer = new Uint8Array(this.#structSize); - ops.op_ffi_call_ptr( - this.pointer, - this.definition, - parameters, buffer, - ); - return buffer; - } - } - } - } - - function isReturnedAsBigInt(type) { - return type === "buffer" || type === "pointer" || type === "function" || - type === "u64" || type === "i64" || - type === "usize" || type === "isize"; - } - - function isI64(type) { - return type === "i64" || type === "isize"; - } - - function isStruct(type) { - return typeof type === "object" && type !== null && - typeof type.struct === "object"; - } - - function getTypeSizeAndAlignment(type, cache = new SafeMap()) { - if (isStruct(type)) { - const cached = cache.get(type); - if (cached !== undefined) { - if (cached === null) { - throw new TypeError("Recursive struct definition"); - } - return cached; - } - cache.set(type, null); - let size = 0; - let alignment = 1; - for (const field of new SafeArrayIterator(type.struct)) { - const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment( - field, - cache, - ); - alignment = MathMax(alignment, fieldAlign); - size = MathCeil(size / fieldAlign) * fieldAlign; - size += fieldSize; - } - size = MathCeil(size / alignment) * alignment; - cache.set(type, size); - return [size, alignment]; - } - - switch (type) { - case "bool": - case "u8": - case "i8": - return [1, 1]; - case "u16": - case "i16": - return [2, 2]; - case "u32": - case "i32": - case "f32": - return [4, 4]; - case "u64": - case "i64": - case "f64": - case "pointer": - case "buffer": - case "function": - case "usize": - case "isize": - return [8, 8]; - default: - throw new TypeError(`Unsupported type: ${type}`); - } - } - - class UnsafeCallback { - #refcount; - // Internal promise only meant to keep Deno from exiting - #refpromise; - #rid; - definition; - callback; - pointer; - - constructor(definition, callback) { - if (definition.nonblocking) { - throw new TypeError( - "Invalid UnsafeCallback, cannot be nonblocking", + ), + () => buffer, ); } - const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( - definition, - callback, + } else { + if (this.#structSize === null) { + return ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, + ); + } else { + const buffer = new Uint8Array(this.#structSize); + ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, + buffer, + ); + return buffer; + } + } + } +} + +function isReturnedAsBigInt(type) { + return type === "buffer" || type === "pointer" || type === "function" || + type === "u64" || type === "i64" || + type === "usize" || type === "isize"; +} + +function isI64(type) { + return type === "i64" || type === "isize"; +} + +function isStruct(type) { + return typeof type === "object" && type !== null && + typeof type.struct === "object"; +} + +function getTypeSizeAndAlignment(type, cache = new SafeMap()) { + if (isStruct(type)) { + const cached = cache.get(type); + if (cached !== undefined) { + if (cached === null) { + throw new TypeError("Recursive struct definition"); + } + return cached; + } + cache.set(type, null); + let size = 0; + let alignment = 1; + for (const field of new SafeArrayIterator(type.struct)) { + const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment( + field, + cache, ); - this.#refcount = 0; - this.#rid = rid; - this.pointer = pointer; - this.definition = definition; - this.callback = callback; - } - - ref() { - if (this.#refcount++ === 0) { - this.#refpromise = core.opAsync( - "op_ffi_unsafe_callback_ref", - this.#rid, - ); - } - return this.#refcount; - } - - unref() { - // Only decrement refcount if it is positive, and only - // unref the callback if refcount reaches zero. - if (this.#refcount > 0 && --this.#refcount === 0) { - ops.op_ffi_unsafe_callback_unref(this.#rid); - } - return this.#refcount; - } - - close() { - this.#refcount = 0; - core.close(this.#rid); + alignment = MathMax(alignment, fieldAlign); + size = MathCeil(size / fieldAlign) * fieldAlign; + size += fieldSize; } + size = MathCeil(size / alignment) * alignment; + cache.set(type, size); + return [size, alignment]; } - const UnsafeCallbackPrototype = UnsafeCallback.prototype; + switch (type) { + case "bool": + case "u8": + case "i8": + return [1, 1]; + case "u16": + case "i16": + return [2, 2]; + case "u32": + case "i32": + case "f32": + return [4, 4]; + case "u64": + case "i64": + case "f64": + case "pointer": + case "buffer": + case "function": + case "usize": + case "isize": + return [8, 8]; + default: + throw new TypeError(`Unsupported type: ${type}`); + } +} - class DynamicLibrary { - #rid; - symbols = {}; +class UnsafeCallback { + #refcount; + // Internal promise only meant to keep Deno from exiting + #refpromise; + #rid; + definition; + callback; + pointer; - constructor(path, symbols) { - ({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols })); - for (const symbol in symbols) { - if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) { - continue; + constructor(definition, callback) { + if (definition.nonblocking) { + throw new TypeError( + "Invalid UnsafeCallback, cannot be nonblocking", + ); + } + const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( + definition, + callback, + ); + this.#refcount = 0; + this.#rid = rid; + this.pointer = pointer; + this.definition = definition; + this.callback = callback; + } + + ref() { + if (this.#refcount++ === 0) { + this.#refpromise = core.opAsync( + "op_ffi_unsafe_callback_ref", + this.#rid, + ); + } + return this.#refcount; + } + + unref() { + // Only decrement refcount if it is positive, and only + // unref the callback if refcount reaches zero. + if (this.#refcount > 0 && --this.#refcount === 0) { + ops.op_ffi_unsafe_callback_unref(this.#rid); + } + return this.#refcount; + } + + close() { + this.#refcount = 0; + core.close(this.#rid); + } +} + +const UnsafeCallbackPrototype = UnsafeCallback.prototype; + +class DynamicLibrary { + #rid; + symbols = {}; + + constructor(path, symbols) { + ({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols })); + for (const symbol in symbols) { + if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) { + continue; + } + + if (ReflectHas(symbols[symbol], "type")) { + const type = symbols[symbol].type; + if (type === "void") { + throw new TypeError( + "Foreign symbol of type 'void' is not supported.", + ); } - if (ReflectHas(symbols[symbol], "type")) { - const type = symbols[symbol].type; - if (type === "void") { - throw new TypeError( - "Foreign symbol of type 'void' is not supported.", - ); - } + const name = symbols[symbol].name || symbol; + const value = ops.op_ffi_get_static( + this.#rid, + name, + type, + ); + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value, + writable: false, + }, + ); + continue; + } + const resultType = symbols[symbol].result; + const isStructResult = isStruct(resultType); + const structSize = isStructResult + ? getTypeSizeAndAlignment(resultType)[0] + : 0; + const needsUnpacking = isReturnedAsBigInt(resultType); - const name = symbols[symbol].name || symbol; - const value = ops.op_ffi_get_static( - this.#rid, - name, - type, - ); - ObjectDefineProperty( - this.symbols, - symbol, - { - configurable: false, - enumerable: true, - value, - writable: false, + const isNonBlocking = symbols[symbol].nonblocking; + if (isNonBlocking) { + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value: (...parameters) => { + if (isStructResult) { + const buffer = new Uint8Array(structSize); + const ret = core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + buffer, + ); + return PromisePrototypeThen( + ret, + () => buffer, + ); + } else { + return core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + ); + } }, - ); - continue; - } - const resultType = symbols[symbol].result; - const isStructResult = isStruct(resultType); - const structSize = isStructResult - ? getTypeSizeAndAlignment(resultType)[0] - : 0; - const needsUnpacking = isReturnedAsBigInt(resultType); + writable: false, + }, + ); + } - const isNonBlocking = symbols[symbol].nonblocking; - if (isNonBlocking) { - ObjectDefineProperty( - this.symbols, - symbol, - { - configurable: false, - enumerable: true, - value: (...parameters) => { - if (isStructResult) { - const buffer = new Uint8Array(structSize); - const ret = core.opAsync( - "op_ffi_call_nonblocking", - this.#rid, - symbol, - parameters, - buffer, - ); - return PromisePrototypeThen( - ret, - () => buffer, - ); - } else { - return core.opAsync( - "op_ffi_call_nonblocking", - this.#rid, - symbol, - parameters, - ); - } - }, - writable: false, - }, - ); - } + if (needsUnpacking && !isNonBlocking) { + const call = this.symbols[symbol]; + const parameters = symbols[symbol].parameters; + const vi = new Int32Array(2); + const vui = new Uint32Array(vi.buffer); + const b = new BigInt64Array(vi.buffer); - if (needsUnpacking && !isNonBlocking) { - const call = this.symbols[symbol]; - const parameters = symbols[symbol].parameters; - const vi = new Int32Array(2); - const vui = new Uint32Array(vi.buffer); - const b = new BigInt64Array(vi.buffer); - - const params = ArrayPrototypeJoin( - ArrayPrototypeMap(parameters, (_, index) => `p${index}`), - ", ", - ); - // Make sure V8 has no excuse to not optimize this function. - this.symbols[symbol] = new Function( - "vi", - "vui", - "b", - "call", - "NumberIsSafeInteger", - "Number", - `return function (${params}) { + const params = ArrayPrototypeJoin( + ArrayPrototypeMap(parameters, (_, index) => `p${index}`), + ", ", + ); + // Make sure V8 has no excuse to not optimize this function. + this.symbols[symbol] = new Function( + "vi", + "vui", + "b", + "call", + "NumberIsSafeInteger", + "Number", + `return function (${params}) { call(${params}${parameters.length > 0 ? ", " : ""}vi); ${ - isI64(resultType) - ? `const n1 = Number(b[0])` - : `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64 - }; + isI64(resultType) + ? `const n1 = Number(b[0])` + : `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64 + }; if (NumberIsSafeInteger(n1)) return n1; return b[0]; }`, - )(vi, vui, b, call, NumberIsSafeInteger, Number); - } else if (isStructResult && !isNonBlocking) { - const call = this.symbols[symbol]; - const parameters = symbols[symbol].parameters; - const params = ArrayPrototypeJoin( - ArrayPrototypeMap(parameters, (_, index) => `p${index}`), - ", ", - ); - this.symbols[symbol] = new Function( - "call", - `return function (${params}) { + )(vi, vui, b, call, NumberIsSafeInteger, Number); + } else if (isStructResult && !isNonBlocking) { + const call = this.symbols[symbol]; + const parameters = symbols[symbol].parameters; + const params = ArrayPrototypeJoin( + ArrayPrototypeMap(parameters, (_, index) => `p${index}`), + ", ", + ); + this.symbols[symbol] = new Function( + "call", + `return function (${params}) { const buffer = new Uint8Array(${structSize}); call(${params}${parameters.length > 0 ? ", " : ""}buffer); return buffer; }`, - )(call); - } + )(call); } } - - close() { - core.close(this.#rid); - } } - function dlopen(path, symbols) { - // URL support is progressively enhanced by util in `runtime/js`. - const pathFromURL = __bootstrap.util.pathFromURL ?? ((p) => p); - return new DynamicLibrary(pathFromURL(path), symbols); + close() { + core.close(this.#rid); } +} - window.__bootstrap.ffi = { - dlopen, - UnsafeCallback, - UnsafePointer, - UnsafePointerView, - UnsafeFnPointer, - }; -})(this); +function dlopen(path, symbols) { + // TODO(@crowlKats): remove me + // URL support is progressively enhanced by util in `runtime/js`. + const pathFromURL = internals.pathFromURL ?? ((p) => p); + return new DynamicLibrary(pathFromURL(path), symbols); +} + +export { + dlopen, + UnsafeCallback, + UnsafeFnPointer, + UnsafePointer, + UnsafePointerView, +}; diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 88e7884575..0034a0d336 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -84,7 +84,7 @@ pub(crate) struct FfiState { pub fn init(unstable: bool) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/ffi", "00_ffi.js", )) diff --git a/ext/flash/01_http.js b/ext/flash/01_http.js index 357bdfbe2f..d2f967adad 100644 --- a/ext/flash/01_http.js +++ b/ext/flash/01_http.js @@ -1,611 +1,596 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import { BlobPrototype } from "internal:ext/web/09_file.js"; +import { TcpConn } from "internal:ext/net/01_net.js"; +import { toInnerResponse } from "internal:ext/fetch/23_response.js"; +import { _flash, fromFlashRequest } from "internal:ext/fetch/23_request.js"; +import { Event } from "internal:ext/web/02_event.js"; +import { + _state, + getReadableStreamResourceBacking, + ReadableStream, + readableStreamClose, + ReadableStreamPrototype, +} from "internal:ext/web/06_streams.js"; +import { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _serverHandleIdleTimeout, + WebSocket, +} from "internal:ext/websocket/01_websocket.js"; +import { _ws } from "internal:ext/http/01_http.js"; +const { + ObjectPrototypeIsPrototypeOf, + PromisePrototype, + PromisePrototypeCatch, + PromisePrototypeThen, + SafePromiseAll, + TypedArrayPrototypeSubarray, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; -((window) => { - const { BlobPrototype } = window.__bootstrap.file; - const { TcpConn } = window.__bootstrap.net; - const { fromFlashRequest, toInnerResponse, _flash } = - window.__bootstrap.fetch; - const core = window.Deno.core; - const { Event } = window.__bootstrap.event; - const { - ReadableStream, - ReadableStreamPrototype, - getReadableStreamResourceBacking, - readableStreamClose, - _state, - } = window.__bootstrap.streams; - const { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - } = window.__bootstrap.webSocket; - const { _ws } = window.__bootstrap.http; - const { - ObjectPrototypeIsPrototypeOf, - PromisePrototype, - PromisePrototypeCatch, - PromisePrototypeThen, - SafePromiseAll, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +const statusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +}; - const statusCodes = { - 100: "Continue", - 101: "Switching Protocols", - 102: "Processing", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 208: "Already Reported", - 226: "IM Used", - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 308: "Permanent Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Payload Too Large", - 414: "URI Too Long", - 415: "Unsupported Media Type", - 416: "Range Not Satisfiable", - 418: "I'm a teapot", - 421: "Misdirected Request", - 422: "Unprocessable Entity", - 423: "Locked", - 424: "Failed Dependency", - 426: "Upgrade Required", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates", - 507: "Insufficient Storage", - 508: "Loop Detected", - 510: "Not Extended", - 511: "Network Authentication Required", - }; +const methods = { + 0: "GET", + 1: "HEAD", + 2: "CONNECT", + 3: "PUT", + 4: "DELETE", + 5: "OPTIONS", + 6: "TRACE", + 7: "POST", + 8: "PATCH", +}; - const methods = { - 0: "GET", - 1: "HEAD", - 2: "CONNECT", - 3: "PUT", - 4: "DELETE", - 5: "OPTIONS", - 6: "TRACE", - 7: "POST", - 8: "PATCH", - }; +let dateInterval; +let date; - let dateInterval; - let date; - - // Construct an HTTP response message. - // All HTTP/1.1 messages consist of a start-line followed by a sequence - // of octets. +// Construct an HTTP response message. +// All HTTP/1.1 messages consist of a start-line followed by a sequence +// of octets. +// +// HTTP-message = start-line +// *( header-field CRLF ) +// CRLF +// [ message-body ] +// +function http1Response( + method, + status, + headerList, + body, + bodyLen, + earlyEnd = false, +) { + // HTTP uses a "." numbering scheme + // HTTP-version = HTTP-name "/" DIGIT "." DIGIT + // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive // - // HTTP-message = start-line - // *( header-field CRLF ) - // CRLF - // [ message-body ] - // - function http1Response( - method, - status, - headerList, - body, - bodyLen, - earlyEnd = false, - ) { - // HTTP uses a "." numbering scheme - // HTTP-version = HTTP-name "/" DIGIT "." DIGIT - // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive - // - // status-line = HTTP-version SP status-code SP reason-phrase CRLF - // Date header: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2 - let str = `HTTP/1.1 ${status} ${statusCodes[status]}\r\nDate: ${date}\r\n`; - for (let i = 0; i < headerList.length; ++i) { - const { 0: name, 1: value } = headerList[i]; - // header-field = field-name ":" OWS field-value OWS - str += `${name}: ${value}\r\n`; - } - - // https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.6 - if (status === 205 || status === 304) { - // MUST NOT generate a payload in a 205 response. - // indicate a zero-length body for the response by - // including a Content-Length header field with a value of 0. - str += "Content-Length: 0\r\n\r\n"; - return str; - } - - // MUST NOT send Content-Length or Transfer-Encoding if status code is 1xx or 204. - if (status === 204 || status < 200) { - str += "\r\n"; - return str; - } - - if (earlyEnd === true) { - return str; - } - - // null body status is validated by inititalizeAResponse in ext/fetch - if (body !== null && body !== undefined) { - str += `Content-Length: ${bodyLen}\r\n\r\n`; - } else { - str += "Transfer-Encoding: chunked\r\n\r\n"; - return str; - } - - // A HEAD request. - if (method === 1) return str; - - if (typeof body === "string") { - str += body ?? ""; - } else { - const head = core.encode(str); - const response = new Uint8Array(head.byteLength + body.byteLength); - response.set(head, 0); - response.set(body, head.byteLength); - return response; - } + // status-line = HTTP-version SP status-code SP reason-phrase CRLF + // Date header: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2 + let str = `HTTP/1.1 ${status} ${statusCodes[status]}\r\nDate: ${date}\r\n`; + for (let i = 0; i < headerList.length; ++i) { + const { 0: name, 1: value } = headerList[i]; + // header-field = field-name ":" OWS field-value OWS + str += `${name}: ${value}\r\n`; + } + // https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.6 + if (status === 205 || status === 304) { + // MUST NOT generate a payload in a 205 response. + // indicate a zero-length body for the response by + // including a Content-Length header field with a value of 0. + str += "Content-Length: 0\r\n\r\n"; return str; } - function prepareFastCalls() { - return core.ops.op_flash_make_request(); + // MUST NOT send Content-Length or Transfer-Encoding if status code is 1xx or 204. + if (status === 204 || status < 200) { + str += "\r\n"; + return str; } - function hostnameForDisplay(hostname) { - // If the hostname is "0.0.0.0", we display "localhost" in console - // because browsers in Windows don't resolve "0.0.0.0". - // See the discussion in https://github.com/denoland/deno_std/issues/1165 - return hostname === "0.0.0.0" ? "localhost" : hostname; + if (earlyEnd === true) { + return str; } - function writeFixedResponse( - server, - requestId, - response, - responseLen, - end, - respondFast, - ) { - let nwritten = 0; - // TypedArray - if (typeof response !== "string") { - nwritten = respondFast(requestId, response, end); - } else { - // string - nwritten = core.ops.op_flash_respond( - server, - requestId, - response, - end, - ); - } - - if (nwritten < responseLen) { - core.opAsync( - "op_flash_respond_async", - server, - requestId, - response.slice(nwritten), - end, - ); - } + // null body status is validated by inititalizeAResponse in ext/fetch + if (body !== null && body !== undefined) { + str += `Content-Length: ${bodyLen}\r\n\r\n`; + } else { + str += "Transfer-Encoding: chunked\r\n\r\n"; + return str; } - // TODO(@littledivy): Woah woah, cut down the number of arguments. - async function handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - tryRespondChunked, - ) { - // there might've been an HTTP upgrade. - if (resp === undefined) { - return; - } - const innerResp = toInnerResponse(resp); - // If response body length is known, it will be sent synchronously in a - // single op, in other case a "response body" resource will be created and - // we'll be streaming it. - /** @type {ReadableStream | Uint8Array | null} */ - let respBody = null; - let isStreamingResponseBody = false; - if (innerResp.body !== null) { - if (typeof innerResp.body.streamOrStatic?.body === "string") { - if (innerResp.body.streamOrStatic.consumed === true) { - throw new TypeError("Body is unusable."); - } - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; - isStreamingResponseBody = false; - } else if ( + // A HEAD request. + if (method === 1) return str; + + if (typeof body === "string") { + str += body ?? ""; + } else { + const head = core.encode(str); + const response = new Uint8Array(head.byteLength + body.byteLength); + response.set(head, 0); + response.set(body, head.byteLength); + return response; + } + + return str; +} + +function prepareFastCalls() { + return ops.op_flash_make_request(); +} + +function hostnameForDisplay(hostname) { + // If the hostname is "0.0.0.0", we display "localhost" in console + // because browsers in Windows don't resolve "0.0.0.0". + // See the discussion in https://github.com/denoland/deno_std/issues/1165 + return hostname === "0.0.0.0" ? "localhost" : hostname; +} + +function writeFixedResponse( + server, + requestId, + response, + responseLen, + end, + respondFast, +) { + let nwritten = 0; + // TypedArray + if (typeof response !== "string") { + nwritten = respondFast(requestId, response, end); + } else { + // string + nwritten = ops.op_flash_respond( + server, + requestId, + response, + end, + ); + } + + if (nwritten < responseLen) { + core.opAsync( + "op_flash_respond_async", + server, + requestId, + response.slice(nwritten), + end, + ); + } +} + +// TODO(@littledivy): Woah woah, cut down the number of arguments. +async function handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, +) { + // there might've been an HTTP upgrade. + if (resp === undefined) { + return; + } + const innerResp = toInnerResponse(resp); + // If response body length is known, it will be sent synchronously in a + // single op, in other case a "response body" resource will be created and + // we'll be streaming it. + /** @type {ReadableStream | Uint8Array | null} */ + let respBody = null; + let isStreamingResponseBody = false; + if (innerResp.body !== null) { + if (typeof innerResp.body.streamOrStatic?.body === "string") { + if (innerResp.body.streamOrStatic.consumed === true) { + throw new TypeError("Body is unusable."); + } + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; + isStreamingResponseBody = false; + } else if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + innerResp.body.streamOrStatic, + ) + ) { + if (innerResp.body.unusable()) { + throw new TypeError("Body is unusable."); + } + if ( + innerResp.body.length === null || ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - innerResp.body.streamOrStatic, + BlobPrototype, + innerResp.body.source, ) ) { - if (innerResp.body.unusable()) { - throw new TypeError("Body is unusable."); - } - if ( - innerResp.body.length === null || - ObjectPrototypeIsPrototypeOf( - BlobPrototype, - innerResp.body.source, - ) - ) { - respBody = innerResp.body.stream; - } else { - const reader = innerResp.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - respBody = new Uint8Array(0); - } else { - respBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - } - isStreamingResponseBody = !( - typeof respBody === "string" || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) - ); + respBody = innerResp.body.stream; } else { - if (innerResp.body.streamOrStatic.consumed === true) { - throw new TypeError("Body is unusable."); - } - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; - } - } else { - respBody = new Uint8Array(0); - } - - const ws = resp[_ws]; - if (isStreamingResponseBody === false) { - const length = respBody.byteLength || core.byteLength(respBody); - const responseStr = http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - respBody, - length, - ); - writeFixedResponse( - serverId, - i, - responseStr, - length, - !ws, // Don't close socket if there is a deferred websocket upgrade. - respondFast, - ); - } - - return (async () => { - if (!ws) { - if (hasBody && body[_state] !== "closed") { - // TODO(@littledivy): Optimize by draining in a single op. - try { - await req.arrayBuffer(); - } catch { /* pass */ } - } - } - - if (isStreamingResponseBody === true) { - const resourceBacking = getReadableStreamResourceBacking(respBody); - if (resourceBacking) { - if (respBody.locked) { - throw new TypeError("ReadableStream is locked."); - } - const reader = respBody.getReader(); // Aquire JS lock. - try { - PromisePrototypeThen( - core.opAsync( - "op_flash_write_resource", - http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - 0, // Content-Length will be set by the op. - null, - true, - ), - serverId, - i, - resourceBacking.rid, - resourceBacking.autoClose, - ), - () => { - // Release JS lock. - readableStreamClose(respBody); - }, - ); - } catch (error) { - await reader.cancel(error); - throw error; - } + const reader = innerResp.body.stream.getReader(); + const r1 = await reader.read(); + if (r1.done) { + respBody = new Uint8Array(0); } else { - const reader = respBody.getReader(); - - // Best case: sends headers + first chunk in a single go. - const { value, done } = await reader.read(); - writeFixedResponse( - serverId, - i, - http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - respBody.byteLength, - null, - ), - respBody.byteLength, - false, - respondFast, - ); - - await tryRespondChunked( - i, - value, - done, - ); - - if (!done) { - while (true) { - const chunk = await reader.read(); - await respondChunked( - i, - chunk.value, - chunk.done, - ); - if (chunk.done) break; - } - } + respBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } } - - if (ws) { - const wsRid = await core.opAsync( - "op_flash_upgrade_websocket", - serverId, - i, - ); - ws[_rid] = wsRid; - ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); - - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); + isStreamingResponseBody = !( + typeof respBody === "string" || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + ); + } else { + if (innerResp.body.streamOrStatic.consumed === true) { + throw new TypeError("Body is unusable."); } - })(); + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; + } + } else { + respBody = new Uint8Array(0); } - function createServe(opFn) { - return async function serve(arg1, arg2) { - let options = undefined; - let handler = undefined; - if (typeof arg1 === "function") { - handler = arg1; - options = arg2; - } else if (typeof arg2 === "function") { - handler = arg2; - options = arg1; + const ws = resp[_ws]; + if (isStreamingResponseBody === false) { + const length = respBody.byteLength || core.byteLength(respBody); + const responseStr = http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + respBody, + length, + ); + writeFixedResponse( + serverId, + i, + responseStr, + length, + !ws, // Don't close socket if there is a deferred websocket upgrade. + respondFast, + ); + } + + return (async () => { + if (!ws) { + if (hasBody && body[_state] !== "closed") { + // TODO(@littledivy): Optimize by draining in a single op. + try { + await req.arrayBuffer(); + } catch { /* pass */ } + } + } + + if (isStreamingResponseBody === true) { + const resourceBacking = getReadableStreamResourceBacking(respBody); + if (resourceBacking) { + if (respBody.locked) { + throw new TypeError("ReadableStream is locked."); + } + const reader = respBody.getReader(); // Aquire JS lock. + try { + PromisePrototypeThen( + core.opAsync( + "op_flash_write_resource", + http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + 0, // Content-Length will be set by the op. + null, + true, + ), + serverId, + i, + resourceBacking.rid, + resourceBacking.autoClose, + ), + () => { + // Release JS lock. + readableStreamClose(respBody); + }, + ); + } catch (error) { + await reader.cancel(error); + throw error; + } } else { - options = arg1; - } - if (handler === undefined) { - if (options === undefined) { - throw new TypeError( - "No handler was provided, so an options bag is mandatory.", - ); - } - handler = options.handler; - } - if (typeof handler !== "function") { - throw new TypeError("A handler function must be provided."); - } - if (options === undefined) { - options = {}; - } + const reader = respBody.getReader(); - const signal = options.signal; - - const onError = options.onError ?? function (error) { - console.error(error); - return new Response("Internal Server Error", { status: 500 }); - }; - - const onListen = options.onListen ?? function ({ port }) { - console.log( - `Listening on http://${ - hostnameForDisplay(listenOpts.hostname) - }:${port}/`, + // Best case: sends headers + first chunk in a single go. + const { value, done } = await reader.read(); + writeFixedResponse( + serverId, + i, + http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + respBody.byteLength, + null, + ), + respBody.byteLength, + false, + respondFast, ); - }; - const listenOpts = { - hostname: options.hostname ?? "127.0.0.1", - port: options.port ?? 9000, - reuseport: options.reusePort ?? false, - }; - if (options.cert || options.key) { - if (!options.cert || !options.key) { - throw new TypeError( - "Both cert and key must be provided to enable HTTPS.", - ); - } - listenOpts.cert = options.cert; - listenOpts.key = options.key; - } + await tryRespondChunked( + i, + value, + done, + ); - const serverId = opFn(listenOpts); - const serverPromise = core.opAsync("op_flash_drive_server", serverId); - - PromisePrototypeCatch( - PromisePrototypeThen( - core.opAsync("op_flash_wait_for_listening", serverId), - (port) => { - onListen({ hostname: listenOpts.hostname, port }); - }, - ), - () => {}, - ); - const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); - - const server = { - id: serverId, - transport: listenOpts.cert && listenOpts.key ? "https" : "http", - hostname: listenOpts.hostname, - port: listenOpts.port, - closed: false, - finished: finishedPromise, - async close() { - if (server.closed) { - return; - } - server.closed = true; - await core.opAsync("op_flash_close_server", serverId); - await server.finished; - }, - async serve() { - let offset = 0; + if (!done) { while (true) { + const chunk = await reader.read(); + await respondChunked( + i, + chunk.value, + chunk.done, + ); + if (chunk.done) break; + } + } + } + } + + if (ws) { + const wsRid = await core.opAsync( + "op_flash_upgrade_websocket", + serverId, + i, + ); + ws[_rid] = wsRid; + ws[_protocol] = resp.headers.get("sec-websocket-protocol"); + + ws[_readyState] = WebSocket.OPEN; + const event = new Event("open"); + ws.dispatchEvent(event); + + ws[_eventLoop](); + if (ws[_idleTimeoutDuration]) { + ws.addEventListener( + "close", + () => clearTimeout(ws[_idleTimeoutTimeout]), + ); + } + ws[_serverHandleIdleTimeout](); + } + })(); +} + +function createServe(opFn) { + return async function serve(arg1, arg2) { + let options = undefined; + let handler = undefined; + if (typeof arg1 === "function") { + handler = arg1; + options = arg2; + } else if (typeof arg2 === "function") { + handler = arg2; + options = arg1; + } else { + options = arg1; + } + if (handler === undefined) { + if (options === undefined) { + throw new TypeError( + "No handler was provided, so an options bag is mandatory.", + ); + } + handler = options.handler; + } + if (typeof handler !== "function") { + throw new TypeError("A handler function must be provided."); + } + if (options === undefined) { + options = {}; + } + + const signal = options.signal; + + const onError = options.onError ?? function (error) { + console.error(error); + return new Response("Internal Server Error", { status: 500 }); + }; + + const onListen = options.onListen ?? function ({ port }) { + console.log( + `Listening on http://${ + hostnameForDisplay(listenOpts.hostname) + }:${port}/`, + ); + }; + + const listenOpts = { + hostname: options.hostname ?? "127.0.0.1", + port: options.port ?? 9000, + reuseport: options.reusePort ?? false, + }; + if (options.cert || options.key) { + if (!options.cert || !options.key) { + throw new TypeError( + "Both cert and key must be provided to enable HTTPS.", + ); + } + listenOpts.cert = options.cert; + listenOpts.key = options.key; + } + + const serverId = opFn(listenOpts); + const serverPromise = core.opAsync("op_flash_drive_server", serverId); + + PromisePrototypeCatch( + PromisePrototypeThen( + core.opAsync("op_flash_wait_for_listening", serverId), + (port) => { + onListen({ hostname: listenOpts.hostname, port }); + }, + ), + () => {}, + ); + const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); + + const server = { + id: serverId, + transport: listenOpts.cert && listenOpts.key ? "https" : "http", + hostname: listenOpts.hostname, + port: listenOpts.port, + closed: false, + finished: finishedPromise, + async close() { + if (server.closed) { + return; + } + server.closed = true; + await core.opAsync("op_flash_close_server", serverId); + await server.finished; + }, + async serve() { + let offset = 0; + while (true) { + if (server.closed) { + break; + } + + let tokens = nextRequestSync(); + if (tokens === 0) { + tokens = await core.opAsync("op_flash_next_async", serverId); if (server.closed) { break; } + } - let tokens = nextRequestSync(); - if (tokens === 0) { - tokens = await core.opAsync("op_flash_next_async", serverId); - if (server.closed) { - break; + for (let i = offset; i < offset + tokens; i++) { + let body = null; + // There might be a body, but we don't expose it for GET/HEAD requests. + // It will be closed automatically once the request has been handled and + // the response has been sent. + const method = getMethodSync(i); + let hasBody = method > 2; // Not GET/HEAD/CONNECT + if (hasBody) { + body = createRequestBodyStream(serverId, i); + if (body === null) { + hasBody = false; } } - for (let i = offset; i < offset + tokens; i++) { - let body = null; - // There might be a body, but we don't expose it for GET/HEAD requests. - // It will be closed automatically once the request has been handled and - // the response has been sent. - const method = getMethodSync(i); - let hasBody = method > 2; // Not GET/HEAD/CONNECT - if (hasBody) { - body = createRequestBodyStream(serverId, i); - if (body === null) { - hasBody = false; - } - } + const req = fromFlashRequest( + serverId, + /* streamRid */ + i, + body, + /* methodCb */ + () => methods[method], + /* urlCb */ + () => { + const path = ops.op_flash_path(serverId, i); + return `${server.transport}://${server.hostname}:${server.port}${path}`; + }, + /* headersCb */ + () => ops.op_flash_headers(serverId, i), + ); - const req = fromFlashRequest( - serverId, - /* streamRid */ - i, - body, - /* methodCb */ - () => methods[method], - /* urlCb */ - () => { - const path = core.ops.op_flash_path(serverId, i); - return `${server.transport}://${server.hostname}:${server.port}${path}`; - }, - /* headersCb */ - () => core.ops.op_flash_headers(serverId, i), - ); - - let resp; - try { - resp = handler(req); - if (ObjectPrototypeIsPrototypeOf(PromisePrototype, resp)) { - PromisePrototypeCatch( - PromisePrototypeThen( - resp, - (resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - tryRespondChunked, - ), - ), - onError, - ); - } else if (typeof resp?.then === "function") { - resp.then((resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - tryRespondChunked, - ) - ).catch(onError); - } else { + let resp; + try { + resp = handler(req); + if (ObjectPrototypeIsPrototypeOf(PromisePrototype, resp)) { + PromisePrototypeCatch( + PromisePrototypeThen( + resp, + (resp) => + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, + ), + ), + onError, + ); + } else if (typeof resp?.then === "function") { + resp.then((resp) => handleResponse( req, resp, @@ -617,147 +602,157 @@ respondFast, respondChunked, tryRespondChunked, - ).catch(onError); - } - } catch (e) { - resp = await onError(e); + ) + ).catch(onError); + } else { + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, + ).catch(onError); } + } catch (e) { + resp = await onError(e); } - - offset += tokens; } - await server.finished; - }, - }; - signal?.addEventListener("abort", () => { - clearInterval(dateInterval); - PromisePrototypeThen(server.close(), () => {}, () => {}); - }, { - once: true, - }); - - function tryRespondChunked(token, chunk, shutdown) { - const nwritten = core.ops.op_try_flash_respond_chunked( - serverId, - token, - chunk ?? new Uint8Array(), - shutdown, - ); - if (nwritten > 0) { - return core.opAsync( - "op_flash_respond_chunked", - serverId, - token, - chunk, - shutdown, - nwritten, - ); + offset += tokens; } - } + await server.finished; + }, + }; - function respondChunked(token, chunk, shutdown) { + signal?.addEventListener("abort", () => { + clearInterval(dateInterval); + PromisePrototypeThen(server.close(), () => {}, () => {}); + }, { + once: true, + }); + + function tryRespondChunked(token, chunk, shutdown) { + const nwritten = ops.op_try_flash_respond_chunked( + serverId, + token, + chunk ?? new Uint8Array(), + shutdown, + ); + if (nwritten > 0) { return core.opAsync( "op_flash_respond_chunked", serverId, token, chunk, shutdown, + nwritten, ); } + } - const fastOp = prepareFastCalls(); - let nextRequestSync = () => fastOp.nextRequest(); - let getMethodSync = (token) => fastOp.getMethod(token); - let respondFast = (token, response, shutdown) => - fastOp.respond(token, response, shutdown); - if (serverId > 0) { - nextRequestSync = () => core.ops.op_flash_next_server(serverId); - getMethodSync = (token) => core.ops.op_flash_method(serverId, token); - respondFast = (token, response, shutdown) => - core.ops.op_flash_respond(serverId, token, response, null, shutdown); - } - - if (!dateInterval) { - date = new Date().toUTCString(); - dateInterval = setInterval(() => { - date = new Date().toUTCString(); - }, 1000); - } - - await SafePromiseAll([ - PromisePrototypeCatch(server.serve(), console.error), - serverPromise, - ]); - }; - } - - function createRequestBodyStream(serverId, token) { - // The first packet is left over bytes after parsing the request - const firstRead = core.ops.op_flash_first_packet( - serverId, - token, - ); - if (!firstRead) return null; - let firstEnqueued = firstRead.byteLength == 0; - - return new ReadableStream({ - type: "bytes", - async pull(controller) { - try { - if (firstEnqueued === false) { - controller.enqueue(firstRead); - firstEnqueued = true; - return; - } - // This is the largest possible size for a single packet on a TLS - // stream. - const chunk = new Uint8Array(16 * 1024 + 256); - const read = await core.opAsync( - "op_flash_read_body", - serverId, - token, - chunk, - ); - if (read > 0) { - // We read some data. Enqueue it onto the stream. - controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); - } else { - // We have reached the end of the body, so we close the stream. - controller.close(); - } - } catch (err) { - // There was an error while reading a chunk of the body, so we - // error. - controller.error(err); - controller.close(); - } - }, - }); - } - - function upgradeHttpRaw(req) { - if (!req[_flash]) { - throw new TypeError( - "Non-flash requests can not be upgraded with `upgradeHttpRaw`. Use `upgradeHttp` instead.", + function respondChunked(token, chunk, shutdown) { + return core.opAsync( + "op_flash_respond_chunked", + serverId, + token, + chunk, + shutdown, ); } - // NOTE(bartlomieju): - // Access these fields so they are cached on `req` object, otherwise - // they wouldn't be available after the connection gets upgraded. - req.url; - req.method; - req.headers; + const fastOp = prepareFastCalls(); + let nextRequestSync = () => fastOp.nextRequest(); + let getMethodSync = (token) => fastOp.getMethod(token); + let respondFast = (token, response, shutdown) => + fastOp.respond(token, response, shutdown); + if (serverId > 0) { + nextRequestSync = () => ops.op_flash_next_server(serverId); + getMethodSync = (token) => ops.op_flash_method(serverId, token); + respondFast = (token, response, shutdown) => + ops.op_flash_respond(serverId, token, response, null, shutdown); + } - const { serverId, streamRid } = req[_flash]; - const connRid = core.ops.op_flash_upgrade_http(streamRid, serverId); - // TODO(@littledivy): return already read first packet too. - return [new TcpConn(connRid), new Uint8Array()]; + if (!dateInterval) { + date = new Date().toUTCString(); + dateInterval = setInterval(() => { + date = new Date().toUTCString(); + }, 1000); + } + + await SafePromiseAll([ + PromisePrototypeCatch(server.serve(), console.error), + serverPromise, + ]); + }; +} + +function createRequestBodyStream(serverId, token) { + // The first packet is left over bytes after parsing the request + const firstRead = ops.op_flash_first_packet( + serverId, + token, + ); + if (!firstRead) return null; + let firstEnqueued = firstRead.byteLength == 0; + + return new ReadableStream({ + type: "bytes", + async pull(controller) { + try { + if (firstEnqueued === false) { + controller.enqueue(firstRead); + firstEnqueued = true; + return; + } + // This is the largest possible size for a single packet on a TLS + // stream. + const chunk = new Uint8Array(16 * 1024 + 256); + const read = await core.opAsync( + "op_flash_read_body", + serverId, + token, + chunk, + ); + if (read > 0) { + // We read some data. Enqueue it onto the stream. + controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); + } else { + // We have reached the end of the body, so we close the stream. + controller.close(); + } + } catch (err) { + // There was an error while reading a chunk of the body, so we + // error. + controller.error(err); + controller.close(); + } + }, + }); +} + +function upgradeHttpRaw(req) { + if (!req[_flash]) { + throw new TypeError( + "Non-flash requests can not be upgraded with `upgradeHttpRaw`. Use `upgradeHttp` instead.", + ); } - window.__bootstrap.flash = { - createServe, - upgradeHttpRaw, - }; -})(this); + // NOTE(bartlomieju): + // Access these fields so they are cached on `req` object, otherwise + // they wouldn't be available after the connection gets upgraded. + req.url; + req.method; + req.headers; + + const { serverId, streamRid } = req[_flash]; + const connRid = ops.op_flash_upgrade_http(streamRid, serverId); + // TODO(@littledivy): return already read first packet too. + return [new TcpConn(connRid), new Uint8Array()]; +} + +export { createServe, upgradeHttpRaw }; diff --git a/ext/flash/lib.rs b/ext/flash/lib.rs index d31e78caae..ab048c9cd8 100644 --- a/ext/flash/lib.rs +++ b/ext/flash/lib.rs @@ -1514,7 +1514,7 @@ pub fn init(unstable: bool) -> Extension { "deno_websocket", "deno_http", ]) - .js(deno_core::include_js_files!( + .esm(deno_core::include_js_files!( prefix "internal:ext/flash", "01_http.js", )) diff --git a/ext/http/01_http.js b/ext/http/01_http.js index cb98d246d8..7ce5f91723 100644 --- a/ext/http/01_http.js +++ b/ext/http/01_http.js @@ -1,296 +1,318 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { InnerBody } from "internal:ext/fetch/22_body.js"; +import { Event, setEventTargetData } from "internal:ext/web/02_event.js"; +import { BlobPrototype } from "internal:ext/web/09_file.js"; +import { + fromInnerResponse, + newInnerResponse, + ResponsePrototype, + toInnerResponse, +} from "internal:ext/fetch/23_response.js"; +import { + _flash, + fromInnerRequest, + newInnerRequest, +} from "internal:ext/fetch/23_request.js"; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; +import { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _server, + _serverHandleIdleTimeout, + WebSocket, +} from "internal:ext/websocket/01_websocket.js"; +import { TcpConn, UnixConn } from "internal:ext/net/01_net.js"; +import { TlsConn } from "internal:ext/net/02_tls.js"; +import { + Deferred, + getReadableStreamResourceBacking, + readableStreamClose, + readableStreamForRid, + ReadableStreamPrototype, +} from "internal:ext/web/06_streams.js"; +const { + ArrayPrototypeIncludes, + ArrayPrototypePush, + ArrayPrototypeSome, + Error, + ObjectPrototypeIsPrototypeOf, + SafeSetIterator, + Set, + SetPrototypeAdd, + SetPrototypeDelete, + StringPrototypeIncludes, + StringPrototypeToLowerCase, + StringPrototypeSplit, + Symbol, + SymbolAsyncIterator, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; -((window) => { - const webidl = window.__bootstrap.webidl; - const { InnerBody } = window.__bootstrap.fetchBody; - const { Event } = window.__bootstrap.event; - const { setEventTargetData } = window.__bootstrap.eventTarget; - const { BlobPrototype } = window.__bootstrap.file; - const { - ResponsePrototype, - fromInnerRequest, - toInnerResponse, - newInnerRequest, - newInnerResponse, - fromInnerResponse, - _flash, - } = window.__bootstrap.fetch; - const core = window.Deno.core; - const { BadResourcePrototype, InterruptedPrototype, ops } = core; - const { ReadableStreamPrototype } = window.__bootstrap.streams; - const abortSignal = window.__bootstrap.abortSignal; - const { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _server, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - } = window.__bootstrap.webSocket; - const { TcpConn, UnixConn } = window.__bootstrap.net; - const { TlsConn } = window.__bootstrap.tls; - const { - Deferred, - getReadableStreamResourceBacking, - readableStreamForRid, - readableStreamClose, - } = window.__bootstrap.streams; - const { - ArrayPrototypeIncludes, - ArrayPrototypePush, - ArrayPrototypeSome, - Error, - ObjectPrototypeIsPrototypeOf, - SafeSetIterator, - Set, - SetPrototypeAdd, - SetPrototypeDelete, - StringPrototypeIncludes, - StringPrototypeToLowerCase, - StringPrototypeSplit, - Symbol, - SymbolAsyncIterator, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +const connErrorSymbol = Symbol("connError"); +const _deferred = Symbol("upgradeHttpDeferred"); - const connErrorSymbol = Symbol("connError"); - const _deferred = Symbol("upgradeHttpDeferred"); +class HttpConn { + #rid = 0; + #closed = false; + #remoteAddr; + #localAddr; - class HttpConn { - #rid = 0; - #closed = false; - #remoteAddr; - #localAddr; + // This set holds resource ids of resources + // that were created during lifecycle of this request. + // When the connection is closed these resources should be closed + // as well. + managedResources = new Set(); - // This set holds resource ids of resources - // that were created during lifecycle of this request. - // When the connection is closed these resources should be closed - // as well. - managedResources = new Set(); + constructor(rid, remoteAddr, localAddr) { + this.#rid = rid; + this.#remoteAddr = remoteAddr; + this.#localAddr = localAddr; + } - constructor(rid, remoteAddr, localAddr) { - this.#rid = rid; - this.#remoteAddr = remoteAddr; - this.#localAddr = localAddr; - } + /** @returns {number} */ + get rid() { + return this.#rid; + } - /** @returns {number} */ - get rid() { - return this.#rid; - } - - /** @returns {Promise} */ - async nextRequest() { - let nextRequest; - try { - nextRequest = await core.opAsync("op_http_accept", this.#rid); - } catch (error) { - this.close(); - // A connection error seen here would cause disrupted responses to throw - // a generic `BadResource` error. Instead store this error and replace - // those with it. - this[connErrorSymbol] = error; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) || - StringPrototypeIncludes(error.message, "connection closed") - ) { - return null; - } - throw error; - } - if (nextRequest == null) { - // Work-around for servers (deno_std/http in particular) that call - // `nextRequest()` before upgrading a previous request which has a - // `connection: upgrade` header. - await null; - - this.close(); + /** @returns {Promise} */ + async nextRequest() { + let nextRequest; + try { + nextRequest = await core.opAsync("op_http_accept", this.#rid); + } catch (error) { + this.close(); + // A connection error seen here would cause disrupted responses to throw + // a generic `BadResource` error. Instead store this error and replace + // those with it. + this[connErrorSymbol] = error; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) || + StringPrototypeIncludes(error.message, "connection closed") + ) { return null; } + throw error; + } + if (nextRequest == null) { + // Work-around for servers (deno_std/http in particular) that call + // `nextRequest()` before upgrading a previous request which has a + // `connection: upgrade` header. + await null; - const { 0: streamRid, 1: method, 2: url } = nextRequest; - SetPrototypeAdd(this.managedResources, streamRid); - - /** @type {ReadableStream | undefined} */ - let body = null; - // There might be a body, but we don't expose it for GET/HEAD requests. - // It will be closed automatically once the request has been handled and - // the response has been sent. - if (method !== "GET" && method !== "HEAD") { - body = readableStreamForRid(streamRid, false); - } - - const innerRequest = newInnerRequest( - () => method, - url, - () => ops.op_http_headers(streamRid), - body !== null ? new InnerBody(body) : null, - false, - ); - const signal = abortSignal.newSignal(); - const request = fromInnerRequest( - innerRequest, - signal, - "immutable", - false, - ); - - const respondWith = createRespondWith( - this, - streamRid, - request, - this.#remoteAddr, - this.#localAddr, - ); - - return { request, respondWith }; + this.close(); + return null; } - /** @returns {void} */ - close() { - if (!this.#closed) { - this.#closed = true; - core.close(this.#rid); - for (const rid of new SafeSetIterator(this.managedResources)) { - SetPrototypeDelete(this.managedResources, rid); - core.close(rid); - } - } + const { 0: streamRid, 1: method, 2: url } = nextRequest; + SetPrototypeAdd(this.managedResources, streamRid); + + /** @type {ReadableStream | undefined} */ + let body = null; + // There might be a body, but we don't expose it for GET/HEAD requests. + // It will be closed automatically once the request has been handled and + // the response has been sent. + if (method !== "GET" && method !== "HEAD") { + body = readableStreamForRid(streamRid, false); } - [SymbolAsyncIterator]() { - // deno-lint-ignore no-this-alias - const httpConn = this; - return { - async next() { - const reqEvt = await httpConn.nextRequest(); - // Change with caution, current form avoids a v8 deopt - return { value: reqEvt ?? undefined, done: reqEvt === null }; - }, - }; + const innerRequest = newInnerRequest( + () => method, + url, + () => ops.op_http_headers(streamRid), + body !== null ? new InnerBody(body) : null, + false, + ); + const signal = abortSignal.newSignal(); + const request = fromInnerRequest( + innerRequest, + signal, + "immutable", + false, + ); + + const respondWith = createRespondWith( + this, + streamRid, + request, + this.#remoteAddr, + this.#localAddr, + ); + + return { request, respondWith }; + } + + /** @returns {void} */ + close() { + if (!this.#closed) { + this.#closed = true; + core.close(this.#rid); + for (const rid of new SafeSetIterator(this.managedResources)) { + SetPrototypeDelete(this.managedResources, rid); + core.close(rid); + } } } - function createRespondWith( - httpConn, - streamRid, - request, - remoteAddr, - localAddr, - ) { - return async function respondWith(resp) { - try { - resp = await resp; - if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) { - throw new TypeError( - "First argument to respondWith must be a Response or a promise resolving to a Response.", - ); + [SymbolAsyncIterator]() { + // deno-lint-ignore no-this-alias + const httpConn = this; + return { + async next() { + const reqEvt = await httpConn.nextRequest(); + // Change with caution, current form avoids a v8 deopt + return { value: reqEvt ?? undefined, done: reqEvt === null }; + }, + }; + } +} + +function createRespondWith( + httpConn, + streamRid, + request, + remoteAddr, + localAddr, +) { + return async function respondWith(resp) { + try { + resp = await resp; + if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) { + throw new TypeError( + "First argument to respondWith must be a Response or a promise resolving to a Response.", + ); + } + + const innerResp = toInnerResponse(resp); + + // If response body length is known, it will be sent synchronously in a + // single op, in other case a "response body" resource will be created and + // we'll be streaming it. + /** @type {ReadableStream | Uint8Array | null} */ + let respBody = null; + if (innerResp.body !== null) { + if (innerResp.body.unusable()) { + throw new TypeError("Body is unusable."); } - - const innerResp = toInnerResponse(resp); - - // If response body length is known, it will be sent synchronously in a - // single op, in other case a "response body" resource will be created and - // we'll be streaming it. - /** @type {ReadableStream | Uint8Array | null} */ - let respBody = null; - if (innerResp.body !== null) { - if (innerResp.body.unusable()) { - throw new TypeError("Body is unusable."); - } + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + innerResp.body.streamOrStatic, + ) + ) { if ( + innerResp.body.length === null || ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - innerResp.body.streamOrStatic, + BlobPrototype, + innerResp.body.source, ) ) { - if ( - innerResp.body.length === null || - ObjectPrototypeIsPrototypeOf( - BlobPrototype, - innerResp.body.source, - ) - ) { - respBody = innerResp.body.stream; - } else { - const reader = innerResp.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - respBody = new Uint8Array(0); - } else { - respBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - } + respBody = innerResp.body.stream; } else { - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; + const reader = innerResp.body.stream.getReader(); + const r1 = await reader.read(); + if (r1.done) { + respBody = new Uint8Array(0); + } else { + respBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); + } } } else { - respBody = new Uint8Array(0); + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; } - const isStreamingResponseBody = !( - typeof respBody === "string" || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + } else { + respBody = new Uint8Array(0); + } + const isStreamingResponseBody = !( + typeof respBody === "string" || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + ); + try { + await core.opAsync( + "op_http_write_headers", + streamRid, + innerResp.status ?? 200, + innerResp.headerList, + isStreamingResponseBody ? null : respBody, ); - try { - await core.opAsync( - "op_http_write_headers", - streamRid, - innerResp.status ?? 200, - innerResp.headerList, - isStreamingResponseBody ? null : respBody, - ); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && - connError != null - ) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - if ( - respBody !== null && - ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) - ) { - await respBody.cancel(error); - } - throw error; + } catch (error) { + const connError = httpConn[connErrorSymbol]; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && + connError != null + ) { + // deno-lint-ignore no-ex-assign + error = new connError.constructor(connError.message); } + if ( + respBody !== null && + ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) + ) { + await respBody.cancel(error); + } + throw error; + } - if (isStreamingResponseBody) { - let success = false; - if ( - respBody === null || - !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) - ) { - throw new TypeError("Unreachable"); + if (isStreamingResponseBody) { + let success = false; + if ( + respBody === null || + !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) + ) { + throw new TypeError("Unreachable"); + } + const resourceBacking = getReadableStreamResourceBacking(respBody); + let reader; + if (resourceBacking) { + if (respBody.locked) { + throw new TypeError("ReadableStream is locked."); } - const resourceBacking = getReadableStreamResourceBacking(respBody); - let reader; - if (resourceBacking) { - if (respBody.locked) { - throw new TypeError("ReadableStream is locked."); + reader = respBody.getReader(); // Aquire JS lock. + try { + await core.opAsync( + "op_http_write_resource", + streamRid, + resourceBacking.rid, + ); + if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid); + readableStreamClose(respBody); // Release JS lock. + success = true; + } catch (error) { + const connError = httpConn[connErrorSymbol]; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && + connError != null + ) { + // deno-lint-ignore no-ex-assign + error = new connError.constructor(connError.message); + } + await reader.cancel(error); + throw error; + } + } else { + reader = respBody.getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) break; + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) { + await reader.cancel(new TypeError("Value not a Uint8Array")); + break; } - reader = respBody.getReader(); // Aquire JS lock. try { - await core.opAsync( - "op_http_write_resource", - streamRid, - resourceBacking.rid, - ); - if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid); - readableStreamClose(respBody); // Release JS lock. - success = true; + await core.opAsync("op_http_write", streamRid, value); } catch (error) { const connError = httpConn[connErrorSymbol]; if ( @@ -303,176 +325,147 @@ await reader.cancel(error); throw error; } - } else { - reader = respBody.getReader(); - while (true) { - const { value, done } = await reader.read(); - if (done) break; - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) { - await reader.cancel(new TypeError("Value not a Uint8Array")); - break; - } - try { - await core.opAsync("op_http_write", streamRid, value); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && - connError != null - ) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - await reader.cancel(error); - throw error; - } - } - success = true; - } - - if (success) { - try { - await core.opAsync("op_http_shutdown", streamRid); - } catch (error) { - await reader.cancel(error); - throw error; - } } + success = true; } - const deferred = request[_deferred]; - if (deferred) { - const res = await core.opAsync("op_http_upgrade", streamRid); - let conn; - if (res.connType === "tcp") { - conn = new TcpConn(res.connRid, remoteAddr, localAddr); - } else if (res.connType === "tls") { - conn = new TlsConn(res.connRid, remoteAddr, localAddr); - } else if (res.connType === "unix") { - conn = new UnixConn(res.connRid, remoteAddr, localAddr); - } else { - throw new Error("unreachable"); + if (success) { + try { + await core.opAsync("op_http_shutdown", streamRid); + } catch (error) { + await reader.cancel(error); + throw error; } - - deferred.resolve([conn, res.readBuf]); - } - const ws = resp[_ws]; - if (ws) { - const wsRid = await core.opAsync( - "op_http_upgrade_websocket", - streamRid, - ); - ws[_rid] = wsRid; - ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - - httpConn.close(); - - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); - - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); - } - } finally { - if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { - core.close(streamRid); } } - }; - } - const _ws = Symbol("[[associated_ws]]"); + const deferred = request[_deferred]; + if (deferred) { + const res = await core.opAsync("op_http_upgrade", streamRid); + let conn; + if (res.connType === "tcp") { + conn = new TcpConn(res.connRid, remoteAddr, localAddr); + } else if (res.connType === "tls") { + conn = new TlsConn(res.connRid, remoteAddr, localAddr); + } else if (res.connType === "unix") { + conn = new UnixConn(res.connRid, remoteAddr, localAddr); + } else { + throw new Error("unreachable"); + } - function upgradeWebSocket(request, options = {}) { - const upgrade = request.headers.get("upgrade"); - const upgradeHasWebSocketOption = upgrade !== null && - ArrayPrototypeSome( - StringPrototypeSplit(upgrade, /\s*,\s*/), - (option) => StringPrototypeToLowerCase(option) === "websocket", - ); - if (!upgradeHasWebSocketOption) { - throw new TypeError( - "Invalid Header: 'upgrade' header must contain 'websocket'", - ); - } - - const connection = request.headers.get("connection"); - const connectionHasUpgradeOption = connection !== null && - ArrayPrototypeSome( - StringPrototypeSplit(connection, /\s*,\s*/), - (option) => StringPrototypeToLowerCase(option) === "upgrade", - ); - if (!connectionHasUpgradeOption) { - throw new TypeError( - "Invalid Header: 'connection' header must contain 'Upgrade'", - ); - } - - const websocketKey = request.headers.get("sec-websocket-key"); - if (websocketKey === null) { - throw new TypeError( - "Invalid Header: 'sec-websocket-key' header must be set", - ); - } - - const accept = ops.op_http_websocket_accept_header(websocketKey); - - const r = newInnerResponse(101); - r.headerList = [ - ["upgrade", "websocket"], - ["connection", "Upgrade"], - ["sec-websocket-accept", accept], - ]; - - const protocolsStr = request.headers.get("sec-websocket-protocol") || ""; - const protocols = StringPrototypeSplit(protocolsStr, ", "); - if (protocols && options.protocol) { - if (ArrayPrototypeIncludes(protocols, options.protocol)) { - ArrayPrototypePush(r.headerList, [ - "sec-websocket-protocol", - options.protocol, - ]); - } else { - throw new TypeError( - `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, + deferred.resolve([conn, res.readBuf]); + } + const ws = resp[_ws]; + if (ws) { + const wsRid = await core.opAsync( + "op_http_upgrade_websocket", + streamRid, ); + ws[_rid] = wsRid; + ws[_protocol] = resp.headers.get("sec-websocket-protocol"); + + httpConn.close(); + + ws[_readyState] = WebSocket.OPEN; + const event = new Event("open"); + ws.dispatchEvent(event); + + ws[_eventLoop](); + if (ws[_idleTimeoutDuration]) { + ws.addEventListener( + "close", + () => clearTimeout(ws[_idleTimeoutTimeout]), + ); + } + ws[_serverHandleIdleTimeout](); + } + } finally { + if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { + core.close(streamRid); } } + }; +} - const response = fromInnerResponse(r, "immutable"); +const _ws = Symbol("[[associated_ws]]"); - const socket = webidl.createBranded(WebSocket); - setEventTargetData(socket); - socket[_server] = true; - response[_ws] = socket; - socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; - socket[_idleTimeoutTimeout] = null; - - return { response, socket }; +function upgradeWebSocket(request, options = {}) { + const upgrade = request.headers.get("upgrade"); + const upgradeHasWebSocketOption = upgrade !== null && + ArrayPrototypeSome( + StringPrototypeSplit(upgrade, /\s*,\s*/), + (option) => StringPrototypeToLowerCase(option) === "websocket", + ); + if (!upgradeHasWebSocketOption) { + throw new TypeError( + "Invalid Header: 'upgrade' header must contain 'websocket'", + ); } - function upgradeHttp(req) { - if (req[_flash]) { + const connection = request.headers.get("connection"); + const connectionHasUpgradeOption = connection !== null && + ArrayPrototypeSome( + StringPrototypeSplit(connection, /\s*,\s*/), + (option) => StringPrototypeToLowerCase(option) === "upgrade", + ); + if (!connectionHasUpgradeOption) { + throw new TypeError( + "Invalid Header: 'connection' header must contain 'Upgrade'", + ); + } + + const websocketKey = request.headers.get("sec-websocket-key"); + if (websocketKey === null) { + throw new TypeError( + "Invalid Header: 'sec-websocket-key' header must be set", + ); + } + + const accept = ops.op_http_websocket_accept_header(websocketKey); + + const r = newInnerResponse(101); + r.headerList = [ + ["upgrade", "websocket"], + ["connection", "Upgrade"], + ["sec-websocket-accept", accept], + ]; + + const protocolsStr = request.headers.get("sec-websocket-protocol") || ""; + const protocols = StringPrototypeSplit(protocolsStr, ", "); + if (protocols && options.protocol) { + if (ArrayPrototypeIncludes(protocols, options.protocol)) { + ArrayPrototypePush(r.headerList, [ + "sec-websocket-protocol", + options.protocol, + ]); + } else { throw new TypeError( - "Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", + `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, ); } - - req[_deferred] = new Deferred(); - return req[_deferred].promise; } - window.__bootstrap.http = { - HttpConn, - upgradeWebSocket, - upgradeHttp, - _ws, - }; -})(this); + const response = fromInnerResponse(r, "immutable"); + + const socket = webidl.createBranded(WebSocket); + setEventTargetData(socket); + socket[_server] = true; + response[_ws] = socket; + socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; + socket[_idleTimeoutTimeout] = null; + + return { response, socket }; +} + +function upgradeHttp(req) { + if (req[_flash]) { + throw new TypeError( + "Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", + ); + } + + req[_deferred] = new Deferred(); + return req[_deferred].promise; +} + +export { _ws, HttpConn, upgradeHttp, upgradeWebSocket }; diff --git a/ext/http/lib.rs b/ext/http/lib.rs index 0e3ebf766d..a0593de8c9 100644 --- a/ext/http/lib.rs +++ b/ext/http/lib.rs @@ -80,7 +80,7 @@ mod reader_stream; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_web", "deno_net", "deno_fetch", "deno_websocket"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/http", "01_http.js", )) diff --git a/ext/net/01_net.js b/ext/net/01_net.js index a6043786f5..46561cae5e 100644 --- a/ext/net/01_net.js +++ b/ext/net/01_net.js @@ -1,423 +1,421 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const { BadResourcePrototype, InterruptedPrototype, ops } = core; - const { - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - writableStreamForRid, - } = window.__bootstrap.streams; - const { - Error, - ObjectPrototypeIsPrototypeOf, - PromiseResolve, - SymbolAsyncIterator, - SymbolFor, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +import { + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + writableStreamForRid, +} from "internal:ext/web/06_streams.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectPrototypeIsPrototypeOf, + PromiseResolve, + SymbolAsyncIterator, + SymbolFor, + TypedArrayPrototypeSubarray, + TypeError, + Uint8Array, +} = primordials; - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - async function write(rid, data) { - return await core.write(rid, data); +async function write(rid, data) { + return await core.write(rid, data); +} + +function shutdown(rid) { + return core.shutdown(rid); +} + +function resolveDns(query, recordType, options) { + return core.opAsync("op_dns_resolve", { query, recordType, options }); +} + +class Conn { + #rid = 0; + #remoteAddr = null; + #localAddr = null; + #unref = false; + #pendingReadPromiseIds = []; + + #readable; + #writable; + + constructor(rid, remoteAddr, localAddr) { + this.#rid = rid; + this.#remoteAddr = remoteAddr; + this.#localAddr = localAddr; } - function shutdown(rid) { - return core.shutdown(rid); + get rid() { + return this.#rid; } - function resolveDns(query, recordType, options) { - return core.opAsync("op_dns_resolve", { query, recordType, options }); + get remoteAddr() { + return this.#remoteAddr; } - class Conn { - #rid = 0; - #remoteAddr = null; - #localAddr = null; - #unref = false; - #pendingReadPromiseIds = []; + get localAddr() { + return this.#localAddr; + } - #readable; - #writable; + write(p) { + return write(this.rid, p); + } - constructor(rid, remoteAddr, localAddr) { - this.#rid = rid; - this.#remoteAddr = remoteAddr; - this.#localAddr = localAddr; + async read(buffer) { + if (buffer.length === 0) { + return 0; } - - get rid() { - return this.#rid; + const promise = core.read(this.rid, buffer); + const promiseId = promise[promiseIdSymbol]; + if (this.#unref) core.unrefOp(promiseId); + this.#pendingReadPromiseIds.push(promiseId); + let nread; + try { + nread = await promise; + } catch (e) { + throw e; + } finally { + this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) => + id !== promiseId + ); } + return nread === 0 ? null : nread; + } - get remoteAddr() { - return this.#remoteAddr; - } + close() { + core.close(this.rid); + } - get localAddr() { - return this.#localAddr; - } + closeWrite() { + return shutdown(this.rid); + } - write(p) { - return write(this.rid, p); - } - - async read(buffer) { - if (buffer.length === 0) { - return 0; - } - const promise = core.read(this.rid, buffer); - const promiseId = promise[promiseIdSymbol]; - if (this.#unref) core.unrefOp(promiseId); - this.#pendingReadPromiseIds.push(promiseId); - let nread; - try { - nread = await promise; - } catch (e) { - throw e; - } finally { - this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) => - id !== promiseId - ); - } - return nread === 0 ? null : nread; - } - - close() { - core.close(this.rid); - } - - closeWrite() { - return shutdown(this.rid); - } - - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRidUnrefable(this.rid); - if (this.#unref) { - readableStreamForRidUnrefableUnref(this.#readable); - } - } - return this.#readable; - } - - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } - - ref() { - this.#unref = false; - if (this.#readable) { - readableStreamForRidUnrefableRef(this.#readable); - } - this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); - } - - unref() { - this.#unref = true; - if (this.#readable) { + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRidUnrefable(this.rid); + if (this.#unref) { readableStreamForRidUnrefableUnref(this.#readable); } - this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); + } + return this.#readable; + } + + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); + } + return this.#writable; + } + + ref() { + this.#unref = false; + if (this.#readable) { + readableStreamForRidUnrefableRef(this.#readable); + } + this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); + } + + unref() { + this.#unref = true; + if (this.#readable) { + readableStreamForRidUnrefableUnref(this.#readable); + } + this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); + } +} + +class TcpConn extends Conn { + setNoDelay(noDelay = true) { + return ops.op_set_nodelay(this.rid, noDelay); + } + + setKeepAlive(keepAlive = true) { + return ops.op_set_keepalive(this.rid, keepAlive); + } +} + +class UnixConn extends Conn {} + +class Listener { + #rid = 0; + #addr = null; + #unref = false; + #promiseId = null; + + constructor(rid, addr) { + this.#rid = rid; + this.#addr = addr; + } + + get rid() { + return this.#rid; + } + + get addr() { + return this.#addr; + } + + async accept() { + let promise; + switch (this.addr.transport) { + case "tcp": + promise = core.opAsync("op_net_accept_tcp", this.rid); + break; + case "unix": + promise = core.opAsync("op_net_accept_unix", this.rid); + break; + default: + throw new Error(`Unsupported transport: ${this.addr.transport}`); + } + this.#promiseId = promise[promiseIdSymbol]; + if (this.#unref) core.unrefOp(this.#promiseId); + const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise; + this.#promiseId = null; + if (this.addr.transport == "tcp") { + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TcpConn(rid, remoteAddr, localAddr); + } else if (this.addr.transport == "unix") { + return new UnixConn( + rid, + { transport: "unix", path: remoteAddr }, + { transport: "unix", path: localAddr }, + ); + } else { + throw new Error("unreachable"); } } - class TcpConn extends Conn { - setNoDelay(noDelay = true) { - return ops.op_set_nodelay(this.rid, noDelay); + async next() { + let conn; + try { + conn = await this.accept(); + } catch (error) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) + ) { + return { value: undefined, done: true }; + } + throw error; } + return { value: conn, done: false }; + } - setKeepAlive(keepAlive = true) { - return ops.op_set_keepalive(this.rid, keepAlive); + return(value) { + this.close(); + return PromiseResolve({ value, done: true }); + } + + close() { + core.close(this.rid); + } + + [SymbolAsyncIterator]() { + return this; + } + + ref() { + this.#unref = false; + if (typeof this.#promiseId === "number") { + core.refOp(this.#promiseId); } } - class UnixConn extends Conn {} - - class Listener { - #rid = 0; - #addr = null; - #unref = false; - #promiseId = null; - - constructor(rid, addr) { - this.#rid = rid; - this.#addr = addr; - } - - get rid() { - return this.#rid; - } - - get addr() { - return this.#addr; - } - - async accept() { - let promise; - switch (this.addr.transport) { - case "tcp": - promise = core.opAsync("op_net_accept_tcp", this.rid); - break; - case "unix": - promise = core.opAsync("op_net_accept_unix", this.rid); - break; - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - this.#promiseId = promise[promiseIdSymbol]; - if (this.#unref) core.unrefOp(this.#promiseId); - const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise; - this.#promiseId = null; - if (this.addr.transport == "tcp") { - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TcpConn(rid, remoteAddr, localAddr); - } else if (this.addr.transport == "unix") { - return new UnixConn( - rid, - { transport: "unix", path: remoteAddr }, - { transport: "unix", path: localAddr }, - ); - } else { - throw new Error("unreachable"); - } - } - - async next() { - let conn; - try { - conn = await this.accept(); - } catch (error) { - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) - ) { - return { value: undefined, done: true }; - } - throw error; - } - return { value: conn, done: false }; - } - - return(value) { - this.close(); - return PromiseResolve({ value, done: true }); - } - - close() { - core.close(this.rid); - } - - [SymbolAsyncIterator]() { - return this; - } - - ref() { - this.#unref = false; - if (typeof this.#promiseId === "number") { - core.refOp(this.#promiseId); - } - } - - unref() { - this.#unref = true; - if (typeof this.#promiseId === "number") { - core.unrefOp(this.#promiseId); - } + unref() { + this.#unref = true; + if (typeof this.#promiseId === "number") { + core.unrefOp(this.#promiseId); } } +} - class Datagram { - #rid = 0; - #addr = null; +class Datagram { + #rid = 0; + #addr = null; - constructor(rid, addr, bufSize = 1024) { - this.#rid = rid; - this.#addr = addr; - this.bufSize = bufSize; - } - - get rid() { - return this.#rid; - } - - get addr() { - return this.#addr; - } - - async receive(p) { - const buf = p || new Uint8Array(this.bufSize); - let nread; - let remoteAddr; - switch (this.addr.transport) { - case "udp": { - ({ 0: nread, 1: remoteAddr } = await core.opAsync( - "op_net_recv_udp", - this.rid, - buf, - )); - remoteAddr.transport = "udp"; - break; - } - case "unixpacket": { - let path; - ({ 0: nread, 1: path } = await core.opAsync( - "op_net_recv_unixpacket", - this.rid, - buf, - )); - remoteAddr = { transport: "unixpacket", path }; - break; - } - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - const sub = TypedArrayPrototypeSubarray(buf, 0, nread); - return [sub, remoteAddr]; - } - - async send(p, opts) { - switch (this.addr.transport) { - case "udp": - return await core.opAsync( - "op_net_send_udp", - this.rid, - { hostname: opts.hostname ?? "127.0.0.1", port: opts.port }, - p, - ); - case "unixpacket": - return await core.opAsync( - "op_net_send_unixpacket", - this.rid, - opts.path, - p, - ); - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - } - - close() { - core.close(this.rid); - } - - async *[SymbolAsyncIterator]() { - while (true) { - try { - yield await this.receive(); - } catch (err) { - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err) - ) { - break; - } - throw err; - } - } - } + constructor(rid, addr, bufSize = 1024) { + this.#rid = rid; + this.#addr = addr; + this.bufSize = bufSize; } - function listen(args) { - switch (args.transport ?? "tcp") { - case "tcp": { - const { 0: rid, 1: addr } = ops.op_net_listen_tcp({ - hostname: args.hostname ?? "0.0.0.0", - port: args.port, - }, args.reusePort); - addr.transport = "tcp"; - return new Listener(rid, addr); + get rid() { + return this.#rid; + } + + get addr() { + return this.#addr; + } + + async receive(p) { + const buf = p || new Uint8Array(this.bufSize); + let nread; + let remoteAddr; + switch (this.addr.transport) { + case "udp": { + ({ 0: nread, 1: remoteAddr } = await core.opAsync( + "op_net_recv_udp", + this.rid, + buf, + )); + remoteAddr.transport = "udp"; + break; } - case "unix": { - const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path); - const addr = { - transport: "unix", - path, - }; - return new Listener(rid, addr); + case "unixpacket": { + let path; + ({ 0: nread, 1: path } = await core.opAsync( + "op_net_recv_unixpacket", + this.rid, + buf, + )); + remoteAddr = { transport: "unixpacket", path }; + break; } default: - throw new TypeError(`Unsupported transport: '${transport}'`); + throw new Error(`Unsupported transport: ${this.addr.transport}`); + } + const sub = TypedArrayPrototypeSubarray(buf, 0, nread); + return [sub, remoteAddr]; + } + + async send(p, opts) { + switch (this.addr.transport) { + case "udp": + return await core.opAsync( + "op_net_send_udp", + this.rid, + { hostname: opts.hostname ?? "127.0.0.1", port: opts.port }, + p, + ); + case "unixpacket": + return await core.opAsync( + "op_net_send_unixpacket", + this.rid, + opts.path, + p, + ); + default: + throw new Error(`Unsupported transport: ${this.addr.transport}`); } } - function createListenDatagram(udpOpFn, unixOpFn) { - return function listenDatagram(args) { - switch (args.transport) { - case "udp": { - const { 0: rid, 1: addr } = udpOpFn( - { - hostname: args.hostname ?? "127.0.0.1", - port: args.port, - }, - args.reuseAddress ?? false, - ); - addr.transport = "udp"; - return new Datagram(rid, addr); - } - case "unixpacket": { - const { 0: rid, 1: path } = unixOpFn(args.path); - const addr = { - transport: "unixpacket", - path, - }; - return new Datagram(rid, addr); - } - default: - throw new TypeError(`Unsupported transport: '${transport}'`); - } - }; + close() { + core.close(this.rid); } - async function connect(args) { - switch (args.transport ?? "tcp") { - case "tcp": { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_tcp", + async *[SymbolAsyncIterator]() { + while (true) { + try { + yield await this.receive(); + } catch (err) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err) + ) { + break; + } + throw err; + } + } + } +} + +function listen(args) { + switch (args.transport ?? "tcp") { + case "tcp": { + const { 0: rid, 1: addr } = ops.op_net_listen_tcp({ + hostname: args.hostname ?? "0.0.0.0", + port: args.port, + }, args.reusePort); + addr.transport = "tcp"; + return new Listener(rid, addr); + } + case "unix": { + const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path); + const addr = { + transport: "unix", + path, + }; + return new Listener(rid, addr); + } + default: + throw new TypeError(`Unsupported transport: '${transport}'`); + } +} + +function createListenDatagram(udpOpFn, unixOpFn) { + return function listenDatagram(args) { + switch (args.transport) { + case "udp": { + const { 0: rid, 1: addr } = udpOpFn( { hostname: args.hostname ?? "127.0.0.1", port: args.port, }, + args.reuseAddress ?? false, ); - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TcpConn(rid, remoteAddr, localAddr); + addr.transport = "udp"; + return new Datagram(rid, addr); } - case "unix": { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_unix", - args.path, - ); - return new UnixConn( - rid, - { transport: "unix", path: remoteAddr }, - { transport: "unix", path: localAddr }, - ); + case "unixpacket": { + const { 0: rid, 1: path } = unixOpFn(args.path); + const addr = { + transport: "unixpacket", + path, + }; + return new Datagram(rid, addr); } default: throw new TypeError(`Unsupported transport: '${transport}'`); } - } - - window.__bootstrap.net = { - connect, - Conn, - TcpConn, - UnixConn, - listen, - createListenDatagram, - Listener, - shutdown, - Datagram, - resolveDns, }; -})(this); +} + +async function connect(args) { + switch (args.transport ?? "tcp") { + case "tcp": { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_tcp", + { + hostname: args.hostname ?? "127.0.0.1", + port: args.port, + }, + ); + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TcpConn(rid, remoteAddr, localAddr); + } + case "unix": { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_unix", + args.path, + ); + return new UnixConn( + rid, + { transport: "unix", path: remoteAddr }, + { transport: "unix", path: localAddr }, + ); + } + default: + throw new TypeError(`Unsupported transport: '${transport}'`); + } +} + +export { + Conn, + connect, + createListenDatagram, + Datagram, + listen, + Listener, + resolveDns, + shutdown, + TcpConn, + UnixConn, +}; diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js index 632e1fbd4a..4701d3da7f 100644 --- a/ext/net/02_tls.js +++ b/ext/net/02_tls.js @@ -1,106 +1,98 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { Listener, Conn } = window.__bootstrap.net; - const { TypeError } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { Conn, Listener } from "internal:ext/net/01_net.js"; +const primordials = globalThis.__bootstrap.primordials; +const { TypeError } = primordials; - function opStartTls(args) { - return core.opAsync("op_tls_start", args); +function opStartTls(args) { + return core.opAsync("op_tls_start", args); +} + +function opTlsHandshake(rid) { + return core.opAsync("op_tls_handshake", rid); +} + +class TlsConn extends Conn { + handshake() { + return opTlsHandshake(this.rid); } +} - function opTlsHandshake(rid) { - return core.opAsync("op_tls_handshake", rid); +async function connectTls({ + port, + hostname = "127.0.0.1", + transport = "tcp", + certFile = undefined, + caCerts = [], + certChain = undefined, + privateKey = undefined, + alpnProtocols = undefined, +}) { + if (transport !== "tcp") { + throw new TypeError(`Unsupported transport: '${transport}'`); } + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_tls", + { hostname, port }, + { certFile, caCerts, certChain, privateKey, alpnProtocols }, + ); + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TlsConn(rid, remoteAddr, localAddr); +} - class TlsConn extends Conn { - handshake() { - return opTlsHandshake(this.rid); - } - } - - async function connectTls({ - port, - hostname = "127.0.0.1", - transport = "tcp", - certFile = undefined, - caCerts = [], - certChain = undefined, - privateKey = undefined, - alpnProtocols = undefined, - }) { - if (transport !== "tcp") { - throw new TypeError(`Unsupported transport: '${transport}'`); - } +class TlsListener extends Listener { + async accept() { const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_tls", - { hostname, port }, - { certFile, caCerts, certChain, privateKey, alpnProtocols }, + "op_net_accept_tls", + this.rid, ); localAddr.transport = "tcp"; remoteAddr.transport = "tcp"; return new TlsConn(rid, remoteAddr, localAddr); } +} - class TlsListener extends Listener { - async accept() { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_accept_tls", - this.rid, - ); - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TlsConn(rid, remoteAddr, localAddr); - } +function listenTls({ + port, + cert, + certFile, + key, + keyFile, + hostname = "0.0.0.0", + transport = "tcp", + alpnProtocols = undefined, + reusePort = false, +}) { + if (transport !== "tcp") { + throw new TypeError(`Unsupported transport: '${transport}'`); } + const { 0: rid, 1: localAddr } = ops.op_net_listen_tls( + { hostname, port }, + { cert, certFile, key, keyFile, alpnProtocols, reusePort }, + ); + return new TlsListener(rid, localAddr); +} - function listenTls({ - port, - cert, - certFile, - key, - keyFile, - hostname = "0.0.0.0", - transport = "tcp", +async function startTls( + conn, + { + hostname = "127.0.0.1", + certFile = undefined, + caCerts = [], alpnProtocols = undefined, - reusePort = false, - }) { - if (transport !== "tcp") { - throw new TypeError(`Unsupported transport: '${transport}'`); - } - const { 0: rid, 1: localAddr } = ops.op_net_listen_tls( - { hostname, port }, - { cert, certFile, key, keyFile, alpnProtocols, reusePort }, - ); - return new TlsListener(rid, localAddr); - } + } = {}, +) { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({ + rid: conn.rid, + hostname, + certFile, + caCerts, + alpnProtocols, + }); + return new TlsConn(rid, remoteAddr, localAddr); +} - async function startTls( - conn, - { - hostname = "127.0.0.1", - certFile = undefined, - caCerts = [], - alpnProtocols = undefined, - } = {}, - ) { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({ - rid: conn.rid, - hostname, - certFile, - caCerts, - alpnProtocols, - }); - return new TlsConn(rid, remoteAddr, localAddr); - } - - window.__bootstrap.tls = { - startTls, - listenTls, - connectTls, - TlsConn, - TlsListener, - }; -})(this); +export { connectTls, listenTls, startTls, TlsConn, TlsListener }; diff --git a/ext/net/lib.rs b/ext/net/lib.rs index 932f8c8c5c..ed620fcdd9 100644 --- a/ext/net/lib.rs +++ b/ext/net/lib.rs @@ -86,7 +86,7 @@ pub fn init( ops.extend(ops_tls::init::

()); Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_web"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/net", "01_net.js", "02_tls.js", diff --git a/ext/node/01_node.js b/ext/node/01_node.js index 4ed5b3edab..de27c5180b 100644 --- a/ext/node/01_node.js +++ b/ext/node/01_node.js @@ -2,127 +2,122 @@ // deno-lint-ignore-file -"use strict"; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeFilter, + ObjectEntries, + ObjectCreate, + ObjectDefineProperty, + Proxy, + ReflectDefineProperty, + ReflectGetOwnPropertyDescriptor, + ReflectOwnKeys, + Set, + SetPrototypeHas, +} = primordials; -((window) => { - const { - ArrayPrototypePush, - ArrayPrototypeFilter, - ObjectEntries, - ObjectCreate, - ObjectDefineProperty, - Proxy, - ReflectDefineProperty, - ReflectGetOwnPropertyDescriptor, - ReflectOwnKeys, - Set, - SetPrototypeHas, - } = window.__bootstrap.primordials; - - function assert(cond) { - if (!cond) { - throw Error("assert"); - } +function assert(cond) { + if (!cond) { + throw Error("assert"); } +} - let initialized = false; - const nodeGlobals = {}; - const nodeGlobalThis = new Proxy(globalThis, { - get(_target, prop, _receiver) { - if (prop in nodeGlobals) { - return nodeGlobals[prop]; - } else { - return globalThis[prop]; - } - }, - set(_target, prop, value) { - if (prop in nodeGlobals) { - nodeGlobals[prop] = value; - } else { - globalThis[prop] = value; - } - return true; - }, - deleteProperty(_target, prop) { - let success = false; - if (prop in nodeGlobals) { - delete nodeGlobals[prop]; - success = true; - } - if (prop in globalThis) { - delete globalThis[prop]; - success = true; - } - return success; - }, - ownKeys(_target) { - const globalThisKeys = ReflectOwnKeys(globalThis); - const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals); - const nodeGlobalsKeySet = new Set(nodeGlobalsKeys); - return [ - ...ArrayPrototypeFilter( - globalThisKeys, - (k) => !SetPrototypeHas(nodeGlobalsKeySet, k), - ), - ...nodeGlobalsKeys, - ]; - }, - defineProperty(_target, prop, desc) { - if (prop in nodeGlobals) { - return ReflectDefineProperty(nodeGlobals, prop, desc); - } else { - return ReflectDefineProperty(globalThis, prop, desc); - } - }, - getOwnPropertyDescriptor(_target, prop) { - if (prop in nodeGlobals) { - return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop); - } else { - return ReflectGetOwnPropertyDescriptor(globalThis, prop); - } - }, - has(_target, prop) { - return prop in nodeGlobals || prop in globalThis; - }, +let initialized = false; +const nodeGlobals = {}; +const nodeGlobalThis = new Proxy(globalThis, { + get(_target, prop, _receiver) { + if (prop in nodeGlobals) { + return nodeGlobals[prop]; + } else { + return globalThis[prop]; + } + }, + set(_target, prop, value) { + if (prop in nodeGlobals) { + nodeGlobals[prop] = value; + } else { + globalThis[prop] = value; + } + return true; + }, + deleteProperty(_target, prop) { + let success = false; + if (prop in nodeGlobals) { + delete nodeGlobals[prop]; + success = true; + } + if (prop in globalThis) { + delete globalThis[prop]; + success = true; + } + return success; + }, + ownKeys(_target) { + const globalThisKeys = ReflectOwnKeys(globalThis); + const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals); + const nodeGlobalsKeySet = new Set(nodeGlobalsKeys); + return [ + ...ArrayPrototypeFilter( + globalThisKeys, + (k) => !SetPrototypeHas(nodeGlobalsKeySet, k), + ), + ...nodeGlobalsKeys, + ]; + }, + defineProperty(_target, prop, desc) { + if (prop in nodeGlobals) { + return ReflectDefineProperty(nodeGlobals, prop, desc); + } else { + return ReflectDefineProperty(globalThis, prop, desc); + } + }, + getOwnPropertyDescriptor(_target, prop) { + if (prop in nodeGlobals) { + return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop); + } else { + return ReflectGetOwnPropertyDescriptor(globalThis, prop); + } + }, + has(_target, prop) { + return prop in nodeGlobals || prop in globalThis; + }, +}); + +const nativeModuleExports = ObjectCreate(null); +const builtinModules = []; + +function initialize(nodeModules, nodeGlobalThisName) { + assert(!initialized); + initialized = true; + for (const [name, exports] of ObjectEntries(nodeModules)) { + nativeModuleExports[name] = exports; + ArrayPrototypePush(builtinModules, name); + } + nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; + nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; + nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; + nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; + nodeGlobals.console = nativeModuleExports["console"]; + nodeGlobals.global = nodeGlobalThis; + nodeGlobals.process = nativeModuleExports["process"]; + nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; + nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; + nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; + + // add a hidden global for the esm code to use in order to reliably + // get node's globalThis + ObjectDefineProperty(globalThis, nodeGlobalThisName, { + enumerable: false, + writable: false, + value: nodeGlobalThis, }); +} - const nativeModuleExports = ObjectCreate(null); - const builtinModules = []; - - function initialize(nodeModules, nodeGlobalThisName) { - assert(!initialized); - initialized = true; - for (const [name, exports] of ObjectEntries(nodeModules)) { - nativeModuleExports[name] = exports; - ArrayPrototypePush(builtinModules, name); - } - nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; - nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; - nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; - nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; - nodeGlobals.console = nativeModuleExports["console"]; - nodeGlobals.global = nodeGlobalThis; - nodeGlobals.process = nativeModuleExports["process"]; - nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; - nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; - nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; - - // add a hidden global for the esm code to use in order to reliably - // get node's globalThis - ObjectDefineProperty(globalThis, nodeGlobalThisName, { - enumerable: false, - writable: false, - value: nodeGlobalThis, - }); - } - - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - node: { - globalThis: nodeGlobalThis, - initialize, - nativeModuleExports, - builtinModules, - }, - }; -})(globalThis); +internals.node = { + globalThis: nodeGlobalThis, + initialize, + nativeModuleExports, + builtinModules, +}; diff --git a/ext/node/02_require.js b/ext/node/02_require.js index bda74c01fc..2b4a9c16c2 100644 --- a/ext/node/02_require.js +++ b/ext/node/02_require.js @@ -2,943 +2,938 @@ // deno-lint-ignore-file -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ObjectGetOwnPropertyDescriptor, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + ObjectSetPrototypeOf, + ObjectKeys, + ObjectPrototype, + ObjectCreate, + Proxy, + SafeMap, + SafeWeakMap, + SafeArrayIterator, + JSONParse, + String, + StringPrototypeEndsWith, + StringPrototypeIndexOf, + StringPrototypeIncludes, + StringPrototypeMatch, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, + StringPrototypeCharCodeAt, + RegExpPrototypeTest, + Error, + TypeError, +} = primordials; +const node = internals.node; -((window) => { - const { - ArrayIsArray, - ArrayPrototypeIncludes, - ArrayPrototypeIndexOf, - ArrayPrototypeJoin, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ObjectGetOwnPropertyDescriptor, - ObjectGetPrototypeOf, - ObjectPrototypeHasOwnProperty, - ObjectSetPrototypeOf, - ObjectKeys, - ObjectPrototype, - ObjectCreate, - Proxy, - SafeMap, - SafeWeakMap, - SafeArrayIterator, - JSONParse, - String, - StringPrototypeEndsWith, - StringPrototypeIndexOf, - StringPrototypeIncludes, - StringPrototypeMatch, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeStartsWith, - StringPrototypeCharCodeAt, - RegExpPrototypeTest, - Error, - TypeError, - } = window.__bootstrap.primordials; - const core = window.Deno.core; - const ops = core.ops; - const { node } = window.__bootstrap.internals; +// Map used to store CJS parsing data. +const cjsParseCache = new SafeWeakMap(); - // Map used to store CJS parsing data. - const cjsParseCache = new SafeWeakMap(); - - function pathDirname(filepath) { - if (filepath == null || filepath === "") { - throw new Error("Empty filepath."); - } - return ops.op_require_path_dirname(filepath); +function pathDirname(filepath) { + if (filepath == null || filepath === "") { + throw new Error("Empty filepath."); } + return ops.op_require_path_dirname(filepath); +} - function pathResolve(...args) { - return ops.op_require_path_resolve(args); +function pathResolve(...args) { + return ops.op_require_path_resolve(args); +} + +function assert(cond) { + if (!cond) { + throw Error("assert"); } +} - function assert(cond) { - if (!cond) { - throw Error("assert"); +const nativeModulePolyfill = new SafeMap(); + +const relativeResolveCache = ObjectCreate(null); +let requireDepth = 0; +let statCache = null; +let isPreloading = false; +let mainModule = null; +let hasBrokenOnInspectBrk = false; +let hasInspectBrk = false; +// Are we running with --node-modules-dir flag? +let usesLocalNodeModulesDir = false; + +function stat(filename) { + // TODO: required only on windows + // filename = path.toNamespacedPath(filename); + if (statCache !== null) { + const result = statCache.get(filename); + if (result !== undefined) { + return result; } } - - const nativeModulePolyfill = new SafeMap(); - - const relativeResolveCache = ObjectCreate(null); - let requireDepth = 0; - let statCache = null; - let isPreloading = false; - let mainModule = null; - let hasBrokenOnInspectBrk = false; - let hasInspectBrk = false; - // Are we running with --node-modules-dir flag? - let usesLocalNodeModulesDir = false; - - function stat(filename) { - // TODO: required only on windows - // filename = path.toNamespacedPath(filename); - if (statCache !== null) { - const result = statCache.get(filename); - if (result !== undefined) { - return result; - } - } - const result = ops.op_require_stat(filename); - if (statCache !== null && result >= 0) { - statCache.set(filename, result); - } - - return result; + const result = ops.op_require_stat(filename); + if (statCache !== null && result >= 0) { + statCache.set(filename, result); } - function updateChildren(parent, child, scan) { - if (!parent) { - return; - } + return result; +} - const children = parent.children; - if (children && !(scan && ArrayPrototypeIncludes(children, child))) { - ArrayPrototypePush(children, child); - } +function updateChildren(parent, child, scan) { + if (!parent) { + return; } - function tryFile(requestPath, _isMain) { - const rc = stat(requestPath); - if (rc !== 0) return; - return toRealPath(requestPath); + const children = parent.children; + if (children && !(scan && ArrayPrototypeIncludes(children, child))) { + ArrayPrototypePush(children, child); } +} - function tryPackage(requestPath, exts, isMain, originalPath) { - const packageJsonPath = pathResolve( - requestPath, - "package.json", +function tryFile(requestPath, _isMain) { + const rc = stat(requestPath); + if (rc !== 0) return; + return toRealPath(requestPath); +} + +function tryPackage(requestPath, exts, isMain, originalPath) { + const packageJsonPath = pathResolve( + requestPath, + "package.json", + ); + const pkg = ops.op_require_read_package_scope(packageJsonPath)?.main; + if (!pkg) { + return tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, ); - const pkg = core.ops.op_require_read_package_scope(packageJsonPath)?.main; - if (!pkg) { - return tryExtensions( - pathResolve(requestPath, "index"), - exts, - isMain, - ); - } + } - const filename = pathResolve(requestPath, pkg); - let actual = tryFile(filename, isMain) || - tryExtensions(filename, exts, isMain) || - tryExtensions( - pathResolve(filename, "index"), - exts, - isMain, + const filename = pathResolve(requestPath, pkg); + let actual = tryFile(filename, isMain) || + tryExtensions(filename, exts, isMain) || + tryExtensions( + pathResolve(filename, "index"), + exts, + isMain, + ); + if (actual === false) { + actual = tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, + ); + if (!actual) { + // eslint-disable-next-line no-restricted-syntax + const err = new Error( + `Cannot find module '${filename}'. ` + + 'Please verify that the package.json has a valid "main" entry', ); - if (actual === false) { - actual = tryExtensions( - pathResolve(requestPath, "index"), - exts, - isMain, + err.code = "MODULE_NOT_FOUND"; + err.path = pathResolve( + requestPath, + "package.json", + ); + err.requestPath = originalPath; + throw err; + } else { + node.globalThis.process.emitWarning( + `Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` + + "Please either fix that or report it to the module author", + "DeprecationWarning", + "DEP0128", ); - if (!actual) { - // eslint-disable-next-line no-restricted-syntax - const err = new Error( - `Cannot find module '${filename}'. ` + - 'Please verify that the package.json has a valid "main" entry', - ); - err.code = "MODULE_NOT_FOUND"; - err.path = pathResolve( - requestPath, - "package.json", - ); - err.requestPath = originalPath; - throw err; - } else { - node.globalThis.process.emitWarning( - `Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` + - "Please either fix that or report it to the module author", - "DeprecationWarning", - "DEP0128", - ); - } } + } + return actual; +} + +const realpathCache = new SafeMap(); +function toRealPath(requestPath) { + const maybeCached = realpathCache.get(requestPath); + if (maybeCached) { + return maybeCached; + } + const rp = ops.op_require_real_path(requestPath); + realpathCache.set(requestPath, rp); + return rp; +} + +function tryExtensions(p, exts, isMain) { + for (let i = 0; i < exts.length; i++) { + const filename = tryFile(p + exts[i], isMain); + + if (filename) { + return filename; + } + } + return false; +} + +// Find the longest (possibly multi-dot) extension registered in +// Module._extensions +function findLongestRegisteredExtension(filename) { + const name = ops.op_require_path_basename(filename); + let currentExtension; + let index; + let startIndex = 0; + while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) { + startIndex = index + 1; + if (index === 0) continue; // Skip dotfiles like .gitignore + currentExtension = StringPrototypeSlice(name, index); + if (Module._extensions[currentExtension]) { + return currentExtension; + } + } + return ".js"; +} + +function getExportsForCircularRequire(module) { + if ( + module.exports && + ObjectGetPrototypeOf(module.exports) === ObjectPrototype && + // Exclude transpiled ES6 modules / TypeScript code because those may + // employ unusual patterns for accessing 'module.exports'. That should + // be okay because ES6 modules have a different approach to circular + // dependencies anyway. + !module.exports.__esModule + ) { + // This is later unset once the module is done loading. + ObjectSetPrototypeOf( + module.exports, + CircularRequirePrototypeWarningProxy, + ); + } + + return module.exports; +} + +function emitCircularRequireWarning(prop) { + node.globalThis.process.emitWarning( + `Accessing non-existent property '${String(prop)}' of module exports ` + + "inside circular dependency", + ); +} + +// A Proxy that can be used as the prototype of a module.exports object and +// warns when non-existent properties are accessed. +const CircularRequirePrototypeWarningProxy = new Proxy({}, { + get(target, prop) { + // Allow __esModule access in any case because it is used in the output + // of transpiled code to determine whether something comes from an + // ES module, and is not used as a regular key of `module.exports`. + if (prop in target || prop === "__esModule") return target[prop]; + emitCircularRequireWarning(prop); + return undefined; + }, + + getOwnPropertyDescriptor(target, prop) { + if ( + ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" + ) { + return ObjectGetOwnPropertyDescriptor(target, prop); + } + emitCircularRequireWarning(prop); + return undefined; + }, +}); + +const moduleParentCache = new SafeWeakMap(); +function Module(id = "", parent) { + this.id = id; + this.path = pathDirname(id); + this.exports = {}; + moduleParentCache.set(this, parent); + updateChildren(parent, this, false); + this.filename = null; + this.loaded = false; + this.children = []; +} + +Module.builtinModules = node.builtinModules; + +Module._extensions = ObjectCreate(null); +Module._cache = ObjectCreate(null); +Module._pathCache = ObjectCreate(null); +let modulePaths = []; +Module.globalPaths = modulePaths; + +const CHAR_FORWARD_SLASH = 47; +const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/; +const encodedSepRegEx = /%2F|%2C/i; + +function finalizeEsmResolution( + resolved, + parentPath, + pkgPath, +) { + if (RegExpPrototypeTest(encodedSepRegEx, resolved)) { + throw new ERR_INVALID_MODULE_SPECIFIER( + resolved, + 'must not include encoded "/" or "\\" characters', + parentPath, + ); + } + // const filename = fileURLToPath(resolved); + const filename = resolved; + const actual = tryFile(filename, false); + if (actual) { return actual; } + throw new ERR_MODULE_NOT_FOUND( + filename, + path.resolve(pkgPath, "package.json"), + ); +} - const realpathCache = new SafeMap(); - function toRealPath(requestPath) { - const maybeCached = realpathCache.get(requestPath); - if (maybeCached) { - return maybeCached; - } - const rp = ops.op_require_real_path(requestPath); - realpathCache.set(requestPath, rp); - return rp; +// This only applies to requests of a specific form: +// 1. name/.* +// 2. @scope/name/.* +const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; +function resolveExports( + modulesPath, + request, + parentPath, + usesLocalNodeModulesDir, +) { + // The implementation's behavior is meant to mirror resolution in ESM. + const [, name, expansion = ""] = + StringPrototypeMatch(request, EXPORTS_PATTERN) || []; + if (!name) { + return; } - function tryExtensions(p, exts, isMain) { - for (let i = 0; i < exts.length; i++) { - const filename = tryFile(p + exts[i], isMain); + return ops.op_require_resolve_exports( + usesLocalNodeModulesDir, + modulesPath, + request, + name, + expansion, + parentPath, + ) ?? false; +} - if (filename) { - return filename; - } - } +Module._findPath = function (request, paths, isMain, parentPath) { + const absoluteRequest = ops.op_require_path_is_absolute(request); + if (absoluteRequest) { + paths = [""]; + } else if (!paths || paths.length === 0) { return false; } - // Find the longest (possibly multi-dot) extension registered in - // Module._extensions - function findLongestRegisteredExtension(filename) { - const name = ops.op_require_path_basename(filename); - let currentExtension; - let index; - let startIndex = 0; - while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) { - startIndex = index + 1; - if (index === 0) continue; // Skip dotfiles like .gitignore - currentExtension = StringPrototypeSlice(name, index); - if (Module._extensions[currentExtension]) { - return currentExtension; - } - } - return ".js"; + const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00"); + const entry = Module._pathCache[cacheKey]; + if (entry) { + return entry; } - function getExportsForCircularRequire(module) { - if ( - module.exports && - ObjectGetPrototypeOf(module.exports) === ObjectPrototype && - // Exclude transpiled ES6 modules / TypeScript code because those may - // employ unusual patterns for accessing 'module.exports'. That should - // be okay because ES6 modules have a different approach to circular - // dependencies anyway. - !module.exports.__esModule - ) { - // This is later unset once the module is done loading. - ObjectSetPrototypeOf( - module.exports, - CircularRequirePrototypeWarningProxy, - ); - } - - return module.exports; + let exts; + let trailingSlash = request.length > 0 && + StringPrototypeCharCodeAt(request, request.length - 1) === + CHAR_FORWARD_SLASH; + if (!trailingSlash) { + trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request); } - function emitCircularRequireWarning(prop) { - node.globalThis.process.emitWarning( - `Accessing non-existent property '${String(prop)}' of module exports ` + - "inside circular dependency", - ); - } + // For each path + for (let i = 0; i < paths.length; i++) { + // Don't search further if path doesn't exist + const curPath = paths[i]; + if (curPath && stat(curPath) < 1) continue; - // A Proxy that can be used as the prototype of a module.exports object and - // warns when non-existent properties are accessed. - const CircularRequirePrototypeWarningProxy = new Proxy({}, { - get(target, prop) { - // Allow __esModule access in any case because it is used in the output - // of transpiled code to determine whether something comes from an - // ES module, and is not used as a regular key of `module.exports`. - if (prop in target || prop === "__esModule") return target[prop]; - emitCircularRequireWarning(prop); - return undefined; - }, - - getOwnPropertyDescriptor(target, prop) { - if ( - ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" - ) { - return ObjectGetOwnPropertyDescriptor(target, prop); - } - emitCircularRequireWarning(prop); - return undefined; - }, - }); - - const moduleParentCache = new SafeWeakMap(); - function Module(id = "", parent) { - this.id = id; - this.path = pathDirname(id); - this.exports = {}; - moduleParentCache.set(this, parent); - updateChildren(parent, this, false); - this.filename = null; - this.loaded = false; - this.children = []; - } - - Module.builtinModules = node.builtinModules; - - Module._extensions = ObjectCreate(null); - Module._cache = ObjectCreate(null); - Module._pathCache = ObjectCreate(null); - let modulePaths = []; - Module.globalPaths = modulePaths; - - const CHAR_FORWARD_SLASH = 47; - const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/; - const encodedSepRegEx = /%2F|%2C/i; - - function finalizeEsmResolution( - resolved, - parentPath, - pkgPath, - ) { - if (RegExpPrototypeTest(encodedSepRegEx, resolved)) { - throw new ERR_INVALID_MODULE_SPECIFIER( - resolved, - 'must not include encoded "/" or "\\" characters', - parentPath, - ); - } - // const filename = fileURLToPath(resolved); - const filename = resolved; - const actual = tryFile(filename, false); - if (actual) { - return actual; - } - throw new ERR_MODULE_NOT_FOUND( - filename, - path.resolve(pkgPath, "package.json"), - ); - } - - // This only applies to requests of a specific form: - // 1. name/.* - // 2. @scope/name/.* - const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; - function resolveExports( - modulesPath, - request, - parentPath, - usesLocalNodeModulesDir, - ) { - // The implementation's behavior is meant to mirror resolution in ESM. - const [, name, expansion = ""] = - StringPrototypeMatch(request, EXPORTS_PATTERN) || []; - if (!name) { - return; - } - - return core.ops.op_require_resolve_exports( - usesLocalNodeModulesDir, - modulesPath, - request, - name, - expansion, - parentPath, - ) ?? false; - } - - Module._findPath = function (request, paths, isMain, parentPath) { - const absoluteRequest = ops.op_require_path_is_absolute(request); - if (absoluteRequest) { - paths = [""]; - } else if (!paths || paths.length === 0) { - return false; - } - - const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00"); - const entry = Module._pathCache[cacheKey]; - if (entry) { - return entry; - } - - let exts; - let trailingSlash = request.length > 0 && - StringPrototypeCharCodeAt(request, request.length - 1) === - CHAR_FORWARD_SLASH; - if (!trailingSlash) { - trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request); - } - - // For each path - for (let i = 0; i < paths.length; i++) { - // Don't search further if path doesn't exist - const curPath = paths[i]; - if (curPath && stat(curPath) < 1) continue; - - if (!absoluteRequest) { - const exportsResolved = resolveExports( - curPath, - request, - parentPath, - usesLocalNodeModulesDir, - ); - if (exportsResolved) { - return exportsResolved; - } - } - - const isDenoDirPackage = core.ops.op_require_is_deno_dir_package( + if (!absoluteRequest) { + const exportsResolved = resolveExports( curPath, - ); - const isRelative = ops.op_require_is_request_relative( request, + parentPath, + usesLocalNodeModulesDir, ); - const basePath = - (isDenoDirPackage && !isRelative && !usesLocalNodeModulesDir) - ? pathResolve(curPath, packageSpecifierSubPath(request)) - : pathResolve(curPath, request); - let filename; + if (exportsResolved) { + return exportsResolved; + } + } - const rc = stat(basePath); - if (!trailingSlash) { - if (rc === 0) { // File. - filename = toRealPath(basePath); - } + const isDenoDirPackage = ops.op_require_is_deno_dir_package( + curPath, + ); + const isRelative = ops.op_require_is_request_relative( + request, + ); + const basePath = + (isDenoDirPackage && !isRelative && !usesLocalNodeModulesDir) + ? pathResolve(curPath, packageSpecifierSubPath(request)) + : pathResolve(curPath, request); + let filename; - if (!filename) { - // Try it with each of the extensions - if (exts === undefined) { - exts = ObjectKeys(Module._extensions); - } - filename = tryExtensions(basePath, exts, isMain); - } + const rc = stat(basePath); + if (!trailingSlash) { + if (rc === 0) { // File. + filename = toRealPath(basePath); } - if (!filename && rc === 1) { // Directory. - // try it with each of the extensions at "index" + if (!filename) { + // Try it with each of the extensions if (exts === undefined) { exts = ObjectKeys(Module._extensions); } - filename = tryPackage(basePath, exts, isMain, request); - } - - if (filename) { - Module._pathCache[cacheKey] = filename; - return filename; + filename = tryExtensions(basePath, exts, isMain); } } - return false; - }; - - Module._nodeModulePaths = function (fromPath) { - return ops.op_require_node_module_paths(fromPath); - }; - - Module._resolveLookupPaths = function (request, parent) { - const paths = []; - - if (core.ops.op_require_is_request_relative(request) && parent?.filename) { - ArrayPrototypePush( - paths, - core.ops.op_require_path_dirname(parent.filename), - ); - return paths; - } - - if (parent?.filename && parent.filename.length > 0) { - const denoDirPath = core.ops.op_require_resolve_deno_dir( - request, - parent.filename, - ); - if (denoDirPath) { - ArrayPrototypePush(paths, denoDirPath); + if (!filename && rc === 1) { // Directory. + // try it with each of the extensions at "index" + if (exts === undefined) { + exts = ObjectKeys(Module._extensions); } + filename = tryPackage(basePath, exts, isMain, request); } - const lookupPathsResult = ops.op_require_resolve_lookup_paths( - request, - parent?.paths, - parent?.filename ?? "", + + if (filename) { + Module._pathCache[cacheKey] = filename; + return filename; + } + } + + return false; +}; + +Module._nodeModulePaths = function (fromPath) { + return ops.op_require_node_module_paths(fromPath); +}; + +Module._resolveLookupPaths = function (request, parent) { + const paths = []; + + if (ops.op_require_is_request_relative(request) && parent?.filename) { + ArrayPrototypePush( + paths, + ops.op_require_path_dirname(parent.filename), ); - if (lookupPathsResult) { - ArrayPrototypePush(paths, ...new SafeArrayIterator(lookupPathsResult)); - } return paths; - }; + } - Module._load = function (request, parent, isMain) { - let relResolveCacheIdentifier; - if (parent) { - // Fast path for (lazy loaded) modules in the same directory. The indirect - // caching is required to allow cache invalidation without changing the old - // cache key names. - relResolveCacheIdentifier = `${parent.path}\x00${request}`; - const filename = relativeResolveCache[relResolveCacheIdentifier]; - if (filename !== undefined) { - const cachedModule = Module._cache[filename]; - if (cachedModule !== undefined) { - updateChildren(parent, cachedModule, true); - if (!cachedModule.loaded) { - return getExportsForCircularRequire(cachedModule); - } - return cachedModule.exports; + if (parent?.filename && parent.filename.length > 0) { + const denoDirPath = ops.op_require_resolve_deno_dir( + request, + parent.filename, + ); + if (denoDirPath) { + ArrayPrototypePush(paths, denoDirPath); + } + } + const lookupPathsResult = ops.op_require_resolve_lookup_paths( + request, + parent?.paths, + parent?.filename ?? "", + ); + if (lookupPathsResult) { + ArrayPrototypePush(paths, ...new SafeArrayIterator(lookupPathsResult)); + } + return paths; +}; + +Module._load = function (request, parent, isMain) { + let relResolveCacheIdentifier; + if (parent) { + // Fast path for (lazy loaded) modules in the same directory. The indirect + // caching is required to allow cache invalidation without changing the old + // cache key names. + relResolveCacheIdentifier = `${parent.path}\x00${request}`; + const filename = relativeResolveCache[relResolveCacheIdentifier]; + if (filename !== undefined) { + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); } - delete relativeResolveCache[relResolveCacheIdentifier]; + return cachedModule.exports; } + delete relativeResolveCache[relResolveCacheIdentifier]; } + } - const filename = Module._resolveFilename(request, parent, isMain); - if (StringPrototypeStartsWith(filename, "node:")) { - // Slice 'node:' prefix - const id = StringPrototypeSlice(filename, 5); + const filename = Module._resolveFilename(request, parent, isMain); + if (StringPrototypeStartsWith(filename, "node:")) { + // Slice 'node:' prefix + const id = StringPrototypeSlice(filename, 5); - const module = loadNativeModule(id, id); - if (!module) { - // TODO: - // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); - throw new Error("Unknown built-in module"); - } - - return module.exports; - } - - const cachedModule = Module._cache[filename]; - if (cachedModule !== undefined) { - updateChildren(parent, cachedModule, true); - if (!cachedModule.loaded) { - return getExportsForCircularRequire(cachedModule); - } - return cachedModule.exports; - } - - const mod = loadNativeModule(filename, request); - if ( - mod - ) { - return mod.exports; - } - // Don't call updateChildren(), Module constructor already does. - const module = cachedModule || new Module(filename, parent); - - if (isMain) { - node.globalThis.process.mainModule = module; - mainModule = module; - module.id = "."; - } - - Module._cache[filename] = module; - if (parent !== undefined) { - relativeResolveCache[relResolveCacheIdentifier] = filename; - } - - let threw = true; - try { - module.load(filename); - threw = false; - } finally { - if (threw) { - delete Module._cache[filename]; - if (parent !== undefined) { - delete relativeResolveCache[relResolveCacheIdentifier]; - const children = parent?.children; - if (ArrayIsArray(children)) { - const index = ArrayPrototypeIndexOf(children, module); - if (index !== -1) { - ArrayPrototypeSplice(children, index, 1); - } - } - } - } else if ( - module.exports && - ObjectGetPrototypeOf(module.exports) === - CircularRequirePrototypeWarningProxy - ) { - ObjectSetPrototypeOf(module.exports, ObjectPrototype); - } + const module = loadNativeModule(id, id); + if (!module) { + // TODO: + // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); + throw new Error("Unknown built-in module"); } return module.exports; - }; + } - Module._resolveFilename = function ( - request, - parent, - isMain, - options, - ) { - if ( - StringPrototypeStartsWith(request, "node:") || - nativeModuleCanBeRequiredByUsers(request) - ) { - return request; + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); } + return cachedModule.exports; + } - let paths; + const mod = loadNativeModule(filename, request); + if ( + mod + ) { + return mod.exports; + } + // Don't call updateChildren(), Module constructor already does. + const module = cachedModule || new Module(filename, parent); - if (typeof options === "object" && options !== null) { - if (ArrayIsArray(options.paths)) { - const isRelative = ops.op_require_is_request_relative( - request, - ); + if (isMain) { + node.globalThis.process.mainModule = module; + mainModule = module; + module.id = "."; + } - if (isRelative) { - paths = options.paths; - } else { - const fakeParent = new Module("", null); + Module._cache[filename] = module; + if (parent !== undefined) { + relativeResolveCache[relResolveCacheIdentifier] = filename; + } - paths = []; + let threw = true; + try { + module.load(filename); + threw = false; + } finally { + if (threw) { + delete Module._cache[filename]; + if (parent !== undefined) { + delete relativeResolveCache[relResolveCacheIdentifier]; + const children = parent?.children; + if (ArrayIsArray(children)) { + const index = ArrayPrototypeIndexOf(children, module); + if (index !== -1) { + ArrayPrototypeSplice(children, index, 1); + } + } + } + } else if ( + module.exports && + ObjectGetPrototypeOf(module.exports) === + CircularRequirePrototypeWarningProxy + ) { + ObjectSetPrototypeOf(module.exports, ObjectPrototype); + } + } - for (let i = 0; i < options.paths.length; i++) { - const path = options.paths[i]; - fakeParent.paths = Module._nodeModulePaths(path); - const lookupPaths = Module._resolveLookupPaths(request, fakeParent); + return module.exports; +}; - for (let j = 0; j < lookupPaths.length; j++) { - if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) { - ArrayPrototypePush(paths, lookupPaths[j]); - } +Module._resolveFilename = function ( + request, + parent, + isMain, + options, +) { + if ( + StringPrototypeStartsWith(request, "node:") || + nativeModuleCanBeRequiredByUsers(request) + ) { + return request; + } + + let paths; + + if (typeof options === "object" && options !== null) { + if (ArrayIsArray(options.paths)) { + const isRelative = ops.op_require_is_request_relative( + request, + ); + + if (isRelative) { + paths = options.paths; + } else { + const fakeParent = new Module("", null); + + paths = []; + + for (let i = 0; i < options.paths.length; i++) { + const path = options.paths[i]; + fakeParent.paths = Module._nodeModulePaths(path); + const lookupPaths = Module._resolveLookupPaths(request, fakeParent); + + for (let j = 0; j < lookupPaths.length; j++) { + if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) { + ArrayPrototypePush(paths, lookupPaths[j]); } } } - } else if (options.paths === undefined) { - paths = Module._resolveLookupPaths(request, parent); - } else { - // TODO: - // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths); - throw new Error("Invalid arg value options.paths", options.path); } - } else { + } else if (options.paths === undefined) { paths = Module._resolveLookupPaths(request, parent); - } - - if (parent?.filename) { - if (request[0] === "#") { - const maybeResolved = core.ops.op_require_package_imports_resolve( - parent.filename, - request, - ); - if (maybeResolved) { - return maybeResolved; - } - } - } - - // Try module self resolution first - const parentPath = ops.op_require_try_self_parent_path( - !!parent, - parent?.filename, - parent?.id, - ); - const selfResolved = ops.op_require_try_self(parentPath, request); - if (selfResolved) { - const cacheKey = request + "\x00" + - (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00")); - Module._pathCache[cacheKey] = selfResolved; - return selfResolved; - } - - // Look up the filename first, since that's the cache key. - const filename = Module._findPath( - request, - paths, - isMain, - parentPath, - ); - if (filename) return filename; - const requireStack = []; - for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) { - ArrayPrototypePush(requireStack, cursor.filename || cursor.id); - } - let message = `Cannot find module '${request}'`; - if (requireStack.length > 0) { - message = message + "\nRequire stack:\n- " + - ArrayPrototypeJoin(requireStack, "\n- "); - } - // eslint-disable-next-line no-restricted-syntax - const err = new Error(message); - err.code = "MODULE_NOT_FOUND"; - err.requireStack = requireStack; - throw err; - }; - - Module.prototype.load = function (filename) { - assert(!this.loaded); - this.filename = filename; - this.paths = Module._nodeModulePaths( - pathDirname(filename), - ); - const extension = findLongestRegisteredExtension(filename); - // allow .mjs to be overriden - if ( - StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"] - ) { - // TODO: use proper error class - throw new Error("require ESM", filename); - } - - Module._extensions[extension](this, filename); - this.loaded = true; - - // TODO: do caching - }; - - // Loads a module at the given file path. Returns that module's - // `exports` property. - Module.prototype.require = function (id) { - if (typeof id !== "string") { - // TODO(bartlomieju): it should use different error type - // ("ERR_INVALID_ARG_VALUE") - throw new TypeError("Invalid argument type"); - } - - if (id === "") { - // TODO(bartlomieju): it should use different error type - // ("ERR_INVALID_ARG_VALUE") - throw new TypeError("id must be non empty"); - } - requireDepth++; - try { - return Module._load(id, this, /* isMain */ false); - } finally { - requireDepth--; - } - }; - - Module.wrapper = [ - // We provide the non-standard APIs in the CommonJS wrapper - // to avoid exposing them in global namespace. - "(function (exports, require, module, __filename, __dirname, globalThis) { const { Buffer, clearImmediate, clearInterval, clearTimeout, console, global, process, setImmediate, setInterval, setTimeout} = globalThis; var window = undefined; (function () {", - "\n}).call(this); })", - ]; - Module.wrap = function (script) { - script = script.replace(/^#!.*?\n/, ""); - return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`; - }; - - function enrichCJSError(error) { - if (error instanceof SyntaxError) { - if ( - StringPrototypeIncludes( - error.message, - "Cannot use import statement outside a module", - ) || - StringPrototypeIncludes(error.message, "Unexpected token 'export'") - ) { - console.error( - 'To load an ES module, set "type": "module" in the package.json or use ' + - "the .mjs extension.", - ); - } - } - } - - function wrapSafe( - filename, - content, - cjsModuleInstance, - ) { - const wrapper = Module.wrap(content); - const [f, err] = core.evalContext(wrapper, filename); - if (err) { - if (node.globalThis.process.mainModule === cjsModuleInstance) { - enrichCJSError(err.thrown); - } - throw err.thrown; - } - return f; - } - - Module.prototype._compile = function (content, filename) { - const compiledWrapper = wrapSafe(filename, content, this); - - const dirname = pathDirname(filename); - const require = makeRequireFunction(this); - const exports = this.exports; - const thisValue = exports; - const module = this; - if (requireDepth === 0) { - statCache = new SafeMap(); - } - - if (hasInspectBrk && !hasBrokenOnInspectBrk) { - hasBrokenOnInspectBrk = true; - core.ops.op_require_break_on_next_statement(); - } - - const result = compiledWrapper.call( - thisValue, - exports, - require, - this, - filename, - dirname, - node.globalThis, - ); - if (requireDepth === 0) { - statCache = null; - } - return result; - }; - - Module._extensions[".js"] = function (module, filename) { - const content = ops.op_require_read_file(filename); - - if (StringPrototypeEndsWith(filename, ".js")) { - const pkg = core.ops.op_require_read_closest_package_json(filename); - if (pkg && pkg.exists && pkg.typ == "module") { - let message = `Trying to import ESM module: ${filename}`; - - if (module.parent) { - message += ` from ${module.parent.filename}`; - } - - message += ` using require()`; - - throw new Error(message); - } - } - - module._compile(content, filename); - }; - - function stripBOM(content) { - if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { - content = StringPrototypeSlice(content, 1); - } - return content; - } - - // Native extension for .json - Module._extensions[".json"] = function (module, filename) { - const content = ops.op_require_read_file(filename); - - try { - module.exports = JSONParse(stripBOM(content)); - } catch (err) { - err.message = filename + ": " + err.message; - throw err; - } - }; - - // Native extension for .node - Module._extensions[".node"] = function (module, filename) { - if (filename.endsWith("fsevents.node")) { - throw new Error("Using fsevents module is currently not supported"); - } - module.exports = ops.op_napi_open(filename, node.globalThis); - }; - - function createRequireFromPath(filename) { - const proxyPath = ops.op_require_proxy_path(filename); - const mod = new Module(proxyPath); - mod.filename = proxyPath; - mod.paths = Module._nodeModulePaths(mod.path); - return makeRequireFunction(mod); - } - - function makeRequireFunction(mod) { - const require = function require(path) { - return mod.require(path); - }; - - function resolve(request, options) { - return Module._resolveFilename(request, mod, false, options); - } - - require.resolve = resolve; - - function paths(request) { - return Module._resolveLookupPaths(request, mod); - } - - resolve.paths = paths; - require.main = mainModule; - // Enable support to add extra extension types. - require.extensions = Module._extensions; - require.cache = Module._cache; - - return require; - } - - // Matches to: - // - /foo/... - // - \foo\... - // - C:/foo/... - // - C:\foo\... - const RE_START_OF_ABS_PATH = /^([/\\]|[a-zA-Z]:[/\\])/; - - function isAbsolute(filenameOrUrl) { - return RE_START_OF_ABS_PATH.test(filenameOrUrl); - } - - function createRequire(filenameOrUrl) { - let fileUrlStr; - if (filenameOrUrl instanceof URL) { - if (filenameOrUrl.protocol !== "file:") { - throw new Error( - `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, - ); - } - fileUrlStr = filenameOrUrl.toString(); - } else if (typeof filenameOrUrl === "string") { - if (!filenameOrUrl.startsWith("file:") && !isAbsolute(filenameOrUrl)) { - throw new Error( - `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, - ); - } - fileUrlStr = filenameOrUrl; } else { + // TODO: + // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths); + throw new Error("Invalid arg value options.paths", options.path); + } + } else { + paths = Module._resolveLookupPaths(request, parent); + } + + if (parent?.filename) { + if (request[0] === "#") { + const maybeResolved = ops.op_require_package_imports_resolve( + parent.filename, + request, + ); + if (maybeResolved) { + return maybeResolved; + } + } + } + + // Try module self resolution first + const parentPath = ops.op_require_try_self_parent_path( + !!parent, + parent?.filename, + parent?.id, + ); + const selfResolved = ops.op_require_try_self(parentPath, request); + if (selfResolved) { + const cacheKey = request + "\x00" + + (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00")); + Module._pathCache[cacheKey] = selfResolved; + return selfResolved; + } + + // Look up the filename first, since that's the cache key. + const filename = Module._findPath( + request, + paths, + isMain, + parentPath, + ); + if (filename) return filename; + const requireStack = []; + for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) { + ArrayPrototypePush(requireStack, cursor.filename || cursor.id); + } + let message = `Cannot find module '${request}'`; + if (requireStack.length > 0) { + message = message + "\nRequire stack:\n- " + + ArrayPrototypeJoin(requireStack, "\n- "); + } + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + err.code = "MODULE_NOT_FOUND"; + err.requireStack = requireStack; + throw err; +}; + +Module.prototype.load = function (filename) { + assert(!this.loaded); + this.filename = filename; + this.paths = Module._nodeModulePaths( + pathDirname(filename), + ); + const extension = findLongestRegisteredExtension(filename); + // allow .mjs to be overriden + if ( + StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"] + ) { + // TODO: use proper error class + throw new Error("require ESM", filename); + } + + Module._extensions[extension](this, filename); + this.loaded = true; + + // TODO: do caching +}; + +// Loads a module at the given file path. Returns that module's +// `exports` property. +Module.prototype.require = function (id) { + if (typeof id !== "string") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("Invalid argument type"); + } + + if (id === "") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("id must be non empty"); + } + requireDepth++; + try { + return Module._load(id, this, /* isMain */ false); + } finally { + requireDepth--; + } +}; + +Module.wrapper = [ + // We provide the non-standard APIs in the CommonJS wrapper + // to avoid exposing them in global namespace. + "(function (exports, require, module, __filename, __dirname, globalThis) { const { Buffer, clearImmediate, clearInterval, clearTimeout, console, global, process, setImmediate, setInterval, setTimeout} = globalThis; var window = undefined; (function () {", + "\n}).call(this); })", +]; +Module.wrap = function (script) { + script = script.replace(/^#!.*?\n/, ""); + return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`; +}; + +function enrichCJSError(error) { + if (error instanceof SyntaxError) { + if ( + StringPrototypeIncludes( + error.message, + "Cannot use import statement outside a module", + ) || + StringPrototypeIncludes(error.message, "Unexpected token 'export'") + ) { + console.error( + 'To load an ES module, set "type": "module" in the package.json or use ' + + "the .mjs extension.", + ); + } + } +} + +function wrapSafe( + filename, + content, + cjsModuleInstance, +) { + const wrapper = Module.wrap(content); + const [f, err] = core.evalContext(wrapper, filename); + if (err) { + if (node.globalThis.process.mainModule === cjsModuleInstance) { + enrichCJSError(err.thrown); + } + throw err.thrown; + } + return f; +} + +Module.prototype._compile = function (content, filename) { + const compiledWrapper = wrapSafe(filename, content, this); + + const dirname = pathDirname(filename); + const require = makeRequireFunction(this); + const exports = this.exports; + const thisValue = exports; + const module = this; + if (requireDepth === 0) { + statCache = new SafeMap(); + } + + if (hasInspectBrk && !hasBrokenOnInspectBrk) { + hasBrokenOnInspectBrk = true; + ops.op_require_break_on_next_statement(); + } + + const result = compiledWrapper.call( + thisValue, + exports, + require, + this, + filename, + dirname, + node.globalThis, + ); + if (requireDepth === 0) { + statCache = null; + } + return result; +}; + +Module._extensions[".js"] = function (module, filename) { + const content = ops.op_require_read_file(filename); + + if (StringPrototypeEndsWith(filename, ".js")) { + const pkg = ops.op_require_read_closest_package_json(filename); + if (pkg && pkg.exists && pkg.typ == "module") { + let message = `Trying to import ESM module: ${filename}`; + + if (module.parent) { + message += ` from ${module.parent.filename}`; + } + + message += ` using require()`; + + throw new Error(message); + } + } + + module._compile(content, filename); +}; + +function stripBOM(content) { + if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { + content = StringPrototypeSlice(content, 1); + } + return content; +} + +// Native extension for .json +Module._extensions[".json"] = function (module, filename) { + const content = ops.op_require_read_file(filename); + + try { + module.exports = JSONParse(stripBOM(content)); + } catch (err) { + err.message = filename + ": " + err.message; + throw err; + } +}; + +// Native extension for .node +Module._extensions[".node"] = function (module, filename) { + if (filename.endsWith("fsevents.node")) { + throw new Error("Using fsevents module is currently not supported"); + } + module.exports = ops.op_napi_open(filename, node.globalThis); +}; + +function createRequireFromPath(filename) { + const proxyPath = ops.op_require_proxy_path(filename); + const mod = new Module(proxyPath); + mod.filename = proxyPath; + mod.paths = Module._nodeModulePaths(mod.path); + return makeRequireFunction(mod); +} + +function makeRequireFunction(mod) { + const require = function require(path) { + return mod.require(path); + }; + + function resolve(request, options) { + return Module._resolveFilename(request, mod, false, options); + } + + require.resolve = resolve; + + function paths(request) { + return Module._resolveLookupPaths(request, mod); + } + + resolve.paths = paths; + require.main = mainModule; + // Enable support to add extra extension types. + require.extensions = Module._extensions; + require.cache = Module._cache; + + return require; +} + +// Matches to: +// - /foo/... +// - \foo\... +// - C:/foo/... +// - C:\foo\... +const RE_START_OF_ABS_PATH = /^([/\\]|[a-zA-Z]:[/\\])/; + +function isAbsolute(filenameOrUrl) { + return RE_START_OF_ABS_PATH.test(filenameOrUrl); +} + +function createRequire(filenameOrUrl) { + let fileUrlStr; + if (filenameOrUrl instanceof URL) { + if (filenameOrUrl.protocol !== "file:") { throw new Error( `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, ); } - const filename = core.ops.op_require_as_file_path(fileUrlStr); - return createRequireFromPath(filename); - } - - Module.createRequire = createRequire; - - Module._initPaths = function () { - const paths = ops.op_require_init_paths(); - modulePaths = paths; - Module.globalPaths = ArrayPrototypeSlice(modulePaths); - }; - - Module.syncBuiltinESMExports = function syncBuiltinESMExports() { - throw new Error("not implemented"); - }; - - Module.Module = Module; - - node.nativeModuleExports.module = Module; - - function loadNativeModule(_id, request) { - if (nativeModulePolyfill.has(request)) { - return nativeModulePolyfill.get(request); + fileUrlStr = filenameOrUrl.toString(); + } else if (typeof filenameOrUrl === "string") { + if (!filenameOrUrl.startsWith("file:") && !isAbsolute(filenameOrUrl)) { + throw new Error( + `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, + ); } - const modExports = node.nativeModuleExports[request]; - if (modExports) { - const nodeMod = new Module(request); - nodeMod.exports = modExports; - nodeMod.loaded = true; - nativeModulePolyfill.set(request, nodeMod); - return nodeMod; - } - return undefined; + fileUrlStr = filenameOrUrl; + } else { + throw new Error( + `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, + ); } + const filename = ops.op_require_as_file_path(fileUrlStr); + return createRequireFromPath(filename); +} - function nativeModuleCanBeRequiredByUsers(request) { - return !!node.nativeModuleExports[request]; +Module.createRequire = createRequire; + +Module._initPaths = function () { + const paths = ops.op_require_init_paths(); + modulePaths = paths; + Module.globalPaths = ArrayPrototypeSlice(modulePaths); +}; + +Module.syncBuiltinESMExports = function syncBuiltinESMExports() { + throw new Error("not implemented"); +}; + +Module.Module = Module; + +node.nativeModuleExports.module = Module; + +function loadNativeModule(_id, request) { + if (nativeModulePolyfill.has(request)) { + return nativeModulePolyfill.get(request); } - - function readPackageScope() { - throw new Error("not implemented"); + const modExports = node.nativeModuleExports[request]; + if (modExports) { + const nodeMod = new Module(request); + nodeMod.exports = modExports; + nodeMod.loaded = true; + nativeModulePolyfill.set(request, nodeMod); + return nodeMod; } + return undefined; +} - /** @param specifier {string} */ - function packageSpecifierSubPath(specifier) { - let parts = StringPrototypeSplit(specifier, "/"); - if (StringPrototypeStartsWith(parts[0], "@")) { - parts = ArrayPrototypeSlice(parts, 2); - } else { - parts = ArrayPrototypeSlice(parts, 1); - } - return ArrayPrototypeJoin(parts, "/"); +function nativeModuleCanBeRequiredByUsers(request) { + return !!node.nativeModuleExports[request]; +} + +function readPackageScope() { + throw new Error("not implemented"); +} + +/** @param specifier {string} */ +function packageSpecifierSubPath(specifier) { + let parts = StringPrototypeSplit(specifier, "/"); + if (StringPrototypeStartsWith(parts[0], "@")) { + parts = ArrayPrototypeSlice(parts, 2); + } else { + parts = ArrayPrototypeSlice(parts, 1); } + return ArrayPrototypeJoin(parts, "/"); +} - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - require: { - setUsesLocalNodeModulesDir() { - usesLocalNodeModulesDir = true; - }, - setInspectBrk() { - hasInspectBrk = true; - }, - Module, - wrapSafe, - toRealPath, - cjsParseCache, - readPackageScope, - }, - }; -})(globalThis); +internals.require = { + setUsesLocalNodeModulesDir() { + usesLocalNodeModulesDir = true; + }, + setInspectBrk() { + hasInspectBrk = true; + }, + Module, + wrapSafe, + toRealPath, + cjsParseCache, + readPackageScope, +}; diff --git a/ext/node/lib.rs b/ext/node/lib.rs index ad8619889d..8db2bb3a7a 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -85,7 +85,7 @@ pub fn init( maybe_npm_resolver: Option>, ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/node", "01_node.js", "02_require.js", diff --git a/ext/url/00_url.js b/ext/url/00_url.js index 1191565ee2..faaba2911e 100644 --- a/ext/url/00_url.js +++ b/ext/url/00_url.js @@ -5,806 +5,803 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSome, + ArrayPrototypeSort, + ArrayPrototypeSplice, + ObjectKeys, + Uint32Array, + SafeArrayIterator, + StringPrototypeSlice, + Symbol, + SymbolFor, + SymbolIterator, + TypeError, +} = primordials; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayIsArray, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSome, - ArrayPrototypeSort, - ArrayPrototypeSplice, - ObjectKeys, - Uint32Array, - SafeArrayIterator, - StringPrototypeSlice, - Symbol, - SymbolFor, - SymbolIterator, - TypeError, - } = window.__bootstrap.primordials; +const _list = Symbol("list"); +const _urlObject = Symbol("url object"); - const _list = Symbol("list"); - const _urlObject = Symbol("url object"); +// WARNING: must match rust code's UrlSetter::* +const SET_HASH = 0; +const SET_HOST = 1; +const SET_HOSTNAME = 2; +const SET_PASSWORD = 3; +const SET_PATHNAME = 4; +const SET_PORT = 5; +const SET_PROTOCOL = 6; +const SET_SEARCH = 7; +const SET_USERNAME = 8; - // WARNING: must match rust code's UrlSetter::* - const SET_HASH = 0; - const SET_HOST = 1; - const SET_HOSTNAME = 2; - const SET_PASSWORD = 3; - const SET_PATHNAME = 4; - const SET_PORT = 5; - const SET_PROTOCOL = 6; - const SET_SEARCH = 7; - const SET_USERNAME = 8; +// Helper functions +function opUrlReparse(href, setter, value) { + const status = ops.op_url_reparse( + href, + setter, + value, + componentsBuf.buffer, + ); + return getSerialization(status, href); +} - // Helper functions - function opUrlReparse(href, setter, value) { - const status = ops.op_url_reparse( +function opUrlParse(href, maybeBase) { + let status; + if (maybeBase === undefined) { + status = ops.op_url_parse(href, componentsBuf.buffer); + } else { + status = ops.op_url_parse_with_base( href, - setter, - value, + maybeBase, componentsBuf.buffer, ); - return getSerialization(status, href); } + return getSerialization(status, href, maybeBase); +} - function opUrlParse(href, maybeBase) { - let status; - if (maybeBase === undefined) { - status = ops.op_url_parse(href, componentsBuf.buffer); - } else { - status = core.ops.op_url_parse_with_base( - href, - maybeBase, - componentsBuf.buffer, +function getSerialization(status, href, maybeBase) { + if (status === 0) { + return href; + } else if (status === 1) { + return ops.op_url_get_serialization(); + } else { + throw new TypeError( + `Invalid URL: '${href}'` + + (maybeBase ? ` with base '${maybeBase}'` : ""), + ); + } +} + +class URLSearchParams { + [_list]; + [_urlObject] = null; + + /** + * @param {string | [string][] | Record} init + */ + constructor(init = "") { + const prefix = "Failed to construct 'URL'"; + init = webidl.converters + ["sequence> or record or USVString"]( + init, + { prefix, context: "Argument 1" }, ); + this[webidl.brand] = webidl.brand; + if (!init) { + // if there is no query string, return early + this[_list] = []; + return; } - return getSerialization(status, href, maybeBase); - } - function getSerialization(status, href, maybeBase) { - if (status === 0) { - return href; - } else if (status === 1) { - return core.ops.op_url_get_serialization(); - } else { - throw new TypeError( - `Invalid URL: '${href}'` + - (maybeBase ? ` with base '${maybeBase}'` : ""), - ); - } - } - - class URLSearchParams { - [_list]; - [_urlObject] = null; - - /** - * @param {string | [string][] | Record} init - */ - constructor(init = "") { - const prefix = "Failed to construct 'URL'"; - init = webidl.converters - ["sequence> or record or USVString"]( - init, - { prefix, context: "Argument 1" }, - ); - this[webidl.brand] = webidl.brand; - if (!init) { - // if there is no query string, return early - this[_list] = []; - return; + if (typeof init === "string") { + // Overload: USVString + // If init is a string and starts with U+003F (?), + // remove the first code point from init. + if (init[0] == "?") { + init = StringPrototypeSlice(init, 1); } - - if (typeof init === "string") { - // Overload: USVString - // If init is a string and starts with U+003F (?), - // remove the first code point from init. - if (init[0] == "?") { - init = StringPrototypeSlice(init, 1); + this[_list] = ops.op_url_parse_search_params(init); + } else if (ArrayIsArray(init)) { + // Overload: sequence> + this[_list] = ArrayPrototypeMap(init, (pair, i) => { + if (pair.length !== 2) { + throw new TypeError( + `${prefix}: Item ${ + i + 0 + } in the parameter list does have length 2 exactly.`, + ); } - this[_list] = ops.op_url_parse_search_params(init); - } else if (ArrayIsArray(init)) { - // Overload: sequence> - this[_list] = ArrayPrototypeMap(init, (pair, i) => { - if (pair.length !== 2) { - throw new TypeError( - `${prefix}: Item ${ - i + 0 - } in the parameter list does have length 2 exactly.`, - ); - } - return [pair[0], pair[1]]; - }); + return [pair[0], pair[1]]; + }); + } else { + // Overload: record + this[_list] = ArrayPrototypeMap( + ObjectKeys(init), + (key) => [key, init[key]], + ); + } + } + + #updateUrlSearch() { + const url = this[_urlObject]; + if (url === null) { + return; + } + url[_updateUrlSearch](this.toString()); + } + + /** + * @param {string} name + * @param {string} value + */ + append(name, value) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + ArrayPrototypePush(this[_list], [name, value]); + this.#updateUrlSearch(); + } + + /** + * @param {string} name + */ + delete(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const list = this[_list]; + let i = 0; + while (i < list.length) { + if (list[i][0] === name) { + ArrayPrototypeSplice(list, i, 1); } else { - // Overload: record - this[_list] = ArrayPrototypeMap( - ObjectKeys(init), - (key) => [key, init[key]], - ); + i++; } } + this.#updateUrlSearch(); + } - #updateUrlSearch() { - const url = this[_urlObject]; - if (url === null) { - return; + /** + * @param {string} name + * @returns {string[]} + */ + getAll(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const values = []; + const entries = this[_list]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry[0] === name) { + ArrayPrototypePush(values, entry[1]); } - url[_updateUrlSearch](this.toString()); } + return values; + } - /** - * @param {string} name - * @param {string} value - */ - append(name, value) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'append' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.USVString(value, { - prefix, - context: "Argument 2", - }); - ArrayPrototypePush(this[_list], [name, value]); - this.#updateUrlSearch(); + /** + * @param {string} name + * @return {string | null} + */ + get(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'get' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const entries = this[_list]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry[0] === name) { + return entry[1]; + } } + return null; + } - /** - * @param {string} name - */ - delete(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'append' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const list = this[_list]; - let i = 0; - while (i < list.length) { - if (list[i][0] === name) { + /** + * @param {string} name + * @return {boolean} + */ + has(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'has' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + return ArrayPrototypeSome(this[_list], (entry) => entry[0] === name); + } + + /** + * @param {string} name + * @param {string} value + */ + set(name, value) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'set' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + + const list = this[_list]; + + // If there are any name-value pairs whose name is name, in list, + // set the value of the first such name-value pair to value + // and remove the others. + let found = false; + let i = 0; + while (i < list.length) { + if (list[i][0] === name) { + if (!found) { + list[i][1] = value; + found = true; + i++; + } else { ArrayPrototypeSplice(list, i, 1); - } else { - i++; } + } else { + i++; } - this.#updateUrlSearch(); } - /** - * @param {string} name - * @returns {string[]} - */ - getAll(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const values = []; - const entries = this[_list]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry[0] === name) { - ArrayPrototypePush(values, entry[1]); - } - } - return values; + // Otherwise, append a new name-value pair whose name is name + // and value is value, to list. + if (!found) { + ArrayPrototypePush(list, [name, value]); } - /** - * @param {string} name - * @return {string | null} - */ - get(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'get' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const entries = this[_list]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry[0] === name) { - return entry[1]; - } - } - return null; - } + this.#updateUrlSearch(); + } - /** - * @param {string} name - * @return {boolean} - */ - has(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'has' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - return ArrayPrototypeSome(this[_list], (entry) => entry[0] === name); - } + sort() { + webidl.assertBranded(this, URLSearchParamsPrototype); + ArrayPrototypeSort( + this[_list], + (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), + ); + this.#updateUrlSearch(); + } - /** - * @param {string} name - * @param {string} value - */ - set(name, value) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'set' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.USVString(value, { + /** + * @return {string} + */ + toString() { + webidl.assertBranded(this, URLSearchParamsPrototype); + return ops.op_url_stringify_search_params(this[_list]); + } +} + +webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); + +webidl.configurePrototype(URLSearchParams); +const URLSearchParamsPrototype = URLSearchParams.prototype; + +webidl.converters["URLSearchParams"] = webidl.createInterfaceConverter( + "URLSearchParams", + URLSearchParamsPrototype, +); + +const _updateUrlSearch = Symbol("updateUrlSearch"); + +function trim(s) { + if (s.length === 1) return ""; + return s; +} + +// Represents a "no port" value. A port in URL cannot be greater than 2^16 − 1 +const NO_PORT = 65536; + +const componentsBuf = new Uint32Array(8); +class URL { + #queryObject = null; + #serialization; + #schemeEnd; + #usernameEnd; + #hostStart; + #hostEnd; + #port; + #pathStart; + #queryStart; + #fragmentStart; + + [_updateUrlSearch](value) { + this.#serialization = opUrlReparse( + this.#serialization, + SET_SEARCH, + value, + ); + this.#updateComponents(); + } + + /** + * @param {string} url + * @param {string} base + */ + constructor(url, base = undefined) { + const prefix = "Failed to construct 'URL'"; + url = webidl.converters.DOMString(url, { prefix, context: "Argument 1" }); + if (base !== undefined) { + base = webidl.converters.DOMString(base, { prefix, context: "Argument 2", }); - - const list = this[_list]; - - // If there are any name-value pairs whose name is name, in list, - // set the value of the first such name-value pair to value - // and remove the others. - let found = false; - let i = 0; - while (i < list.length) { - if (list[i][0] === name) { - if (!found) { - list[i][1] = value; - found = true; - i++; - } else { - ArrayPrototypeSplice(list, i, 1); - } - } else { - i++; - } - } - - // Otherwise, append a new name-value pair whose name is name - // and value is value, to list. - if (!found) { - ArrayPrototypePush(list, [name, value]); - } - - this.#updateUrlSearch(); } + this[webidl.brand] = webidl.brand; + this.#serialization = opUrlParse(url, base); + this.#updateComponents(); + } - sort() { - webidl.assertBranded(this, URLSearchParamsPrototype); - ArrayPrototypeSort( - this[_list], - (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), + #updateComponents() { + ({ + 0: this.#schemeEnd, + 1: this.#usernameEnd, + 2: this.#hostStart, + 3: this.#hostEnd, + 4: this.#port, + 5: this.#pathStart, + 6: this.#queryStart, + 7: this.#fragmentStart, + } = componentsBuf); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + const object = { + href: this.href, + origin: this.origin, + protocol: this.protocol, + username: this.username, + password: this.password, + host: this.host, + hostname: this.hostname, + port: this.port, + pathname: this.pathname, + hash: this.hash, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object, inspectOptions)}`; + } + + #updateSearchParams() { + if (this.#queryObject !== null) { + const params = this.#queryObject[_list]; + const newParams = ops.op_url_parse_search_params( + StringPrototypeSlice(this.search, 1), + ); + ArrayPrototypeSplice( + params, + 0, + params.length, + ...new SafeArrayIterator(newParams), ); - this.#updateUrlSearch(); - } - - /** - * @return {string} - */ - toString() { - webidl.assertBranded(this, URLSearchParamsPrototype); - return ops.op_url_stringify_search_params(this[_list]); } } - webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); - - webidl.configurePrototype(URLSearchParams); - const URLSearchParamsPrototype = URLSearchParams.prototype; - - webidl.converters["URLSearchParams"] = webidl.createInterfaceConverter( - "URLSearchParams", - URLSearchParamsPrototype, - ); - - const _updateUrlSearch = Symbol("updateUrlSearch"); - - function trim(s) { - if (s.length === 1) return ""; - return s; + #hasAuthority() { + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 + return this.#serialization.slice(this.#schemeEnd).startsWith("://"); } - // Represents a "no port" value. A port in URL cannot be greater than 2^16 − 1 - const NO_PORT = 65536; + /** @return {string} */ + get hash() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L263 + return this.#fragmentStart + ? trim(this.#serialization.slice(this.#fragmentStart)) + : ""; + } - const componentsBuf = new Uint32Array(8); - class URL { - #queryObject = null; - #serialization; - #schemeEnd; - #usernameEnd; - #hostStart; - #hostEnd; - #port; - #pathStart; - #queryStart; - #fragmentStart; + /** @param {string} value */ + set hash(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'hash' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_HASH, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } - [_updateUrlSearch](value) { + /** @return {string} */ + get host() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L101 + return this.#serialization.slice(this.#hostStart, this.#pathStart); + } + + /** @param {string} value */ + set host(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'host' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_HOST, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get hostname() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L988 + return this.#serialization.slice(this.#hostStart, this.#hostEnd); + } + + /** @param {string} value */ + set hostname(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'hostname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_HOSTNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get href() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } + + /** @param {string} value */ + set href(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'href' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + this.#serialization = opUrlParse(value); + this.#updateComponents(); + this.#updateSearchParams(); + } + + /** @return {string} */ + get origin() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/origin.rs#L14 + const scheme = this.#serialization.slice(0, this.#schemeEnd); + if ( + scheme === "http" || scheme === "https" || scheme === "ftp" || + scheme === "ws" || scheme === "wss" + ) { + return `${scheme}://${this.host}`; + } + + if (scheme === "blob") { + // TODO(@littledivy): Fast path. + try { + return new URL(this.pathname).origin; + } catch { + return "null"; + } + } + + return "null"; + } + + /** @return {string} */ + get password() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L914 + if ( + this.#hasAuthority() && + this.#usernameEnd !== this.#serialization.length && + this.#serialization[this.#usernameEnd] === ":" + ) { + return this.#serialization.slice( + this.#usernameEnd + 1, + this.#hostStart - 1, + ); + } + return ""; + } + + /** @param {string} value */ + set password(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'password' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PASSWORD, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get pathname() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L1203 + if (!this.#queryStart && !this.#fragmentStart) { + return this.#serialization.slice(this.#pathStart); + } + + const nextComponentStart = this.#queryStart || this.#fragmentStart; + return this.#serialization.slice(this.#pathStart, nextComponentStart); + } + + /** @param {string} value */ + set pathname(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'pathname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PATHNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get port() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L196 + if (this.#port === NO_PORT) { + return this.#serialization.slice(this.#hostEnd, this.#pathStart); + } else { + return this.#serialization.slice( + this.#hostEnd + 1, /* : */ + this.#pathStart, + ); + } + } + + /** @param {string} value */ + set port(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'port' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PORT, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get protocol() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L56 + return this.#serialization.slice(0, this.#schemeEnd + 1 /* : */); + } + + /** @param {string} value */ + set protocol(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'protocol' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PROTOCOL, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ + } + } + + /** @return {string} */ + get search() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L249 + const afterPath = this.#queryStart || this.#fragmentStart || + this.#serialization.length; + const afterQuery = this.#fragmentStart || this.#serialization.length; + return trim(this.#serialization.slice(afterPath, afterQuery)); + } + + /** @param {string} value */ + set search(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'search' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { this.#serialization = opUrlReparse( this.#serialization, SET_SEARCH, value, ); this.#updateComponents(); - } - - /** - * @param {string} url - * @param {string} base - */ - constructor(url, base = undefined) { - const prefix = "Failed to construct 'URL'"; - url = webidl.converters.DOMString(url, { prefix, context: "Argument 1" }); - if (base !== undefined) { - base = webidl.converters.DOMString(base, { - prefix, - context: "Argument 2", - }); - } - this[webidl.brand] = webidl.brand; - this.#serialization = opUrlParse(url, base); - this.#updateComponents(); - } - - #updateComponents() { - ({ - 0: this.#schemeEnd, - 1: this.#usernameEnd, - 2: this.#hostStart, - 3: this.#hostEnd, - 4: this.#port, - 5: this.#pathStart, - 6: this.#queryStart, - 7: this.#fragmentStart, - } = componentsBuf); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { - const object = { - href: this.href, - origin: this.origin, - protocol: this.protocol, - username: this.username, - password: this.password, - host: this.host, - hostname: this.hostname, - port: this.port, - pathname: this.pathname, - hash: this.hash, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object, inspectOptions)}`; - } - - #updateSearchParams() { - if (this.#queryObject !== null) { - const params = this.#queryObject[_list]; - const newParams = ops.op_url_parse_search_params( - StringPrototypeSlice(this.search, 1), - ); - ArrayPrototypeSplice( - params, - 0, - params.length, - ...new SafeArrayIterator(newParams), - ); - } - } - - #hasAuthority() { - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 - return this.#serialization.slice(this.#schemeEnd).startsWith("://"); - } - - /** @return {string} */ - get hash() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L263 - return this.#fragmentStart - ? trim(this.#serialization.slice(this.#fragmentStart)) - : ""; - } - - /** @param {string} value */ - set hash(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'hash' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HASH, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get host() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L101 - return this.#serialization.slice(this.#hostStart, this.#pathStart); - } - - /** @param {string} value */ - set host(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'host' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HOST, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get hostname() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L988 - return this.#serialization.slice(this.#hostStart, this.#hostEnd); - } - - /** @param {string} value */ - set hostname(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'hostname' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HOSTNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get href() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; - } - - /** @param {string} value */ - set href(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'href' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - this.#serialization = opUrlParse(value); - this.#updateComponents(); this.#updateSearchParams(); + } catch { + /* pass */ } + } - /** @return {string} */ - get origin() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/origin.rs#L14 - const scheme = this.#serialization.slice(0, this.#schemeEnd); - if ( - scheme === "http" || scheme === "https" || scheme === "ftp" || - scheme === "ws" || scheme === "wss" - ) { - return `${scheme}://${this.host}`; - } - - if (scheme === "blob") { - // TODO(@littledivy): Fast path. - try { - return new URL(this.pathname).origin; - } catch { - return "null"; - } - } - - return "null"; - } - - /** @return {string} */ - get password() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L914 - if ( - this.#hasAuthority() && - this.#usernameEnd !== this.#serialization.length && - this.#serialization[this.#usernameEnd] === ":" - ) { - return this.#serialization.slice( - this.#usernameEnd + 1, - this.#hostStart - 1, - ); - } + /** @return {string} */ + get username() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L881 + const schemeSeperatorLen = 3; /* :// */ + if ( + this.#hasAuthority() && + this.#usernameEnd > this.#schemeEnd + schemeSeperatorLen + ) { + return this.#serialization.slice( + this.#schemeEnd + schemeSeperatorLen, + this.#usernameEnd, + ); + } else { return ""; } + } - /** @param {string} value */ - set password(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'password' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PASSWORD, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get pathname() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L1203 - if (!this.#queryStart && !this.#fragmentStart) { - return this.#serialization.slice(this.#pathStart); - } - - const nextComponentStart = this.#queryStart || this.#fragmentStart; - return this.#serialization.slice(this.#pathStart, nextComponentStart); - } - - /** @param {string} value */ - set pathname(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'pathname' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PATHNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get port() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L196 - if (this.#port === NO_PORT) { - return this.#serialization.slice(this.#hostEnd, this.#pathStart); - } else { - return this.#serialization.slice( - this.#hostEnd + 1, /* : */ - this.#pathStart, - ); - } - } - - /** @param {string} value */ - set port(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'port' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PORT, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get protocol() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L56 - return this.#serialization.slice(0, this.#schemeEnd + 1 /* : */); - } - - /** @param {string} value */ - set protocol(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'protocol' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PROTOCOL, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get search() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L249 - const afterPath = this.#queryStart || this.#fragmentStart || - this.#serialization.length; - const afterQuery = this.#fragmentStart || this.#serialization.length; - return trim(this.#serialization.slice(afterPath, afterQuery)); - } - - /** @param {string} value */ - set search(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'search' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_SEARCH, - value, - ); - this.#updateComponents(); - this.#updateSearchParams(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get username() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L881 - const schemeSeperatorLen = 3; /* :// */ - if ( - this.#hasAuthority() && - this.#usernameEnd > this.#schemeEnd + schemeSeperatorLen - ) { - return this.#serialization.slice( - this.#schemeEnd + schemeSeperatorLen, - this.#usernameEnd, - ); - } else { - return ""; - } - } - - /** @param {string} value */ - set username(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'username' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_USERNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get searchParams() { - if (this.#queryObject == null) { - this.#queryObject = new URLSearchParams(this.search); - this.#queryObject[_urlObject] = this; - } - return this.#queryObject; - } - - /** @return {string} */ - toString() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; - } - - /** @return {string} */ - toJSON() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @param {string} value */ + set username(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'username' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_USERNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } } - webidl.configurePrototype(URL); - const URLPrototype = URL.prototype; - - /** - * This function implements application/x-www-form-urlencoded parsing. - * https://url.spec.whatwg.org/#concept-urlencoded-parser - * @param {Uint8Array} bytes - * @returns {[string, string][]} - */ - function parseUrlEncoded(bytes) { - return ops.op_url_parse_search_params(null, bytes); + /** @return {string} */ + get searchParams() { + if (this.#queryObject == null) { + this.#queryObject = new URLSearchParams(this.search); + this.#queryObject[_urlObject] = this; + } + return this.#queryObject; } - webidl - .converters[ - "sequence> or record or USVString" - ] = (V, opts) => { - // Union for (sequence> or record or USVString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence>"](V, opts); - } - return webidl.converters["record"](V, opts); - } - return webidl.converters.USVString(V, opts); - }; + /** @return {string} */ + toString() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } - window.__bootstrap.url = { - URL, - URLPrototype, - URLSearchParams, - URLSearchParamsPrototype, - parseUrlEncoded, + /** @return {string} */ + toJSON() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } +} + +webidl.configurePrototype(URL); +const URLPrototype = URL.prototype; + +/** + * This function implements application/x-www-form-urlencoded parsing. + * https://url.spec.whatwg.org/#concept-urlencoded-parser + * @param {Uint8Array} bytes + * @returns {[string, string][]} + */ +function parseUrlEncoded(bytes) { + return ops.op_url_parse_search_params(null, bytes); +} + +webidl + .converters[ + "sequence> or record or USVString" + ] = (V, opts) => { + // Union for (sequence> or record or USVString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence>"](V, opts); + } + return webidl.converters["record"](V, opts); + } + return webidl.converters.USVString(V, opts); }; -})(this); + +export { + parseUrlEncoded, + URL, + URLPrototype, + URLSearchParams, + URLSearchParamsPrototype, +}; diff --git a/ext/url/01_urlpattern.js b/ext/url/01_urlpattern.js index 14f0525514..1c5882553c 100644 --- a/ext/url/01_urlpattern.js +++ b/ext/url/01_urlpattern.js @@ -7,268 +7,263 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ObjectKeys, + ObjectFromEntries, + RegExp, + RegExpPrototypeExec, + RegExpPrototypeTest, + Symbol, + SymbolFor, + TypeError, +} = primordials; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayPrototypeMap, - ObjectKeys, - ObjectFromEntries, - RegExp, - RegExpPrototypeExec, - RegExpPrototypeTest, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const _components = Symbol("components"); - const _components = Symbol("components"); +/** + * @typedef Components + * @property {Component} protocol + * @property {Component} username + * @property {Component} password + * @property {Component} hostname + * @property {Component} port + * @property {Component} pathname + * @property {Component} search + * @property {Component} hash + */ + +/** + * @typedef Component + * @property {string} patternString + * @property {RegExp} regexp + * @property {string[]} groupNameList + */ + +class URLPattern { + /** @type {Components} */ + [_components]; /** - * @typedef Components - * @property {Component} protocol - * @property {Component} username - * @property {Component} password - * @property {Component} hostname - * @property {Component} port - * @property {Component} pathname - * @property {Component} search - * @property {Component} hash + * @param {URLPatternInput} input + * @param {string} [baseURL] */ - - /** - * @typedef Component - * @property {string} patternString - * @property {RegExp} regexp - * @property {string[]} groupNameList - */ - - class URLPattern { - /** @type {Components} */ - [_components]; - - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - */ - constructor(input, baseURL = undefined) { - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { + constructor(input, baseURL = undefined) { + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } - - const components = ops.op_urlpattern_parse(input, baseURL); - - const keys = ObjectKeys(components); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - try { - components[key].regexp = new RegExp( - components[key].regexpString, - "u", - ); - } catch (e) { - throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); - } - } - - this[_components] = components; } - get protocol() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].protocol.patternString; - } + const components = ops.op_urlpattern_parse(input, baseURL); - get username() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].username.patternString; - } - - get password() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].password.patternString; - } - - get hostname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hostname.patternString; - } - - get port() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].port.patternString; - } - - get pathname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].pathname.patternString; - } - - get search() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].search.patternString; - } - - get hash() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hash.patternString; - } - - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - * @returns {boolean} - */ - test(input, baseURL = undefined) { - webidl.assertBranded(this, URLPatternPrototype); - const prefix = "Failed to execute 'test' on 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { - prefix, - context: "Argument 1", - }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } - - const res = ops.op_urlpattern_process_match_input( - input, - baseURL, - ); - if (res === null) { - return false; - } - - const values = res[0]; - - const keys = ObjectKeys(values); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) { - return false; - } - } - - return true; - } - - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - * @returns {URLPatternResult | null} - */ - exec(input, baseURL = undefined) { - webidl.assertBranded(this, URLPatternPrototype); - const prefix = "Failed to execute 'exec' on 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { - prefix, - context: "Argument 1", - }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } - - const res = ops.op_urlpattern_process_match_input( - input, - baseURL, - ); - if (res === null) { - return null; - } - - const { 0: values, 1: inputs } = res; - if (inputs[1] === null) { - inputs.pop(); - } - - /** @type {URLPatternResult} */ - const result = { inputs }; - - const keys = ObjectKeys(values); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - /** @type {Component} */ - const component = this[_components][key]; - const input = values[key]; - const match = RegExpPrototypeExec(component.regexp, input); - if (match === null) { - return null; - } - const groupEntries = ArrayPrototypeMap( - component.groupNameList, - (name, i) => [name, match[i + 1] ?? ""], + const keys = ObjectKeys(components); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + try { + components[key].regexp = new RegExp( + components[key].regexpString, + "u", ); - const groups = ObjectFromEntries(groupEntries); - result[key] = { - input, - groups, - }; + } catch (e) { + throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); } - - return result; } - [SymbolFor("Deno.customInspect")](inspect) { - return `URLPattern ${ - inspect({ - protocol: this.protocol, - username: this.username, - password: this.password, - hostname: this.hostname, - port: this.port, - pathname: this.pathname, - search: this.search, - hash: this.hash, - }) - }`; - } + this[_components] = components; } - webidl.configurePrototype(URLPattern); - const URLPatternPrototype = URLPattern.prototype; + get protocol() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].protocol.patternString; + } - webidl.converters.URLPatternInit = webidl - .createDictionaryConverter("URLPatternInit", [ - { key: "protocol", converter: webidl.converters.USVString }, - { key: "username", converter: webidl.converters.USVString }, - { key: "password", converter: webidl.converters.USVString }, - { key: "hostname", converter: webidl.converters.USVString }, - { key: "port", converter: webidl.converters.USVString }, - { key: "pathname", converter: webidl.converters.USVString }, - { key: "search", converter: webidl.converters.USVString }, - { key: "hash", converter: webidl.converters.USVString }, - { key: "baseURL", converter: webidl.converters.USVString }, - ]); + get username() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].username.patternString; + } - webidl.converters["URLPatternInput"] = (V, opts) => { - // Union for (URLPatternInit or USVString) - if (typeof V == "object") { - return webidl.converters.URLPatternInit(V, opts); + get password() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].password.patternString; + } + + get hostname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].hostname.patternString; + } + + get port() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].port.patternString; + } + + get pathname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].pathname.patternString; + } + + get search() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].search.patternString; + } + + get hash() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].hash.patternString; + } + + /** + * @param {URLPatternInput} input + * @param {string} [baseURL] + * @returns {boolean} + */ + test(input, baseURL = undefined) { + webidl.assertBranded(this, URLPatternPrototype); + const prefix = "Failed to execute 'test' on 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { + prefix, + context: "Argument 2", + }); } - return webidl.converters.USVString(V, opts); - }; - window.__bootstrap.urlPattern = { - URLPattern, - }; -})(globalThis); + const res = ops.op_urlpattern_process_match_input( + input, + baseURL, + ); + if (res === null) { + return false; + } + + const values = res[0]; + + const keys = ObjectKeys(values); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) { + return false; + } + } + + return true; + } + + /** + * @param {URLPatternInput} input + * @param {string} [baseURL] + * @returns {URLPatternResult | null} + */ + exec(input, baseURL = undefined) { + webidl.assertBranded(this, URLPatternPrototype); + const prefix = "Failed to execute 'exec' on 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { + prefix, + context: "Argument 2", + }); + } + + const res = ops.op_urlpattern_process_match_input( + input, + baseURL, + ); + if (res === null) { + return null; + } + + const { 0: values, 1: inputs } = res; + if (inputs[1] === null) { + inputs.pop(); + } + + /** @type {URLPatternResult} */ + const result = { inputs }; + + const keys = ObjectKeys(values); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + /** @type {Component} */ + const component = this[_components][key]; + const input = values[key]; + const match = RegExpPrototypeExec(component.regexp, input); + if (match === null) { + return null; + } + const groupEntries = ArrayPrototypeMap( + component.groupNameList, + (name, i) => [name, match[i + 1] ?? ""], + ); + const groups = ObjectFromEntries(groupEntries); + result[key] = { + input, + groups, + }; + } + + return result; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return `URLPattern ${ + inspect({ + protocol: this.protocol, + username: this.username, + password: this.password, + hostname: this.hostname, + port: this.port, + pathname: this.pathname, + search: this.search, + hash: this.hash, + }) + }`; + } +} + +webidl.configurePrototype(URLPattern); +const URLPatternPrototype = URLPattern.prototype; + +webidl.converters.URLPatternInit = webidl + .createDictionaryConverter("URLPatternInit", [ + { key: "protocol", converter: webidl.converters.USVString }, + { key: "username", converter: webidl.converters.USVString }, + { key: "password", converter: webidl.converters.USVString }, + { key: "hostname", converter: webidl.converters.USVString }, + { key: "port", converter: webidl.converters.USVString }, + { key: "pathname", converter: webidl.converters.USVString }, + { key: "search", converter: webidl.converters.USVString }, + { key: "hash", converter: webidl.converters.USVString }, + { key: "baseURL", converter: webidl.converters.USVString }, + ]); + +webidl.converters["URLPatternInput"] = (V, opts) => { + // Union for (URLPatternInit or USVString) + if (typeof V == "object") { + return webidl.converters.URLPatternInit(V, opts); + } + return webidl.converters.USVString(V, opts); +}; + +export { URLPattern }; diff --git a/ext/url/benches/url_ops.rs b/ext/url/benches/url_ops.rs index 5a5997fc82..4b41eb3a04 100644 --- a/ext/url/benches/url_ops.rs +++ b/ext/url/benches/url_ops.rs @@ -12,9 +12,11 @@ fn setup() -> Vec { deno_webidl::init(), deno_url::init(), Extension::builder("bench_setup") - .js(vec![( - "setup", - "const { URL } = globalThis.__bootstrap.url;", + .esm(vec![( + "internal:setup", + r#"import { URL } from "internal:ext/url/00_url.js"; + globalThis.URL = URL; + "#, )]) .build(), ] diff --git a/ext/url/internal.d.ts b/ext/url/internal.d.ts index 7065c432f4..b8a2a000aa 100644 --- a/ext/url/internal.d.ts +++ b/ext/url/internal.d.ts @@ -1,20 +1,14 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-var - /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var url: { - URL: typeof URL; - URLSearchParams: typeof URLSearchParams; - parseUrlEncoded(bytes: Uint8Array): [string, string][]; - }; - - declare var urlPattern: { - URLPattern: typeof URLPattern; - }; - } +declare module "internal:ext/url/00_url.js" { + const URL: typeof URL; + const URLSearchParams: typeof URLSearchParams; + function parseUrlEncoded(bytes: Uint8Array): [string, string][]; +} + +declare module "internal:ext/url/01_urlpattern.js" { + const URLPattern: typeof URLPattern; } diff --git a/ext/url/lib.rs b/ext/url/lib.rs index 064590f290..95ca326c45 100644 --- a/ext/url/lib.rs +++ b/ext/url/lib.rs @@ -20,7 +20,7 @@ use crate::urlpattern::op_urlpattern_process_match_input; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/url", "00_url.js", "01_urlpattern.js", diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js index 3f3f98165f..c44b124c94 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -6,334 +6,331 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeJoin, + ArrayPrototypeMap, + Error, + JSONStringify, + NumberPrototypeToString, + RegExp, + SafeArrayIterator, + String, + StringPrototypeCharAt, + StringPrototypeCharCodeAt, + StringPrototypeMatch, + StringPrototypePadStart, + StringPrototypeReplace, + StringPrototypeSlice, + StringPrototypeSubstring, + StringPrototypeToLowerCase, + StringPrototypeToUpperCase, + TypeError, +} = primordials; -((window) => { - const core = Deno.core; - const ops = core.ops; - const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - Error, - JSONStringify, - NumberPrototypeToString, - RegExp, - SafeArrayIterator, - String, - StringPrototypeCharAt, - StringPrototypeCharCodeAt, - StringPrototypeMatch, - StringPrototypePadStart, - StringPrototypeReplace, - StringPrototypeSlice, - StringPrototypeSubstring, - StringPrototypeToLowerCase, - StringPrototypeToUpperCase, - TypeError, - } = window.__bootstrap.primordials; +const ASCII_DIGIT = ["\u0030-\u0039"]; +const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; +const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; +const ASCII_ALPHA = [ + ...new SafeArrayIterator(ASCII_UPPER_ALPHA), + ...new SafeArrayIterator(ASCII_LOWER_ALPHA), +]; +const ASCII_ALPHANUMERIC = [ + ...new SafeArrayIterator(ASCII_DIGIT), + ...new SafeArrayIterator(ASCII_ALPHA), +]; - const ASCII_DIGIT = ["\u0030-\u0039"]; - const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; - const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; - const ASCII_ALPHA = [ - ...new SafeArrayIterator(ASCII_UPPER_ALPHA), - ...new SafeArrayIterator(ASCII_LOWER_ALPHA), - ]; - const ASCII_ALPHANUMERIC = [ - ...new SafeArrayIterator(ASCII_DIGIT), - ...new SafeArrayIterator(ASCII_ALPHA), - ]; +const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; +const HTTP_WHITESPACE = [ + "\u000A", + "\u000D", + ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), +]; - const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; - const HTTP_WHITESPACE = [ - "\u000A", - "\u000D", - ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), - ]; +const HTTP_TOKEN_CODE_POINT = [ + "\u0021", + "\u0023", + "\u0024", + "\u0025", + "\u0026", + "\u0027", + "\u002A", + "\u002B", + "\u002D", + "\u002E", + "\u005E", + "\u005F", + "\u0060", + "\u007C", + "\u007E", + ...new SafeArrayIterator(ASCII_ALPHANUMERIC), +]; +const HTTP_TOKEN_CODE_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, +); +const HTTP_QUOTED_STRING_TOKEN_POINT = [ + "\u0009", + "\u0020-\u007E", + "\u0080-\u00FF", +]; +const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, +); +const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); +const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( + `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, + "g", +); +const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( + `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, + "g", +); +const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); +const HTTP_BETWEEN_WHITESPACE = new RegExp( + `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`, +); +const HTTP_WHITESPACE_PREFIX_RE = new RegExp( + `^[${HTTP_WHITESPACE_MATCHER}]+`, + "g", +); +const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( + `[${HTTP_WHITESPACE_MATCHER}]+$`, + "g", +); - const HTTP_TOKEN_CODE_POINT = [ - "\u0021", - "\u0023", - "\u0024", - "\u0025", - "\u0026", - "\u0027", - "\u002A", - "\u002B", - "\u002D", - "\u002E", - "\u005E", - "\u005F", - "\u0060", - "\u007C", - "\u007E", - ...new SafeArrayIterator(ASCII_ALPHANUMERIC), - ]; - const HTTP_TOKEN_CODE_POINT_RE = new RegExp( - `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, - ); - const HTTP_QUOTED_STRING_TOKEN_POINT = [ - "\u0009", - "\u0020-\u007E", - "\u0080-\u00FF", - ]; - const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( - `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, - ); - const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); - const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( - `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, - "g", - ); - const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( - `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, - "g", - ); - const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); - const HTTP_BETWEEN_WHITESPACE = new RegExp( - `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`, - ); - const HTTP_WHITESPACE_PREFIX_RE = new RegExp( - `^[${HTTP_WHITESPACE_MATCHER}]+`, - "g", - ); - const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( - `[${HTTP_WHITESPACE_MATCHER}]+$`, - "g", - ); - - /** - * Turn a string of chars into a regex safe matcher. - * @param {string[]} chars - * @returns {string} - */ - function regexMatcher(chars) { - const matchers = ArrayPrototypeMap(chars, (char) => { - if (char.length === 1) { - const a = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), - 4, - "0", - ); - return `\\u${a}`; - } else if (char.length === 3 && char[1] === "-") { - const a = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), - 4, - "0", - ); - const b = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16), - 4, - "0", - ); - return `\\u${a}-\\u${b}`; - } else { - throw TypeError("unreachable"); - } - }); - return ArrayPrototypeJoin(matchers, ""); - } - - /** - * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points - * @param {string} input - * @param {number} position - * @param {(char: string) => boolean} condition - * @returns {{result: string, position: number}} - */ - function collectSequenceOfCodepoints(input, position, condition) { - const start = position; - for ( - let c = StringPrototypeCharAt(input, position); - position < input.length && condition(c); - c = StringPrototypeCharAt(input, ++position) - ); - return { result: StringPrototypeSlice(input, start, position), position }; - } - - /** - * @param {string} s - * @returns {string} - */ - function byteUpperCase(s) { - return StringPrototypeReplace( - String(s), - /[a-z]/g, - function byteUpperCaseReplace(c) { - return StringPrototypeToUpperCase(c); - }, - ); - } - - /** - * @param {string} s - * @returns {string} - */ - function byteLowerCase(s) { - // NOTE: correct since all callers convert to ByteString first - // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter - return StringPrototypeToLowerCase(s); - } - - /** - * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string - * @param {string} input - * @param {number} position - * @param {boolean} extractValue - * @returns {{result: string, position: number}} - */ - function collectHttpQuotedString(input, position, extractValue) { - // 1. - const positionStart = position; - // 2. - let value = ""; - // 3. - if (input[position] !== "\u0022") throw new TypeError('must be "'); - // 4. - position++; - // 5. - while (true) { - // 5.1. - const res = collectSequenceOfCodepoints( - input, - position, - (c) => c !== "\u0022" && c !== "\u005C", +/** + * Turn a string of chars into a regex safe matcher. + * @param {string[]} chars + * @returns {string} + */ +function regexMatcher(chars) { + const matchers = ArrayPrototypeMap(chars, (char) => { + if (char.length === 1) { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", ); - value += res.result; - position = res.position; - // 5.2. - if (position >= input.length) break; - // 5.3. - const quoteOrBackslash = input[position]; - // 5.4. - position++; - // 5.5. - if (quoteOrBackslash === "\u005C") { - // 5.5.1. - if (position >= input.length) { - value += "\u005C"; - break; - } - // 5.5.2. - value += input[position]; - // 5.5.3. - position++; - } else { // 5.6. - // 5.6.1 - if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); - // 5.6.2 + return `\\u${a}`; + } else if (char.length === 3 && char[1] === "-") { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", + ); + const b = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16), + 4, + "0", + ); + return `\\u${a}-\\u${b}`; + } else { + throw TypeError("unreachable"); + } + }); + return ArrayPrototypeJoin(matchers, ""); +} + +/** + * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + * @param {string} input + * @param {number} position + * @param {(char: string) => boolean} condition + * @returns {{result: string, position: number}} + */ +function collectSequenceOfCodepoints(input, position, condition) { + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && condition(c); + c = StringPrototypeCharAt(input, ++position) + ); + return { result: StringPrototypeSlice(input, start, position), position }; +} + +/** + * @param {string} s + * @returns {string} + */ +function byteUpperCase(s) { + return StringPrototypeReplace( + String(s), + /[a-z]/g, + function byteUpperCaseReplace(c) { + return StringPrototypeToUpperCase(c); + }, + ); +} + +/** + * @param {string} s + * @returns {string} + */ +function byteLowerCase(s) { + // NOTE: correct since all callers convert to ByteString first + // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter + return StringPrototypeToLowerCase(s); +} + +/** + * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string + * @param {string} input + * @param {number} position + * @param {boolean} extractValue + * @returns {{result: string, position: number}} + */ +function collectHttpQuotedString(input, position, extractValue) { + // 1. + const positionStart = position; + // 2. + let value = ""; + // 3. + if (input[position] !== "\u0022") throw new TypeError('must be "'); + // 4. + position++; + // 5. + while (true) { + // 5.1. + const res = collectSequenceOfCodepoints( + input, + position, + (c) => c !== "\u0022" && c !== "\u005C", + ); + value += res.result; + position = res.position; + // 5.2. + if (position >= input.length) break; + // 5.3. + const quoteOrBackslash = input[position]; + // 5.4. + position++; + // 5.5. + if (quoteOrBackslash === "\u005C") { + // 5.5.1. + if (position >= input.length) { + value += "\u005C"; break; } - } - // 6. - if (extractValue) return { result: value, position }; - // 7. - return { - result: StringPrototypeSubstring(input, positionStart, position + 1), - position, - }; - } - - /** - * @param {Uint8Array} data - * @returns {string} - */ - function forgivingBase64Encode(data) { - return ops.op_base64_encode(data); - } - - /** - * @param {string} data - * @returns {Uint8Array} - */ - function forgivingBase64Decode(data) { - return ops.op_base64_decode(data); - } - - /** - * @param {string} char - * @returns {boolean} - */ - function isHttpWhitespace(char) { - switch (char) { - case "\u0009": - case "\u000A": - case "\u000D": - case "\u0020": - return true; - default: - return false; + // 5.5.2. + value += input[position]; + // 5.5.3. + position++; + } else { // 5.6. + // 5.6.1 + if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); + // 5.6.2 + break; } } - - /** - * @param {string} s - * @returns {string} - */ - function httpTrim(s) { - if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) { - return s; - } - return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? ""; - } - - class AssertionError extends Error { - constructor(msg) { - super(msg); - this.name = "AssertionError"; - } - } - - /** - * @param {unknown} cond - * @param {string=} msg - * @returns {asserts cond} - */ - function assert(cond, msg = "Assertion failed.") { - if (!cond) { - throw new AssertionError(msg); - } - } - - /** - * @param {unknown} value - * @returns {string} - */ - function serializeJSValueToJSONString(value) { - const result = JSONStringify(value); - if (result === undefined) { - throw new TypeError("Value is not JSON serializable."); - } - return result; - } - - window.__bootstrap.infra = { - collectSequenceOfCodepoints, - ASCII_DIGIT, - ASCII_UPPER_ALPHA, - ASCII_LOWER_ALPHA, - ASCII_ALPHA, - ASCII_ALPHANUMERIC, - HTTP_TAB_OR_SPACE, - HTTP_WHITESPACE, - HTTP_TOKEN_CODE_POINT, - HTTP_TOKEN_CODE_POINT_RE, - HTTP_QUOTED_STRING_TOKEN_POINT, - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - HTTP_TAB_OR_SPACE_PREFIX_RE, - HTTP_TAB_OR_SPACE_SUFFIX_RE, - HTTP_WHITESPACE_PREFIX_RE, - HTTP_WHITESPACE_SUFFIX_RE, - httpTrim, - regexMatcher, - byteUpperCase, - byteLowerCase, - collectHttpQuotedString, - forgivingBase64Encode, - forgivingBase64Decode, - AssertionError, - assert, - serializeJSValueToJSONString, + // 6. + if (extractValue) return { result: value, position }; + // 7. + return { + result: StringPrototypeSubstring(input, positionStart, position + 1), + position, }; -})(globalThis); +} + +/** + * @param {Uint8Array} data + * @returns {string} + */ +function forgivingBase64Encode(data) { + return ops.op_base64_encode(data); +} + +/** + * @param {string} data + * @returns {Uint8Array} + */ +function forgivingBase64Decode(data) { + return ops.op_base64_decode(data); +} + +/** + * @param {string} char + * @returns {boolean} + */ +function isHttpWhitespace(char) { + switch (char) { + case "\u0009": + case "\u000A": + case "\u000D": + case "\u0020": + return true; + default: + return false; + } +} + +/** + * @param {string} s + * @returns {string} + */ +function httpTrim(s) { + if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) { + return s; + } + return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? ""; +} + +class AssertionError extends Error { + constructor(msg) { + super(msg); + this.name = "AssertionError"; + } +} + +/** + * @param {unknown} cond + * @param {string=} msg + * @returns {asserts cond} + */ +function assert(cond, msg = "Assertion failed.") { + if (!cond) { + throw new AssertionError(msg); + } +} + +/** + * @param {unknown} value + * @returns {string} + */ +function serializeJSValueToJSONString(value) { + const result = JSONStringify(value); + if (result === undefined) { + throw new TypeError("Value is not JSON serializable."); + } + return result; +} + +export { + ASCII_ALPHA, + ASCII_ALPHANUMERIC, + ASCII_DIGIT, + ASCII_LOWER_ALPHA, + ASCII_UPPER_ALPHA, + assert, + AssertionError, + byteLowerCase, + byteUpperCase, + collectHttpQuotedString, + collectSequenceOfCodepoints, + forgivingBase64Decode, + forgivingBase64Encode, + HTTP_QUOTED_STRING_TOKEN_POINT, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TAB_OR_SPACE, + HTTP_TAB_OR_SPACE_PREFIX_RE, + HTTP_TAB_OR_SPACE_SUFFIX_RE, + HTTP_TOKEN_CODE_POINT, + HTTP_TOKEN_CODE_POINT_RE, + HTTP_WHITESPACE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, + httpTrim, + regexMatcher, + serializeJSValueToJSONString, +}; diff --git a/ext/web/01_dom_exception.js b/ext/web/01_dom_exception.js index a4556c03c7..cbec9ca22b 100644 --- a/ext/web/01_dom_exception.js +++ b/ext/web/01_dom_exception.js @@ -7,197 +7,194 @@ /// /// -"use strict"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeSlice, + Error, + ErrorPrototype, + ObjectDefineProperty, + ObjectCreate, + ObjectEntries, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Symbol, + SymbolFor, +} = primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; -((window) => { - const { - ArrayPrototypeSlice, - Error, - ErrorPrototype, - ObjectDefineProperty, - ObjectCreate, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Symbol, - SymbolFor, - } = window.__bootstrap.primordials; - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; +const _name = Symbol("name"); +const _message = Symbol("message"); +const _code = Symbol("code"); - const _name = Symbol("name"); - const _message = Symbol("message"); - const _code = Symbol("code"); +// Defined in WebIDL 4.3. +// https://webidl.spec.whatwg.org/#idl-DOMException +const INDEX_SIZE_ERR = 1; +const DOMSTRING_SIZE_ERR = 2; +const HIERARCHY_REQUEST_ERR = 3; +const WRONG_DOCUMENT_ERR = 4; +const INVALID_CHARACTER_ERR = 5; +const NO_DATA_ALLOWED_ERR = 6; +const NO_MODIFICATION_ALLOWED_ERR = 7; +const NOT_FOUND_ERR = 8; +const NOT_SUPPORTED_ERR = 9; +const INUSE_ATTRIBUTE_ERR = 10; +const INVALID_STATE_ERR = 11; +const SYNTAX_ERR = 12; +const INVALID_MODIFICATION_ERR = 13; +const NAMESPACE_ERR = 14; +const INVALID_ACCESS_ERR = 15; +const VALIDATION_ERR = 16; +const TYPE_MISMATCH_ERR = 17; +const SECURITY_ERR = 18; +const NETWORK_ERR = 19; +const ABORT_ERR = 20; +const URL_MISMATCH_ERR = 21; +const QUOTA_EXCEEDED_ERR = 22; +const TIMEOUT_ERR = 23; +const INVALID_NODE_TYPE_ERR = 24; +const DATA_CLONE_ERR = 25; - // Defined in WebIDL 4.3. - // https://webidl.spec.whatwg.org/#idl-DOMException - const INDEX_SIZE_ERR = 1; - const DOMSTRING_SIZE_ERR = 2; - const HIERARCHY_REQUEST_ERR = 3; - const WRONG_DOCUMENT_ERR = 4; - const INVALID_CHARACTER_ERR = 5; - const NO_DATA_ALLOWED_ERR = 6; - const NO_MODIFICATION_ALLOWED_ERR = 7; - const NOT_FOUND_ERR = 8; - const NOT_SUPPORTED_ERR = 9; - const INUSE_ATTRIBUTE_ERR = 10; - const INVALID_STATE_ERR = 11; - const SYNTAX_ERR = 12; - const INVALID_MODIFICATION_ERR = 13; - const NAMESPACE_ERR = 14; - const INVALID_ACCESS_ERR = 15; - const VALIDATION_ERR = 16; - const TYPE_MISMATCH_ERR = 17; - const SECURITY_ERR = 18; - const NETWORK_ERR = 19; - const ABORT_ERR = 20; - const URL_MISMATCH_ERR = 21; - const QUOTA_EXCEEDED_ERR = 22; - const TIMEOUT_ERR = 23; - const INVALID_NODE_TYPE_ERR = 24; - const DATA_CLONE_ERR = 25; +// Defined in WebIDL 2.8.1. +// https://webidl.spec.whatwg.org/#dfn-error-names-table +/** @type {Record} */ +// the prototype should be null, to prevent user code from looking +// up Object.prototype properties, such as "toString" +const nameToCodeMapping = ObjectCreate(null, { + IndexSizeError: { value: INDEX_SIZE_ERR }, + HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR }, + WrongDocumentError: { value: WRONG_DOCUMENT_ERR }, + InvalidCharacterError: { value: INVALID_CHARACTER_ERR }, + NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR }, + NotFoundError: { value: NOT_FOUND_ERR }, + NotSupportedError: { value: NOT_SUPPORTED_ERR }, + InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR }, + InvalidStateError: { value: INVALID_STATE_ERR }, + SyntaxError: { value: SYNTAX_ERR }, + InvalidModificationError: { value: INVALID_MODIFICATION_ERR }, + NamespaceError: { value: NAMESPACE_ERR }, + InvalidAccessError: { value: INVALID_ACCESS_ERR }, + TypeMismatchError: { value: TYPE_MISMATCH_ERR }, + SecurityError: { value: SECURITY_ERR }, + NetworkError: { value: NETWORK_ERR }, + AbortError: { value: ABORT_ERR }, + URLMismatchError: { value: URL_MISMATCH_ERR }, + QuotaExceededError: { value: QUOTA_EXCEEDED_ERR }, + TimeoutError: { value: TIMEOUT_ERR }, + InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR }, + DataCloneError: { value: DATA_CLONE_ERR }, +}); - // Defined in WebIDL 2.8.1. - // https://webidl.spec.whatwg.org/#dfn-error-names-table - /** @type {Record} */ - // the prototype should be null, to prevent user code from looking - // up Object.prototype properties, such as "toString" - const nameToCodeMapping = ObjectCreate(null, { - IndexSizeError: { value: INDEX_SIZE_ERR }, - HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR }, - WrongDocumentError: { value: WRONG_DOCUMENT_ERR }, - InvalidCharacterError: { value: INVALID_CHARACTER_ERR }, - NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR }, - NotFoundError: { value: NOT_FOUND_ERR }, - NotSupportedError: { value: NOT_SUPPORTED_ERR }, - InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR }, - InvalidStateError: { value: INVALID_STATE_ERR }, - SyntaxError: { value: SYNTAX_ERR }, - InvalidModificationError: { value: INVALID_MODIFICATION_ERR }, - NamespaceError: { value: NAMESPACE_ERR }, - InvalidAccessError: { value: INVALID_ACCESS_ERR }, - TypeMismatchError: { value: TYPE_MISMATCH_ERR }, - SecurityError: { value: SECURITY_ERR }, - NetworkError: { value: NETWORK_ERR }, - AbortError: { value: ABORT_ERR }, - URLMismatchError: { value: URL_MISMATCH_ERR }, - QuotaExceededError: { value: QUOTA_EXCEEDED_ERR }, - TimeoutError: { value: TIMEOUT_ERR }, - InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR }, - DataCloneError: { value: DATA_CLONE_ERR }, - }); +// Defined in WebIDL 4.3. +// https://webidl.spec.whatwg.org/#idl-DOMException +class DOMException { + [_message]; + [_name]; + [_code]; - // Defined in WebIDL 4.3. - // https://webidl.spec.whatwg.org/#idl-DOMException - class DOMException { - [_message]; - [_name]; - [_code]; + // https://webidl.spec.whatwg.org/#dom-domexception-domexception + constructor(message = "", name = "Error") { + message = webidl.converters.DOMString(message, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 1", + }); + name = webidl.converters.DOMString(name, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 2", + }); + const code = nameToCodeMapping[name] ?? 0; - // https://webidl.spec.whatwg.org/#dom-domexception-domexception - constructor(message = "", name = "Error") { - message = webidl.converters.DOMString(message, { - prefix: "Failed to construct 'DOMException'", - context: "Argument 1", - }); - name = webidl.converters.DOMString(name, { - prefix: "Failed to construct 'DOMException'", - context: "Argument 2", - }); - const code = nameToCodeMapping[name] ?? 0; + this[_message] = message; + this[_name] = name; + this[_code] = code; + this[webidl.brand] = webidl.brand; - this[_message] = message; - this[_name] = name; - this[_code] = code; - this[webidl.brand] = webidl.brand; + const error = new Error(message); + error.name = "DOMException"; + ObjectDefineProperty(this, "stack", { + value: error.stack, + writable: true, + configurable: true, + }); - const error = new Error(message); - error.name = "DOMException"; - ObjectDefineProperty(this, "stack", { - value: error.stack, - writable: true, - configurable: true, - }); - - // `DOMException` isn't a native error, so `Error.prepareStackTrace()` is - // not called when accessing `.stack`, meaning our structured stack trace - // hack doesn't apply. This patches it in. - ObjectDefineProperty(this, "__callSiteEvals", { - value: ArrayPrototypeSlice(error.__callSiteEvals, 1), - configurable: true, - }); - } - - get message() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_message]; - } - - get name() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_name]; - } - - get code() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_code]; - } - - [SymbolFor("Deno.customInspect")](inspect) { - if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) { - return `DOMException: ${this[_message]}`; - } else { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: false, - keys: [ - "message", - "name", - "code", - ], - })); - } - } + // `DOMException` isn't a native error, so `Error.prepareStackTrace()` is + // not called when accessing `.stack`, meaning our structured stack trace + // hack doesn't apply. This patches it in. + ObjectDefineProperty(this, "__callSiteEvals", { + value: ArrayPrototypeSlice(error.__callSiteEvals, 1), + configurable: true, + }); } - ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); - - webidl.configurePrototype(DOMException); - const DOMExceptionPrototype = DOMException.prototype; - - const entries = ObjectEntries({ - INDEX_SIZE_ERR, - DOMSTRING_SIZE_ERR, - HIERARCHY_REQUEST_ERR, - WRONG_DOCUMENT_ERR, - INVALID_CHARACTER_ERR, - NO_DATA_ALLOWED_ERR, - NO_MODIFICATION_ALLOWED_ERR, - NOT_FOUND_ERR, - NOT_SUPPORTED_ERR, - INUSE_ATTRIBUTE_ERR, - INVALID_STATE_ERR, - SYNTAX_ERR, - INVALID_MODIFICATION_ERR, - NAMESPACE_ERR, - INVALID_ACCESS_ERR, - VALIDATION_ERR, - TYPE_MISMATCH_ERR, - SECURITY_ERR, - NETWORK_ERR, - ABORT_ERR, - URL_MISMATCH_ERR, - QUOTA_EXCEEDED_ERR, - TIMEOUT_ERR, - INVALID_NODE_TYPE_ERR, - DATA_CLONE_ERR, - }); - for (let i = 0; i < entries.length; ++i) { - const { 0: key, 1: value } = entries[i]; - const desc = { value, enumerable: true }; - ObjectDefineProperty(DOMException, key, desc); - ObjectDefineProperty(DOMException.prototype, key, desc); + get message() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_message]; } - window.__bootstrap.domException = { DOMException }; -})(this); + get name() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_name]; + } + + get code() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_code]; + } + + [SymbolFor("Deno.customInspect")](inspect) { + if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) { + return `DOMException: ${this[_message]}`; + } else { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: false, + keys: [ + "message", + "name", + "code", + ], + })); + } + } +} + +ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); + +webidl.configurePrototype(DOMException); +const DOMExceptionPrototype = DOMException.prototype; + +const entries = ObjectEntries({ + INDEX_SIZE_ERR, + DOMSTRING_SIZE_ERR, + HIERARCHY_REQUEST_ERR, + WRONG_DOCUMENT_ERR, + INVALID_CHARACTER_ERR, + NO_DATA_ALLOWED_ERR, + NO_MODIFICATION_ALLOWED_ERR, + NOT_FOUND_ERR, + NOT_SUPPORTED_ERR, + INUSE_ATTRIBUTE_ERR, + INVALID_STATE_ERR, + SYNTAX_ERR, + INVALID_MODIFICATION_ERR, + NAMESPACE_ERR, + INVALID_ACCESS_ERR, + VALIDATION_ERR, + TYPE_MISMATCH_ERR, + SECURITY_ERR, + NETWORK_ERR, + ABORT_ERR, + URL_MISMATCH_ERR, + QUOTA_EXCEEDED_ERR, + TIMEOUT_ERR, + INVALID_NODE_TYPE_ERR, + DATA_CLONE_ERR, +}); +for (let i = 0; i < entries.length; ++i) { + const { 0: key, 1: value } = entries[i]; + const desc = { value, enumerable: true }; + ObjectDefineProperty(DOMException, key, desc); + ObjectDefineProperty(DOMException.prototype, key, desc); +} + +export default DOMException; diff --git a/ext/web/01_mimesniff.js b/ext/web/01_mimesniff.js index 2d67d5f954..17d954eb4c 100644 --- a/ext/web/01_mimesniff.js +++ b/ext/web/01_mimesniff.js @@ -6,255 +6,247 @@ /// /// -"use strict"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIncludes, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + RegExpPrototypeTest, + SafeMapIterator, + StringPrototypeReplaceAll, + StringPrototypeToLowerCase, +} = primordials; +import { + collectHttpQuotedString, + collectSequenceOfCodepoints, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TOKEN_CODE_POINT_RE, + HTTP_WHITESPACE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, +} from "internal:ext/web/00_infra.js"; -((window) => { - const { - ArrayPrototypeIncludes, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - RegExpPrototypeTest, - SafeMapIterator, - StringPrototypeReplaceAll, - StringPrototypeToLowerCase, - } = window.__bootstrap.primordials; - const { - collectSequenceOfCodepoints, - HTTP_WHITESPACE, - HTTP_WHITESPACE_PREFIX_RE, - HTTP_WHITESPACE_SUFFIX_RE, - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - HTTP_TOKEN_CODE_POINT_RE, - collectHttpQuotedString, - } = window.__bootstrap.infra; +/** + * @typedef MimeType + * @property {string} type + * @property {string} subtype + * @property {Map} parameters + */ - /** - * @typedef MimeType - * @property {string} type - * @property {string} subtype - * @property {Map} parameters - */ +/** + * @param {string} input + * @returns {MimeType | null} + */ +function parseMimeType(input) { + // 1. + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); - /** - * @param {string} input - * @returns {MimeType | null} - */ - function parseMimeType(input) { - // 1. - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); + // 2. + let position = 0; + const endOfInput = input.length; - // 2. - let position = 0; - const endOfInput = input.length; + // 3. + const res1 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u002F", + ); + const type = res1.result; + position = res1.position; - // 3. + // 4. + if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { + return null; + } + + // 5. + if (position >= endOfInput) return null; + + // 6. + position++; + + // 7. + const res2 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u003B", + ); + let subtype = res2.result; + position = res2.position; + + // 8. + subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); + + // 9. + if ( + subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) + ) { + return null; + } + + // 10. + const mimeType = { + type: StringPrototypeToLowerCase(type), + subtype: StringPrototypeToLowerCase(subtype), + /** @type {Map} */ + parameters: new Map(), + }; + + // 11. + while (position < endOfInput) { + // 11.1. + position++; + + // 11.2. const res1 = collectSequenceOfCodepoints( input, position, - (c) => c != "\u002F", + (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), ); - const type = res1.result; position = res1.position; - // 4. - if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { - return null; - } - - // 5. - if (position >= endOfInput) return null; - - // 6. - position++; - - // 7. + // 11.3. const res2 = collectSequenceOfCodepoints( input, position, - (c) => c != "\u003B", + (c) => c !== "\u003B" && c !== "\u003D", ); - let subtype = res2.result; + let parameterName = res2.result; position = res2.position; - // 8. - subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); + // 11.4. + parameterName = StringPrototypeToLowerCase(parameterName); - // 9. - if ( - subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) - ) { - return null; - } - - // 10. - const mimeType = { - type: StringPrototypeToLowerCase(type), - subtype: StringPrototypeToLowerCase(subtype), - /** @type {Map} */ - parameters: new Map(), - }; - - // 11. - while (position < endOfInput) { - // 11.1. + // 11.5. + if (position < endOfInput) { + if (input[position] == "\u003B") continue; position++; + } - // 11.2. - const res1 = collectSequenceOfCodepoints( + // 11.6. + if (position >= endOfInput) break; + + // 11.7. + let parameterValue = null; + + // 11.8. + if (input[position] === "\u0022") { + // 11.8.1. + const res = collectHttpQuotedString(input, position, true); + parameterValue = res.result; + position = res.position; + + // 11.8.2. + position++; + } else { // 11.9. + // 11.9.1. + const res = collectSequenceOfCodepoints( input, position, - (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), + (c) => c !== "\u003B", ); - position = res1.position; + parameterValue = res.result; + position = res.position; - // 11.3. - const res2 = collectSequenceOfCodepoints( - input, - position, - (c) => c !== "\u003B" && c !== "\u003D", + // 11.9.2. + parameterValue = StringPrototypeReplaceAll( + parameterValue, + HTTP_WHITESPACE_SUFFIX_RE, + "", ); - let parameterName = res2.result; - position = res2.position; - // 11.4. - parameterName = StringPrototypeToLowerCase(parameterName); + // 11.9.3. + if (parameterValue === "") continue; + } - // 11.5. - if (position < endOfInput) { - if (input[position] == "\u003B") continue; - position++; + // 11.10. + if ( + parameterName !== "" && + RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && + RegExpPrototypeTest( + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + parameterValue, + ) && + !MapPrototypeHas(mimeType.parameters, parameterName) + ) { + MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); + } + } + + // 12. + return mimeType; +} + +/** + * @param {MimeType} mimeType + * @returns {string} + */ +function essence(mimeType) { + return `${mimeType.type}/${mimeType.subtype}`; +} + +/** + * @param {MimeType} mimeType + * @returns {string} + */ +function serializeMimeType(mimeType) { + let serialization = essence(mimeType); + for (const param of new SafeMapIterator(mimeType.parameters)) { + serialization += `;${param[0]}=`; + let value = param[1]; + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) { + value = StringPrototypeReplaceAll(value, "\\", "\\\\"); + value = StringPrototypeReplaceAll(value, '"', '\\"'); + value = `"${value}"`; + } + serialization += value; + } + return serialization; +} + +/** + * Part of the Fetch spec's "extract a MIME type" algorithm + * (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type). + * @param {string[] | null} headerValues The result of getting, decoding and + * splitting the "Content-Type" header. + * @returns {MimeType | null} + */ +function extractMimeType(headerValues) { + if (headerValues === null) return null; + + let charset = null; + let essence_ = null; + let mimeType = null; + for (let i = 0; i < headerValues.length; ++i) { + const value = headerValues[i]; + const temporaryMimeType = parseMimeType(value); + if ( + temporaryMimeType === null || + essence(temporaryMimeType) == "*/*" + ) { + continue; + } + mimeType = temporaryMimeType; + if (essence(mimeType) !== essence_) { + charset = null; + const newCharset = MapPrototypeGet(mimeType.parameters, "charset"); + if (newCharset !== undefined) { + charset = newCharset; } - - // 11.6. - if (position >= endOfInput) break; - - // 11.7. - let parameterValue = null; - - // 11.8. - if (input[position] === "\u0022") { - // 11.8.1. - const res = collectHttpQuotedString(input, position, true); - parameterValue = res.result; - position = res.position; - - // 11.8.2. - position++; - } else { // 11.9. - // 11.9.1. - const res = collectSequenceOfCodepoints( - input, - position, - (c) => c !== "\u003B", - ); - parameterValue = res.result; - position = res.position; - - // 11.9.2. - parameterValue = StringPrototypeReplaceAll( - parameterValue, - HTTP_WHITESPACE_SUFFIX_RE, - "", - ); - - // 11.9.3. - if (parameterValue === "") continue; - } - - // 11.10. + essence_ = essence(mimeType); + } else { if ( - parameterName !== "" && - RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && - RegExpPrototypeTest( - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - parameterValue, - ) && - !MapPrototypeHas(mimeType.parameters, parameterName) + !MapPrototypeHas(mimeType.parameters, "charset") && + charset !== null ) { - MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); + MapPrototypeSet(mimeType.parameters, "charset", charset); } } - - // 12. - return mimeType; } + return mimeType; +} - /** - * @param {MimeType} mimeType - * @returns {string} - */ - function essence(mimeType) { - return `${mimeType.type}/${mimeType.subtype}`; - } - - /** - * @param {MimeType} mimeType - * @returns {string} - */ - function serializeMimeType(mimeType) { - let serialization = essence(mimeType); - for (const param of new SafeMapIterator(mimeType.parameters)) { - serialization += `;${param[0]}=`; - let value = param[1]; - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) { - value = StringPrototypeReplaceAll(value, "\\", "\\\\"); - value = StringPrototypeReplaceAll(value, '"', '\\"'); - value = `"${value}"`; - } - serialization += value; - } - return serialization; - } - - /** - * Part of the Fetch spec's "extract a MIME type" algorithm - * (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type). - * @param {string[] | null} headerValues The result of getting, decoding and - * splitting the "Content-Type" header. - * @returns {MimeType | null} - */ - function extractMimeType(headerValues) { - if (headerValues === null) return null; - - let charset = null; - let essence_ = null; - let mimeType = null; - for (let i = 0; i < headerValues.length; ++i) { - const value = headerValues[i]; - const temporaryMimeType = parseMimeType(value); - if ( - temporaryMimeType === null || - essence(temporaryMimeType) == "*/*" - ) { - continue; - } - mimeType = temporaryMimeType; - if (essence(mimeType) !== essence_) { - charset = null; - const newCharset = MapPrototypeGet(mimeType.parameters, "charset"); - if (newCharset !== undefined) { - charset = newCharset; - } - essence_ = essence(mimeType); - } else { - if ( - !MapPrototypeHas(mimeType.parameters, "charset") && - charset !== null - ) { - MapPrototypeSet(mimeType.parameters, "charset", charset); - } - } - } - return mimeType; - } - - window.__bootstrap.mimesniff = { - parseMimeType, - essence, - serializeMimeType, - extractMimeType, - }; -})(this); +export { essence, extractMimeType, parseMimeType, serializeMimeType }; diff --git a/ext/web/02_event.js b/ext/web/02_event.js index c99eb8f6ed..de5210d33b 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -4,1520 +4,1525 @@ // Many parts of the DOM are not implemented in Deno, but the logic for those // parts still exists. This means you will observe a lot of strange structures // and impossible logic branches based on what Deno currently supports. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - const consoleInternal = window.__bootstrap.console; - const { - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ArrayPrototypeIndexOf, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ArrayPrototypeUnshift, - Boolean, - DateNow, - Error, - FunctionPrototypeCall, - Map, - MapPrototypeGet, - MapPrototypeSet, - ObjectCreate, - ObjectDefineProperty, - ObjectGetOwnPropertyDescriptor, - ObjectPrototypeIsPrototypeOf, - ReflectDefineProperty, - ReflectHas, - SafeArrayIterator, - StringPrototypeStartsWith, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + Boolean, + DateNow, + Error, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectCreate, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectPrototypeIsPrototypeOf, + ReflectDefineProperty, + ReflectHas, + SafeArrayIterator, + StringPrototypeStartsWith, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, +} = primordials; - // accessors for non runtime visible data +// This should be set via setGlobalThis this is required so that if even +// user deletes globalThis it is still usable +let globalThis_; - function getDispatched(event) { - return Boolean(event[_dispatched]); +function saveGlobalThisReference(val) { + globalThis_ = val; +} + +// accessors for non runtime visible data + +function getDispatched(event) { + return Boolean(event[_dispatched]); +} + +function getPath(event) { + return event[_path] ?? []; +} + +function getStopImmediatePropagation(event) { + return Boolean(event[_stopImmediatePropagationFlag]); +} + +function setCurrentTarget( + event, + value, +) { + event[_attributes].currentTarget = value; +} + +function setIsTrusted(event, value) { + event[_isTrusted] = value; +} + +function setDispatched(event, value) { + event[_dispatched] = value; +} + +function setEventPhase(event, value) { + event[_attributes].eventPhase = value; +} + +function setInPassiveListener(event, value) { + event[_inPassiveListener] = value; +} + +function setPath(event, value) { + event[_path] = value; +} + +function setRelatedTarget( + event, + value, +) { + event[_attributes].relatedTarget = value; +} + +function setTarget(event, value) { + event[_attributes].target = value; +} + +function setStopImmediatePropagation( + event, + value, +) { + event[_stopImmediatePropagationFlag] = value; +} + +// Type guards that widen the event type + +function hasRelatedTarget( + event, +) { + return ReflectHas(event, "relatedTarget"); +} + +const isTrusted = ObjectGetOwnPropertyDescriptor({ + get isTrusted() { + return this[_isTrusted]; + }, +}, "isTrusted").get; + +const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ + key: "bubbles", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "cancelable", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "composed", + defaultValue: false, + converter: webidl.converters.boolean, +}]); + +const _attributes = Symbol("[[attributes]]"); +const _canceledFlag = Symbol("[[canceledFlag]]"); +const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); +const _stopImmediatePropagationFlag = Symbol( + "[[stopImmediatePropagationFlag]]", +); +const _inPassiveListener = Symbol("[[inPassiveListener]]"); +const _dispatched = Symbol("[[dispatched]]"); +const _isTrusted = Symbol("[[isTrusted]]"); +const _path = Symbol("[[path]]"); +// internal. +const _skipInternalInit = Symbol("[[skipSlowInit]]"); + +class Event { + constructor(type, eventInitDict = {}) { + // TODO(lucacasonato): remove when this interface is spec aligned + this[SymbolToStringTag] = "Event"; + this[_canceledFlag] = false; + this[_stopPropagationFlag] = false; + this[_stopImmediatePropagationFlag] = false; + this[_inPassiveListener] = false; + this[_dispatched] = false; + this[_isTrusted] = false; + this[_path] = []; + + if (!eventInitDict[_skipInternalInit]) { + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'Event'", + }); + type = webidl.converters.DOMString(type, { + prefix: "Failed to construct 'Event'", + context: "Argument 1", + }); + const eventInit = eventInitConverter(eventInitDict, { + prefix: "Failed to construct 'Event'", + context: "Argument 2", + }); + this[_attributes] = { + type, + ...eventInit, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // [LegacyUnforgeable] + ReflectDefineProperty(this, "isTrusted", { + enumerable: true, + get: isTrusted, + }); + } else { + this[_attributes] = { + type, + data: eventInitDict.data ?? null, + bubbles: eventInitDict.bubbles ?? false, + cancelable: eventInitDict.cancelable ?? false, + composed: eventInitDict.composed ?? false, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // TODO(@littledivy): Not spec compliant but performance is hurt badly + // for users of `_skipInternalInit`. + this.isTrusted = false; + } } - function getPath(event) { - return event[_path] ?? []; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), + keys: EVENT_PROPS, + })); } - function getStopImmediatePropagation(event) { - return Boolean(event[_stopImmediatePropagationFlag]); + get type() { + return this[_attributes].type; } - function setCurrentTarget( - event, - value, - ) { - event[_attributes].currentTarget = value; + get target() { + return this[_attributes].target; } - function setIsTrusted(event, value) { - event[_isTrusted] = value; + get srcElement() { + return null; } - function setDispatched(event, value) { - event[_dispatched] = value; + set srcElement(_) { + // this member is deprecated } - function setEventPhase(event, value) { - event[_attributes].eventPhase = value; + get currentTarget() { + return this[_attributes].currentTarget; } - function setInPassiveListener(event, value) { - event[_inPassiveListener] = value; - } + composedPath() { + const path = this[_path]; + if (path.length === 0) { + return []; + } - function setPath(event, value) { - event[_path] = value; - } + if (!this.currentTarget) { + throw new Error("assertion error"); + } + const composedPath = [ + { + item: this.currentTarget, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }, + ]; - function setRelatedTarget( - event, - value, - ) { - event[_attributes].relatedTarget = value; - } + let currentTargetIndex = 0; + let currentTargetHiddenSubtreeLevel = 0; - function setTarget(event, value) { - event[_attributes].target = value; - } + for (let index = path.length - 1; index >= 0; index--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - function setStopImmediatePropagation( - event, - value, - ) { - event[_stopImmediatePropagationFlag] = value; - } + if (rootOfClosedTree) { + currentTargetHiddenSubtreeLevel++; + } - // Type guards that widen the event type + if (item === this.currentTarget) { + currentTargetIndex = index; + break; + } - function hasRelatedTarget( - event, - ) { - return ReflectHas(event, "relatedTarget"); - } - - const isTrusted = ObjectGetOwnPropertyDescriptor({ - get isTrusted() { - return this[_isTrusted]; - }, - }, "isTrusted").get; - - const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ - key: "bubbles", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "cancelable", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "composed", - defaultValue: false, - converter: webidl.converters.boolean, - }]); - - const _attributes = Symbol("[[attributes]]"); - const _canceledFlag = Symbol("[[canceledFlag]]"); - const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); - const _stopImmediatePropagationFlag = Symbol( - "[[stopImmediatePropagationFlag]]", - ); - const _inPassiveListener = Symbol("[[inPassiveListener]]"); - const _dispatched = Symbol("[[dispatched]]"); - const _isTrusted = Symbol("[[isTrusted]]"); - const _path = Symbol("[[path]]"); - // internal. - const _skipInternalInit = Symbol("[[skipSlowInit]]"); - - class Event { - constructor(type, eventInitDict = {}) { - // TODO(lucacasonato): remove when this interface is spec aligned - this[SymbolToStringTag] = "Event"; - this[_canceledFlag] = false; - this[_stopPropagationFlag] = false; - this[_stopImmediatePropagationFlag] = false; - this[_inPassiveListener] = false; - this[_dispatched] = false; - this[_isTrusted] = false; - this[_path] = []; - - if (!eventInitDict[_skipInternalInit]) { - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'Event'", - }); - type = webidl.converters.DOMString(type, { - prefix: "Failed to construct 'Event'", - context: "Argument 1", - }); - const eventInit = eventInitConverter(eventInitDict, { - prefix: "Failed to construct 'Event'", - context: "Argument 2", - }); - this[_attributes] = { - type, - ...eventInit, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // [LegacyUnforgeable] - ReflectDefineProperty(this, "isTrusted", { - enumerable: true, - get: isTrusted, - }); - } else { - this[_attributes] = { - type, - data: eventInitDict.data ?? null, - bubbles: eventInitDict.bubbles ?? false, - cancelable: eventInitDict.cancelable ?? false, - composed: eventInitDict.composed ?? false, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // TODO(@littledivy): Not spec compliant but performance is hurt badly - // for users of `_skipInternalInit`. - this.isTrusted = false; + if (slotInClosedTree) { + currentTargetHiddenSubtreeLevel--; } } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), - keys: EVENT_PROPS, - })); - } + let currentHiddenLevel = currentTargetHiddenSubtreeLevel; + let maxHiddenLevel = currentTargetHiddenSubtreeLevel; - get type() { - return this[_attributes].type; - } + for (let i = currentTargetIndex - 1; i >= 0; i--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - get target() { - return this[_attributes].target; - } - - get srcElement() { - return null; - } - - set srcElement(_) { - // this member is deprecated - } - - get currentTarget() { - return this[_attributes].currentTarget; - } - - composedPath() { - const path = this[_path]; - if (path.length === 0) { - return []; + if (rootOfClosedTree) { + currentHiddenLevel++; } - if (!this.currentTarget) { - throw new Error("assertion error"); - } - const composedPath = [ - { - item: this.currentTarget, + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypeUnshift(composedPath, { + item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], - }, - ]; - - let currentTargetIndex = 0; - let currentTargetHiddenSubtreeLevel = 0; - - for (let index = path.length - 1; index >= 0; index--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - - if (rootOfClosedTree) { - currentTargetHiddenSubtreeLevel++; - } - - if (item === this.currentTarget) { - currentTargetIndex = index; - break; - } - - if (slotInClosedTree) { - currentTargetHiddenSubtreeLevel--; - } + }); } - let currentHiddenLevel = currentTargetHiddenSubtreeLevel; - let maxHiddenLevel = currentTargetHiddenSubtreeLevel; + if (slotInClosedTree) { + currentHiddenLevel--; - for (let i = currentTargetIndex - 1; i >= 0; i--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - - if (rootOfClosedTree) { - currentHiddenLevel++; + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } - - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypeUnshift(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (slotInClosedTree) { - currentHiddenLevel--; - - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } - } - } - - currentHiddenLevel = currentTargetHiddenSubtreeLevel; - maxHiddenLevel = currentTargetHiddenSubtreeLevel; - - for (let index = currentTargetIndex + 1; index < path.length; index++) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - - if (slotInClosedTree) { - currentHiddenLevel++; - } - - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypePush(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (rootOfClosedTree) { - currentHiddenLevel--; - - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } - } - } - return ArrayPrototypeMap(composedPath, (p) => p.item); - } - - get NONE() { - return Event.NONE; - } - - get CAPTURING_PHASE() { - return Event.CAPTURING_PHASE; - } - - get AT_TARGET() { - return Event.AT_TARGET; - } - - get BUBBLING_PHASE() { - return Event.BUBBLING_PHASE; - } - - static get NONE() { - return 0; - } - - static get CAPTURING_PHASE() { - return 1; - } - - static get AT_TARGET() { - return 2; - } - - static get BUBBLING_PHASE() { - return 3; - } - - get eventPhase() { - return this[_attributes].eventPhase; - } - - stopPropagation() { - this[_stopPropagationFlag] = true; - } - - get cancelBubble() { - return this[_stopPropagationFlag]; - } - - set cancelBubble(value) { - this[_stopPropagationFlag] = webidl.converters.boolean(value); - } - - stopImmediatePropagation() { - this[_stopPropagationFlag] = true; - this[_stopImmediatePropagationFlag] = true; - } - - get bubbles() { - return this[_attributes].bubbles; - } - - get cancelable() { - return this[_attributes].cancelable; - } - - get returnValue() { - return !this[_canceledFlag]; - } - - set returnValue(value) { - if (!webidl.converters.boolean(value)) { - this[_canceledFlag] = true; } } - preventDefault() { - if (this[_attributes].cancelable && !this[_inPassiveListener]) { - this[_canceledFlag] = true; + currentHiddenLevel = currentTargetHiddenSubtreeLevel; + maxHiddenLevel = currentTargetHiddenSubtreeLevel; + + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; + + if (slotInClosedTree) { + currentHiddenLevel++; + } + + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypePush(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } + + if (rootOfClosedTree) { + currentHiddenLevel--; + + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; + } } } + return ArrayPrototypeMap(composedPath, (p) => p.item); + } - get defaultPrevented() { - return this[_canceledFlag]; + get NONE() { + return Event.NONE; + } + + get CAPTURING_PHASE() { + return Event.CAPTURING_PHASE; + } + + get AT_TARGET() { + return Event.AT_TARGET; + } + + get BUBBLING_PHASE() { + return Event.BUBBLING_PHASE; + } + + static get NONE() { + return 0; + } + + static get CAPTURING_PHASE() { + return 1; + } + + static get AT_TARGET() { + return 2; + } + + static get BUBBLING_PHASE() { + return 3; + } + + get eventPhase() { + return this[_attributes].eventPhase; + } + + stopPropagation() { + this[_stopPropagationFlag] = true; + } + + get cancelBubble() { + return this[_stopPropagationFlag]; + } + + set cancelBubble(value) { + this[_stopPropagationFlag] = webidl.converters.boolean(value); + } + + stopImmediatePropagation() { + this[_stopPropagationFlag] = true; + this[_stopImmediatePropagationFlag] = true; + } + + get bubbles() { + return this[_attributes].bubbles; + } + + get cancelable() { + return this[_attributes].cancelable; + } + + get returnValue() { + return !this[_canceledFlag]; + } + + set returnValue(value) { + if (!webidl.converters.boolean(value)) { + this[_canceledFlag] = true; } + } - get composed() { - return this[_attributes].composed; + preventDefault() { + if (this[_attributes].cancelable && !this[_inPassiveListener]) { + this[_canceledFlag] = true; } + } - get initialized() { + get defaultPrevented() { + return this[_canceledFlag]; + } + + get composed() { + return this[_attributes].composed; + } + + get initialized() { + return true; + } + + get timeStamp() { + return this[_attributes].timeStamp; + } +} + +function defineEnumerableProps( + Ctor, + props, +) { + for (let i = 0; i < props.length; ++i) { + const prop = props[i]; + ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); + } +} + +const EVENT_PROPS = [ + "bubbles", + "cancelable", + "composed", + "currentTarget", + "defaultPrevented", + "eventPhase", + "srcElement", + "target", + "returnValue", + "timeStamp", + "type", +]; + +defineEnumerableProps(Event, EVENT_PROPS); + +// This is currently the only node type we are using, so instead of implementing +// the whole of the Node interface at the moment, this just gives us the one +// value to power the standards based logic +const DOCUMENT_FRAGMENT_NODE = 11; + +// DOM Logic Helper functions and type guards + +/** Get the parent node, for event targets that have a parent. + * + * Ref: https://dom.spec.whatwg.org/#get-the-parent */ +function getParent(eventTarget) { + return isNode(eventTarget) ? eventTarget.parentNode : null; +} + +function getRoot(eventTarget) { + return isNode(eventTarget) + ? eventTarget.getRootNode({ composed: true }) + : null; +} + +function isNode( + eventTarget, +) { + return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); +} + +// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor +function isShadowInclusiveAncestor( + ancestor, + node, +) { + while (isNode(node)) { + if (node === ancestor) { return true; } - get timeStamp() { - return this[_attributes].timeStamp; + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); } } - function defineEnumerableProps( - Ctor, - props, - ) { - for (let i = 0; i < props.length; ++i) { - const prop = props[i]; - ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); - } - } + return false; +} - const EVENT_PROPS = [ - "bubbles", - "cancelable", - "composed", - "currentTarget", - "defaultPrevented", - "eventPhase", - "srcElement", - "target", - "returnValue", - "timeStamp", - "type", - ]; +function isShadowRoot(nodeImpl) { + return Boolean( + nodeImpl && + isNode(nodeImpl) && + nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && + getHost(nodeImpl) != null, + ); +} - defineEnumerableProps(Event, EVENT_PROPS); +function isSlotable( + nodeImpl, +) { + return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); +} - // This is currently the only node type we are using, so instead of implementing - // the whole of the Node interface at the moment, this just gives us the one - // value to power the standards based logic - const DOCUMENT_FRAGMENT_NODE = 11; +// DOM Logic functions - // DOM Logic Helper functions and type guards +/** Append a path item to an event's path. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-path-append + */ +function appendToEventPath( + eventImpl, + target, + targetOverride, + relatedTarget, + touchTargets, + slotInClosedTree, +) { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && + getMode(target) === "closed"; - /** Get the parent node, for event targets that have a parent. - * - * Ref: https://dom.spec.whatwg.org/#get-the-parent */ - function getParent(eventTarget) { - return isNode(eventTarget) ? eventTarget.parentNode : null; - } - - function getRoot(eventTarget) { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; - } - - function isNode( - eventTarget, - ) { - return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); - } - - // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor - function isShadowInclusiveAncestor( - ancestor, - node, - ) { - while (isNode(node)) { - if (node === ancestor) { - return true; - } - - if (isShadowRoot(node)) { - node = node && getHost(node); - } else { - node = getParent(node); - } - } - - return false; - } - - function isShadowRoot(nodeImpl) { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); - } - - function isSlotable( - nodeImpl, - ) { - return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); - } - - // DOM Logic functions - - /** Append a path item to an event's path. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-path-append - */ - function appendToEventPath( - eventImpl, - target, - targetOverride, + ArrayPrototypePush(getPath(eventImpl), { + item: target, + itemInShadowTree, + target: targetOverride, relatedTarget, - touchTargets, + touchTargetList: touchTargets, + rootOfClosedTree, slotInClosedTree, - ) { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = isShadowRoot(target) && - getMode(target) === "closed"; + }); +} - ArrayPrototypePush(getPath(eventImpl), { - item: target, - itemInShadowTree, - target: targetOverride, +function dispatch( + targetImpl, + eventImpl, + targetOverride, +) { + let clearTargets = false; + let activationTarget = null; + + setDispatched(eventImpl, true); + + targetOverride = targetOverride ?? targetImpl; + const eventRelatedTarget = hasRelatedTarget(eventImpl) + ? eventImpl.relatedTarget + : null; + let relatedTarget = retarget(eventRelatedTarget, targetImpl); + + if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { + const touchTargets = []; + + appendToEventPath( + eventImpl, + targetImpl, + targetOverride, relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); - } + touchTargets, + false, + ); - function dispatch( - targetImpl, - eventImpl, - targetOverride, - ) { - let clearTargets = false; - let activationTarget = null; + const isActivationEvent = eventImpl.type === "click"; - setDispatched(eventImpl, true); + if (isActivationEvent && getHasActivationBehavior(targetImpl)) { + activationTarget = targetImpl; + } - targetOverride = targetOverride ?? targetImpl; - const eventRelatedTarget = hasRelatedTarget(eventImpl) - ? eventImpl.relatedTarget + let slotInClosedTree = false; + let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) + ? targetImpl : null; - let relatedTarget = retarget(eventRelatedTarget, targetImpl); + let parent = getParent(targetImpl); - if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { - const touchTargets = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; - - if (isActivationEvent && getHasActivationBehavior(targetImpl)) { - activationTarget = targetImpl; - } - - let slotInClosedTree = false; - let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) - ? targetImpl - : null; - let parent = getParent(targetImpl); - - // Populate event path - // https://dom.spec.whatwg.org/#event-path - while (parent !== null) { - if (slotable !== null) { - slotable = null; - - const parentRoot = getRoot(parent); - if ( - isShadowRoot(parentRoot) && - parentRoot && - getMode(parentRoot) === "closed" - ) { - slotInClosedTree = true; - } - } - - relatedTarget = retarget(eventRelatedTarget, parent); + // Populate event path + // https://dom.spec.whatwg.org/#event-path + while (parent !== null) { + if (slotable !== null) { + slotable = null; + const parentRoot = getRoot(parent); if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) + isShadowRoot(parentRoot) && + parentRoot && + getMode(parentRoot) === "closed" ) { - appendToEventPath( - eventImpl, - parent, - null, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } else if (parent === relatedTarget) { - parent = null; - } else { - targetImpl = parent; - - if ( - isActivationEvent && - activationTarget === null && - getHasActivationBehavior(targetImpl) - ) { - activationTarget = targetImpl; - } - - appendToEventPath( - eventImpl, - parent, - targetImpl, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } - - if (parent !== null) { - parent = getParent(parent); - } - - slotInClosedTree = false; - } - - let clearTargetsTupleIndex = -1; - const path = getPath(eventImpl); - for ( - let i = path.length - 1; - i >= 0 && clearTargetsTupleIndex === -1; - i-- - ) { - if (path[i].target !== null) { - clearTargetsTupleIndex = i; - } - } - const clearTargetsTuple = path[clearTargetsTupleIndex]; - - clearTargets = (isNode(clearTargetsTuple.target) && - isShadowRoot(getRoot(clearTargetsTuple.target))) || - (isNode(clearTargetsTuple.relatedTarget) && - isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - - setEventPhase(eventImpl, Event.CAPTURING_PHASE); - - for (let i = path.length - 1; i >= 0; --i) { - const tuple = path[i]; - - if (tuple.target === null) { - invokeEventListeners(tuple, eventImpl); + slotInClosedTree = true; } } - for (let i = 0; i < path.length; i++) { - const tuple = path[i]; - - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } - - if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET - ) { - invokeEventListeners(tuple, eventImpl); - } - } - } - - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); - - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); - } - - // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented - // if (activationTarget !== null) { - // if (!eventImpl.defaultPrevented) { - // activationTarget._activationBehavior(); - // } - // } - - return !eventImpl.defaultPrevented; - } - - /** Inner invoking of the event listeners where the resolved listeners are - * called. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ - function innerInvokeEventListeners( - eventImpl, - targetListeners, - ) { - let found = false; - - const { type } = eventImpl; - - if (!targetListeners || !targetListeners[type]) { - return found; - } - - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = ArrayPrototypeSlice(targetListeners[type]); - - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; - - let capture, once, passive; - if (typeof listener.options === "boolean") { - capture = listener.options; - once = false; - passive = false; - } else { - capture = listener.options.capture; - once = listener.options.once; - passive = listener.options.passive; - } - - // Check if the event listener has been removed since the listeners has been cloned. - if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { - continue; - } - - found = true; + relatedTarget = retarget(eventRelatedTarget, parent); if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) ) { - continue; - } - - if (once) { - ArrayPrototypeSplice( - targetListeners[type], - ArrayPrototypeIndexOf(targetListeners[type], listener), - 1, - ); - } - - if (passive) { - setInPassiveListener(eventImpl, true); - } - - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - FunctionPrototypeCall( - listener.callback, - eventImpl.currentTarget, + appendToEventPath( eventImpl, + parent, + null, + relatedTarget, + touchTargets, + slotInClosedTree, + ); + } else if (parent === relatedTarget) { + parent = null; + } else { + targetImpl = parent; + + if ( + isActivationEvent && + activationTarget === null && + getHasActivationBehavior(targetImpl) + ) { + activationTarget = targetImpl; + } + + appendToEventPath( + eventImpl, + parent, + targetImpl, + relatedTarget, + touchTargets, + slotInClosedTree, ); } - setInPassiveListener(eventImpl, false); + if (parent !== null) { + parent = getParent(parent); + } - if (getStopImmediatePropagation(eventImpl)) { - return found; + slotInClosedTree = false; + } + + let clearTargetsTupleIndex = -1; + const path = getPath(eventImpl); + for ( + let i = path.length - 1; + i >= 0 && clearTargetsTupleIndex === -1; + i-- + ) { + if (path[i].target !== null) { + clearTargetsTupleIndex = i; + } + } + const clearTargetsTuple = path[clearTargetsTupleIndex]; + + clearTargets = (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); + + setEventPhase(eventImpl, Event.CAPTURING_PHASE); + + for (let i = path.length - 1; i >= 0; --i) { + const tuple = path[i]; + + if (tuple.target === null) { + invokeEventListeners(tuple, eventImpl); } } + for (let i = 0; i < path.length; i++) { + const tuple = path[i]; + + if (tuple.target !== null) { + setEventPhase(eventImpl, Event.AT_TARGET); + } else { + setEventPhase(eventImpl, Event.BUBBLING_PHASE); + } + + if ( + (eventImpl.eventPhase === Event.BUBBLING_PHASE && + eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET + ) { + invokeEventListeners(tuple, eventImpl); + } + } + } + + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); + + if (clearTargets) { + setTarget(eventImpl, null); + setRelatedTarget(eventImpl, null); + } + + // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } + + return !eventImpl.defaultPrevented; +} + +/** Inner invoking of the event listeners where the resolved listeners are + * called. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ +function innerInvokeEventListeners( + eventImpl, + targetListeners, +) { + let found = false; + + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { return found; } - /** Invokes the listeners on a given event path with the supplied event. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ - function invokeEventListeners(tuple, eventImpl) { - const path = getPath(eventImpl); - const tupleIndex = ArrayPrototypeIndexOf(path, tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = path[i]; - if (t.target) { - setTarget(eventImpl, t.target); - break; - } - } + // Copy event listeners before iterating since the list can be modified during the iteration. + const handlers = ArrayPrototypeSlice(targetListeners[type]); - setRelatedTarget(eventImpl, tuple.relatedTarget); + for (let i = 0; i < handlers.length; i++) { + const listener = handlers[i]; - if (eventImpl.cancelBubble) { - return; - } - - setCurrentTarget(eventImpl, tuple.item); - - try { - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); - } catch (error) { - reportException(error); - } - } - - function normalizeEventHandlerOptions( - options, - ) { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; + let capture, once, passive; + if (typeof listener.options === "boolean") { + capture = listener.options; + once = false; + passive = false; } else { - return options; + capture = listener.options.capture; + once = listener.options.once; + passive = listener.options.passive; + } + + // Check if the event listener has been removed since the listeners has been cloned. + if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { + continue; + } + + found = true; + + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } + + if (once) { + ArrayPrototypeSplice( + targetListeners[type], + ArrayPrototypeIndexOf(targetListeners[type], listener), + 1, + ); + } + + if (passive) { + setInPassiveListener(eventImpl, true); + } + + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); + } + } else { + FunctionPrototypeCall( + listener.callback, + eventImpl.currentTarget, + eventImpl, + ); + } + + setInPassiveListener(eventImpl, false); + + if (getStopImmediatePropagation(eventImpl)) { + return found; } } - /** Retarget the target following the spec logic. - * - * Ref: https://dom.spec.whatwg.org/#retarget */ - function retarget(a, b) { - while (true) { - if (!isNode(a)) { + return found; +} + +/** Invokes the listeners on a given event path with the supplied event. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ +function invokeEventListeners(tuple, eventImpl) { + const path = getPath(eventImpl); + const tupleIndex = ArrayPrototypeIndexOf(path, tuple); + for (let i = tupleIndex; i >= 0; i--) { + const t = path[i]; + if (t.target) { + setTarget(eventImpl, t.target); + break; + } + } + + setRelatedTarget(eventImpl, tuple.relatedTarget); + + if (eventImpl.cancelBubble) { + return; + } + + setCurrentTarget(eventImpl, tuple.item); + + try { + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); + } catch (error) { + reportException(error); + } +} + +function normalizeEventHandlerOptions( + options, +) { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + }; + } else { + return options; + } +} + +/** Retarget the target following the spec logic. + * + * Ref: https://dom.spec.whatwg.org/#retarget */ +function retarget(a, b) { + while (true) { + if (!isNode(a)) { + return a; + } + + const aRoot = a.getRootNode(); + + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { return a; } - const aRoot = a.getRootNode(); - - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } - - a = getHost(aRoot); - } + a = getHost(aRoot); } } +} - // Accessors for non-public data +// Accessors for non-public data - const eventTargetData = Symbol(); +const eventTargetData = Symbol(); - function setEventTargetData(target) { - target[eventTargetData] = getDefaultTargetData(); - } +function setEventTargetData(target) { + target[eventTargetData] = getDefaultTargetData(); +} - function getAssignedSlot(target) { - return Boolean(target?.[eventTargetData]?.assignedSlot); - } +function getAssignedSlot(target) { + return Boolean(target?.[eventTargetData]?.assignedSlot); +} - function getHasActivationBehavior(target) { - return Boolean(target?.[eventTargetData]?.hasActivationBehavior); - } +function getHasActivationBehavior(target) { + return Boolean(target?.[eventTargetData]?.hasActivationBehavior); +} - function getHost(target) { - return target?.[eventTargetData]?.host ?? null; - } +function getHost(target) { + return target?.[eventTargetData]?.host ?? null; +} - function getListeners(target) { - return target?.[eventTargetData]?.listeners ?? {}; - } +function getListeners(target) { + return target?.[eventTargetData]?.listeners ?? {}; +} - function getMode(target) { - return target?.[eventTargetData]?.mode ?? null; - } +function getMode(target) { + return target?.[eventTargetData]?.mode ?? null; +} - function listenerCount(target, type) { - return getListeners(target)?.[type]?.length ?? 0; - } +function listenerCount(target, type) { + return getListeners(target)?.[type]?.length ?? 0; +} - function getDefaultTargetData() { - return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: ObjectCreate(null), - mode: "", - }; - } - - // This is lazy loaded because there is a circular dependency with AbortSignal. - let addEventListenerOptionsConverter; - - function lazyAddEventListenerOptionsConverter() { - addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( - "AddEventListenerOptions", - [ - { - key: "capture", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "passive", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "once", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - ], - ); - } - - webidl.converters.AddEventListenerOptions = (V, opts) => { - if (webidl.type(V) !== "Object" || V === null) { - V = { capture: Boolean(V) }; - } - - lazyAddEventListenerOptionsConverter(); - return addEventListenerOptionsConverter(V, opts); +function getDefaultTargetData() { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: ObjectCreate(null), + mode: "", }; +} - class EventTarget { - constructor() { - this[eventTargetData] = getDefaultTargetData(); - this[webidl.brand] = webidl.brand; - } +// This is lazy loaded because there is a circular dependency with AbortSignal. +let addEventListenerOptionsConverter; - addEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; - - webidl.requiredArguments(arguments.length, 2, { - prefix, - }); - - options = webidl.converters.AddEventListenerOptions(options, { - prefix, - context: "Argument 3", - }); - - if (callback === null) { - return; - } - - const { listeners } = self[eventTargetData]; - - if (!(ReflectHas(listeners, type))) { - listeners[type] = []; - } - - const listenerList = listeners[type]; - for (let i = 0; i < listenerList.length; ++i) { - const listener = listenerList[i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - return; - } - } - if (options?.signal) { - const signal = options?.signal; - if (signal.aborted) { - // If signal is not null and its aborted flag is set, then return. - return; - } else { - // If listener’s signal is not null, then add the following abort - // abort steps to it: Remove an event listener. - signal.addEventListener("abort", () => { - self.removeEventListener(type, callback, options); - }); - } - } - - ArrayPrototypePush(listeners[type], { callback, options }); - } - - removeEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 2, { - prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", - }); - - const { listeners } = self[eventTargetData]; - if (callback !== null && ReflectHas(listeners, type)) { - listeners[type] = ArrayPrototypeFilter( - listeners[type], - (listener) => listener.callback !== callback, - ); - } else if (callback === null || !listeners[type]) { - return; - } - - options = normalizeEventHandlerOptions(options); - - for (let i = 0; i < listeners[type].length; ++i) { - const listener = listeners[type][i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - ArrayPrototypeSplice(listeners[type], i, 1); - break; - } - } - } - - dispatchEvent(event) { - // If `this` is not present, then fallback to global scope. We don't use - // `globalThis` directly here, because it could be deleted by user. - // Instead use saved reference to global scope when the script was - // executed. - const self = this ?? window; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", - }); - - const { listeners } = self[eventTargetData]; - if (!ReflectHas(listeners, event.type)) { - setTarget(event, this); - return true; - } - - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - return dispatch(self, event); - } - - getParent(_event) { - return null; - } - } - - webidl.configurePrototype(EventTarget); - const EventTargetPrototype = EventTarget.prototype; - - defineEnumerableProps(EventTarget, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", - ]); - - class ErrorEvent extends Event { - #message = ""; - #filename = ""; - #lineno = ""; - #colno = ""; - #error = ""; - - get message() { - return this.#message; - } - get filename() { - return this.#filename; - } - get lineno() { - return this.#lineno; - } - get colno() { - return this.#colno; - } - get error() { - return this.#error; - } - - constructor( - type, +function lazyAddEventListenerOptionsConverter() { + addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( + "AddEventListenerOptions", + [ { - bubbles, - cancelable, - composed, - message = "", - filename = "", - lineno = 0, - colno = 0, - error, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + key: "capture", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "passive", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "once", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "signal", + converter: webidl.converters.AbortSignal, + }, + ], + ); +} - this.#message = message; - this.#filename = filename; - this.#lineno = lineno; - this.#colno = colno; - this.#error = error; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "message", - "filename", - "lineno", - "colno", - "error", - ], - })); - } - - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ErrorEvent"; +webidl.converters.AddEventListenerOptions = (V, opts) => { + if (webidl.type(V) !== "Object" || V === null) { + V = { capture: Boolean(V) }; } - defineEnumerableProps(ErrorEvent, [ - "message", - "filename", - "lineno", - "colno", - "error", - ]); + lazyAddEventListenerOptionsConverter(); + return addEventListenerOptionsConverter(V, opts); +}; - class CloseEvent extends Event { - #wasClean = ""; - #code = ""; - #reason = ""; +class EventTarget { + constructor() { + this[eventTargetData] = getDefaultTargetData(); + this[webidl.brand] = webidl.brand; + } - get wasClean() { - return this.#wasClean; - } - get code() { - return this.#code; - } - get reason() { - return this.#reason; + addEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; + + webidl.requiredArguments(arguments.length, 2, { + prefix, + }); + + options = webidl.converters.AddEventListenerOptions(options, { + prefix, + context: "Argument 3", + }); + + if (callback === null) { + return; } - constructor(type, { + const { listeners } = self[eventTargetData]; + + if (!(ReflectHas(listeners, type))) { + listeners[type] = []; + } + + const listenerList = listeners[type]; + for (let i = 0; i < listenerList.length; ++i) { + const listener = listenerList[i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + return; + } + } + if (options?.signal) { + const signal = options?.signal; + if (signal.aborted) { + // If signal is not null and its aborted flag is set, then return. + return; + } else { + // If listener’s signal is not null, then add the following abort + // abort steps to it: Remove an event listener. + signal.addEventListener("abort", () => { + self.removeEventListener(type, callback, options); + }); + } + } + + ArrayPrototypePush(listeners[type], { callback, options }); + } + + removeEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", + }); + + const { listeners } = self[eventTargetData]; + if (callback !== null && ReflectHas(listeners, type)) { + listeners[type] = ArrayPrototypeFilter( + listeners[type], + (listener) => listener.callback !== callback, + ); + } else if (callback === null || !listeners[type]) { + return; + } + + options = normalizeEventHandlerOptions(options); + + for (let i = 0; i < listeners[type].length; ++i) { + const listener = listeners[type][i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + ArrayPrototypeSplice(listeners[type], i, 1); + break; + } + } + } + + dispatchEvent(event) { + // If `this` is not present, then fallback to global scope. We don't use + // `globalThis` directly here, because it could be deleted by user. + // Instead use saved reference to global scope when the script was + // executed. + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", + }); + + const { listeners } = self[eventTargetData]; + if (!ReflectHas(listeners, event.type)) { + setTarget(event, this); + return true; + } + + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); + } + + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); + } + + return dispatch(self, event); + } + + getParent(_event) { + return null; + } +} + +webidl.configurePrototype(EventTarget); +const EventTargetPrototype = EventTarget.prototype; + +defineEnumerableProps(EventTarget, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", +]); + +class ErrorEvent extends Event { + #message = ""; + #filename = ""; + #lineno = ""; + #colno = ""; + #error = ""; + + get message() { + return this.#message; + } + get filename() { + return this.#filename; + } + get lineno() { + return this.#lineno; + } + get colno() { + return this.#colno; + } + get error() { + return this.#error; + } + + constructor( + type, + { bubbles, cancelable, composed, - wasClean = false, - code = 0, - reason = "", - } = {}) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + message = "", + filename = "", + lineno = 0, + colno = 0, + error, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.#wasClean = wasClean; - this.#code = code; - this.#reason = reason; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "wasClean", - "code", - "reason", - ], - })); - } + this.#message = message; + this.#filename = filename; + this.#lineno = lineno; + this.#colno = colno; + this.#error = error; } - class MessageEvent extends Event { - get source() { - return null; - } - - constructor(type, eventInitDict) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - [_skipInternalInit]: eventInitDict?.[_skipInternalInit], - }); - - this.data = eventInitDict?.data ?? null; - this.ports = eventInitDict?.ports ?? []; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "data", - "origin", - "lastEventId", - ], - })); - } - - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CloseEvent"; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "message", + "filename", + "lineno", + "colno", + "error", + ], + })); } - class CustomEvent extends Event { - #detail = null; + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ErrorEvent"; +} - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'CustomEvent'", - }); - const { detail } = eventInitDict; - this.#detail = detail; - } +defineEnumerableProps(ErrorEvent, [ + "message", + "filename", + "lineno", + "colno", + "error", +]); - get detail() { - return this.#detail; - } +class CloseEvent extends Event { + #wasClean = ""; + #code = ""; + #reason = ""; - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "detail", - ], - })); - } - - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CustomEvent"; + get wasClean() { + return this.#wasClean; + } + get code() { + return this.#code; + } + get reason() { + return this.#reason; } - ReflectDefineProperty(CustomEvent.prototype, "detail", { + constructor(type, { + bubbles, + cancelable, + composed, + wasClean = false, + code = 0, + reason = "", + } = {}) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); + + this.#wasClean = wasClean; + this.#code = code; + this.#reason = reason; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "wasClean", + "code", + "reason", + ], + })); + } +} + +class MessageEvent extends Event { + get source() { + return null; + } + + constructor(type, eventInitDict) { + super(type, { + bubbles: eventInitDict?.bubbles ?? false, + cancelable: eventInitDict?.cancelable ?? false, + composed: eventInitDict?.composed ?? false, + [_skipInternalInit]: eventInitDict?.[_skipInternalInit], + }); + + this.data = eventInitDict?.data ?? null; + this.ports = eventInitDict?.ports ?? []; + this.origin = eventInitDict?.origin ?? ""; + this.lastEventId = eventInitDict?.lastEventId ?? ""; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "data", + "origin", + "lastEventId", + ], + })); + } + + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CloseEvent"; +} + +class CustomEvent extends Event { + #detail = null; + + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'CustomEvent'", + }); + const { detail } = eventInitDict; + this.#detail = detail; + } + + get detail() { + return this.#detail; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "detail", + ], + })); + } + + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CustomEvent"; +} + +ReflectDefineProperty(CustomEvent.prototype, "detail", { + enumerable: true, +}); + +// ProgressEvent could also be used in other DOM progress event emits. +// Current use is for FileReader. +class ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + + this.lengthComputable = eventInitDict?.lengthComputable ?? false; + this.loaded = eventInitDict?.loaded ?? 0; + this.total = eventInitDict?.total ?? 0; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "lengthComputable", + "loaded", + "total", + ], + })); + } + + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ProgressEvent"; +} + +class PromiseRejectionEvent extends Event { + #promise = null; + #reason = null; + + get promise() { + return this.#promise; + } + get reason() { + return this.#reason; + } + + constructor( + type, + { + bubbles, + cancelable, + composed, + promise, + reason, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); + + this.#promise = promise; + this.#reason = reason; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PromiseRejectionEvent.prototype, + this, + ), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "promise", + "reason", + ], + })); + } + + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "PromiseRejectionEvent"; +} + +defineEnumerableProps(PromiseRejectionEvent, [ + "promise", + "reason", +]); + +const _eventHandlers = Symbol("eventHandlers"); + +function makeWrappedHandler(handler, isSpecialErrorEventHandler) { + function wrappedHandler(evt) { + if (typeof wrappedHandler.handler !== "function") { + return; + } + + if ( + isSpecialErrorEventHandler && + ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && + evt.type === "error" + ) { + const ret = FunctionPrototypeCall( + wrappedHandler.handler, + this, + evt.message, + evt.filename, + evt.lineno, + evt.colno, + evt.error, + ); + if (ret === true) { + evt.preventDefault(); + } + return; + } + + return FunctionPrototypeCall(wrappedHandler.handler, this, evt); + } + wrappedHandler.handler = handler; + return wrappedHandler; +} + +// `init` is an optional function that will be called the first time that the +// event handler property is set. It will be called with the object on which +// the property is set as its argument. +// `isSpecialErrorEventHandler` can be set to true to opt into the special +// behavior of event handlers for the "error" event in a global scope. +function defineEventHandler( + emitter, + name, + init = undefined, + isSpecialErrorEventHandler = false, +) { + // HTML specification section 8.1.7.1 + ObjectDefineProperty(emitter, `on${name}`, { + get() { + if (!this[_eventHandlers]) { + return null; + } + + return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; + }, + set(value) { + // All three Web IDL event handler types are nullable callback functions + // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning + // anything other than an object is treated as null. + if (typeof value !== "object" && typeof value !== "function") { + value = null; + } + + if (!this[_eventHandlers]) { + this[_eventHandlers] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else if (value !== null) { + handlerWrapper = makeWrappedHandler( + value, + isSpecialErrorEventHandler, + ); + this.addEventListener(name, handlerWrapper); + init?.(this); + } + MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); + }, + configurable: true, enumerable: true, }); +} - // ProgressEvent could also be used in other DOM progress event emits. - // Current use is for FileReader. - class ProgressEvent extends Event { - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); - - this.lengthComputable = eventInitDict?.lengthComputable ?? false; - this.loaded = eventInitDict?.loaded ?? 0; - this.total = eventInitDict?.total ?? 0; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "lengthComputable", - "loaded", - "total", - ], - })); - } - - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ProgressEvent"; - } - - class PromiseRejectionEvent extends Event { - #promise = null; - #reason = null; - - get promise() { - return this.#promise; - } - get reason() { - return this.#reason; - } - - constructor( - type, - { - bubbles, - cancelable, - composed, - promise, - reason, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); - - this.#promise = promise; - this.#reason = reason; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PromiseRejectionEvent.prototype, - this, - ), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "promise", - "reason", - ], - })); - } - - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "PromiseRejectionEvent"; - } - - defineEnumerableProps(PromiseRejectionEvent, [ - "promise", - "reason", - ]); - - const _eventHandlers = Symbol("eventHandlers"); - - function makeWrappedHandler(handler, isSpecialErrorEventHandler) { - function wrappedHandler(evt) { - if (typeof wrappedHandler.handler !== "function") { - return; - } +let reportExceptionStackedCalls = 0; +// https://html.spec.whatwg.org/#report-the-exception +function reportException(error) { + reportExceptionStackedCalls++; + const jsError = core.destructureError(error); + const message = jsError.exceptionMessage; + let filename = ""; + let lineno = 0; + let colno = 0; + if (jsError.frames.length > 0) { + filename = jsError.frames[0].fileName; + lineno = jsError.frames[0].lineNumber; + colno = jsError.frames[0].columnNumber; + } else { + const jsError = core.destructureError(new Error()); + const frames = jsError.frames; + for (let i = 0; i < frames.length; ++i) { + const frame = frames[i]; if ( - isSpecialErrorEventHandler && - ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && - evt.type === "error" + typeof frame.fileName == "string" && + !StringPrototypeStartsWith(frame.fileName, "internal:") ) { - const ret = FunctionPrototypeCall( - wrappedHandler.handler, - this, - evt.message, - evt.filename, - evt.lineno, - evt.colno, - evt.error, - ); - if (ret === true) { - evt.preventDefault(); - } - return; - } - - return FunctionPrototypeCall(wrappedHandler.handler, this, evt); - } - wrappedHandler.handler = handler; - return wrappedHandler; - } - - // `init` is an optional function that will be called the first time that the - // event handler property is set. It will be called with the object on which - // the property is set as its argument. - // `isSpecialErrorEventHandler` can be set to true to opt into the special - // behavior of event handlers for the "error" event in a global scope. - function defineEventHandler( - emitter, - name, - init = undefined, - isSpecialErrorEventHandler = false, - ) { - // HTML specification section 8.1.7.1 - ObjectDefineProperty(emitter, `on${name}`, { - get() { - if (!this[_eventHandlers]) { - return null; - } - - return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; - }, - set(value) { - // All three Web IDL event handler types are nullable callback functions - // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning - // anything other than an object is treated as null. - if (typeof value !== "object" && typeof value !== "function") { - value = null; - } - - if (!this[_eventHandlers]) { - this[_eventHandlers] = new Map(); - } - let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); - if (handlerWrapper) { - handlerWrapper.handler = value; - } else if (value !== null) { - handlerWrapper = makeWrappedHandler( - value, - isSpecialErrorEventHandler, - ); - this.addEventListener(name, handlerWrapper); - init?.(this); - } - MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); - }, - configurable: true, - enumerable: true, - }); - } - - let reportExceptionStackedCalls = 0; - - // https://html.spec.whatwg.org/#report-the-exception - function reportException(error) { - reportExceptionStackedCalls++; - const jsError = core.destructureError(error); - const message = jsError.exceptionMessage; - let filename = ""; - let lineno = 0; - let colno = 0; - if (jsError.frames.length > 0) { - filename = jsError.frames[0].fileName; - lineno = jsError.frames[0].lineNumber; - colno = jsError.frames[0].columnNumber; - } else { - const jsError = core.destructureError(new Error()); - const frames = jsError.frames; - for (let i = 0; i < frames.length; ++i) { - const frame = frames[i]; - if ( - typeof frame.fileName == "string" && - !StringPrototypeStartsWith(frame.fileName, "internal:") - ) { - filename = frame.fileName; - lineno = frame.lineNumber; - colno = frame.columnNumber; - break; - } + filename = frame.fileName; + lineno = frame.lineNumber; + colno = frame.columnNumber; + break; } } - const event = new ErrorEvent("error", { - cancelable: true, - message, - filename, - lineno, - colno, - error, - }); - // Avoid recursing `reportException()` via error handlers more than once. - if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) { - ops.op_dispatch_exception(error); - } - reportExceptionStackedCalls--; } - - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } + const event = new ErrorEvent("error", { + cancelable: true, + message, + filename, + lineno, + colno, + error, + }); + // Avoid recursing `reportException()` via error handlers more than once. + if (reportExceptionStackedCalls > 1 || globalThis_.dispatchEvent(event)) { + ops.op_dispatch_exception(error); } + reportExceptionStackedCalls--; +} - // https://html.spec.whatwg.org/#dom-reporterror - function reportError(error) { - checkThis(this); - const prefix = "Failed to call 'reportError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reportException(error); +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis_) { + throw new TypeError("Illegal invocation"); } +} - window.__bootstrap.eventTarget = { - EventTarget, - setEventTargetData, - listenerCount, - }; - window.__bootstrap.event = { - reportException, - setIsTrusted, - setTarget, - defineEventHandler, - _skipInternalInit, - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - CustomEvent, - ProgressEvent, - PromiseRejectionEvent, - reportError, - }; -})(this); +// https://html.spec.whatwg.org/#dom-reporterror +function reportError(error) { + checkThis(this); + const prefix = "Failed to call 'reportError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reportException(error); +} + +export { + _skipInternalInit, + CloseEvent, + CustomEvent, + defineEventHandler, + ErrorEvent, + Event, + EventTarget, + listenerCount, + MessageEvent, + ProgressEvent, + PromiseRejectionEvent, + reportError, + reportException, + saveGlobalThisReference, + setEventTargetData, + setIsTrusted, + setTarget, +}; diff --git a/ext/web/02_structured_clone.js b/ext/web/02_structured_clone.js index 793cb1c75e..373ae0ab2e 100644 --- a/ext/web/02_structured_clone.js +++ b/ext/web/02_structured_clone.js @@ -6,138 +6,135 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBuffer, + ArrayBufferPrototype, + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeSlice, + ArrayBufferIsView, + DataView, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, + ObjectPrototypeIsPrototypeOf, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + TypeErrorPrototype, + WeakMap, + WeakMapPrototypeSet, + Int8Array, + Int16Array, + Int32Array, + BigInt64Array, + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + BigUint64Array, + Float32Array, + Float64Array, +} = primordials; -((window) => { - const core = window.Deno.core; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBuffer, - ArrayBufferPrototype, - ArrayBufferPrototypeGetByteLength, - ArrayBufferPrototypeSlice, - ArrayBufferIsView, - DataView, - DataViewPrototypeGetBuffer, - DataViewPrototypeGetByteLength, - DataViewPrototypeGetByteOffset, - ObjectPrototypeIsPrototypeOf, - TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeGetByteOffset, - TypedArrayPrototypeGetLength, - TypedArrayPrototypeGetSymbolToStringTag, - TypeErrorPrototype, - WeakMap, - WeakMapPrototypeSet, - Int8Array, - Int16Array, - Int32Array, - BigInt64Array, - Uint8Array, - Uint8ClampedArray, - Uint16Array, - Uint32Array, - BigUint64Array, - Float32Array, - Float64Array, - } = window.__bootstrap.primordials; +const objectCloneMemo = new WeakMap(); - const objectCloneMemo = new WeakMap(); - - function cloneArrayBuffer( +function cloneArrayBuffer( + srcBuffer, + srcByteOffset, + srcLength, + _cloneConstructor, +) { + // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway + return ArrayBufferPrototypeSlice( srcBuffer, srcByteOffset, - srcLength, - _cloneConstructor, - ) { - // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway - return ArrayBufferPrototypeSlice( - srcBuffer, - srcByteOffset, - srcByteOffset + srcLength, + srcByteOffset + srcLength, + ); +} + +// TODO(petamoriken): Resizable ArrayBuffer support in the future +/** Clone a value in a similar way to structured cloning. It is similar to a + * StructureDeserialize(StructuredSerialize(...)). */ +function structuredClone(value) { + // Performance optimization for buffers, otherwise + // `serialize/deserialize` will allocate new buffer. + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) { + const cloned = cloneArrayBuffer( + value, + 0, + ArrayBufferPrototypeGetByteLength(value), + ArrayBuffer, + ); + WeakMapPrototypeSet(objectCloneMemo, value, cloned); + return cloned; + } + + if (ArrayBufferIsView(value)) { + const tag = TypedArrayPrototypeGetSymbolToStringTag(value); + // DataView + if (tag === undefined) { + return new DataView( + structuredClone(DataViewPrototypeGetBuffer(value)), + DataViewPrototypeGetByteOffset(value), + DataViewPrototypeGetByteLength(value), + ); + } + // TypedArray + let Constructor; + switch (tag) { + case "Int8Array": + Constructor = Int8Array; + break; + case "Int16Array": + Constructor = Int16Array; + break; + case "Int32Array": + Constructor = Int32Array; + break; + case "BigInt64Array": + Constructor = BigInt64Array; + break; + case "Uint8Array": + Constructor = Uint8Array; + break; + case "Uint8ClampedArray": + Constructor = Uint8ClampedArray; + break; + case "Uint16Array": + Constructor = Uint16Array; + break; + case "Uint32Array": + Constructor = Uint32Array; + break; + case "BigUint64Array": + Constructor = BigUint64Array; + break; + case "Float32Array": + Constructor = Float32Array; + break; + case "Float64Array": + Constructor = Float64Array; + break; + } + return new Constructor( + structuredClone(TypedArrayPrototypeGetBuffer(value)), + TypedArrayPrototypeGetByteOffset(value), + TypedArrayPrototypeGetLength(value), ); } - // TODO(petamoriken): Resizable ArrayBuffer support in the future - /** Clone a value in a similar way to structured cloning. It is similar to a - * StructureDeserialize(StructuredSerialize(...)). */ - function structuredClone(value) { - // Performance optimization for buffers, otherwise - // `serialize/deserialize` will allocate new buffer. - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) { - const cloned = cloneArrayBuffer( - value, - 0, - ArrayBufferPrototypeGetByteLength(value), - ArrayBuffer, - ); - WeakMapPrototypeSet(objectCloneMemo, value, cloned); - return cloned; - } - - if (ArrayBufferIsView(value)) { - const tag = TypedArrayPrototypeGetSymbolToStringTag(value); - // DataView - if (tag === undefined) { - return new DataView( - structuredClone(DataViewPrototypeGetBuffer(value)), - DataViewPrototypeGetByteOffset(value), - DataViewPrototypeGetByteLength(value), - ); - } - // TypedArray - let Constructor; - switch (tag) { - case "Int8Array": - Constructor = Int8Array; - break; - case "Int16Array": - Constructor = Int16Array; - break; - case "Int32Array": - Constructor = Int32Array; - break; - case "BigInt64Array": - Constructor = BigInt64Array; - break; - case "Uint8Array": - Constructor = Uint8Array; - break; - case "Uint8ClampedArray": - Constructor = Uint8ClampedArray; - break; - case "Uint16Array": - Constructor = Uint16Array; - break; - case "Uint32Array": - Constructor = Uint32Array; - break; - case "BigUint64Array": - Constructor = BigUint64Array; - break; - case "Float32Array": - Constructor = Float32Array; - break; - case "Float64Array": - Constructor = Float64Array; - break; - } - return new Constructor( - structuredClone(TypedArrayPrototypeGetBuffer(value)), - TypedArrayPrototypeGetByteOffset(value), - TypedArrayPrototypeGetLength(value), - ); - } - - try { - return core.deserialize(core.serialize(value)); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException(e.message, "DataCloneError"); - } - throw e; + try { + return core.deserialize(core.serialize(value)); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException(e.message, "DataCloneError"); } + throw e; } +} - window.__bootstrap.structuredClone = structuredClone; -})(globalThis); +export { structuredClone }; diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index a582cf428a..302b6f62cd 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -1,375 +1,372 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - ArrayPrototypePush, - ArrayPrototypeShift, - FunctionPrototypeCall, - Map, - MapPrototypeDelete, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - Uint8Array, - Uint32Array, - // deno-lint-ignore camelcase - NumberPOSITIVE_INFINITY, - PromisePrototypeThen, - SafeArrayIterator, - SymbolFor, - TypeError, - indirectEval, - } = window.__bootstrap.primordials; - const { webidl } = window.__bootstrap; - const { reportException } = window.__bootstrap.event; - const { assert } = window.__bootstrap.infra; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeShift, + FunctionPrototypeCall, + Map, + MapPrototypeDelete, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + Uint8Array, + Uint32Array, + // deno-lint-ignore camelcase + NumberPOSITIVE_INFINITY, + PromisePrototypeThen, + SafeArrayIterator, + SymbolFor, + TypeError, + indirectEval, +} = primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { reportException } from "internal:ext/web/02_event.js"; +import { assert } from "internal:ext/web/00_infra.js"; - const hrU8 = new Uint8Array(8); - const hr = new Uint32Array(hrU8.buffer); - function opNow() { - ops.op_now(hrU8); - return (hr[0] * 1000 + hr[1] / 1e6); +const hrU8 = new Uint8Array(8); +const hr = new Uint32Array(hrU8.buffer); +function opNow() { + ops.op_now(hrU8); + return (hr[0] * 1000 + hr[1] / 1e6); +} + +// --------------------------------------------------------------------------- + +/** + * The task queue corresponding to the timer task source. + * + * @type { {action: () => void, nestingLevel: number}[] } + */ +const timerTasks = []; + +/** + * The current task's timer nesting level, or zero if we're not currently + * running a timer task (since the minimum nesting level is 1). + * + * @type {number} + */ +let timerNestingLevel = 0; + +function handleTimerMacrotask() { + if (timerTasks.length === 0) { + return true; } - // --------------------------------------------------------------------------- + const task = ArrayPrototypeShift(timerTasks); - /** - * The task queue corresponding to the timer task source. - * - * @type { {action: () => void, nestingLevel: number}[] } - */ - const timerTasks = []; + timerNestingLevel = task.nestingLevel; - /** - * The current task's timer nesting level, or zero if we're not currently - * running a timer task (since the minimum nesting level is 1). - * - * @type {number} - */ - let timerNestingLevel = 0; + try { + task.action(); + } finally { + timerNestingLevel = 0; + } + return timerTasks.length === 0; +} - function handleTimerMacrotask() { - if (timerTasks.length === 0) { - return true; - } +// --------------------------------------------------------------------------- - const task = ArrayPrototypeShift(timerTasks); +/** + * The keys in this map correspond to the key ID's in the spec's map of active + * timers. The values are the timeout's cancel rid. + * + * @type {Map} + */ +const activeTimers = new Map(); - timerNestingLevel = task.nestingLevel; +let nextId = 1; - try { - task.action(); - } finally { - timerNestingLevel = 0; - } - return timerTasks.length === 0; +/** + * @param {Function | string} callback + * @param {number} timeout + * @param {Array} args + * @param {boolean} repeat + * @param {number | undefined} prevId + * @returns {number} The timer ID + */ +function initializeTimer( + callback, + timeout, + args, + repeat, + prevId, +) { + // 2. If previousId was given, let id be previousId; otherwise, let + // previousId be an implementation-defined integer than is greater than zero + // and does not already exist in global's map of active timers. + let id; + let timerInfo; + if (prevId !== undefined) { + // `prevId` is only passed for follow-up calls on intervals + assert(repeat); + id = prevId; + timerInfo = MapPrototypeGet(activeTimers, id); + } else { + // TODO(@andreubotella): Deal with overflow. + // https://github.com/whatwg/html/issues/7358 + id = nextId++; + const cancelRid = ops.op_timer_handle(); + timerInfo = { cancelRid, isRef: true, promiseId: -1 }; + + // Step 4 in "run steps after a timeout". + MapPrototypeSet(activeTimers, id, timerInfo); } - // --------------------------------------------------------------------------- + // 3. If the surrounding agent's event loop's currently running task is a + // task that was created by this algorithm, then let nesting level be the + // task's timer nesting level. Otherwise, let nesting level be zero. + // 4. If timeout is less than 0, then set timeout to 0. + // 5. If nesting level is greater than 5, and timeout is less than 4, then + // set timeout to 4. + // + // The nesting level of 5 and minimum of 4 ms are spec-mandated magic + // constants. + if (timeout < 0) timeout = 0; + if (timerNestingLevel > 5 && timeout < 4) timeout = 4; - /** - * The keys in this map correspond to the key ID's in the spec's map of active - * timers. The values are the timeout's cancel rid. - * - * @type {Map} - */ - const activeTimers = new Map(); + // 9. Let task be a task that runs the following steps: + const task = { + action: () => { + // 1. If id does not exist in global's map of active timers, then abort + // these steps. + // + // This is relevant if the timer has been canceled after the sleep op + // resolves but before this task runs. + if (!MapPrototypeHas(activeTimers, id)) { + return; + } - let nextId = 1; + // 2. + // 3. + if (typeof callback === "function") { + try { + FunctionPrototypeCall( + callback, + globalThis, + ...new SafeArrayIterator(args), + ); + } catch (error) { + reportException(error); + } + } else { + indirectEval(callback); + } - /** - * @param {Function | string} callback - * @param {number} timeout - * @param {Array} args - * @param {boolean} repeat - * @param {number | undefined} prevId - * @returns {number} The timer ID - */ - function initializeTimer( - callback, + if (repeat) { + if (MapPrototypeHas(activeTimers, id)) { + // 4. If id does not exist in global's map of active timers, then + // abort these steps. + // NOTE: If might have been removed via the author code in handler + // calling clearTimeout() or clearInterval(). + // 5. If repeat is true, then perform the timer initialization steps + // again, given global, handler, timeout, arguments, true, and id. + initializeTimer(callback, timeout, args, true, id); + } + } else { + // 6. Otherwise, remove global's map of active timers[id]. + core.tryClose(timerInfo.cancelRid); + MapPrototypeDelete(activeTimers, id); + } + }, + + // 10. Increment nesting level by one. + // 11. Set task's timer nesting level to nesting level. + nestingLevel: timerNestingLevel + 1, + }; + + // 12. Let completionStep be an algorithm step which queues a global task on + // the timer task source given global to run task. + // 13. Run steps after a timeout given global, "setTimeout/setInterval", + // timeout, completionStep, and id. + runAfterTimeout( + () => ArrayPrototypePush(timerTasks, task), timeout, - args, - repeat, - prevId, - ) { - // 2. If previousId was given, let id be previousId; otherwise, let - // previousId be an implementation-defined integer than is greater than zero - // and does not already exist in global's map of active timers. - let id; - let timerInfo; - if (prevId !== undefined) { - // `prevId` is only passed for follow-up calls on intervals - assert(repeat); - id = prevId; - timerInfo = MapPrototypeGet(activeTimers, id); - } else { - // TODO(@andreubotella): Deal with overflow. - // https://github.com/whatwg/html/issues/7358 - id = nextId++; - const cancelRid = ops.op_timer_handle(); - timerInfo = { cancelRid, isRef: true, promiseId: -1 }; + timerInfo, + ); - // Step 4 in "run steps after a timeout". - MapPrototypeSet(activeTimers, id, timerInfo); - } + return id; +} - // 3. If the surrounding agent's event loop's currently running task is a - // task that was created by this algorithm, then let nesting level be the - // task's timer nesting level. Otherwise, let nesting level be zero. - // 4. If timeout is less than 0, then set timeout to 0. - // 5. If nesting level is greater than 5, and timeout is less than 4, then - // set timeout to 4. - // - // The nesting level of 5 and minimum of 4 ms are spec-mandated magic - // constants. - if (timeout < 0) timeout = 0; - if (timerNestingLevel > 5 && timeout < 4) timeout = 4; +// --------------------------------------------------------------------------- - // 9. Let task be a task that runs the following steps: - const task = { - action: () => { - // 1. If id does not exist in global's map of active timers, then abort - // these steps. - // - // This is relevant if the timer has been canceled after the sleep op - // resolves but before this task runs. - if (!MapPrototypeHas(activeTimers, id)) { - return; - } +/** + * @typedef ScheduledTimer + * @property {number} millis + * @property {() => void} cb + * @property {boolean} resolved + * @property {ScheduledTimer | null} prev + * @property {ScheduledTimer | null} next + */ - // 2. - // 3. - if (typeof callback === "function") { - try { - FunctionPrototypeCall( - callback, - globalThis, - ...new SafeArrayIterator(args), - ); - } catch (error) { - reportException(error); - } - } else { - indirectEval(callback); - } +/** + * A doubly linked list of timers. + * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } } + */ +const scheduledTimers = { head: null, tail: null }; - if (repeat) { - if (MapPrototypeHas(activeTimers, id)) { - // 4. If id does not exist in global's map of active timers, then - // abort these steps. - // NOTE: If might have been removed via the author code in handler - // calling clearTimeout() or clearInterval(). - // 5. If repeat is true, then perform the timer initialization steps - // again, given global, handler, timeout, arguments, true, and id. - initializeTimer(callback, timeout, args, true, id); - } - } else { - // 6. Otherwise, remove global's map of active timers[id]. - core.tryClose(timerInfo.cancelRid); - MapPrototypeDelete(activeTimers, id); - } - }, - - // 10. Increment nesting level by one. - // 11. Set task's timer nesting level to nesting level. - nestingLevel: timerNestingLevel + 1, - }; - - // 12. Let completionStep be an algorithm step which queues a global task on - // the timer task source given global to run task. - // 13. Run steps after a timeout given global, "setTimeout/setInterval", - // timeout, completionStep, and id. - runAfterTimeout( - () => ArrayPrototypePush(timerTasks, task), - timeout, - timerInfo, - ); - - return id; - } - - // --------------------------------------------------------------------------- - - /** - * @typedef ScheduledTimer - * @property {number} millis - * @property {() => void} cb - * @property {boolean} resolved - * @property {ScheduledTimer | null} prev - * @property {ScheduledTimer | null} next - */ - - /** - * A doubly linked list of timers. - * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } } - */ - const scheduledTimers = { head: null, tail: null }; - - /** - * @param {() => void} cb Will be run after the timeout, if it hasn't been - * cancelled. - * @param {number} millis - * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo - */ - function runAfterTimeout(cb, millis, timerInfo) { - const cancelRid = timerInfo.cancelRid; - const sleepPromise = core.opAsync("op_sleep", millis, cancelRid); - timerInfo.promiseId = - sleepPromise[SymbolFor("Deno.core.internalPromiseId")]; - if (!timerInfo.isRef) { - core.unrefOp(timerInfo.promiseId); - } - - /** @type {ScheduledTimer} */ - const timerObject = { - millis, - cb, - resolved: false, - prev: scheduledTimers.tail, - next: null, - }; - - // Add timerObject to the end of the list. - if (scheduledTimers.tail === null) { - assert(scheduledTimers.head === null); - scheduledTimers.head = scheduledTimers.tail = timerObject; - } else { - scheduledTimers.tail.next = timerObject; - scheduledTimers.tail = timerObject; - } - - // 1. - PromisePrototypeThen( - sleepPromise, - (cancelled) => { - if (!cancelled) { - // The timer was cancelled. - removeFromScheduledTimers(timerObject); - return; - } - // 2. Wait until any invocations of this algorithm that had the same - // global and orderingIdentifier, that started before this one, and - // whose milliseconds is equal to or less than this one's, have - // completed. - // 4. Perform completionSteps. - - // IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the - // right order, whenever one resolves, we run through the scheduled - // timers list (which is in the order in which they were scheduled), and - // we call the callback for every timer which both: - // a) has resolved, and - // b) its timeout is lower than the lowest unresolved timeout found so - // far in the list. - - timerObject.resolved = true; - - let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY; - - let currentEntry = scheduledTimers.head; - while (currentEntry !== null) { - if (currentEntry.millis < lowestUnresolvedTimeout) { - if (currentEntry.resolved) { - currentEntry.cb(); - removeFromScheduledTimers(currentEntry); - } else { - lowestUnresolvedTimeout = currentEntry.millis; - } - } - - currentEntry = currentEntry.next; - } - }, - ); - } - - /** @param {ScheduledTimer} timerObj */ - function removeFromScheduledTimers(timerObj) { - if (timerObj.prev !== null) { - timerObj.prev.next = timerObj.next; - } else { - assert(scheduledTimers.head === timerObj); - scheduledTimers.head = timerObj.next; - } - if (timerObj.next !== null) { - timerObj.next.prev = timerObj.prev; - } else { - assert(scheduledTimers.tail === timerObj); - scheduledTimers.tail = timerObj.prev; - } - } - - // --------------------------------------------------------------------------- - - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } - } - - function setTimeout(callback, timeout = 0, ...args) { - checkThis(this); - if (typeof callback !== "function") { - callback = webidl.converters.DOMString(callback); - } - timeout = webidl.converters.long(timeout); - - return initializeTimer(callback, timeout, args, false); - } - - function setInterval(callback, timeout = 0, ...args) { - checkThis(this); - if (typeof callback !== "function") { - callback = webidl.converters.DOMString(callback); - } - timeout = webidl.converters.long(timeout); - - return initializeTimer(callback, timeout, args, true); - } - - function clearTimeout(id = 0) { - checkThis(this); - id = webidl.converters.long(id); - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo !== undefined) { - core.tryClose(timerInfo.cancelRid); - MapPrototypeDelete(activeTimers, id); - } - } - - function clearInterval(id = 0) { - checkThis(this); - clearTimeout(id); - } - - function refTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || timerInfo.isRef) { - return; - } - timerInfo.isRef = true; - core.refOp(timerInfo.promiseId); - } - - function unrefTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || !timerInfo.isRef) { - return; - } - timerInfo.isRef = false; +/** + * @param {() => void} cb Will be run after the timeout, if it hasn't been + * cancelled. + * @param {number} millis + * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo + */ +function runAfterTimeout(cb, millis, timerInfo) { + const cancelRid = timerInfo.cancelRid; + const sleepPromise = core.opAsync("op_sleep", millis, cancelRid); + timerInfo.promiseId = sleepPromise[SymbolFor("Deno.core.internalPromiseId")]; + if (!timerInfo.isRef) { core.unrefOp(timerInfo.promiseId); } - window.__bootstrap.timers = { - setTimeout, - setInterval, - clearTimeout, - clearInterval, - handleTimerMacrotask, - opNow, - refTimer, - unrefTimer, + /** @type {ScheduledTimer} */ + const timerObject = { + millis, + cb, + resolved: false, + prev: scheduledTimers.tail, + next: null, }; -})(this); + + // Add timerObject to the end of the list. + if (scheduledTimers.tail === null) { + assert(scheduledTimers.head === null); + scheduledTimers.head = scheduledTimers.tail = timerObject; + } else { + scheduledTimers.tail.next = timerObject; + scheduledTimers.tail = timerObject; + } + + // 1. + PromisePrototypeThen( + sleepPromise, + (cancelled) => { + if (!cancelled) { + // The timer was cancelled. + removeFromScheduledTimers(timerObject); + return; + } + // 2. Wait until any invocations of this algorithm that had the same + // global and orderingIdentifier, that started before this one, and + // whose milliseconds is equal to or less than this one's, have + // completed. + // 4. Perform completionSteps. + + // IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the + // right order, whenever one resolves, we run through the scheduled + // timers list (which is in the order in which they were scheduled), and + // we call the callback for every timer which both: + // a) has resolved, and + // b) its timeout is lower than the lowest unresolved timeout found so + // far in the list. + + timerObject.resolved = true; + + let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY; + + let currentEntry = scheduledTimers.head; + while (currentEntry !== null) { + if (currentEntry.millis < lowestUnresolvedTimeout) { + if (currentEntry.resolved) { + currentEntry.cb(); + removeFromScheduledTimers(currentEntry); + } else { + lowestUnresolvedTimeout = currentEntry.millis; + } + } + + currentEntry = currentEntry.next; + } + }, + ); +} + +/** @param {ScheduledTimer} timerObj */ +function removeFromScheduledTimers(timerObj) { + if (timerObj.prev !== null) { + timerObj.prev.next = timerObj.next; + } else { + assert(scheduledTimers.head === timerObj); + scheduledTimers.head = timerObj.next; + } + if (timerObj.next !== null) { + timerObj.next.prev = timerObj.prev; + } else { + assert(scheduledTimers.tail === timerObj); + scheduledTimers.tail = timerObj.prev; + } +} + +// --------------------------------------------------------------------------- + +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { + throw new TypeError("Illegal invocation"); + } +} + +function setTimeout(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); + } + timeout = webidl.converters.long(timeout); + + return initializeTimer(callback, timeout, args, false); +} + +function setInterval(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); + } + timeout = webidl.converters.long(timeout); + + return initializeTimer(callback, timeout, args, true); +} + +function clearTimeout(id = 0) { + checkThis(this); + id = webidl.converters.long(id); + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo !== undefined) { + core.tryClose(timerInfo.cancelRid); + MapPrototypeDelete(activeTimers, id); + } +} + +function clearInterval(id = 0) { + checkThis(this); + clearTimeout(id); +} + +function refTimer(id) { + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo === undefined || timerInfo.isRef) { + return; + } + timerInfo.isRef = true; + core.refOp(timerInfo.promiseId); +} + +function unrefTimer(id) { + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo === undefined || !timerInfo.isRef) { + return; + } + timerInfo.isRef = false; + core.unrefOp(timerInfo.promiseId); +} + +export { + clearInterval, + clearTimeout, + handleTimerMacrotask, + opNow, + refTimer, + setInterval, + setTimeout, + unrefTimer, +}; diff --git a/ext/web/03_abort_signal.js b/ext/web/03_abort_signal.js index cce1bac7e7..96757f41f9 100644 --- a/ext/web/03_abort_signal.js +++ b/ext/web/03_abort_signal.js @@ -1,200 +1,205 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // @ts-check /// -((window) => { - const webidl = window.__bootstrap.webidl; - const { Event, setIsTrusted, defineEventHandler } = window.__bootstrap.event; - const { EventTarget, listenerCount } = window.__bootstrap.eventTarget; - const { - SafeArrayIterator, - SafeSetIterator, - Set, - SetPrototypeAdd, - SetPrototypeDelete, - Symbol, - TypeError, - } = window.__bootstrap.primordials; - const { setTimeout, refTimer, unrefTimer } = window.__bootstrap.timers; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + defineEventHandler, + Event, + EventTarget, + listenerCount, + setIsTrusted, +} from "internal:ext/web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeArrayIterator, + SafeSetIterator, + Set, + SetPrototypeAdd, + SetPrototypeDelete, + Symbol, + TypeError, +} = primordials; +import { + refTimer, + setTimeout, + unrefTimer, +} from "internal:ext/web/02_timers.js"; - const add = Symbol("[[add]]"); - const signalAbort = Symbol("[[signalAbort]]"); - const remove = Symbol("[[remove]]"); - const abortReason = Symbol("[[abortReason]]"); - const abortAlgos = Symbol("[[abortAlgos]]"); - const signal = Symbol("[[signal]]"); - const timerId = Symbol("[[timerId]]"); +const add = Symbol("[[add]]"); +const signalAbort = Symbol("[[signalAbort]]"); +const remove = Symbol("[[remove]]"); +const abortReason = Symbol("[[abortReason]]"); +const abortAlgos = Symbol("[[abortAlgos]]"); +const signal = Symbol("[[signal]]"); +const timerId = Symbol("[[timerId]]"); - const illegalConstructorKey = Symbol("illegalConstructorKey"); +const illegalConstructorKey = Symbol("illegalConstructorKey"); - class AbortSignal extends EventTarget { - static abort(reason = undefined) { - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - const signal = new AbortSignal(illegalConstructorKey); - signal[signalAbort](reason); - return signal; - } - - static timeout(millis) { - const prefix = "Failed to call 'AbortSignal.timeout'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - millis = webidl.converters["unsigned long long"](millis, { - enforceRange: true, - }); - - const signal = new AbortSignal(illegalConstructorKey); - signal[timerId] = setTimeout( - () => { - signal[timerId] = null; - signal[signalAbort]( - new DOMException("Signal timed out.", "TimeoutError"), - ); - }, - millis, - ); - unrefTimer(signal[timerId]); - return signal; - } - - [add](algorithm) { - if (this.aborted) { - return; - } - if (this[abortAlgos] === null) { - this[abortAlgos] = new Set(); - } - SetPrototypeAdd(this[abortAlgos], algorithm); - } - - [signalAbort]( - reason = new DOMException("The signal has been aborted", "AbortError"), - ) { - if (this.aborted) { - return; - } - this[abortReason] = reason; - if (this[abortAlgos] !== null) { - for (const algorithm of new SafeSetIterator(this[abortAlgos])) { - algorithm(); - } - this[abortAlgos] = null; - } - const event = new Event("abort"); - setIsTrusted(event, true); - this.dispatchEvent(event); - } - - [remove](algorithm) { - this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); - } - - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - this[abortReason] = undefined; - this[abortAlgos] = null; - this[timerId] = null; - this[webidl.brand] = webidl.brand; - } - - get aborted() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason] !== undefined; - } - - get reason() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason]; - } - - throwIfAborted() { - webidl.assertBranded(this, AbortSignalPrototype); - if (this[abortReason] !== undefined) { - throw this[abortReason]; - } - } - - // `addEventListener` and `removeEventListener` have to be overriden in - // order to have the timer block the event loop while there are listeners. - // `[add]` and `[remove]` don't ref and unref the timer because they can - // only be used by Deno internals, which use it to essentially cancel async - // ops which would block the event loop. - addEventListener(...args) { - super.addEventListener(...new SafeArrayIterator(args)); - if (this[timerId] !== null && listenerCount(this, "abort") > 0) { - refTimer(this[timerId]); - } - } - - removeEventListener(...args) { - super.removeEventListener(...new SafeArrayIterator(args)); - if (this[timerId] !== null && listenerCount(this, "abort") === 0) { - unrefTimer(this[timerId]); - } - } - } - defineEventHandler(AbortSignal.prototype, "abort"); - - webidl.configurePrototype(AbortSignal); - const AbortSignalPrototype = AbortSignal.prototype; - - class AbortController { - [signal] = new AbortSignal(illegalConstructorKey); - - constructor() { - this[webidl.brand] = webidl.brand; - } - - get signal() { - webidl.assertBranded(this, AbortControllerPrototype); - return this[signal]; - } - - abort(reason) { - webidl.assertBranded(this, AbortControllerPrototype); - this[signal][signalAbort](reason); +class AbortSignal extends EventTarget { + static abort(reason = undefined) { + if (reason !== undefined) { + reason = webidl.converters.any(reason); } + const signal = new AbortSignal(illegalConstructorKey); + signal[signalAbort](reason); + return signal; } - webidl.configurePrototype(AbortController); - const AbortControllerPrototype = AbortController.prototype; + static timeout(millis) { + const prefix = "Failed to call 'AbortSignal.timeout'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + millis = webidl.converters["unsigned long long"](millis, { + enforceRange: true, + }); - webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( - "AbortSignal", - AbortSignal.prototype, - ); - - function newSignal() { - return new AbortSignal(illegalConstructorKey); + const signal = new AbortSignal(illegalConstructorKey); + signal[timerId] = setTimeout( + () => { + signal[timerId] = null; + signal[signalAbort]( + new DOMException("Signal timed out.", "TimeoutError"), + ); + }, + millis, + ); + unrefTimer(signal[timerId]); + return signal; } - function follow(followingSignal, parentSignal) { - if (followingSignal.aborted) { + [add](algorithm) { + if (this.aborted) { return; } - if (parentSignal.aborted) { - followingSignal[signalAbort](parentSignal.reason); - } else { - parentSignal[add](() => - followingSignal[signalAbort](parentSignal.reason) - ); + if (this[abortAlgos] === null) { + this[abortAlgos] = new Set(); + } + SetPrototypeAdd(this[abortAlgos], algorithm); + } + + [signalAbort]( + reason = new DOMException("The signal has been aborted", "AbortError"), + ) { + if (this.aborted) { + return; + } + this[abortReason] = reason; + if (this[abortAlgos] !== null) { + for (const algorithm of new SafeSetIterator(this[abortAlgos])) { + algorithm(); + } + this[abortAlgos] = null; + } + const event = new Event("abort"); + setIsTrusted(event, true); + this.dispatchEvent(event); + } + + [remove](algorithm) { + this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); + } + + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + this[abortReason] = undefined; + this[abortAlgos] = null; + this[timerId] = null; + this[webidl.brand] = webidl.brand; + } + + get aborted() { + webidl.assertBranded(this, AbortSignalPrototype); + return this[abortReason] !== undefined; + } + + get reason() { + webidl.assertBranded(this, AbortSignalPrototype); + return this[abortReason]; + } + + throwIfAborted() { + webidl.assertBranded(this, AbortSignalPrototype); + if (this[abortReason] !== undefined) { + throw this[abortReason]; } } - window.__bootstrap.abortSignal = { - AbortSignal, - AbortController, - AbortSignalPrototype, - add, - signalAbort, - remove, - follow, - newSignal, - }; -})(this); + // `addEventListener` and `removeEventListener` have to be overriden in + // order to have the timer block the event loop while there are listeners. + // `[add]` and `[remove]` don't ref and unref the timer because they can + // only be used by Deno internals, which use it to essentially cancel async + // ops which would block the event loop. + addEventListener(...args) { + super.addEventListener(...new SafeArrayIterator(args)); + if (this[timerId] !== null && listenerCount(this, "abort") > 0) { + refTimer(this[timerId]); + } + } + + removeEventListener(...args) { + super.removeEventListener(...new SafeArrayIterator(args)); + if (this[timerId] !== null && listenerCount(this, "abort") === 0) { + unrefTimer(this[timerId]); + } + } +} +defineEventHandler(AbortSignal.prototype, "abort"); + +webidl.configurePrototype(AbortSignal); +const AbortSignalPrototype = AbortSignal.prototype; + +class AbortController { + [signal] = new AbortSignal(illegalConstructorKey); + + constructor() { + this[webidl.brand] = webidl.brand; + } + + get signal() { + webidl.assertBranded(this, AbortControllerPrototype); + return this[signal]; + } + + abort(reason) { + webidl.assertBranded(this, AbortControllerPrototype); + this[signal][signalAbort](reason); + } +} + +webidl.configurePrototype(AbortController); +const AbortControllerPrototype = AbortController.prototype; + +webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( + "AbortSignal", + AbortSignal.prototype, +); + +function newSignal() { + return new AbortSignal(illegalConstructorKey); +} + +function follow(followingSignal, parentSignal) { + if (followingSignal.aborted) { + return; + } + if (parentSignal.aborted) { + followingSignal[signalAbort](parentSignal.reason); + } else { + parentSignal[add](() => followingSignal[signalAbort](parentSignal.reason)); + } +} + +export { + AbortController, + AbortSignal, + AbortSignalPrototype, + add, + follow, + newSignal, + remove, + signalAbort, +}; diff --git a/ext/web/04_global_interfaces.js b/ext/web/04_global_interfaces.js index 840f93ba9c..6a42968dbe 100644 --- a/ext/web/04_global_interfaces.js +++ b/ext/web/04_global_interfaces.js @@ -1,79 +1,83 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // @ts-check /// -((window) => { - const { EventTarget } = window.__bootstrap.eventTarget; - const { - Symbol, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; +import { EventTarget } from "internal:ext/web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Symbol, + SymbolToStringTag, + TypeError, +} = primordials; - const illegalConstructorKey = Symbol("illegalConstructorKey"); +const illegalConstructorKey = Symbol("illegalConstructorKey"); - class Window extends EventTarget { - constructor(key = null) { - if (key !== illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - } - - get [SymbolToStringTag]() { - return "Window"; +class Window extends EventTarget { + constructor(key = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); } - class WorkerGlobalScope extends EventTarget { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - } + get [SymbolToStringTag]() { + return "Window"; + } +} - get [SymbolToStringTag]() { - return "WorkerGlobalScope"; +class WorkerGlobalScope extends EventTarget { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); } - class DedicatedWorkerGlobalScope extends WorkerGlobalScope { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - } + get [SymbolToStringTag]() { + return "WorkerGlobalScope"; + } +} - get [SymbolToStringTag]() { - return "DedicatedWorkerGlobalScope"; +class DedicatedWorkerGlobalScope extends WorkerGlobalScope { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); } - window.__bootstrap.globalInterfaces = { - DedicatedWorkerGlobalScope, - Window, - WorkerGlobalScope, - dedicatedWorkerGlobalScopeConstructorDescriptor: { - configurable: true, - enumerable: false, - value: DedicatedWorkerGlobalScope, - writable: true, - }, - windowConstructorDescriptor: { - configurable: true, - enumerable: false, - value: Window, - writable: true, - }, - workerGlobalScopeConstructorDescriptor: { - configurable: true, - enumerable: false, - value: WorkerGlobalScope, - writable: true, - }, - }; -})(this); + get [SymbolToStringTag]() { + return "DedicatedWorkerGlobalScope"; + } +} + +const dedicatedWorkerGlobalScopeConstructorDescriptor = { + configurable: true, + enumerable: false, + value: DedicatedWorkerGlobalScope, + writable: true, +}; + +const windowConstructorDescriptor = { + configurable: true, + enumerable: false, + value: Window, + writable: true, +}; + +const workerGlobalScopeConstructorDescriptor = { + configurable: true, + enumerable: false, + value: WorkerGlobalScope, + writable: true, +}; + +export { + DedicatedWorkerGlobalScope, + dedicatedWorkerGlobalScopeConstructorDescriptor, + Window, + windowConstructorDescriptor, + WorkerGlobalScope, + workerGlobalScopeConstructorDescriptor, +}; diff --git a/ext/web/05_base64.js b/ext/web/05_base64.js index dac366ca00..9f11ec97c3 100644 --- a/ext/web/05_base64.js +++ b/ext/web/05_base64.js @@ -6,68 +6,62 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ObjectPrototypeIsPrototypeOf, + TypeErrorPrototype, +} = primordials; -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - const { - ObjectPrototypeIsPrototypeOf, - TypeErrorPrototype, - } = window.__bootstrap.primordials; - - /** - * @param {string} data - * @returns {string} - */ - function atob(data) { - const prefix = "Failed to execute 'atob'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - data = webidl.converters.DOMString(data, { - prefix, - context: "Argument 1", - }); - try { - return ops.op_base64_atob(data); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException( - "Failed to decode base64: invalid character", - "InvalidCharacterError", - ); - } - throw e; +/** + * @param {string} data + * @returns {string} + */ +function atob(data) { + const prefix = "Failed to execute 'atob'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + data = webidl.converters.DOMString(data, { + prefix, + context: "Argument 1", + }); + try { + return ops.op_base64_atob(data); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException( + "Failed to decode base64: invalid character", + "InvalidCharacterError", + ); } + throw e; } +} - /** - * @param {string} data - * @returns {string} - */ - function btoa(data) { - const prefix = "Failed to execute 'btoa'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - data = webidl.converters.DOMString(data, { - prefix, - context: "Argument 1", - }); - try { - return ops.op_base64_btoa(data); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException( - "The string to be encoded contains characters outside of the Latin1 range.", - "InvalidCharacterError", - ); - } - throw e; +/** + * @param {string} data + * @returns {string} + */ +function btoa(data) { + const prefix = "Failed to execute 'btoa'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + data = webidl.converters.DOMString(data, { + prefix, + context: "Argument 1", + }); + try { + return ops.op_base64_btoa(data); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException( + "The string to be encoded contains characters outside of the Latin1 range.", + "InvalidCharacterError", + ); } + throw e; } +} - window.__bootstrap.base64 = { - atob, - btoa, - }; -})(globalThis); +export { atob, btoa }; diff --git a/ext/web/06_streams.js b/ext/web/06_streams.js index 1bad4f314a..a88b608936 100644 --- a/ext/web/06_streams.js +++ b/ext/web/06_streams.js @@ -5,3092 +5,3199 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const webidl = window.__bootstrap.webidl; - const { add, remove, signalAbort, newSignal, AbortSignalPrototype } = - window.__bootstrap.abortSignal; - const { - ArrayBuffer, - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeShift, - AsyncGeneratorPrototype, - BigInt64ArrayPrototype, - BigUint64ArrayPrototype, - DataView, - FinalizationRegistry, - Int8ArrayPrototype, - Int16ArrayPrototype, - Int32ArrayPrototype, - NumberIsInteger, - NumberIsNaN, - MathMin, - ObjectCreate, - ObjectDefineProperties, - ObjectDefineProperty, - ObjectGetPrototypeOf, - ObjectPrototype, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Promise, - PromisePrototypeCatch, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - queueMicrotask, - RangeError, - ReflectHas, - SafePromiseAll, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - Symbol, - SymbolAsyncIterator, - SymbolFor, - TypeError, - TypedArrayPrototypeSet, - Uint8Array, - Uint8ArrayPrototype, - Uint16ArrayPrototype, - Uint32ArrayPrototype, - Uint8ClampedArrayPrototype, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeHas, - WeakMapPrototypeSet, - } = globalThis.__bootstrap.primordials; - const consoleInternal = window.__bootstrap.console; - const ops = core.ops; - const { AssertionError, assert } = window.__bootstrap.infra; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + AbortSignalPrototype, + add, + newSignal, + remove, + signalAbort, +} from "internal:ext/web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBuffer, + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + AsyncGeneratorPrototype, + BigInt64ArrayPrototype, + BigUint64ArrayPrototype, + DataView, + FinalizationRegistry, + Int8ArrayPrototype, + Int16ArrayPrototype, + Int32ArrayPrototype, + NumberIsInteger, + NumberIsNaN, + MathMin, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectPrototype, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Promise, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + queueMicrotask, + RangeError, + ReflectHas, + SafePromiseAll, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + Symbol, + SymbolAsyncIterator, + SymbolFor, + TypeError, + TypedArrayPrototypeSet, + Uint8Array, + Uint8ArrayPrototype, + Uint16ArrayPrototype, + Uint32ArrayPrototype, + Uint8ClampedArrayPrototype, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeHas, + WeakMapPrototypeSet, +} = primordials; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +import { assert, AssertionError } from "internal:ext/web/00_infra.js"; - /** @template T */ - class Deferred { - /** @type {Promise} */ - #promise; - /** @type {(reject?: any) => void} */ - #reject; - /** @type {(value: T | PromiseLike) => void} */ - #resolve; - /** @type {"pending" | "fulfilled"} */ - #state = "pending"; +/** @template T */ +class Deferred { + /** @type {Promise} */ + #promise; + /** @type {(reject?: any) => void} */ + #reject; + /** @type {(value: T | PromiseLike) => void} */ + #resolve; + /** @type {"pending" | "fulfilled"} */ + #state = "pending"; - constructor() { - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - }); + constructor() { + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); + } + + /** @returns {Promise} */ + get promise() { + return this.#promise; + } + + /** @returns {"pending" | "fulfilled"} */ + get state() { + return this.#state; + } + + /** @param {any=} reason */ + reject(reason) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; } + this.#state = "fulfilled"; + this.#reject(reason); + } - /** @returns {Promise} */ - get promise() { - return this.#promise; - } - - /** @returns {"pending" | "fulfilled"} */ - get state() { - return this.#state; - } - - /** @param {any=} reason */ - reject(reason) { - // already settled promises are a no-op - if (this.#state !== "pending") { - return; - } - this.#state = "fulfilled"; - this.#reject(reason); - } - - /** @param {T | PromiseLike} value */ - resolve(value) { - // already settled promises are a no-op - if (this.#state !== "pending") { - return; - } - this.#state = "fulfilled"; - this.#resolve(value); + /** @param {T | PromiseLike} value */ + resolve(value) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; } + this.#state = "fulfilled"; + this.#resolve(value); } +} - /** - * @template T - * @param {T | PromiseLike} value - * @returns {Promise} - */ - function resolvePromiseWith(value) { - return new Promise((resolve) => resolve(value)); +/** + * @template T + * @param {T | PromiseLike} value + * @returns {Promise} + */ +function resolvePromiseWith(value) { + return new Promise((resolve) => resolve(value)); +} + +/** @param {any} e */ +function rethrowAssertionErrorRejection(e) { + if (e && ObjectPrototypeIsPrototypeOf(AssertionError.prototype, e)) { + queueMicrotask(() => { + console.error(`Internal Error: ${e.stack}`); + }); } +} - /** @param {any} e */ - function rethrowAssertionErrorRejection(e) { - if (e && ObjectPrototypeIsPrototypeOf(AssertionError.prototype, e)) { - queueMicrotask(() => { - console.error(`Internal Error: ${e.stack}`); - }); - } +/** @param {Promise} promise */ +function setPromiseIsHandledToTrue(promise) { + PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} fulfillmentHandler + * @param {(reason: any) => TResult2 | PromiseLike=} rejectionHandler + * @returns {Promise} + */ +function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { + return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); +} + +/** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onFulfilled + * @returns {void} + */ +function uponFulfillment(promise, onFulfilled) { + uponPromise(promise, onFulfilled); +} + +/** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onRejected + * @returns {void} + */ +function uponRejection(promise, onRejected) { + uponPromise(promise, undefined, onRejected); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} onFulfilled + * @param {(reason: any) => TResult2 | PromiseLike=} onRejected + * @returns {void} + */ +function uponPromise(promise, onFulfilled, onRejected) { + PromisePrototypeThen( + PromisePrototypeThen(promise, onFulfilled, onRejected), + undefined, + rethrowAssertionErrorRejection, + ); +} + +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function isDetachedBuffer(O) { + return O.byteLength === 0 && ops.op_arraybuffer_was_detached(O); +} + +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function canTransferArrayBuffer(O) { + assert(typeof O === "object"); + assert( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, O) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O), + ); + if (isDetachedBuffer(O)) { + return false; } + // TODO(@crowlKats): 4. If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false. + return true; +} - /** @param {Promise} promise */ - function setPromiseIsHandledToTrue(promise) { - PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); - } +/** + * @param {ArrayBufferLike} O + * @returns {ArrayBufferLike} + */ +function transferArrayBuffer(O) { + return ops.op_transfer_arraybuffer(O); +} - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise} promise - * @param {(value: T) => TResult1 | PromiseLike} fulfillmentHandler - * @param {(reason: any) => TResult2 | PromiseLike=} rejectionHandler - * @returns {Promise} - */ - function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { - return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); - } +/** + * @param {ArrayBufferView} O + * @returns {Uint8Array} + */ +function cloneAsUint8Array(O) { + assert(typeof O === "object"); + assert(ArrayBufferIsView(O)); + assert(!isDetachedBuffer(O.buffer)); + const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength); + return new Uint8Array(buffer); +} - /** - * @template T - * @template TResult - * @param {Promise} promise - * @param {(value: T) => TResult | PromiseLike} onFulfilled - * @returns {void} - */ - function uponFulfillment(promise, onFulfilled) { - uponPromise(promise, onFulfilled); - } +const _abortAlgorithm = Symbol("[[abortAlgorithm]]"); +const _abortSteps = Symbol("[[AbortSteps]]"); +const _autoAllocateChunkSize = Symbol("[[autoAllocateChunkSize]]"); +const _backpressure = Symbol("[[backpressure]]"); +const _backpressureChangePromise = Symbol("[[backpressureChangePromise]]"); +const _byobRequest = Symbol("[[byobRequest]]"); +const _cancelAlgorithm = Symbol("[[cancelAlgorithm]]"); +const _cancelSteps = Symbol("[[CancelSteps]]"); +const _close = Symbol("close sentinel"); +const _closeAlgorithm = Symbol("[[closeAlgorithm]]"); +const _closedPromise = Symbol("[[closedPromise]]"); +const _closeRequest = Symbol("[[closeRequest]]"); +const _closeRequested = Symbol("[[closeRequested]]"); +const _controller = Symbol("[[controller]]"); +const _detached = Symbol("[[Detached]]"); +const _disturbed = Symbol("[[disturbed]]"); +const _errorSteps = Symbol("[[ErrorSteps]]"); +const _flushAlgorithm = Symbol("[[flushAlgorithm]]"); +const _globalObject = Symbol("[[globalObject]]"); +const _highWaterMark = Symbol("[[highWaterMark]]"); +const _inFlightCloseRequest = Symbol("[[inFlightCloseRequest]]"); +const _inFlightWriteRequest = Symbol("[[inFlightWriteRequest]]"); +const _pendingAbortRequest = Symbol("[pendingAbortRequest]"); +const _pendingPullIntos = Symbol("[[pendingPullIntos]]"); +const _preventCancel = Symbol("[[preventCancel]]"); +const _pullAgain = Symbol("[[pullAgain]]"); +const _pullAlgorithm = Symbol("[[pullAlgorithm]]"); +const _pulling = Symbol("[[pulling]]"); +const _pullSteps = Symbol("[[PullSteps]]"); +const _releaseSteps = Symbol("[[ReleaseSteps]]"); +const _queue = Symbol("[[queue]]"); +const _queueTotalSize = Symbol("[[queueTotalSize]]"); +const _readable = Symbol("[[readable]]"); +const _reader = Symbol("[[reader]]"); +const _readRequests = Symbol("[[readRequests]]"); +const _readIntoRequests = Symbol("[[readIntoRequests]]"); +const _readyPromise = Symbol("[[readyPromise]]"); +const _signal = Symbol("[[signal]]"); +const _started = Symbol("[[started]]"); +const _state = Symbol("[[state]]"); +const _storedError = Symbol("[[storedError]]"); +const _strategyHWM = Symbol("[[strategyHWM]]"); +const _strategySizeAlgorithm = Symbol("[[strategySizeAlgorithm]]"); +const _stream = Symbol("[[stream]]"); +const _transformAlgorithm = Symbol("[[transformAlgorithm]]"); +const _view = Symbol("[[view]]"); +const _writable = Symbol("[[writable]]"); +const _writeAlgorithm = Symbol("[[writeAlgorithm]]"); +const _writer = Symbol("[[writer]]"); +const _writeRequests = Symbol("[[writeRequests]]"); - /** - * @template T - * @template TResult - * @param {Promise} promise - * @param {(value: T) => TResult | PromiseLike} onRejected - * @returns {void} - */ - function uponRejection(promise, onRejected) { - uponPromise(promise, undefined, onRejected); - } +/** + * @template R + * @param {ReadableStream} stream + * @returns {ReadableStreamDefaultReader} + */ +function acquireReadableStreamDefaultReader(stream) { + return new ReadableStreamDefaultReader(stream); +} - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise} promise - * @param {(value: T) => TResult1 | PromiseLike} onFulfilled - * @param {(reason: any) => TResult2 | PromiseLike=} onRejected - * @returns {void} - */ - function uponPromise(promise, onFulfilled, onRejected) { - PromisePrototypeThen( - PromisePrototypeThen(promise, onFulfilled, onRejected), - undefined, - rethrowAssertionErrorRejection, - ); - } +/** + * @template R + * @param {ReadableStream} stream + * @returns {ReadableStreamBYOBReader} + */ +function acquireReadableStreamBYOBReader(stream) { + const reader = webidl.createBranded(ReadableStreamBYOBReader); + setUpReadableStreamBYOBReader(reader, stream); + return reader; +} - /** - * @param {ArrayBufferLike} O - * @returns {boolean} - */ - function isDetachedBuffer(O) { - return O.byteLength === 0 && ops.op_arraybuffer_was_detached(O); - } +/** + * @template W + * @param {WritableStream} stream + * @returns {WritableStreamDefaultWriter} + */ +function acquireWritableStreamDefaultWriter(stream) { + return new WritableStreamDefaultWriter(stream); +} - /** - * @param {ArrayBufferLike} O - * @returns {boolean} - */ - function canTransferArrayBuffer(O) { - assert(typeof O === "object"); - assert( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, O) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O), - ); - if (isDetachedBuffer(O)) { - return false; - } - // TODO(@crowlKats): 4. If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false. - return true; - } - - /** - * @param {ArrayBufferLike} O - * @returns {ArrayBufferLike} - */ - function transferArrayBuffer(O) { - return ops.op_transfer_arraybuffer(O); - } - - /** - * @param {ArrayBufferView} O - * @returns {Uint8Array} - */ - function cloneAsUint8Array(O) { - assert(typeof O === "object"); - assert(ArrayBufferIsView(O)); - assert(!isDetachedBuffer(O.buffer)); - const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength); - return new Uint8Array(buffer); - } - - const _abortAlgorithm = Symbol("[[abortAlgorithm]]"); - const _abortSteps = Symbol("[[AbortSteps]]"); - const _autoAllocateChunkSize = Symbol("[[autoAllocateChunkSize]]"); - const _backpressure = Symbol("[[backpressure]]"); - const _backpressureChangePromise = Symbol("[[backpressureChangePromise]]"); - const _byobRequest = Symbol("[[byobRequest]]"); - const _cancelAlgorithm = Symbol("[[cancelAlgorithm]]"); - const _cancelSteps = Symbol("[[CancelSteps]]"); - const _close = Symbol("close sentinel"); - const _closeAlgorithm = Symbol("[[closeAlgorithm]]"); - const _closedPromise = Symbol("[[closedPromise]]"); - const _closeRequest = Symbol("[[closeRequest]]"); - const _closeRequested = Symbol("[[closeRequested]]"); - const _controller = Symbol("[[controller]]"); - const _detached = Symbol("[[Detached]]"); - const _disturbed = Symbol("[[disturbed]]"); - const _errorSteps = Symbol("[[ErrorSteps]]"); - const _flushAlgorithm = Symbol("[[flushAlgorithm]]"); - const _globalObject = Symbol("[[globalObject]]"); - const _highWaterMark = Symbol("[[highWaterMark]]"); - const _inFlightCloseRequest = Symbol("[[inFlightCloseRequest]]"); - const _inFlightWriteRequest = Symbol("[[inFlightWriteRequest]]"); - const _pendingAbortRequest = Symbol("[pendingAbortRequest]"); - const _pendingPullIntos = Symbol("[[pendingPullIntos]]"); - const _preventCancel = Symbol("[[preventCancel]]"); - const _pullAgain = Symbol("[[pullAgain]]"); - const _pullAlgorithm = Symbol("[[pullAlgorithm]]"); - const _pulling = Symbol("[[pulling]]"); - const _pullSteps = Symbol("[[PullSteps]]"); - const _releaseSteps = Symbol("[[ReleaseSteps]]"); - const _queue = Symbol("[[queue]]"); - const _queueTotalSize = Symbol("[[queueTotalSize]]"); - const _readable = Symbol("[[readable]]"); - const _reader = Symbol("[[reader]]"); - const _readRequests = Symbol("[[readRequests]]"); - const _readIntoRequests = Symbol("[[readIntoRequests]]"); - const _readyPromise = Symbol("[[readyPromise]]"); - const _signal = Symbol("[[signal]]"); - const _started = Symbol("[[started]]"); - const _state = Symbol("[[state]]"); - const _storedError = Symbol("[[storedError]]"); - const _strategyHWM = Symbol("[[strategyHWM]]"); - const _strategySizeAlgorithm = Symbol("[[strategySizeAlgorithm]]"); - const _stream = Symbol("[[stream]]"); - const _transformAlgorithm = Symbol("[[transformAlgorithm]]"); - const _view = Symbol("[[view]]"); - const _writable = Symbol("[[writable]]"); - const _writeAlgorithm = Symbol("[[writeAlgorithm]]"); - const _writer = Symbol("[[writer]]"); - const _writeRequests = Symbol("[[writeRequests]]"); - - /** - * @template R - * @param {ReadableStream} stream - * @returns {ReadableStreamDefaultReader} - */ - function acquireReadableStreamDefaultReader(stream) { - return new ReadableStreamDefaultReader(stream); - } - - /** - * @template R - * @param {ReadableStream} stream - * @returns {ReadableStreamBYOBReader} - */ - function acquireReadableStreamBYOBReader(stream) { - const reader = webidl.createBranded(ReadableStreamBYOBReader); - setUpReadableStreamBYOBReader(reader, stream); - return reader; - } - - /** - * @template W - * @param {WritableStream} stream - * @returns {WritableStreamDefaultWriter} - */ - function acquireWritableStreamDefaultWriter(stream) { - return new WritableStreamDefaultWriter(stream); - } - - /** - * @template R - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number=} highWaterMark - * @param {((chunk: R) => number)=} sizeAlgorithm - * @returns {ReadableStream} - */ - function createReadableStream( +/** + * @template R + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number=} highWaterMark + * @param {((chunk: R) => number)=} sizeAlgorithm + * @returns {ReadableStream} + */ +function createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark = 1, + sizeAlgorithm = () => 1, +) { + assert(isNonNegativeNumber(highWaterMark)); + /** @type {ReadableStream} */ + const stream = webidl.createBranded(ReadableStream); + initializeReadableStream(stream); + const controller = webidl.createBranded(ReadableStreamDefaultController); + setUpReadableStreamDefaultController( + stream, + controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, - highWaterMark = 1, - sizeAlgorithm = () => 1, - ) { - assert(isNonNegativeNumber(highWaterMark)); - /** @type {ReadableStream} */ - const stream = webidl.createBranded(ReadableStream); - initializeReadableStream(stream); - const controller = webidl.createBranded(ReadableStreamDefaultController); - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - return stream; - } + highWaterMark, + sizeAlgorithm, + ); + return stream; +} - /** - * @template W - * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm - * @param {(chunk: W) => Promise} writeAlgorithm - * @param {() => Promise} closeAlgorithm - * @param {(reason: any) => Promise} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - * @returns {WritableStream} - */ - function createWritableStream( +/** + * @template W + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + * @returns {WritableStream} + */ +function createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(isNonNegativeNumber(highWaterMark)); + const stream = webidl.createBranded(WritableStream); + initializeWritableStream(stream); + const controller = webidl.createBranded(WritableStreamDefaultController); + setUpWritableStreamDefaultController( + stream, + controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm, - ) { - assert(isNonNegativeNumber(highWaterMark)); - const stream = webidl.createBranded(WritableStream); - initializeWritableStream(stream); - const controller = webidl.createBranded(WritableStreamDefaultController); - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, + ); + return stream; +} + +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T} + */ +function dequeueValue(container) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + assert(container[_queue].length); + const valueWithSize = ArrayPrototypeShift(container[_queue]); + container[_queueTotalSize] -= valueWithSize.size; + if (container[_queueTotalSize] < 0) { + container[_queueTotalSize] = 0; + } + return valueWithSize.value; +} + +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @param {T} value + * @param {number} size + * @returns {void} + */ +function enqueueValueWithSize(container, value, size) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + if (isNonNegativeNumber(size) === false) { + throw RangeError("chunk size isn't a positive number"); + } + if (size === Infinity) { + throw RangeError("chunk size is invalid"); + } + ArrayPrototypePush(container[_queue], { value, size }); + container[_queueTotalSize] += size; +} + +/** + * @param {QueuingStrategy} strategy + * @param {number} defaultHWM + */ +function extractHighWaterMark(strategy, defaultHWM) { + if (strategy.highWaterMark === undefined) { + return defaultHWM; + } + const highWaterMark = strategy.highWaterMark; + if (NumberIsNaN(highWaterMark) || highWaterMark < 0) { + throw RangeError( + `Expected highWaterMark to be a positive number or Infinity, got "${highWaterMark}".`, ); - return stream; } + return highWaterMark; +} - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @returns {T} - */ - function dequeueValue(container) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), +/** + * @template T + * @param {QueuingStrategy} strategy + * @return {(chunk: T) => number} + */ +function extractSizeAlgorithm(strategy) { + if (strategy.size === undefined) { + return () => 1; + } + return (chunk) => + webidl.invokeCallbackFunction( + strategy.size, + [chunk], + undefined, + webidl.converters["unrestricted double"], + { prefix: "Failed to call `sizeAlgorithm`" }, ); - assert(container[_queue].length); - const valueWithSize = ArrayPrototypeShift(container[_queue]); - container[_queueTotalSize] -= valueWithSize.size; - if (container[_queueTotalSize] < 0) { - container[_queueTotalSize] = 0; - } - return valueWithSize.value; - } +} - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @param {T} value - * @param {number} size - * @returns {void} - */ - function enqueueValueWithSize(container, value, size) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), - ); - if (isNonNegativeNumber(size) === false) { - throw RangeError("chunk size isn't a positive number"); - } - if (size === Infinity) { - throw RangeError("chunk size is invalid"); - } - ArrayPrototypePush(container[_queue], { value, size }); - container[_queueTotalSize] += size; - } - - /** - * @param {QueuingStrategy} strategy - * @param {number} defaultHWM - */ - function extractHighWaterMark(strategy, defaultHWM) { - if (strategy.highWaterMark === undefined) { - return defaultHWM; - } - const highWaterMark = strategy.highWaterMark; - if (NumberIsNaN(highWaterMark) || highWaterMark < 0) { - throw RangeError( - `Expected highWaterMark to be a positive number or Infinity, got "${highWaterMark}".`, - ); - } - return highWaterMark; - } - - /** - * @template T - * @param {QueuingStrategy} strategy - * @return {(chunk: T) => number} - */ - function extractSizeAlgorithm(strategy) { - if (strategy.size === undefined) { - return () => 1; - } - return (chunk) => - webidl.invokeCallbackFunction( - strategy.size, - [chunk], - undefined, - webidl.converters["unrestricted double"], - { prefix: "Failed to call `sizeAlgorithm`" }, - ); - } - - /** - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @returns {ReadableStream} - */ - function createReadableByteStream( +/** + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @returns {ReadableStream} + */ +function createReadableByteStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, +) { + const stream = webidl.createBranded(ReadableStream); + initializeReadableStream(stream); + const controller = webidl.createBranded(ReadableByteStreamController); + setUpReadableByteStreamController( + stream, + controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, - ) { - const stream = webidl.createBranded(ReadableStream); - initializeReadableStream(stream); - const controller = webidl.createBranded(ReadableByteStreamController); - setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - 0, - undefined, - ); - return stream; + 0, + undefined, + ); + return stream; +} + +/** + * @param {ReadableStream} stream + * @returns {void} + */ +function initializeReadableStream(stream) { + stream[_state] = "readable"; + stream[_reader] = stream[_storedError] = undefined; + stream[_disturbed] = false; +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Deferred} startPromise + * @param {number} writableHighWaterMark + * @param {(chunk: I) => number} writableSizeAlgorithm + * @param {number} readableHighWaterMark + * @param {(chunk: O) => number} readableSizeAlgorithm + */ +function initializeTransformStream( + stream, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, +) { + function startAlgorithm() { + return startPromise.promise; } - /** - * @param {ReadableStream} stream - * @returns {void} - */ - function initializeReadableStream(stream) { - stream[_state] = "readable"; - stream[_reader] = stream[_storedError] = undefined; - stream[_disturbed] = false; + function writeAlgorithm(chunk) { + return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); } - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {Deferred} startPromise - * @param {number} writableHighWaterMark - * @param {(chunk: I) => number} writableSizeAlgorithm - * @param {number} readableHighWaterMark - * @param {(chunk: O) => number} readableSizeAlgorithm - */ - function initializeTransformStream( - stream, - startPromise, + function abortAlgorithm(reason) { + return transformStreamDefaultSinkAbortAlgorithm(stream, reason); + } + + function closeAlgorithm() { + return transformStreamDefaultSinkCloseAlgorithm(stream); + } + + stream[_writable] = createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm, + ); + + function pullAlgorithm() { + return transformStreamDefaultSourcePullAlgorithm(stream); + } + + function cancelAlgorithm(reason) { + transformStreamErrorWritableAndUnblockWrite(stream, reason); + return resolvePromiseWith(undefined); + } + + stream[_readable] = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm, - ) { - function startAlgorithm() { - return startPromise.promise; - } + ); - function writeAlgorithm(chunk) { - return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); - } + stream[_backpressure] = stream[_backpressureChangePromise] = undefined; + transformStreamSetBackpressure(stream, true); + stream[_controller] = undefined; +} - function abortAlgorithm(reason) { - return transformStreamDefaultSinkAbortAlgorithm(stream, reason); - } +/** @param {WritableStream} stream */ +function initializeWritableStream(stream) { + stream[_state] = "writable"; + stream[_storedError] = + stream[_writer] = + stream[_controller] = + stream[_inFlightWriteRequest] = + stream[_closeRequest] = + stream[_inFlightCloseRequest] = + stream[_pendingAbortRequest] = + undefined; + stream[_writeRequests] = []; + stream[_backpressure] = false; +} - function closeAlgorithm() { - return transformStreamDefaultSinkCloseAlgorithm(stream); - } - - stream[_writable] = createWritableStream( - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - writableHighWaterMark, - writableSizeAlgorithm, - ); - - function pullAlgorithm() { - return transformStreamDefaultSourcePullAlgorithm(stream); - } - - function cancelAlgorithm(reason) { - transformStreamErrorWritableAndUnblockWrite(stream, reason); - return resolvePromiseWith(undefined); - } - - stream[_readable] = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ); - - stream[_backpressure] = stream[_backpressureChangePromise] = undefined; - transformStreamSetBackpressure(stream, true); - stream[_controller] = undefined; +/** + * @param {unknown} v + * @returns {v is number} + */ +function isNonNegativeNumber(v) { + if (typeof v !== "number") { + return false; } - - /** @param {WritableStream} stream */ - function initializeWritableStream(stream) { - stream[_state] = "writable"; - stream[_storedError] = - stream[_writer] = - stream[_controller] = - stream[_inFlightWriteRequest] = - stream[_closeRequest] = - stream[_inFlightCloseRequest] = - stream[_pendingAbortRequest] = - undefined; - stream[_writeRequests] = []; - stream[_backpressure] = false; + if (NumberIsNaN(v)) { + return false; } - - /** - * @param {unknown} v - * @returns {v is number} - */ - function isNonNegativeNumber(v) { - if (typeof v !== "number") { - return false; - } - if (NumberIsNaN(v)) { - return false; - } - if (v < 0) { - return false; - } - return true; + if (v < 0) { + return false; } + return true; +} - /** - * @param {unknown} value - * @returns {value is ReadableStream} - */ - function isReadableStream(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _controller)); +/** + * @param {unknown} value + * @returns {value is ReadableStream} + */ +function isReadableStream(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _controller)); +} + +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function isReadableStreamLocked(stream) { + if (stream[_reader] === undefined) { + return false; } + return true; +} - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamLocked(stream) { - if (stream[_reader] === undefined) { - return false; - } - return true; - } +/** + * @param {unknown} value + * @returns {value is ReadableStreamDefaultReader} + */ +function isReadableStreamDefaultReader(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _readRequests)); +} - /** - * @param {unknown} value - * @returns {value is ReadableStreamDefaultReader} - */ - function isReadableStreamDefaultReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readRequests)); - } +/** + * @param {unknown} value + * @returns {value is ReadableStreamBYOBReader} + */ +function isReadableStreamBYOBReader(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _readIntoRequests)); +} - /** - * @param {unknown} value - * @returns {value is ReadableStreamBYOBReader} - */ - function isReadableStreamBYOBReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readIntoRequests)); - } +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function isReadableStreamDisturbed(stream) { + assert(isReadableStream(stream)); + return stream[_disturbed]; +} - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamDisturbed(stream) { - assert(isReadableStream(stream)); - return stream[_disturbed]; - } +const DEFAULT_CHUNK_SIZE = 64 * 1024; // 64 KiB - const DEFAULT_CHUNK_SIZE = 64 * 1024; // 64 KiB +// A finalization registry to clean up underlying resources that are GC'ed. +const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { + core.tryClose(rid); +}); - // A finalization registry to clean up underlying resources that are GC'ed. - const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { +const _readAll = Symbol("[[readAll]]"); +const _original = Symbol("[[original]]"); +/** + * Create a new ReadableStream object that is backed by a Resource that + * implements `Resource::read_return`. This object contains enough metadata to + * allow callers to bypass the JavaScript ReadableStream implementation and + * read directly from the underlying resource if they so choose (FastStream). + * + * @param {number} rid The resource ID to read from. + * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. + * @returns {ReadableStream} + */ +function readableStreamForRid(rid, autoClose = true) { + const stream = webidl.createBranded(ReadableStream); + stream[_resourceBacking] = { rid, autoClose }; + + const tryClose = () => { + if (!autoClose) return; + RESOURCE_REGISTRY.unregister(stream); core.tryClose(rid); - }); + }; - const _readAll = Symbol("[[readAll]]"); - const _original = Symbol("[[original]]"); - /** - * Create a new ReadableStream object that is backed by a Resource that - * implements `Resource::read_return`. This object contains enough metadata to - * allow callers to bypass the JavaScript ReadableStream implementation and - * read directly from the underlying resource if they so choose (FastStream). - * - * @param {number} rid The resource ID to read from. - * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. - * @returns {ReadableStream} - */ - function readableStreamForRid(rid, autoClose = true) { - const stream = webidl.createBranded(ReadableStream); - stream[_resourceBacking] = { rid, autoClose }; - - const tryClose = () => { - if (!autoClose) return; - RESOURCE_REGISTRY.unregister(stream); - core.tryClose(rid); - }; - - if (autoClose) { - RESOURCE_REGISTRY.register(stream, rid, stream); - } - - const underlyingSource = { - type: "bytes", - async pull(controller) { - const v = controller.byobRequest.view; - try { - if (controller[_readAll] === true) { - // fast path for tee'd streams consuming body - const chunk = await core.readAll(rid); - if (chunk.byteLength > 0) { - controller.enqueue(chunk); - } - controller.close(); - tryClose(); - return; - } - - const bytesRead = await core.read(rid, v); - if (bytesRead === 0) { - tryClose(); - controller.close(); - controller.byobRequest.respond(0); - } else { - controller.byobRequest.respond(bytesRead); - } - } catch (e) { - controller.error(e); - tryClose(); - } - }, - cancel() { - tryClose(); - }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, - }; - initializeReadableStream(stream); - setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSource, - 0, - ); - return stream; + if (autoClose) { + RESOURCE_REGISTRY.register(stream, rid, stream); } - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - const _isUnref = Symbol("isUnref"); - /** - * Create a new ReadableStream object that is backed by a Resource that - * implements `Resource::read_return`. This readable stream supports being - * refed and unrefed by calling `readableStreamForRidUnrefableRef` and - * `readableStreamForRidUnrefableUnref` on it. Unrefable streams are not - * FastStream compatible. - * - * @param {number} rid The resource ID to read from. - * @returns {ReadableStream} - */ - function readableStreamForRidUnrefable(rid) { - const stream = webidl.createBranded(ReadableStream); - stream[promiseIdSymbol] = undefined; - stream[_isUnref] = false; - stream[_resourceBackingUnrefable] = { rid, autoClose: true }; - const underlyingSource = { - type: "bytes", - async pull(controller) { - const v = controller.byobRequest.view; - try { - const promise = core.read(rid, v); - const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; - if (stream[_isUnref]) core.unrefOp(promiseId); - const bytesRead = await promise; - stream[promiseIdSymbol] = undefined; - if (bytesRead === 0) { - core.tryClose(rid); - controller.close(); - controller.byobRequest.respond(0); - } else { - controller.byobRequest.respond(bytesRead); - } - } catch (e) { - controller.error(e); - core.tryClose(rid); - } - }, - cancel() { - core.tryClose(rid); - }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, - }; - initializeReadableStream(stream); - setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSource, - 0, - ); - return stream; - } - - function readableStreamIsUnrefable(stream) { - return ReflectHas(stream, _isUnref); - } - - function readableStreamForRidUnrefableRef(stream) { - if (!readableStreamIsUnrefable(stream)) { - throw new TypeError("Not an unrefable stream"); - } - stream[_isUnref] = false; - if (stream[promiseIdSymbol] !== undefined) { - core.refOp(stream[promiseIdSymbol]); - } - } - - function readableStreamForRidUnrefableUnref(stream) { - if (!readableStreamIsUnrefable(stream)) { - throw new TypeError("Not an unrefable stream"); - } - stream[_isUnref] = true; - if (stream[promiseIdSymbol] !== undefined) { - core.unrefOp(stream[promiseIdSymbol]); - } - } - - function getReadableStreamResourceBacking(stream) { - return stream[_resourceBacking]; - } - - function getReadableStreamResourceBackingUnrefable(stream) { - return stream[_resourceBackingUnrefable]; - } - - async function readableStreamCollectIntoUint8Array(stream) { - const resourceBacking = getReadableStreamResourceBacking(stream) || - getReadableStreamResourceBackingUnrefable(stream); - const reader = acquireReadableStreamDefaultReader(stream); - - if (resourceBacking) { - // fast path, read whole body in a single op call + const underlyingSource = { + type: "bytes", + async pull(controller) { + const v = controller.byobRequest.view; try { - readableStreamDisturb(stream); - const promise = core.opAsync("op_read_all", resourceBacking.rid); - if (readableStreamIsUnrefable(stream)) { - const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; - if (stream[_isUnref]) core.unrefOp(promiseId); - } - const buf = await promise; - readableStreamThrowIfErrored(stream); - readableStreamClose(stream); - return buf; - } catch (err) { - readableStreamThrowIfErrored(stream); - readableStreamError(stream, err); - throw err; - } finally { - if (resourceBacking.autoClose) { - core.tryClose(resourceBacking.rid); - } - } - } - - // slow path - /** @type {Uint8Array[]} */ - const chunks = []; - let totalLength = 0; - - // tee'd stream - if (stream[_original]) { - // One of the branches is consuming the stream - // signal controller.pull that we can consume it in a single op - stream[_original][_controller][_readAll] = true; - } - - while (true) { - const { value: chunk, done } = await reader.read(); - - if (done) break; - - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { - throw new TypeError( - "Can't convert value to Uint8Array while consuming the stream", - ); - } - - ArrayPrototypePush(chunks, chunk); - totalLength += chunk.byteLength; - } - - const finalBuffer = new Uint8Array(totalLength); - let offset = 0; - for (let i = 0; i < chunks.length; ++i) { - const chunk = chunks[i]; - TypedArrayPrototypeSet(finalBuffer, chunk, offset); - offset += chunk.byteLength; - } - return finalBuffer; - } - - /** - * Create a new Writable object that is backed by a Resource that implements - * `Resource::write` / `Resource::write_all`. This object contains enough - * metadata to allow callers to bypass the JavaScript WritableStream - * implementation and write directly to the underlying resource if they so - * choose (FastStream). - * - * @param {number} rid The resource ID to write to. - * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. - * @returns {ReadableStream} - */ - function writableStreamForRid(rid, autoClose = true) { - const stream = webidl.createBranded(WritableStream); - stream[_resourceBacking] = { rid, autoClose }; - - const tryClose = () => { - if (!autoClose) return; - RESOURCE_REGISTRY.unregister(stream); - core.tryClose(rid); - }; - - if (autoClose) { - RESOURCE_REGISTRY.register(stream, rid, stream); - } - - const underlyingSink = { - async write(chunk, controller) { - try { - await core.writeAll(rid, chunk); - } catch (e) { - controller.error(e); - tryClose(); - } - }, - close() { - tryClose(); - }, - abort() { - tryClose(); - }, - }; - initializeWritableStream(stream); - setUpWritableStreamDefaultControllerFromUnderlyingSink( - stream, - underlyingSink, - underlyingSink, - 1, - () => 1, - ); - return stream; - } - - function getWritableStreamResourceBacking(stream) { - return stream[_resourceBacking]; - } - - /* - * @param {ReadableStream} stream - */ - function readableStreamThrowIfErrored(stream) { - if (stream[_state] === "errored") { - throw stream[_storedError]; - } - } - - /** - * @param {unknown} value - * @returns {value is WritableStream} - */ - function isWritableStream(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _controller)); - } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function isWritableStreamLocked(stream) { - if (stream[_writer] === undefined) { - return false; - } - return true; - } - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @returns {T | _close} - */ - function peekQueueValue(container) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), - ); - assert(container[_queue].length); - const valueWithSize = container[_queue][0]; - return valueWithSize.value; - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerCallPullIfNeeded(controller) { - const shouldPull = readableByteStreamControllerShouldCallPull(controller); - if (!shouldPull) { - return; - } - if (controller[_pulling]) { - controller[_pullAgain] = true; - return; - } - assert(controller[_pullAgain] === false); - controller[_pulling] = true; - /** @type {Promise} */ - const pullPromise = controller[_pullAlgorithm](controller); - setPromiseIsHandledToTrue( - PromisePrototypeThen( - pullPromise, - () => { - controller[_pulling] = false; - if (controller[_pullAgain]) { - controller[_pullAgain] = false; - readableByteStreamControllerCallPullIfNeeded(controller); + if (controller[_readAll] === true) { + // fast path for tee'd streams consuming body + const chunk = await core.readAll(rid); + if (chunk.byteLength > 0) { + controller.enqueue(chunk); } - }, - (e) => { - readableByteStreamControllerError(controller, e); - }, - ), + controller.close(); + tryClose(); + return; + } + + const bytesRead = await core.read(rid, v); + if (bytesRead === 0) { + tryClose(); + controller.close(); + controller.byobRequest.respond(0); + } else { + controller.byobRequest.respond(bytesRead); + } + } catch (e) { + controller.error(e); + tryClose(); + } + }, + cancel() { + tryClose(); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }; + initializeReadableStream(stream); + setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSource, + 0, + ); + return stream; +} + +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); +const _isUnref = Symbol("isUnref"); +/** + * Create a new ReadableStream object that is backed by a Resource that + * implements `Resource::read_return`. This readable stream supports being + * refed and unrefed by calling `readableStreamForRidUnrefableRef` and + * `readableStreamForRidUnrefableUnref` on it. Unrefable streams are not + * FastStream compatible. + * + * @param {number} rid The resource ID to read from. + * @returns {ReadableStream} + */ +function readableStreamForRidUnrefable(rid) { + const stream = webidl.createBranded(ReadableStream); + stream[promiseIdSymbol] = undefined; + stream[_isUnref] = false; + stream[_resourceBackingUnrefable] = { rid, autoClose: true }; + const underlyingSource = { + type: "bytes", + async pull(controller) { + const v = controller.byobRequest.view; + try { + const promise = core.read(rid, v); + const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; + if (stream[_isUnref]) core.unrefOp(promiseId); + const bytesRead = await promise; + stream[promiseIdSymbol] = undefined; + if (bytesRead === 0) { + core.tryClose(rid); + controller.close(); + controller.byobRequest.respond(0); + } else { + controller.byobRequest.respond(bytesRead); + } + } catch (e) { + controller.error(e); + core.tryClose(rid); + } + }, + cancel() { + core.tryClose(rid); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }; + initializeReadableStream(stream); + setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSource, + 0, + ); + return stream; +} + +function readableStreamIsUnrefable(stream) { + return ReflectHas(stream, _isUnref); +} + +function readableStreamForRidUnrefableRef(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); + } + stream[_isUnref] = false; + if (stream[promiseIdSymbol] !== undefined) { + core.refOp(stream[promiseIdSymbol]); + } +} + +function readableStreamForRidUnrefableUnref(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); + } + stream[_isUnref] = true; + if (stream[promiseIdSymbol] !== undefined) { + core.unrefOp(stream[promiseIdSymbol]); + } +} + +function getReadableStreamResourceBacking(stream) { + return stream[_resourceBacking]; +} + +function getReadableStreamResourceBackingUnrefable(stream) { + return stream[_resourceBackingUnrefable]; +} + +async function readableStreamCollectIntoUint8Array(stream) { + const resourceBacking = getReadableStreamResourceBacking(stream) || + getReadableStreamResourceBackingUnrefable(stream); + const reader = acquireReadableStreamDefaultReader(stream); + + if (resourceBacking) { + // fast path, read whole body in a single op call + try { + readableStreamDisturb(stream); + const promise = core.opAsync("op_read_all", resourceBacking.rid); + if (readableStreamIsUnrefable(stream)) { + const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; + if (stream[_isUnref]) core.unrefOp(promiseId); + } + const buf = await promise; + readableStreamThrowIfErrored(stream); + readableStreamClose(stream); + return buf; + } catch (err) { + readableStreamThrowIfErrored(stream); + readableStreamError(stream, err); + throw err; + } finally { + if (resourceBacking.autoClose) { + core.tryClose(resourceBacking.rid); + } + } + } + + // slow path + /** @type {Uint8Array[]} */ + const chunks = []; + let totalLength = 0; + + // tee'd stream + if (stream[_original]) { + // One of the branches is consuming the stream + // signal controller.pull that we can consume it in a single op + stream[_original][_controller][_readAll] = true; + } + + while (true) { + const { value: chunk, done } = await reader.read(); + + if (done) break; + + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { + throw new TypeError( + "Can't convert value to Uint8Array while consuming the stream", + ); + } + + ArrayPrototypePush(chunks, chunk); + totalLength += chunk.byteLength; + } + + const finalBuffer = new Uint8Array(totalLength); + let offset = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + TypedArrayPrototypeSet(finalBuffer, chunk, offset); + offset += chunk.byteLength; + } + return finalBuffer; +} + +/** + * Create a new Writable object that is backed by a Resource that implements + * `Resource::write` / `Resource::write_all`. This object contains enough + * metadata to allow callers to bypass the JavaScript WritableStream + * implementation and write directly to the underlying resource if they so + * choose (FastStream). + * + * @param {number} rid The resource ID to write to. + * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. + * @returns {ReadableStream} + */ +function writableStreamForRid(rid, autoClose = true) { + const stream = webidl.createBranded(WritableStream); + stream[_resourceBacking] = { rid, autoClose }; + + const tryClose = () => { + if (!autoClose) return; + RESOURCE_REGISTRY.unregister(stream); + core.tryClose(rid); + }; + + if (autoClose) { + RESOURCE_REGISTRY.register(stream, rid, stream); + } + + const underlyingSink = { + async write(chunk, controller) { + try { + await core.writeAll(rid, chunk); + } catch (e) { + controller.error(e); + tryClose(); + } + }, + close() { + tryClose(); + }, + abort() { + tryClose(); + }, + }; + initializeWritableStream(stream); + setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSink, + 1, + () => 1, + ); + return stream; +} + +function getWritableStreamResourceBacking(stream) { + return stream[_resourceBacking]; +} + +/* + * @param {ReadableStream} stream + */ +function readableStreamThrowIfErrored(stream) { + if (stream[_state] === "errored") { + throw stream[_storedError]; + } +} + +/** + * @param {unknown} value + * @returns {value is WritableStream} + */ +function isWritableStream(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _controller)); +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function isWritableStreamLocked(stream) { + if (stream[_writer] === undefined) { + return false; + } + return true; +} +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T | _close} + */ +function peekQueueValue(container) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + assert(container[_queue].length); + const valueWithSize = container[_queue][0]; + return valueWithSize.value; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerCallPullIfNeeded(controller) { + const shouldPull = readableByteStreamControllerShouldCallPull(controller); + if (!shouldPull) { + return; + } + if (controller[_pulling]) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + /** @type {Promise} */ + const pullPromise = controller[_pullAlgorithm](controller); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + pullPromise, + () => { + controller[_pulling] = false; + if (controller[_pullAgain]) { + controller[_pullAgain] = false; + readableByteStreamControllerCallPullIfNeeded(controller); + } + }, + (e) => { + readableByteStreamControllerError(controller, e); + }, + ), + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {any} e + */ +function readableByteStreamControllerError(controller, e) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + readableByteStreamControllerClearPendingPullIntos(controller); + resetQueue(controller); + readableByteStreamControllerClearAlgorithms(controller); + readableStreamError(stream, e); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClearPendingPullIntos(controller) { + readableByteStreamControllerInvalidateBYOBRequest(controller); + controller[_pendingPullIntos] = []; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClose(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (controller[_closeRequested] || stream[_state] !== "readable") { + return; + } + if (controller[_queueTotalSize] > 0) { + controller[_closeRequested] = true; + return; + } + if (controller[_pendingPullIntos].length !== 0) { + const firstPendingPullInto = controller[_pendingPullIntos][0]; + if (firstPendingPullInto.bytesFilled > 0) { + const e = new TypeError( + "Insufficient bytes to fill elements in the given buffer", + ); + readableByteStreamControllerError(controller, e); + throw e; + } + } + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(stream); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} chunk + */ +function readableByteStreamControllerEnqueue(controller, chunk) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + controller[_closeRequested] || + controller[_stream][_state] !== "readable" + ) { + return; + } + + const { buffer, byteOffset, byteLength } = chunk; + if (isDetachedBuffer(buffer)) { + throw new TypeError( + "chunk's buffer is detached and so cannot be enqueued", ); } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClearAlgorithms(controller) { - controller[_pullAlgorithm] = undefined; - controller[_cancelAlgorithm] = undefined; - } - - /** - * @param {ReadableByteStreamController} controller - * @param {any} e - */ - function readableByteStreamControllerError(controller, e) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if (stream[_state] !== "readable") { - return; - } - readableByteStreamControllerClearPendingPullIntos(controller); - resetQueue(controller); - readableByteStreamControllerClearAlgorithms(controller); - readableStreamError(stream, e); - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClearPendingPullIntos(controller) { - readableByteStreamControllerInvalidateBYOBRequest(controller); - controller[_pendingPullIntos] = []; - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClose(controller) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if (controller[_closeRequested] || stream[_state] !== "readable") { - return; - } - if (controller[_queueTotalSize] > 0) { - controller[_closeRequested] = true; - return; - } - if (controller[_pendingPullIntos].length !== 0) { - const firstPendingPullInto = controller[_pendingPullIntos][0]; - if (firstPendingPullInto.bytesFilled > 0) { - const e = new TypeError( - "Insufficient bytes to fill elements in the given buffer", - ); - readableByteStreamControllerError(controller, e); - throw e; - } - } - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(stream); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} chunk - */ - function readableByteStreamControllerEnqueue(controller, chunk) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if ( - controller[_closeRequested] || - controller[_stream][_state] !== "readable" - ) { - return; - } - - const { buffer, byteOffset, byteLength } = chunk; - if (isDetachedBuffer(buffer)) { + const transferredBuffer = transferArrayBuffer(buffer); + if (controller[_pendingPullIntos].length !== 0) { + const firstPendingPullInto = controller[_pendingPullIntos][0]; + if (isDetachedBuffer(firstPendingPullInto.buffer)) { throw new TypeError( - "chunk's buffer is detached and so cannot be enqueued", + "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk", ); } - const transferredBuffer = transferArrayBuffer(buffer); - if (controller[_pendingPullIntos].length !== 0) { - const firstPendingPullInto = controller[_pendingPullIntos][0]; - if (isDetachedBuffer(firstPendingPullInto.buffer)) { - throw new TypeError( - "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk", - ); - } - readableByteStreamControllerInvalidateBYOBRequest(controller); - firstPendingPullInto.buffer = transferArrayBuffer( - firstPendingPullInto.buffer, + readableByteStreamControllerInvalidateBYOBRequest(controller); + firstPendingPullInto.buffer = transferArrayBuffer( + firstPendingPullInto.buffer, + ); + if (firstPendingPullInto.readerType === "none") { + readableByteStreamControllerEnqueueDetachedPullIntoToQueue( + controller, + firstPendingPullInto, ); - if (firstPendingPullInto.readerType === "none") { - readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - firstPendingPullInto, - ); - } } - if (readableStreamHasDefaultReader(stream)) { - readableByteStreamControllerProcessReadRequestsUsingQueue(controller); - if (readableStreamGetNumReadRequests(stream) === 0) { - assert(controller[_pendingPullIntos].length === 0); - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, - ); - } else { - assert(controller[_queue].length === 0); - if (controller[_pendingPullIntos].length !== 0) { - assert(controller[_pendingPullIntos][0].readerType === "default"); - readableByteStreamControllerShiftPendingPullInto(controller); - } - const transferredView = new Uint8Array( - transferredBuffer, - byteOffset, - byteLength, - ); - readableStreamFulfillReadRequest(stream, transferredView, false); - } - } else if (readableStreamHasBYOBReader(stream)) { + } + if (readableStreamHasDefaultReader(stream)) { + readableByteStreamControllerProcessReadRequestsUsingQueue(controller); + if (readableStreamGetNumReadRequests(stream) === 0) { + assert(controller[_pendingPullIntos].length === 0); readableByteStreamControllerEnqueueChunkToQueue( controller, transferredBuffer, byteOffset, byteLength, ); - readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, - ); } else { - assert(isReadableStreamLocked(stream) === false); - readableByteStreamControllerEnqueueChunkToQueue( - controller, + assert(controller[_queue].length === 0); + if (controller[_pendingPullIntos].length !== 0) { + assert(controller[_pendingPullIntos][0].readerType === "default"); + readableByteStreamControllerShiftPendingPullInto(controller); + } + const transferredView = new Uint8Array( transferredBuffer, byteOffset, byteLength, ); + readableStreamFulfillReadRequest(stream, transferredView, false); } - readableByteStreamControllerCallPullIfNeeded(controller); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueChunkToQueue( - controller, - buffer, - byteOffset, - byteLength, - ) { - ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); - controller[_queueTotalSize] += byteLength; - } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - buffer, - byteOffset, - byteLength, - ) { - let cloneResult; - try { - cloneResult = buffer.slice(byteOffset, byteOffset + byteLength); - } catch (e) { - readableByteStreamControllerError(controller, e); - } + } else if (readableStreamHasBYOBReader(stream)) { readableByteStreamControllerEnqueueChunkToQueue( controller, - cloneResult, - 0, + transferredBuffer, + byteOffset, + byteLength, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); + } else { + assert(isReadableStreamLocked(stream) === false); + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, byteLength, ); } + readableByteStreamControllerCallPullIfNeeded(controller); +} - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferLike} buffer + * @param {number} byteOffset + * @param {number} byteLength + * @returns {void} + */ +function readableByteStreamControllerEnqueueChunkToQueue( + controller, + buffer, + byteOffset, + byteLength, +) { + ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); + controller[_queueTotalSize] += byteLength; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferLike} buffer + * @param {number} byteOffset + * @param {number} byteLength + * @returns {void} + */ +function readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + buffer, + byteOffset, + byteLength, +) { + let cloneResult; + try { + cloneResult = buffer.slice(byteOffset, byteOffset + byteLength); + } catch (e) { + readableByteStreamControllerError(controller, e); + } + readableByteStreamControllerEnqueueChunkToQueue( controller, - pullIntoDescriptor, + cloneResult, + 0, + byteLength, + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( + controller, + pullIntoDescriptor, +) { + assert(pullIntoDescriptor.readerType === "none"); + if (pullIntoDescriptor.bytesFilled > 0) { + readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + pullIntoDescriptor.bytesFilled, + ); + } + readableByteStreamControllerShiftPendingPullInto(controller); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {ReadableStreamBYOBRequest | null} + */ +function readableByteStreamControllerGetBYOBRequest(controller) { + if ( + controller[_byobRequest] === null && + controller[_pendingPullIntos].length !== 0 ) { - assert(pullIntoDescriptor.readerType === "none"); - if (pullIntoDescriptor.bytesFilled > 0) { - readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - pullIntoDescriptor.bytesFilled, - ); - } - readableByteStreamControllerShiftPendingPullInto(controller); - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {ReadableStreamBYOBRequest | null} - */ - function readableByteStreamControllerGetBYOBRequest(controller) { - if ( - controller[_byobRequest] === null && - controller[_pendingPullIntos].length !== 0 - ) { - const firstDescriptor = controller[_pendingPullIntos][0]; - const view = new Uint8Array( - firstDescriptor.buffer, - firstDescriptor.byteOffset + firstDescriptor.bytesFilled, - firstDescriptor.byteLength - firstDescriptor.bytesFilled, - ); - const byobRequest = webidl.createBranded(ReadableStreamBYOBRequest); - byobRequest[_controller] = controller; - byobRequest[_view] = view; - controller[_byobRequest] = byobRequest; - } - return controller[_byobRequest]; - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {number | null} - */ - function readableByteStreamControllerGetDesiredSize(controller) { - const state = controller[_stream][_state]; - if (state === "errored") { - return null; - } - if (state === "closed") { - return 0; - } - return controller[_strategyHWM] - controller[_queueTotalSize]; - } - - /** - * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container - * @returns {void} - */ - function resetQueue(container) { - container[_queue] = []; - container[_queueTotalSize] = 0; - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerHandleQueueDrain(controller) { - assert(controller[_stream][_state] === "readable"); - if ( - controller[_queueTotalSize] === 0 && controller[_closeRequested] - ) { - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(controller[_stream]); - } else { - readableByteStreamControllerCallPullIfNeeded(controller); - } - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {boolean} - */ - function readableByteStreamControllerShouldCallPull(controller) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if ( - stream[_state] !== "readable" || - controller[_closeRequested] || - !controller[_started] - ) { - return false; - } - if ( - readableStreamHasDefaultReader(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; - } - if ( - readableStreamHasBYOBReader(stream) && - readableStreamGetNumReadIntoRequests(stream) > 0 - ) { - return true; - } - const desiredSize = readableByteStreamControllerGetDesiredSize(controller); - assert(desiredSize !== null); - return desiredSize > 0; - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {ReadRequest} readRequest - * @returns {void} - */ - function readableStreamAddReadRequest(stream, readRequest) { - assert(isReadableStreamDefaultReader(stream[_reader])); - assert(stream[_state] === "readable"); - ArrayPrototypePush(stream[_reader][_readRequests], readRequest); - } - - /** - * @param {ReadableStream} stream - * @param {ReadIntoRequest} readRequest - * @returns {void} - */ - function readableStreamAddReadIntoRequest(stream, readRequest) { - assert(isReadableStreamBYOBReader(stream[_reader])); - assert(stream[_state] === "readable" || stream[_state] === "closed"); - ArrayPrototypePush(stream[_reader][_readIntoRequests], readRequest); - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function readableStreamCancel(stream, reason) { - stream[_disturbed] = true; - if (stream[_state] === "closed") { - return resolvePromiseWith(undefined); - } - if (stream[_state] === "errored") { - return PromiseReject(stream[_storedError]); - } - readableStreamClose(stream); - const reader = stream[_reader]; - if (reader !== undefined && isReadableStreamBYOBReader(reader)) { - const readIntoRequests = reader[_readIntoRequests]; - reader[_readIntoRequests] = []; - for (let i = 0; i < readIntoRequests.length; ++i) { - const readIntoRequest = readIntoRequests[i]; - readIntoRequest.closeSteps(undefined); - } - } - /** @type {Promise} */ - const sourceCancelPromise = stream[_controller][_cancelSteps](reason); - return PromisePrototypeThen(sourceCancelPromise, () => undefined); - } - - /** - * @template R - * @param {ReadableStream} stream - * @returns {void} - */ - function readableStreamClose(stream) { - assert(stream[_state] === "readable"); - stream[_state] = "closed"; - /** @type {ReadableStreamDefaultReader | undefined} */ - const reader = stream[_reader]; - if (!reader) { - return; - } - if (isReadableStreamDefaultReader(reader)) { - /** @type {Array>} */ - const readRequests = reader[_readRequests]; - reader[_readRequests] = []; - for (let i = 0; i < readRequests.length; ++i) { - const readRequest = readRequests[i]; - readRequest.closeSteps(); - } - } - // This promise can be double resolved. - // See: https://github.com/whatwg/streams/issues/1100 - reader[_closedPromise].resolve(undefined); - } - - /** - * @template R - * @param {ReadableStream} stream - * @returns {void} - */ - function readableStreamDisturb(stream) { - stream[_disturbed] = true; - } - - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerCallPullIfNeeded(controller) { - const shouldPull = readableStreamDefaultcontrollerShouldCallPull( - controller, + const firstDescriptor = controller[_pendingPullIntos][0]; + const view = new Uint8Array( + firstDescriptor.buffer, + firstDescriptor.byteOffset + firstDescriptor.bytesFilled, + firstDescriptor.byteLength - firstDescriptor.bytesFilled, ); - if (shouldPull === false) { - return; - } - if (controller[_pulling] === true) { - controller[_pullAgain] = true; - return; - } - assert(controller[_pullAgain] === false); - controller[_pulling] = true; - const pullPromise = controller[_pullAlgorithm](controller); - uponFulfillment(pullPromise, () => { - controller[_pulling] = false; - if (controller[_pullAgain] === true) { - controller[_pullAgain] = false; - readableStreamDefaultControllerCallPullIfNeeded(controller); - } - }); - uponRejection(pullPromise, (e) => { - readableStreamDefaultControllerError(controller, e); - }); + const byobRequest = webidl.createBranded(ReadableStreamBYOBRequest); + byobRequest[_controller] = controller; + byobRequest[_view] = view; + controller[_byobRequest] = byobRequest; } + return controller[_byobRequest]; +} - /** - * @param {ReadableStreamDefaultController} controller - * @returns {boolean} - */ - function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { - const state = controller[_stream][_state]; - if (controller[_closeRequested] === false && state === "readable") { - return true; - } else { - return false; - } +/** + * @param {ReadableByteStreamController} controller + * @returns {number | null} + */ +function readableByteStreamControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; } - - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerClearAlgorithms(controller) { - controller[_pullAlgorithm] = undefined; - controller[_cancelAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; + if (state === "closed") { + return 0; } + return controller[_strategyHWM] - controller[_queueTotalSize]; +} - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerClose(controller) { - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return; - } - const stream = controller[_stream]; - controller[_closeRequested] = true; - if (controller[_queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamClose(stream); - } +/** + * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container + * @returns {void} + */ +function resetQueue(container) { + container[_queue] = []; + container[_queueTotalSize] = 0; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerHandleQueueDrain(controller) { + assert(controller[_stream][_state] === "readable"); + if ( + controller[_queueTotalSize] === 0 && controller[_closeRequested] + ) { + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(controller[_stream]); + } else { + readableByteStreamControllerCallPullIfNeeded(controller); } +} - /** - * @template R - * @param {ReadableStreamDefaultController} controller - * @param {R} chunk - * @returns {void} - */ - function readableStreamDefaultControllerEnqueue(controller, chunk) { - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return; - } - const stream = controller[_stream]; - if ( - isReadableStreamLocked(stream) === true && - readableStreamGetNumReadRequests(stream) > 0 - ) { - readableStreamFulfillReadRequest(stream, chunk, false); - } else { - let chunkSize; - try { - chunkSize = controller[_strategySizeAlgorithm](chunk); - } catch (e) { - readableStreamDefaultControllerError(controller, e); - throw e; - } - - try { - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (e) { - readableStreamDefaultControllerError(controller, e); - throw e; - } - } - readableStreamDefaultControllerCallPullIfNeeded(controller); - } - - /** - * @param {ReadableStreamDefaultController} controller - * @param {any} e - */ - function readableStreamDefaultControllerError(controller, e) { - const stream = controller[_stream]; - if (stream[_state] !== "readable") { - return; - } - resetQueue(controller); - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamError(stream, e); - } - - /** - * @param {ReadableStreamDefaultController} controller - * @returns {number | null} - */ - function readableStreamDefaultControllerGetDesiredSize(controller) { - const state = controller[_stream][_state]; - if (state === "errored") { - return null; - } - if (state === "closed") { - return 0; - } - return controller[_strategyHWM] - controller[_queueTotalSize]; - } - - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultcontrollerHasBackpressure(controller) { - if (readableStreamDefaultcontrollerShouldCallPull(controller) === true) { - return false; - } else { - return true; - } - } - - /** - * @param {ReadableStreamDefaultController} controller - * @returns {boolean} - */ - function readableStreamDefaultcontrollerShouldCallPull(controller) { - const stream = controller[_stream]; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return false; - } - if (controller[_started] === false) { - return false; - } - if ( - isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; - } - const desiredSize = readableStreamDefaultControllerGetDesiredSize( - controller, - ); - assert(desiredSize !== null); - if (desiredSize > 0) { - return true; - } +/** + * @param {ReadableByteStreamController} controller + * @returns {boolean} + */ +function readableByteStreamControllerShouldCallPull(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + stream[_state] !== "readable" || + controller[_closeRequested] || + !controller[_started] + ) { return false; } + if ( + readableStreamHasDefaultReader(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; + } + if ( + readableStreamHasBYOBReader(stream) && + readableStreamGetNumReadIntoRequests(stream) > 0 + ) { + return true; + } + const desiredSize = readableByteStreamControllerGetDesiredSize(controller); + assert(desiredSize !== null); + return desiredSize > 0; +} - /** - * @param {ReadableStreamBYOBReader} reader - * @param {ArrayBufferView} view - * @param {ReadIntoRequest} readIntoRequest - * @returns {void} - */ - function readableStreamBYOBReaderRead(reader, view, readIntoRequest) { - const stream = reader[_stream]; - assert(stream); - stream[_disturbed] = true; - if (stream[_state] === "errored") { - readIntoRequest.errorSteps(stream[_storedError]); - } else { - readableByteStreamControllerPullInto( - stream[_controller], - view, - readIntoRequest, - ); +/** + * @template R + * @param {ReadableStream} stream + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableStreamAddReadRequest(stream, readRequest) { + assert(isReadableStreamDefaultReader(stream[_reader])); + assert(stream[_state] === "readable"); + ArrayPrototypePush(stream[_reader][_readRequests], readRequest); +} + +/** + * @param {ReadableStream} stream + * @param {ReadIntoRequest} readRequest + * @returns {void} + */ +function readableStreamAddReadIntoRequest(stream, readRequest) { + assert(isReadableStreamBYOBReader(stream[_reader])); + assert(stream[_state] === "readable" || stream[_state] === "closed"); + ArrayPrototypePush(stream[_reader][_readIntoRequests], readRequest); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function readableStreamCancel(stream, reason) { + stream[_disturbed] = true; + if (stream[_state] === "closed") { + return resolvePromiseWith(undefined); + } + if (stream[_state] === "errored") { + return PromiseReject(stream[_storedError]); + } + readableStreamClose(stream); + const reader = stream[_reader]; + if (reader !== undefined && isReadableStreamBYOBReader(reader)) { + const readIntoRequests = reader[_readIntoRequests]; + reader[_readIntoRequests] = []; + for (let i = 0; i < readIntoRequests.length; ++i) { + const readIntoRequest = readIntoRequests[i]; + readIntoRequest.closeSteps(undefined); } } + /** @type {Promise} */ + const sourceCancelPromise = stream[_controller][_cancelSteps](reason); + return PromisePrototypeThen(sourceCancelPromise, () => undefined); +} - /** - * @param {ReadableStreamBYOBReader} reader - */ - function readableStreamBYOBReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); +/** + * @template R + * @param {ReadableStream} stream + * @returns {void} + */ +function readableStreamClose(stream) { + assert(stream[_state] === "readable"); + stream[_state] = "closed"; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (!reader) { + return; } - - /** - * @param {ReadableStreamBYOBReader} reader - * @param {any} e - */ - function readableStreamDefaultReaderErrorReadRequests(reader, e) { + if (isReadableStreamDefaultReader(reader)) { + /** @type {Array>} */ const readRequests = reader[_readRequests]; reader[_readRequests] = []; for (let i = 0; i < readRequests.length; ++i) { const readRequest = readRequests[i]; - readRequest.errorSteps(e); + readRequest.closeSteps(); } } + // This promise can be double resolved. + // See: https://github.com/whatwg/streams/issues/1100 + reader[_closedPromise].resolve(undefined); +} - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( +/** + * @template R + * @param {ReadableStream} stream + * @returns {void} + */ +function readableStreamDisturb(stream) { + stream[_disturbed] = true; +} + +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerCallPullIfNeeded(controller) { + const shouldPull = readableStreamDefaultcontrollerShouldCallPull( controller, + ); + if (shouldPull === false) { + return; + } + if (controller[_pulling] === true) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + const pullPromise = controller[_pullAlgorithm](controller); + uponFulfillment(pullPromise, () => { + controller[_pulling] = false; + if (controller[_pullAgain] === true) { + controller[_pullAgain] = false; + readableStreamDefaultControllerCallPullIfNeeded(controller); + } + }); + uponRejection(pullPromise, (e) => { + readableStreamDefaultControllerError(controller, e); + }); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ +function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { + const state = controller[_stream][_state]; + if (controller[_closeRequested] === false && state === "readable") { + return true; + } else { + return false; + } +} + +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; +} + +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerClose(controller) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false ) { - assert(!controller[_closeRequested]); - while (controller[_pendingPullIntos].length !== 0) { - if (controller[_queueTotalSize] === 0) { - return; - } - const pullIntoDescriptor = controller[_pendingPullIntos][0]; - if ( - readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, - pullIntoDescriptor, - ) - ) { - readableByteStreamControllerShiftPendingPullInto(controller); - readableByteStreamControllerCommitPullIntoDescriptor( - controller[_stream], - pullIntoDescriptor, - ); - } + return; + } + const stream = controller[_stream]; + controller[_closeRequested] = true; + if (controller[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamClose(stream); + } +} + +/** + * @template R + * @param {ReadableStreamDefaultController} controller + * @param {R} chunk + * @returns {void} + */ +function readableStreamDefaultControllerEnqueue(controller, chunk) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; + } + const stream = controller[_stream]; + if ( + isReadableStreamLocked(stream) === true && + readableStreamGetNumReadRequests(stream) > 0 + ) { + readableStreamFulfillReadRequest(stream, chunk, false); + } else { + let chunkSize; + try { + chunkSize = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; + } + + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; } } - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerProcessReadRequestsUsingQueue( - controller, + readableStreamDefaultControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @param {any} e + */ +function readableStreamDefaultControllerError(controller, e) { + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + resetQueue(controller); + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamError(stream, e); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @returns {number | null} + */ +function readableStreamDefaultControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; + } + if (state === "closed") { + return 0; + } + return controller[_strategyHWM] - controller[_queueTotalSize]; +} + +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultcontrollerHasBackpressure(controller) { + if (readableStreamDefaultcontrollerShouldCallPull(controller) === true) { + return false; + } else { + return true; + } +} + +/** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ +function readableStreamDefaultcontrollerShouldCallPull(controller) { + const stream = controller[_stream]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false ) { - const reader = controller[_stream][_reader]; - assert(isReadableStreamDefaultReader(reader)); - while (reader[_readRequests].length !== 0) { - if (controller[_queueTotalSize] === 0) { - return; - } - const readRequest = ArrayPrototypeShift(reader[_readRequests]); - readableByteStreamControllerFillReadRequestFromQueue( + return false; + } + if (controller[_started] === false) { + return false; + } + if ( + isReadableStreamLocked(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; + } + const desiredSize = readableStreamDefaultControllerGetDesiredSize( + controller, + ); + assert(desiredSize !== null); + if (desiredSize > 0) { + return true; + } + return false; +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {ArrayBufferView} view + * @param {ReadIntoRequest} readIntoRequest + * @returns {void} + */ +function readableStreamBYOBReaderRead(reader, view, readIntoRequest) { + const stream = reader[_stream]; + assert(stream); + stream[_disturbed] = true; + if (stream[_state] === "errored") { + readIntoRequest.errorSteps(stream[_storedError]); + } else { + readableByteStreamControllerPullInto( + stream[_controller], + view, + readIntoRequest, + ); + } +} + +/** + * @param {ReadableStreamBYOBReader} reader + */ +function readableStreamBYOBReaderRelease(reader) { + readableStreamReaderGenericRelease(reader); + const e = new TypeError("The reader was released."); + readableStreamBYOBReaderErrorReadIntoRequests(reader, e); +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {any} e + */ +function readableStreamDefaultReaderErrorReadRequests(reader, e) { + const readRequests = reader[_readRequests]; + reader[_readRequests] = []; + for (let i = 0; i < readRequests.length; ++i) { + const readRequest = readRequests[i]; + readRequest.errorSteps(e); + } +} + +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, +) { + assert(!controller[_closeRequested]); + while (controller[_pendingPullIntos].length !== 0) { + if (controller[_queueTotalSize] === 0) { + return; + } + const pullIntoDescriptor = controller[_pendingPullIntos][0]; + if ( + readableByteStreamControllerFillPullIntoDescriptorFromQueue( controller, - readRequest, + pullIntoDescriptor, + ) + ) { + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], + pullIntoDescriptor, ); } } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} view - * @param {ReadIntoRequest} readIntoRequest - * @returns {void} - */ - function readableByteStreamControllerPullInto( - controller, - view, - readIntoRequest, - ) { - const stream = controller[_stream]; - let elementSize = 1; - let ctor = DataView; - - if ( - ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint8ClampedArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(BigInt64ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, view) - ) { - elementSize = view.constructor.BYTES_PER_ELEMENT; - ctor = view.constructor; +} +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerProcessReadRequestsUsingQueue( + controller, +) { + const reader = controller[_stream][_reader]; + assert(isReadableStreamDefaultReader(reader)); + while (reader[_readRequests].length !== 0) { + if (controller[_queueTotalSize] === 0) { + return; } - const byteOffset = view.byteOffset; - const byteLength = view.byteLength; + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + readableByteStreamControllerFillReadRequestFromQueue( + controller, + readRequest, + ); + } +} - /** @type {ArrayBufferLike} */ - let buffer; +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} view + * @param {ReadIntoRequest} readIntoRequest + * @returns {void} + */ +function readableByteStreamControllerPullInto( + controller, + view, + readIntoRequest, +) { + const stream = controller[_stream]; + let elementSize = 1; + let ctor = DataView; - try { - buffer = transferArrayBuffer(view.buffer); - } catch (e) { + if ( + ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint8ClampedArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(BigInt64ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, view) + ) { + elementSize = view.constructor.BYTES_PER_ELEMENT; + ctor = view.constructor; + } + const byteOffset = view.byteOffset; + const byteLength = view.byteLength; + + /** @type {ArrayBufferLike} */ + let buffer; + + try { + buffer = transferArrayBuffer(view.buffer); + } catch (e) { + readIntoRequest.errorSteps(e); + return; + } + + /** @type {PullIntoDescriptor} */ + const pullIntoDescriptor = { + buffer, + bufferByteLength: buffer.byteLength, + byteOffset, + byteLength, + bytesFilled: 0, + elementSize, + viewConstructor: ctor, + readerType: "byob", + }; + + if (controller[_pendingPullIntos].length !== 0) { + ArrayPrototypePush(controller[_pendingPullIntos], pullIntoDescriptor); + readableStreamAddReadIntoRequest(stream, readIntoRequest); + return; + } + if (stream[_state] === "closed") { + const emptyView = new ctor( + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + 0, + ); + readIntoRequest.closeSteps(emptyView); + return; + } + if (controller[_queueTotalSize] > 0) { + if ( + readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, + ) + ) { + const filledView = readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, + ); + readableByteStreamControllerHandleQueueDrain(controller); + readIntoRequest.chunkSteps(filledView); + return; + } + if (controller[_closeRequested]) { + const e = new TypeError( + "Insufficient bytes to fill elements in the given buffer", + ); + readableByteStreamControllerError(controller, e); readIntoRequest.errorSteps(e); return; } + } + controller[_pendingPullIntos].push(pullIntoDescriptor); + readableStreamAddReadIntoRequest(stream, readIntoRequest); + readableByteStreamControllerCallPullIfNeeded(controller); +} - /** @type {PullIntoDescriptor} */ - const pullIntoDescriptor = { - buffer, - bufferByteLength: buffer.byteLength, - byteOffset, - byteLength, - bytesFilled: 0, - elementSize, - viewConstructor: ctor, - readerType: "byob", - }; - - if (controller[_pendingPullIntos].length !== 0) { - ArrayPrototypePush(controller[_pendingPullIntos], pullIntoDescriptor); - readableStreamAddReadIntoRequest(stream, readIntoRequest); - return; - } - if (stream[_state] === "closed") { - const emptyView = new ctor( - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - 0, +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @returns {void} + */ +function readableByteStreamControllerRespond(controller, bytesWritten) { + assert(controller[_pendingPullIntos].length !== 0); + const firstDescriptor = controller[_pendingPullIntos][0]; + const state = controller[_stream][_state]; + if (state === "closed") { + if (bytesWritten !== 0) { + throw new TypeError( + "bytesWritten must be 0 when calling respond() on a closed stream", ); - readIntoRequest.closeSteps(emptyView); - return; } - if (controller[_queueTotalSize] > 0) { - if ( - readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, - pullIntoDescriptor, - ) - ) { - const filledView = - readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, - ); - readableByteStreamControllerHandleQueueDrain(controller); - readIntoRequest.chunkSteps(filledView); - return; - } - if (controller[_closeRequested]) { - const e = new TypeError( - "Insufficient bytes to fill elements in the given buffer", - ); - readableByteStreamControllerError(controller, e); - readIntoRequest.errorSteps(e); - return; - } + } else { + assert(state === "readable"); + if (bytesWritten === 0) { + throw new TypeError( + "bytesWritten must be greater than 0 when calling respond() on a readable stream", + ); + } + if ( + (firstDescriptor.bytesFilled + bytesWritten) > + firstDescriptor.byteLength + ) { + throw new RangeError("bytesWritten out of range"); } - controller[_pendingPullIntos].push(pullIntoDescriptor); - readableStreamAddReadIntoRequest(stream, readIntoRequest); - readableByteStreamControllerCallPullIfNeeded(controller); } + firstDescriptor.buffer = transferArrayBuffer(firstDescriptor.buffer); + readableByteStreamControllerRespondInternal(controller, bytesWritten); +} - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @returns {void} - */ - function readableByteStreamControllerRespond(controller, bytesWritten) { - assert(controller[_pendingPullIntos].length !== 0); - const firstDescriptor = controller[_pendingPullIntos][0]; - const state = controller[_stream][_state]; - if (state === "closed") { - if (bytesWritten !== 0) { - throw new TypeError( - "bytesWritten must be 0 when calling respond() on a closed stream", - ); - } - } else { - assert(state === "readable"); - if (bytesWritten === 0) { - throw new TypeError( - "bytesWritten must be greater than 0 when calling respond() on a readable stream", - ); - } - if ( - (firstDescriptor.bytesFilled + bytesWritten) > - firstDescriptor.byteLength - ) { - throw new RangeError("bytesWritten out of range"); - } - } - firstDescriptor.buffer = transferArrayBuffer(firstDescriptor.buffer); - readableByteStreamControllerRespondInternal(controller, bytesWritten); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerRespondInReadableState( +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerRespondInReadableState( + controller, + bytesWritten, + pullIntoDescriptor, +) { + assert( + (pullIntoDescriptor.bytesFilled + bytesWritten) <= + pullIntoDescriptor.byteLength, + ); + readableByteStreamControllerFillHeadPullIntoDescriptor( controller, bytesWritten, pullIntoDescriptor, - ) { - assert( - (pullIntoDescriptor.bytesFilled + bytesWritten) <= - pullIntoDescriptor.byteLength, - ); - readableByteStreamControllerFillHeadPullIntoDescriptor( + ); + if (pullIntoDescriptor.readerType === "none") { + readableByteStreamControllerEnqueueDetachedPullIntoToQueue( controller, - bytesWritten, - pullIntoDescriptor, - ); - if (pullIntoDescriptor.readerType === "none") { - readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - pullIntoDescriptor, - ); - readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, - ); - return; - } - if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { - return; - } - readableByteStreamControllerShiftPendingPullInto(controller); - const remainderSize = pullIntoDescriptor.bytesFilled % - pullIntoDescriptor.elementSize; - if (remainderSize > 0) { - const end = pullIntoDescriptor.byteOffset + - pullIntoDescriptor.bytesFilled; - readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - pullIntoDescriptor.buffer, - end - remainderSize, - remainderSize, - ); - } - pullIntoDescriptor.bytesFilled -= remainderSize; - readableByteStreamControllerCommitPullIntoDescriptor( - controller[_stream], pullIntoDescriptor, ); readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( controller, ); + return; } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @returns {void} - */ - function readableByteStreamControllerRespondInternal( - controller, - bytesWritten, - ) { - const firstDescriptor = controller[_pendingPullIntos][0]; - assert(canTransferArrayBuffer(firstDescriptor.buffer)); - readableByteStreamControllerInvalidateBYOBRequest(controller); - const state = controller[_stream][_state]; - if (state === "closed") { - assert(bytesWritten === 0); - readableByteStreamControllerRespondInClosedState( - controller, - firstDescriptor, - ); - } else { - assert(state === "readable"); - assert(bytesWritten > 0); - readableByteStreamControllerRespondInReadableState( - controller, - bytesWritten, - firstDescriptor, - ); - } - readableByteStreamControllerCallPullIfNeeded(controller); + if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { + return; } - - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerInvalidateBYOBRequest(controller) { - if (controller[_byobRequest] === null) { - return; - } - controller[_byobRequest][_controller] = undefined; - controller[_byobRequest][_view] = null; - controller[_byobRequest] = null; - } - - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} firstDescriptor - */ - function readableByteStreamControllerRespondInClosedState( - controller, - firstDescriptor, - ) { - assert(firstDescriptor.bytesFilled === 0); - if (firstDescriptor.readerType === "none") { - readableByteStreamControllerShiftPendingPullInto(controller); - } - const stream = controller[_stream]; - if (readableStreamHasBYOBReader(stream)) { - while (readableStreamGetNumReadIntoRequests(stream) > 0) { - const pullIntoDescriptor = - readableByteStreamControllerShiftPendingPullInto(controller); - readableByteStreamControllerCommitPullIntoDescriptor( - stream, - pullIntoDescriptor, - ); - } - } - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {PullIntoDescriptor} pullIntoDescriptor - */ - function readableByteStreamControllerCommitPullIntoDescriptor( - stream, - pullIntoDescriptor, - ) { - assert(stream[_state] !== "errored"); - assert(pullIntoDescriptor.readerType !== "none"); - let done = false; - if (stream[_state] === "closed") { - assert(pullIntoDescriptor.bytesFilled === 0); - done = true; - } - const filledView = readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, + readableByteStreamControllerShiftPendingPullInto(controller); + const remainderSize = pullIntoDescriptor.bytesFilled % + pullIntoDescriptor.elementSize; + if (remainderSize > 0) { + const end = pullIntoDescriptor.byteOffset + + pullIntoDescriptor.bytesFilled; + readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + pullIntoDescriptor.buffer, + end - remainderSize, + remainderSize, ); - if (pullIntoDescriptor.readerType === "default") { - readableStreamFulfillReadRequest(stream, filledView, done); - } else { - assert(pullIntoDescriptor.readerType === "byob"); - readableStreamFulfillReadIntoRequest(stream, filledView, done); - } } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} view - */ - function readableByteStreamControllerRespondWithNewView(controller, view) { - assert(controller[_pendingPullIntos].length !== 0); - assert(!isDetachedBuffer(view.buffer)); - const firstDescriptor = controller[_pendingPullIntos][0]; - const state = controller[_stream][_state]; - if (state === "closed") { - if (view.byteLength !== 0) { - throw new TypeError( - "The view's length must be 0 when calling respondWithNewView() on a closed stream", - ); - } - } else { - assert(state === "readable"); - if (view.byteLength === 0) { - throw new TypeError( - "The view's length must be greater than 0 when calling respondWithNewView() on a readable stream", - ); - } - } - if ( - (firstDescriptor.byteOffset + firstDescriptor.bytesFilled) !== - view.byteOffset - ) { - throw new RangeError( - "The region specified by view does not match byobRequest", - ); - } - if (firstDescriptor.bufferByteLength !== view.buffer.byteLength) { - throw new RangeError( - "The buffer of view has different capacity than byobRequest", - ); - } - if ( - (firstDescriptor.bytesFilled + view.byteLength) > - firstDescriptor.byteLength - ) { - throw new RangeError( - "The region specified by view is larger than byobRequest", - ); - } - const viewByteLength = view.byteLength; - firstDescriptor.buffer = transferArrayBuffer(view.buffer); - readableByteStreamControllerRespondInternal(controller, viewByteLength); - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {PullIntoDescriptor} - */ - function readableByteStreamControllerShiftPendingPullInto(controller) { - assert(controller[_byobRequest] === null); - return ArrayPrototypeShift(controller[_pendingPullIntos]); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {boolean} - */ - function readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, + pullIntoDescriptor.bytesFilled -= remainderSize; + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], pullIntoDescriptor, - ) { - const elementSize = pullIntoDescriptor.elementSize; - const currentAlignedBytes = pullIntoDescriptor.bytesFilled - - (pullIntoDescriptor.bytesFilled % elementSize); - const maxBytesToCopy = MathMin( - controller[_queueTotalSize], - pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @returns {void} + */ +function readableByteStreamControllerRespondInternal( + controller, + bytesWritten, +) { + const firstDescriptor = controller[_pendingPullIntos][0]; + assert(canTransferArrayBuffer(firstDescriptor.buffer)); + readableByteStreamControllerInvalidateBYOBRequest(controller); + const state = controller[_stream][_state]; + if (state === "closed") { + assert(bytesWritten === 0); + readableByteStreamControllerRespondInClosedState( + controller, + firstDescriptor, ); - const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; - const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); - let totalBytesToCopyRemaining = maxBytesToCopy; - let ready = false; - if (maxAlignedBytes > currentAlignedBytes) { - totalBytesToCopyRemaining = maxAlignedBytes - - pullIntoDescriptor.bytesFilled; - ready = true; - } - const queue = controller[_queue]; - while (totalBytesToCopyRemaining > 0) { - const headOfQueue = queue[0]; - const bytesToCopy = MathMin( - totalBytesToCopyRemaining, - headOfQueue.byteLength, - ); - const destStart = pullIntoDescriptor.byteOffset + - pullIntoDescriptor.bytesFilled; + } else { + assert(state === "readable"); + assert(bytesWritten > 0); + readableByteStreamControllerRespondInReadableState( + controller, + bytesWritten, + firstDescriptor, + ); + } + readableByteStreamControllerCallPullIfNeeded(controller); +} - const destBuffer = new Uint8Array( - pullIntoDescriptor.buffer, - destStart, - bytesToCopy, - ); - const srcBuffer = new Uint8Array( - headOfQueue.buffer, - headOfQueue.byteOffset, - bytesToCopy, - ); - destBuffer.set(srcBuffer); +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerInvalidateBYOBRequest(controller) { + if (controller[_byobRequest] === null) { + return; + } + controller[_byobRequest][_controller] = undefined; + controller[_byobRequest][_view] = null; + controller[_byobRequest] = null; +} - if (headOfQueue.byteLength === bytesToCopy) { - ArrayPrototypeShift(queue); - } else { - headOfQueue.byteOffset += bytesToCopy; - headOfQueue.byteLength -= bytesToCopy; - } - controller[_queueTotalSize] -= bytesToCopy; - readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - bytesToCopy, +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} firstDescriptor + */ +function readableByteStreamControllerRespondInClosedState( + controller, + firstDescriptor, +) { + assert(firstDescriptor.bytesFilled === 0); + if (firstDescriptor.readerType === "none") { + readableByteStreamControllerShiftPendingPullInto(controller); + } + const stream = controller[_stream]; + if (readableStreamHasBYOBReader(stream)) { + while (readableStreamGetNumReadIntoRequests(stream) > 0) { + const pullIntoDescriptor = + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + stream, pullIntoDescriptor, ); - totalBytesToCopyRemaining -= bytesToCopy; } - if (!ready) { - assert(controller[_queueTotalSize] === 0); - assert(pullIntoDescriptor.bytesFilled > 0); - assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {PullIntoDescriptor} pullIntoDescriptor + */ +function readableByteStreamControllerCommitPullIntoDescriptor( + stream, + pullIntoDescriptor, +) { + assert(stream[_state] !== "errored"); + assert(pullIntoDescriptor.readerType !== "none"); + let done = false; + if (stream[_state] === "closed") { + assert(pullIntoDescriptor.bytesFilled === 0); + done = true; + } + const filledView = readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, + ); + if (pullIntoDescriptor.readerType === "default") { + readableStreamFulfillReadRequest(stream, filledView, done); + } else { + assert(pullIntoDescriptor.readerType === "byob"); + readableStreamFulfillReadIntoRequest(stream, filledView, done); + } +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} view + */ +function readableByteStreamControllerRespondWithNewView(controller, view) { + assert(controller[_pendingPullIntos].length !== 0); + assert(!isDetachedBuffer(view.buffer)); + const firstDescriptor = controller[_pendingPullIntos][0]; + const state = controller[_stream][_state]; + if (state === "closed") { + if (view.byteLength !== 0) { + throw new TypeError( + "The view's length must be 0 when calling respondWithNewView() on a closed stream", + ); + } + } else { + assert(state === "readable"); + if (view.byteLength === 0) { + throw new TypeError( + "The view's length must be greater than 0 when calling respondWithNewView() on a readable stream", + ); } - return ready; } - - /** - * @param {ReadableByteStreamController} controller - * @param {ReadRequest} readRequest - * @returns {void} - */ - function readableByteStreamControllerFillReadRequestFromQueue( - controller, - readRequest, + if ( + (firstDescriptor.byteOffset + firstDescriptor.bytesFilled) !== + view.byteOffset ) { - assert(controller[_queueTotalSize] > 0); - const entry = ArrayPrototypeShift(controller[_queue]); - controller[_queueTotalSize] -= entry.byteLength; - readableByteStreamControllerHandleQueueDrain(controller); - const view = new Uint8Array( - entry.buffer, - entry.byteOffset, - entry.byteLength, - ); - readRequest.chunkSteps(view); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} size - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - size, - pullIntoDescriptor, - ) { - assert( - controller[_pendingPullIntos].length === 0 || - controller[_pendingPullIntos][0] === pullIntoDescriptor, - ); - assert(controller[_byobRequest] === null); - pullIntoDescriptor.bytesFilled += size; - } - - /** - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {ArrayBufferView} - */ - function readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, - ) { - const bytesFilled = pullIntoDescriptor.bytesFilled; - const elementSize = pullIntoDescriptor.elementSize; - assert(bytesFilled <= pullIntoDescriptor.byteLength); - assert((bytesFilled % elementSize) === 0); - const buffer = transferArrayBuffer(pullIntoDescriptor.buffer); - return new pullIntoDescriptor.viewConstructor( - buffer, - pullIntoDescriptor.byteOffset, - bytesFilled / elementSize, + throw new RangeError( + "The region specified by view does not match byobRequest", ); } + if (firstDescriptor.bufferByteLength !== view.buffer.byteLength) { + throw new RangeError( + "The buffer of view has different capacity than byobRequest", + ); + } + if ( + (firstDescriptor.bytesFilled + view.byteLength) > + firstDescriptor.byteLength + ) { + throw new RangeError( + "The region specified by view is larger than byobRequest", + ); + } + const viewByteLength = view.byteLength; + firstDescriptor.buffer = transferArrayBuffer(view.buffer); + readableByteStreamControllerRespondInternal(controller, viewByteLength); +} - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - * @param {ReadRequest} readRequest - * @returns {void} - */ - function readableStreamDefaultReaderRead(reader, readRequest) { - const stream = reader[_stream]; - assert(stream); - stream[_disturbed] = true; - if (stream[_state] === "closed") { - readRequest.closeSteps(); - } else if (stream[_state] === "errored") { - readRequest.errorSteps(stream[_storedError]); +/** + * @param {ReadableByteStreamController} controller + * @returns {PullIntoDescriptor} + */ +function readableByteStreamControllerShiftPendingPullInto(controller) { + assert(controller[_byobRequest] === null); + return ArrayPrototypeShift(controller[_pendingPullIntos]); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {boolean} + */ +function readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, +) { + const elementSize = pullIntoDescriptor.elementSize; + const currentAlignedBytes = pullIntoDescriptor.bytesFilled - + (pullIntoDescriptor.bytesFilled % elementSize); + const maxBytesToCopy = MathMin( + controller[_queueTotalSize], + pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, + ); + const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; + const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); + let totalBytesToCopyRemaining = maxBytesToCopy; + let ready = false; + if (maxAlignedBytes > currentAlignedBytes) { + totalBytesToCopyRemaining = maxAlignedBytes - + pullIntoDescriptor.bytesFilled; + ready = true; + } + const queue = controller[_queue]; + while (totalBytesToCopyRemaining > 0) { + const headOfQueue = queue[0]; + const bytesToCopy = MathMin( + totalBytesToCopyRemaining, + headOfQueue.byteLength, + ); + const destStart = pullIntoDescriptor.byteOffset + + pullIntoDescriptor.bytesFilled; + + const destBuffer = new Uint8Array( + pullIntoDescriptor.buffer, + destStart, + bytesToCopy, + ); + const srcBuffer = new Uint8Array( + headOfQueue.buffer, + headOfQueue.byteOffset, + bytesToCopy, + ); + destBuffer.set(srcBuffer); + + if (headOfQueue.byteLength === bytesToCopy) { + ArrayPrototypeShift(queue); } else { - assert(stream[_state] === "readable"); - stream[_controller][_pullSteps](readRequest); + headOfQueue.byteOffset += bytesToCopy; + headOfQueue.byteLength -= bytesToCopy; } + controller[_queueTotalSize] -= bytesToCopy; + readableByteStreamControllerFillHeadPullIntoDescriptor( + controller, + bytesToCopy, + pullIntoDescriptor, + ); + totalBytesToCopyRemaining -= bytesToCopy; } - - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - */ - function readableStreamDefaultReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamDefaultReaderErrorReadRequests(reader, e); + if (!ready) { + assert(controller[_queueTotalSize] === 0); + assert(pullIntoDescriptor.bytesFilled > 0); + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); } + return ready; +} - /** - * @template R - * @param {ReadableStream} stream - * @param {any} e - */ - function readableStreamError(stream, e) { +/** + * @param {ReadableByteStreamController} controller + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableByteStreamControllerFillReadRequestFromQueue( + controller, + readRequest, +) { + assert(controller[_queueTotalSize] > 0); + const entry = ArrayPrototypeShift(controller[_queue]); + controller[_queueTotalSize] -= entry.byteLength; + readableByteStreamControllerHandleQueueDrain(controller); + const view = new Uint8Array( + entry.buffer, + entry.byteOffset, + entry.byteLength, + ); + readRequest.chunkSteps(view); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} size + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerFillHeadPullIntoDescriptor( + controller, + size, + pullIntoDescriptor, +) { + assert( + controller[_pendingPullIntos].length === 0 || + controller[_pendingPullIntos][0] === pullIntoDescriptor, + ); + assert(controller[_byobRequest] === null); + pullIntoDescriptor.bytesFilled += size; +} + +/** + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {ArrayBufferView} + */ +function readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, +) { + const bytesFilled = pullIntoDescriptor.bytesFilled; + const elementSize = pullIntoDescriptor.elementSize; + assert(bytesFilled <= pullIntoDescriptor.byteLength); + assert((bytesFilled % elementSize) === 0); + const buffer = transferArrayBuffer(pullIntoDescriptor.buffer); + return new pullIntoDescriptor.viewConstructor( + buffer, + pullIntoDescriptor.byteOffset, + bytesFilled / elementSize, + ); +} + +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableStreamDefaultReaderRead(reader, readRequest) { + const stream = reader[_stream]; + assert(stream); + stream[_disturbed] = true; + if (stream[_state] === "closed") { + readRequest.closeSteps(); + } else if (stream[_state] === "errored") { + readRequest.errorSteps(stream[_storedError]); + } else { assert(stream[_state] === "readable"); - stream[_state] = "errored"; - stream[_storedError] = e; - /** @type {ReadableStreamDefaultReader | undefined} */ - const reader = stream[_reader]; - if (reader === undefined) { - return; - } - /** @type {Deferred} */ - const closedPromise = reader[_closedPromise]; - closedPromise.reject(e); - setPromiseIsHandledToTrue(closedPromise.promise); - if (isReadableStreamDefaultReader(reader)) { - readableStreamDefaultReaderErrorReadRequests(reader, e); - } else { - assert(isReadableStreamBYOBReader(reader)); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); - } + stream[_controller][_pullSteps](readRequest); } +} - /** - * @template R - * @param {ReadableStream} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadIntoRequest(stream, chunk, done) { - assert(readableStreamHasBYOBReader(stream)); - /** @type {ReadableStreamDefaultReader} */ - const reader = stream[_reader]; - assert(reader[_readIntoRequests].length !== 0); - /** @type {ReadIntoRequest} */ - const readIntoRequest = ArrayPrototypeShift(reader[_readIntoRequests]); - if (done) { - readIntoRequest.closeSteps(chunk); - } else { - readIntoRequest.chunkSteps(chunk); - } +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + */ +function readableStreamDefaultReaderRelease(reader) { + readableStreamReaderGenericRelease(reader); + const e = new TypeError("The reader was released."); + readableStreamDefaultReaderErrorReadRequests(reader, e); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {any} e + */ +function readableStreamError(stream, e) { + assert(stream[_state] === "readable"); + stream[_state] = "errored"; + stream[_storedError] = e; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (reader === undefined) { + return; } - - /** - * @template R - * @param {ReadableStream} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadRequest(stream, chunk, done) { - assert(readableStreamHasDefaultReader(stream) === true); - /** @type {ReadableStreamDefaultReader} */ - const reader = stream[_reader]; - assert(reader[_readRequests].length); - /** @type {ReadRequest} */ - const readRequest = ArrayPrototypeShift(reader[_readRequests]); - if (done) { - readRequest.closeSteps(); - } else { - readRequest.chunkSteps(chunk); - } + /** @type {Deferred} */ + const closedPromise = reader[_closedPromise]; + closedPromise.reject(e); + setPromiseIsHandledToTrue(closedPromise.promise); + if (isReadableStreamDefaultReader(reader)) { + readableStreamDefaultReaderErrorReadRequests(reader, e); + } else { + assert(isReadableStreamBYOBReader(reader)); + readableStreamBYOBReaderErrorReadIntoRequests(reader, e); } +} - /** - * @param {ReadableStream} stream - * @return {number} - */ - function readableStreamGetNumReadIntoRequests(stream) { - assert(readableStreamHasBYOBReader(stream) === true); - return stream[_reader][_readIntoRequests].length; +/** + * @template R + * @param {ReadableStream} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadIntoRequest(stream, chunk, done) { + assert(readableStreamHasBYOBReader(stream)); + /** @type {ReadableStreamDefaultReader} */ + const reader = stream[_reader]; + assert(reader[_readIntoRequests].length !== 0); + /** @type {ReadIntoRequest} */ + const readIntoRequest = ArrayPrototypeShift(reader[_readIntoRequests]); + if (done) { + readIntoRequest.closeSteps(chunk); + } else { + readIntoRequest.chunkSteps(chunk); } +} - /** - * @param {ReadableStream} stream - * @return {number} - */ - function readableStreamGetNumReadRequests(stream) { - assert(readableStreamHasDefaultReader(stream) === true); - return stream[_reader][_readRequests].length; +/** + * @template R + * @param {ReadableStream} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadRequest(stream, chunk, done) { + assert(readableStreamHasDefaultReader(stream) === true); + /** @type {ReadableStreamDefaultReader} */ + const reader = stream[_reader]; + assert(reader[_readRequests].length); + /** @type {ReadRequest} */ + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + if (done) { + readRequest.closeSteps(); + } else { + readRequest.chunkSteps(chunk); } +} - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function readableStreamHasBYOBReader(stream) { - const reader = stream[_reader]; - if (reader === undefined) { - return false; - } - if (isReadableStreamBYOBReader(reader)) { - return true; - } +/** + * @param {ReadableStream} stream + * @return {number} + */ +function readableStreamGetNumReadIntoRequests(stream) { + assert(readableStreamHasBYOBReader(stream) === true); + return stream[_reader][_readIntoRequests].length; +} + +/** + * @param {ReadableStream} stream + * @return {number} + */ +function readableStreamGetNumReadRequests(stream) { + assert(readableStreamHasDefaultReader(stream) === true); + return stream[_reader][_readRequests].length; +} + +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function readableStreamHasBYOBReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { return false; } + if (isReadableStreamBYOBReader(reader)) { + return true; + } + return false; +} - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function readableStreamHasDefaultReader(stream) { - const reader = stream[_reader]; - if (reader === undefined) { - return false; - } - if (isReadableStreamDefaultReader(reader)) { - return true; - } +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function readableStreamHasDefaultReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { return false; } + if (isReadableStreamDefaultReader(reader)) { + return true; + } + return false; +} - /** - * @template T - * @param {ReadableStream} source - * @param {WritableStream} dest - * @param {boolean} preventClose - * @param {boolean} preventAbort - * @param {boolean} preventCancel - * @param {AbortSignal=} signal - * @returns {Promise} - */ - function readableStreamPipeTo( - source, - dest, - preventClose, - preventAbort, - preventCancel, - signal, - ) { - assert(isReadableStream(source)); - assert(isWritableStream(dest)); - assert( - typeof preventClose === "boolean" && typeof preventAbort === "boolean" && - typeof preventCancel === "boolean", - ); - assert( - signal === undefined || - ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, signal), - ); - assert(!isReadableStreamLocked(source)); - assert(!isWritableStreamLocked(dest)); - // We use acquireReadableStreamDefaultReader even in case of ReadableByteStreamController - // as the spec allows us, and the only reason to use BYOBReader is to do some smart things - // with it, but the spec does not specify what things, so to simplify we stick to DefaultReader. - const reader = acquireReadableStreamDefaultReader(source); - const writer = acquireWritableStreamDefaultWriter(dest); - source[_disturbed] = true; - let shuttingDown = false; - let currentWrite = resolvePromiseWith(undefined); - /** @type {Deferred} */ - const promise = new Deferred(); - /** @type {() => void} */ - let abortAlgorithm; - if (signal) { - abortAlgorithm = () => { - const error = signal.reason; - /** @type {Array<() => Promise>} */ - const actions = []; - if (preventAbort === false) { - ArrayPrototypePush(actions, () => { - if (dest[_state] === "writable") { - return writableStreamAbort(dest, error); - } else { - return resolvePromiseWith(undefined); - } - }); - } - if (preventCancel === false) { - ArrayPrototypePush(actions, () => { - if (source[_state] === "readable") { - return readableStreamCancel(source, error); - } else { - return resolvePromiseWith(undefined); - } - }); - } - shutdownWithAction( - () => - SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), - true, - error, - ); - }; - - if (signal.aborted) { - abortAlgorithm(); - return promise.promise; - } - signal[add](abortAlgorithm); - } - - function pipeLoop() { - return new Promise((resolveLoop, rejectLoop) => { - /** @param {boolean} done */ - function next(done) { - if (done) { - resolveLoop(); +/** + * @template T + * @param {ReadableStream} source + * @param {WritableStream} dest + * @param {boolean} preventClose + * @param {boolean} preventAbort + * @param {boolean} preventCancel + * @param {AbortSignal=} signal + * @returns {Promise} + */ +function readableStreamPipeTo( + source, + dest, + preventClose, + preventAbort, + preventCancel, + signal, +) { + assert(isReadableStream(source)); + assert(isWritableStream(dest)); + assert( + typeof preventClose === "boolean" && typeof preventAbort === "boolean" && + typeof preventCancel === "boolean", + ); + assert( + signal === undefined || + ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, signal), + ); + assert(!isReadableStreamLocked(source)); + assert(!isWritableStreamLocked(dest)); + // We use acquireReadableStreamDefaultReader even in case of ReadableByteStreamController + // as the spec allows us, and the only reason to use BYOBReader is to do some smart things + // with it, but the spec does not specify what things, so to simplify we stick to DefaultReader. + const reader = acquireReadableStreamDefaultReader(source); + const writer = acquireWritableStreamDefaultWriter(dest); + source[_disturbed] = true; + let shuttingDown = false; + let currentWrite = resolvePromiseWith(undefined); + /** @type {Deferred} */ + const promise = new Deferred(); + /** @type {() => void} */ + let abortAlgorithm; + if (signal) { + abortAlgorithm = () => { + const error = signal.reason; + /** @type {Array<() => Promise>} */ + const actions = []; + if (preventAbort === false) { + ArrayPrototypePush(actions, () => { + if (dest[_state] === "writable") { + return writableStreamAbort(dest, error); } else { - uponPromise(pipeStep(), next, rejectLoop); + return resolvePromiseWith(undefined); } - } - next(false); - }); - } - - /** @returns {Promise} */ - function pipeStep() { - if (shuttingDown === true) { - return resolvePromiseWith(true); - } - - return transformPromiseWith(writer[_readyPromise].promise, () => { - return new Promise((resolveRead, rejectRead) => { - readableStreamDefaultReaderRead( - reader, - { - chunkSteps(chunk) { - currentWrite = transformPromiseWith( - writableStreamDefaultWriterWrite(writer, chunk), - undefined, - () => {}, - ); - resolveRead(false); - }, - closeSteps() { - resolveRead(true); - }, - errorSteps: rejectRead, - }, - ); }); - }); + } + if (preventCancel === false) { + ArrayPrototypePush(actions, () => { + if (source[_state] === "readable") { + return readableStreamCancel(source, error); + } else { + return resolvePromiseWith(undefined); + } + }); + } + shutdownWithAction( + () => SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), + true, + error, + ); + }; + + if (signal.aborted) { + abortAlgorithm(); + return promise.promise; + } + signal[add](abortAlgorithm); + } + + function pipeLoop() { + return new Promise((resolveLoop, rejectLoop) => { + /** @param {boolean} done */ + function next(done) { + if (done) { + resolveLoop(); + } else { + uponPromise(pipeStep(), next, rejectLoop); + } + } + next(false); + }); + } + + /** @returns {Promise} */ + function pipeStep() { + if (shuttingDown === true) { + return resolvePromiseWith(true); } - isOrBecomesErrored( - source, - reader[_closedPromise].promise, - (storedError) => { - if (preventAbort === false) { - shutdownWithAction( - () => writableStreamAbort(dest, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); - } - }, - ); + return transformPromiseWith(writer[_readyPromise].promise, () => { + return new Promise((resolveRead, rejectRead) => { + readableStreamDefaultReaderRead( + reader, + { + chunkSteps(chunk) { + currentWrite = transformPromiseWith( + writableStreamDefaultWriterWrite(writer, chunk), + undefined, + () => {}, + ); + resolveRead(false); + }, + closeSteps() { + resolveRead(true); + }, + errorSteps: rejectRead, + }, + ); + }); + }); + } - isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { - if (preventCancel === false) { + isOrBecomesErrored( + source, + reader[_closedPromise].promise, + (storedError) => { + if (preventAbort === false) { shutdownWithAction( - () => readableStreamCancel(source, storedError), + () => writableStreamAbort(dest, storedError), true, storedError, ); } else { shutdown(true, storedError); } - }); + }, + ); - isOrBecomesClosed(source, reader[_closedPromise].promise, () => { - if (preventClose === false) { - shutdownWithAction(() => - writableStreamDefaultWriterCloseWithErrorPropagation(writer) - ); - } else { - shutdown(); - } - }); + isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, storedError), + true, + storedError, + ); + } else { + shutdown(true, storedError); + } + }); + + isOrBecomesClosed(source, reader[_closedPromise].promise, () => { + if (preventClose === false) { + shutdownWithAction(() => + writableStreamDefaultWriterCloseWithErrorPropagation(writer) + ); + } else { + shutdown(); + } + }); + + if ( + writableStreamCloseQueuedOrInFlight(dest) === true || + dest[_state] === "closed" + ) { + const destClosed = new TypeError( + "The destination writable stream closed before all the data could be piped to it.", + ); + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, destClosed), + true, + destClosed, + ); + } else { + shutdown(true, destClosed); + } + } + + setPromiseIsHandledToTrue(pipeLoop()); + + return promise.promise; + + /** @returns {Promise} */ + function waitForWritesToFinish() { + const oldCurrentWrite = currentWrite; + return transformPromiseWith( + currentWrite, + () => + oldCurrentWrite !== currentWrite ? waitForWritesToFinish() : undefined, + ); + } + + /** + * @param {ReadableStream | WritableStream} stream + * @param {Promise} promise + * @param {(e: any) => void} action + */ + function isOrBecomesErrored(stream, promise, action) { + if (stream[_state] === "errored") { + action(stream[_storedError]); + } else { + uponRejection(promise, action); + } + } + + /** + * @param {ReadableStream} stream + * @param {Promise} promise + * @param {() => void} action + */ + function isOrBecomesClosed(stream, promise, action) { + if (stream[_state] === "closed") { + action(); + } else { + uponFulfillment(promise, action); + } + } + + /** + * @param {() => Promise} action + * @param {boolean=} originalIsError + * @param {any=} originalError + */ + function shutdownWithAction(action, originalIsError, originalError) { + function doTheRest() { + uponPromise( + action(), + () => finalize(originalIsError, originalError), + (newError) => finalize(true, newError), + ); + } + + if (shuttingDown === true) { + return; + } + shuttingDown = true; if ( - writableStreamCloseQueuedOrInFlight(dest) === true || - dest[_state] === "closed" + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false ) { - const destClosed = new TypeError( - "The destination writable stream closed before all the data could be piped to it.", - ); - if (preventCancel === false) { - shutdownWithAction( - () => readableStreamCancel(source, destClosed), - true, - destClosed, - ); - } else { - shutdown(true, destClosed); - } - } - - setPromiseIsHandledToTrue(pipeLoop()); - - return promise.promise; - - /** @returns {Promise} */ - function waitForWritesToFinish() { - const oldCurrentWrite = currentWrite; - return transformPromiseWith( - currentWrite, - () => - oldCurrentWrite !== currentWrite - ? waitForWritesToFinish() - : undefined, - ); - } - - /** - * @param {ReadableStream | WritableStream} stream - * @param {Promise} promise - * @param {(e: any) => void} action - */ - function isOrBecomesErrored(stream, promise, action) { - if (stream[_state] === "errored") { - action(stream[_storedError]); - } else { - uponRejection(promise, action); - } - } - - /** - * @param {ReadableStream} stream - * @param {Promise} promise - * @param {() => void} action - */ - function isOrBecomesClosed(stream, promise, action) { - if (stream[_state] === "closed") { - action(); - } else { - uponFulfillment(promise, action); - } - } - - /** - * @param {() => Promise} action - * @param {boolean=} originalIsError - * @param {any=} originalError - */ - function shutdownWithAction(action, originalIsError, originalError) { - function doTheRest() { - uponPromise( - action(), - () => finalize(originalIsError, originalError), - (newError) => finalize(true, newError), - ); - } - - if (shuttingDown === true) { - return; - } - shuttingDown = true; - - if ( - dest[_state] === "writable" && - writableStreamCloseQueuedOrInFlight(dest) === false - ) { - uponFulfillment(waitForWritesToFinish(), doTheRest); - } else { - doTheRest(); - } - } - - /** - * @param {boolean=} isError - * @param {any=} error - */ - function shutdown(isError, error) { - if (shuttingDown) { - return; - } - shuttingDown = true; - if ( - dest[_state] === "writable" && - writableStreamCloseQueuedOrInFlight(dest) === false - ) { - uponFulfillment( - waitForWritesToFinish(), - () => finalize(isError, error), - ); - } else { - finalize(isError, error); - } - } - - /** - * @param {boolean=} isError - * @param {any=} error - */ - function finalize(isError, error) { - writableStreamDefaultWriterRelease(writer); - readableStreamDefaultReaderRelease(reader); - - if (signal !== undefined) { - signal[remove](abortAlgorithm); - } - if (isError) { - promise.reject(error); - } else { - promise.resolve(undefined); - } - } - } - - /** - * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader - * @param {any} reason - * @returns {Promise} - */ - function readableStreamReaderGenericCancel(reader, reason) { - const stream = reader[_stream]; - assert(stream !== undefined); - return readableStreamCancel(stream, reason); - } - - /** - * @template R - * @param {ReadableStreamDefaultReader | ReadableStreamBYOBReader} reader - * @param {ReadableStream} stream - */ - function readableStreamReaderGenericInitialize(reader, stream) { - reader[_stream] = stream; - stream[_reader] = reader; - if (stream[_state] === "readable") { - reader[_closedPromise] = new Deferred(); - } else if (stream[_state] === "closed") { - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].resolve(undefined); + uponFulfillment(waitForWritesToFinish(), doTheRest); } else { - assert(stream[_state] === "errored"); - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(reader[_closedPromise].promise); + doTheRest(); } } /** - * @template R - * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader + * @param {boolean=} isError + * @param {any=} error */ - function readableStreamReaderGenericRelease(reader) { - const stream = reader[_stream]; - assert(stream !== undefined); - assert(stream[_reader] === reader); - if (stream[_state] === "readable") { - reader[_closedPromise].reject( - new TypeError( - "Reader was released and can no longer be used to monitor the stream's closedness.", - ), + function shutdown(isError, error) { + if (shuttingDown) { + return; + } + shuttingDown = true; + if ( + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false + ) { + uponFulfillment( + waitForWritesToFinish(), + () => finalize(isError, error), ); } else { - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].reject( - new TypeError( - "Reader was released and can no longer be used to monitor the stream's closedness.", - ), - ); + finalize(isError, error); } + } + + /** + * @param {boolean=} isError + * @param {any=} error + */ + function finalize(isError, error) { + writableStreamDefaultWriterRelease(writer); + readableStreamDefaultReaderRelease(reader); + + if (signal !== undefined) { + signal[remove](abortAlgorithm); + } + if (isError) { + promise.reject(error); + } else { + promise.resolve(undefined); + } + } +} + +/** + * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader + * @param {any} reason + * @returns {Promise} + */ +function readableStreamReaderGenericCancel(reader, reason) { + const stream = reader[_stream]; + assert(stream !== undefined); + return readableStreamCancel(stream, reason); +} + +/** + * @template R + * @param {ReadableStreamDefaultReader | ReadableStreamBYOBReader} reader + * @param {ReadableStream} stream + */ +function readableStreamReaderGenericInitialize(reader, stream) { + reader[_stream] = stream; + stream[_reader] = reader; + if (stream[_state] === "readable") { + reader[_closedPromise] = new Deferred(); + } else if (stream[_state] === "closed") { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].resolve(undefined); + } else { + assert(stream[_state] === "errored"); + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject(stream[_storedError]); setPromiseIsHandledToTrue(reader[_closedPromise].promise); - stream[_controller][_releaseSteps](); - stream[_reader] = undefined; - reader[_stream] = undefined; } +} - /** - * @param {ReadableStreamBYOBReader} reader - * @param {any} e - */ - function readableStreamBYOBReaderErrorReadIntoRequests(reader, e) { - const readIntoRequests = reader[_readIntoRequests]; - reader[_readIntoRequests] = []; - for (let i = 0; i < readIntoRequests.length; ++i) { - const readIntoRequest = readIntoRequests[i]; - readIntoRequest.errorSteps(e); +/** + * @template R + * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader + */ +function readableStreamReaderGenericRelease(reader) { + const stream = reader[_stream]; + assert(stream !== undefined); + assert(stream[_reader] === reader); + if (stream[_state] === "readable") { + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } else { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } + setPromiseIsHandledToTrue(reader[_closedPromise].promise); + stream[_controller][_releaseSteps](); + stream[_reader] = undefined; + reader[_stream] = undefined; +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {any} e + */ +function readableStreamBYOBReaderErrorReadIntoRequests(reader, e) { + const readIntoRequests = reader[_readIntoRequests]; + reader[_readIntoRequests] = []; + for (let i = 0; i < readIntoRequests.length; ++i) { + const readIntoRequest = readIntoRequests[i]; + readIntoRequest.errorSteps(e); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream, ReadableStream]} + */ +function readableStreamTee(stream, cloneForBranch2) { + assert(isReadableStream(stream)); + assert(typeof cloneForBranch2 === "boolean"); + if ( + ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + ) + ) { + return readableByteStreamTee(stream); + } else { + return readableStreamDefaultTee(stream, cloneForBranch2); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream, ReadableStream]} + */ +function readableStreamDefaultTee(stream, cloneForBranch2) { + assert(isReadableStream(stream)); + assert(typeof cloneForBranch2 === "boolean"); + const reader = acquireReadableStreamDefaultReader(stream); + let reading = false; + let readAgain = false; + let canceled1 = false; + let canceled2 = false; + /** @type {any} */ + let reason1; + /** @type {any} */ + let reason2; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch1; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch2; + + /** @type {Deferred} */ + const cancelPromise = new Deferred(); + + function pullAlgorithm() { + if (reading === true) { + readAgain = true; + return resolvePromiseWith(undefined); } - } + reading = true; + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(value) { + queueMicrotask(() => { + readAgain = false; + const value1 = value; + const value2 = value; - /** - * @template R - * @param {ReadableStream} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream, ReadableStream]} - */ - function readableStreamTee(stream, cloneForBranch2) { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); - if ( - ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - ) - ) { - return readableByteStreamTee(stream); - } else { - return readableStreamDefaultTee(stream, cloneForBranch2); - } - } + // TODO(lucacasonato): respect clonedForBranch2. - /** - * @template R - * @param {ReadableStream} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream, ReadableStream]} - */ - function readableStreamDefaultTee(stream, cloneForBranch2) { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); - const reader = acquireReadableStreamDefaultReader(stream); - let reading = false; - let readAgain = false; - let canceled1 = false; - let canceled2 = false; - /** @type {any} */ - let reason1; - /** @type {any} */ - let reason2; - /** @type {ReadableStream} */ - // deno-lint-ignore prefer-const - let branch1; - /** @type {ReadableStream} */ - // deno-lint-ignore prefer-const - let branch2; - - /** @type {Deferred} */ - const cancelPromise = new Deferred(); - - function pullAlgorithm() { - if (reading === true) { - readAgain = true; - return resolvePromiseWith(undefined); - } - reading = true; - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(value) { - queueMicrotask(() => { - readAgain = false; - const value1 = value; - const value2 = value; - - // TODO(lucacasonato): respect clonedForBranch2. - - if (canceled1 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ branch1[ - _controller - ], - value1, - ); - } - if (canceled2 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ branch2[ - _controller - ], - value2, - ); - } - - reading = false; - if (readAgain === true) { - pullAlgorithm(); - } - }); - }, - closeSteps() { - reading = false; if (canceled1 === false) { - readableStreamDefaultControllerClose( + readableStreamDefaultControllerEnqueue( /** @type {ReadableStreamDefaultController} */ branch1[ _controller ], + value1, ); } if (canceled2 === false) { - readableStreamDefaultControllerClose( + readableStreamDefaultControllerEnqueue( /** @type {ReadableStreamDefaultController} */ branch2[ _controller ], + value2, ); } - if (canceled1 === false || canceled2 === false) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { + reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - return resolvePromiseWith(undefined); - } - - /** - * @param {any} reason - * @returns {Promise} - */ - function cancel1Algorithm(reason) { - canceled1 = true; - reason1 = reason; - if (canceled2 === true) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - /** - * @param {any} reason - * @returns {Promise} - */ - function cancel2Algorithm(reason) { - canceled2 = true; - reason2 = reason; - if (canceled1 === true) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function startAlgorithm() {} - - branch1 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel1Algorithm, - ); - branch2 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel2Algorithm, - ); - - uponRejection(reader[_closedPromise].promise, (r) => { - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ branch1[ - _controller - ], - r, - ); - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ branch2[ - _controller - ], - r, - ); - if (canceled1 === false || canceled2 === false) { - cancelPromise.resolve(undefined); - } - }); - - return [branch1, branch2]; + if (readAgain === true) { + pullAlgorithm(); + } + }); + }, + closeSteps() { + reading = false; + if (canceled1 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ branch1[ + _controller + ], + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ branch2[ + _controller + ], + ); + } + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + return resolvePromiseWith(undefined); } /** - * @template R - * @param {ReadableStream} stream - * @returns {[ReadableStream, ReadableStream]} + * @param {any} reason + * @returns {Promise} */ - function readableByteStreamTee(stream) { - assert(isReadableStream(stream)); - assert( - ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - ), - ); - let reader = acquireReadableStreamDefaultReader(stream); - let reading = false; - let readAgainForBranch1 = false; - let readAgainForBranch2 = false; - let canceled1 = false; - let canceled2 = false; - let reason1 = undefined; - let reason2 = undefined; - let branch1 = undefined; - let branch2 = undefined; - /** @type {Deferred} */ - const cancelPromise = new Deferred(); + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } - /** - * @param {ReadableStreamBYOBReader} thisReader - */ - function forwardReaderError(thisReader) { - PromisePrototypeCatch(thisReader[_closedPromise].promise, (e) => { - if (thisReader !== reader) { - return; + /** + * @param {any} reason + * @returns {Promise} + */ + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } + + function startAlgorithm() {} + + branch1 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel1Algorithm, + ); + branch2 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel2Algorithm, + ); + + uponRejection(reader[_closedPromise].promise, (r) => { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ branch1[ + _controller + ], + r, + ); + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ branch2[ + _controller + ], + r, + ); + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }); + + return [branch1, branch2]; +} + +/** + * @template R + * @param {ReadableStream} stream + * @returns {[ReadableStream, ReadableStream]} + */ +function readableByteStreamTee(stream) { + assert(isReadableStream(stream)); + assert( + ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + ), + ); + let reader = acquireReadableStreamDefaultReader(stream); + let reading = false; + let readAgainForBranch1 = false; + let readAgainForBranch2 = false; + let canceled1 = false; + let canceled2 = false; + let reason1 = undefined; + let reason2 = undefined; + let branch1 = undefined; + let branch2 = undefined; + /** @type {Deferred} */ + const cancelPromise = new Deferred(); + + /** + * @param {ReadableStreamBYOBReader} thisReader + */ + function forwardReaderError(thisReader) { + PromisePrototypeCatch(thisReader[_closedPromise].promise, (e) => { + if (thisReader !== reader) { + return; + } + readableByteStreamControllerError(branch1[_controller], e); + readableByteStreamControllerError(branch2[_controller], e); + if (!canceled1 || !canceled2) { + cancelPromise.resolve(undefined); + } + }); + } + + function pullWithDefaultReader() { + if (isReadableStreamBYOBReader(reader)) { + assert(reader[_readIntoRequests].length === 0); + readableStreamBYOBReaderRelease(reader); + reader = acquireReadableStreamDefaultReader(stream); + forwardReaderError(reader); + } + + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + queueMicrotask(() => { + readAgainForBranch1 = false; + readAgainForBranch2 = false; + const chunk1 = chunk; + let chunk2 = chunk; + if (!canceled1 && !canceled2) { + try { + chunk2 = cloneAsUint8Array(chunk); + } catch (e) { + readableByteStreamControllerError(branch1[_controller], e); + readableByteStreamControllerError(branch2[_controller], e); + cancelPromise.resolve(readableStreamCancel(stream, e)); + return; + } + } + if (!canceled1) { + readableByteStreamControllerEnqueue(branch1[_controller], chunk1); + } + if (!canceled2) { + readableByteStreamControllerEnqueue(branch2[_controller], chunk2); + } + reading = false; + if (readAgainForBranch1) { + pull1Algorithm(); + } else if (readAgainForBranch2) { + pull2Algorithm(); + } + }); + }, + closeSteps() { + reading = false; + if (!canceled1) { + readableByteStreamControllerClose(branch1[_controller]); + } + if (!canceled2) { + readableByteStreamControllerClose(branch2[_controller]); + } + if (branch1[_controller][_pendingPullIntos].length !== 0) { + readableByteStreamControllerRespond(branch1[_controller], 0); + } + if (branch2[_controller][_pendingPullIntos].length !== 0) { + readableByteStreamControllerRespond(branch2[_controller], 0); } - readableByteStreamControllerError(branch1[_controller], e); - readableByteStreamControllerError(branch2[_controller], e); if (!canceled1 || !canceled2) { cancelPromise.resolve(undefined); } - }); + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + } + + function pullWithBYOBReader(view, forBranch2) { + if (isReadableStreamDefaultReader(reader)) { + assert(reader[_readRequests].length === 0); + readableStreamDefaultReaderRelease(reader); + reader = acquireReadableStreamBYOBReader(stream); + forwardReaderError(reader); } + const byobBranch = forBranch2 ? branch2 : branch1; + const otherBranch = forBranch2 ? branch1 : branch2; - function pullWithDefaultReader() { - if (isReadableStreamBYOBReader(reader)) { - assert(reader[_readIntoRequests].length === 0); - readableStreamBYOBReaderRelease(reader); - reader = acquireReadableStreamDefaultReader(stream); - forwardReaderError(reader); - } - - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - queueMicrotask(() => { - readAgainForBranch1 = false; - readAgainForBranch2 = false; - const chunk1 = chunk; - let chunk2 = chunk; - if (!canceled1 && !canceled2) { - try { - chunk2 = cloneAsUint8Array(chunk); - } catch (e) { - readableByteStreamControllerError(branch1[_controller], e); - readableByteStreamControllerError(branch2[_controller], e); - cancelPromise.resolve(readableStreamCancel(stream, e)); - return; - } - } - if (!canceled1) { - readableByteStreamControllerEnqueue(branch1[_controller], chunk1); - } - if (!canceled2) { - readableByteStreamControllerEnqueue(branch2[_controller], chunk2); - } - reading = false; - if (readAgainForBranch1) { - pull1Algorithm(); - } else if (readAgainForBranch2) { - pull2Algorithm(); - } - }); - }, - closeSteps() { - reading = false; - if (!canceled1) { - readableByteStreamControllerClose(branch1[_controller]); - } - if (!canceled2) { - readableByteStreamControllerClose(branch2[_controller]); - } - if (branch1[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch1[_controller], 0); - } - if (branch2[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch2[_controller], 0); - } - if (!canceled1 || !canceled2) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { - reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - } - - function pullWithBYOBReader(view, forBranch2) { - if (isReadableStreamDefaultReader(reader)) { - assert(reader[_readRequests].length === 0); - readableStreamDefaultReaderRelease(reader); - reader = acquireReadableStreamBYOBReader(stream); - forwardReaderError(reader); - } - const byobBranch = forBranch2 ? branch2 : branch1; - const otherBranch = forBranch2 ? branch1 : branch2; - - /** @type {ReadIntoRequest} */ - const readIntoRequest = { - chunkSteps(chunk) { - queueMicrotask(() => { - readAgainForBranch1 = false; - readAgainForBranch2 = false; - const byobCanceled = forBranch2 ? canceled2 : canceled1; - const otherCanceled = forBranch2 ? canceled1 : canceled2; - if (!otherCanceled) { - let clonedChunk; - try { - clonedChunk = cloneAsUint8Array(chunk); - } catch (e) { - readableByteStreamControllerError(byobBranch[_controller], e); - readableByteStreamControllerError(otherBranch[_controller], e); - cancelPromise.resolve(readableStreamCancel(stream, e)); - return; - } - if (!byobCanceled) { - readableByteStreamControllerRespondWithNewView( - byobBranch[_controller], - chunk, - ); - } - readableByteStreamControllerEnqueue( - otherBranch[_controller], - clonedChunk, - ); - } else if (!byobCanceled) { - readableByteStreamControllerRespondWithNewView( - byobBranch[_controller], - chunk, - ); - } - reading = false; - if (readAgainForBranch1) { - pull1Algorithm(); - } else if (readAgainForBranch2) { - pull2Algorithm(); - } - }); - }, - closeSteps(chunk) { - reading = false; + /** @type {ReadIntoRequest} */ + const readIntoRequest = { + chunkSteps(chunk) { + queueMicrotask(() => { + readAgainForBranch1 = false; + readAgainForBranch2 = false; const byobCanceled = forBranch2 ? canceled2 : canceled1; const otherCanceled = forBranch2 ? canceled1 : canceled2; - if (!byobCanceled) { - readableByteStreamControllerClose(byobBranch[_controller]); - } if (!otherCanceled) { - readableByteStreamControllerClose(otherBranch[_controller]); - } - if (chunk !== undefined) { - assert(chunk.byteLength === 0); + let clonedChunk; + try { + clonedChunk = cloneAsUint8Array(chunk); + } catch (e) { + readableByteStreamControllerError(byobBranch[_controller], e); + readableByteStreamControllerError(otherBranch[_controller], e); + cancelPromise.resolve(readableStreamCancel(stream, e)); + return; + } if (!byobCanceled) { readableByteStreamControllerRespondWithNewView( byobBranch[_controller], chunk, ); } - if ( - !otherCanceled && - otherBranch[_controller][_pendingPullIntos].length !== 0 - ) { - readableByteStreamControllerRespond(otherBranch[_controller], 0); - } + readableByteStreamControllerEnqueue( + otherBranch[_controller], + clonedChunk, + ); + } else if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); } - if (!byobCanceled || !otherCanceled) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { reading = false; - }, - }; - readableStreamBYOBReaderRead(reader, view, readIntoRequest); - } - - function pull1Algorithm() { - if (reading) { - readAgainForBranch1 = true; - return PromiseResolve(undefined); - } - reading = true; - const byobRequest = readableByteStreamControllerGetBYOBRequest( - branch1[_controller], - ); - if (byobRequest === null) { - pullWithDefaultReader(); - } else { - pullWithBYOBReader(byobRequest[_view], false); - } - return PromiseResolve(undefined); - } - - function pull2Algorithm() { - if (reading) { - readAgainForBranch2 = true; - return PromiseResolve(undefined); - } - reading = true; - const byobRequest = readableByteStreamControllerGetBYOBRequest( - branch2[_controller], - ); - if (byobRequest === null) { - pullWithDefaultReader(); - } else { - pullWithBYOBReader(byobRequest[_view], true); - } - return PromiseResolve(undefined); - } - - function cancel1Algorithm(reason) { - canceled1 = true; - reason1 = reason; - if (canceled2) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function cancel2Algorithm(reason) { - canceled2 = true; - reason2 = reason; - if (canceled1) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function startAlgorithm() { - return undefined; - } - - branch1 = createReadableByteStream( - startAlgorithm, - pull1Algorithm, - cancel1Algorithm, - ); - branch2 = createReadableByteStream( - startAlgorithm, - pull2Algorithm, - cancel2Algorithm, - ); - - branch1[_original] = stream; - branch2[_original] = stream; - - forwardReaderError(reader); - return [branch1, branch2]; + if (readAgainForBranch1) { + pull1Algorithm(); + } else if (readAgainForBranch2) { + pull2Algorithm(); + } + }); + }, + closeSteps(chunk) { + reading = false; + const byobCanceled = forBranch2 ? canceled2 : canceled1; + const otherCanceled = forBranch2 ? canceled1 : canceled2; + if (!byobCanceled) { + readableByteStreamControllerClose(byobBranch[_controller]); + } + if (!otherCanceled) { + readableByteStreamControllerClose(otherBranch[_controller]); + } + if (chunk !== undefined) { + assert(chunk.byteLength === 0); + if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); + } + if ( + !otherCanceled && + otherBranch[_controller][_pendingPullIntos].length !== 0 + ) { + readableByteStreamControllerRespond(otherBranch[_controller], 0); + } + } + if (!byobCanceled || !otherCanceled) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamBYOBReaderRead(reader, view, readIntoRequest); } - /** - * @param {ReadableStream} stream - * @param {ReadableByteStreamController} controller - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number} highWaterMark - * @param {number | undefined} autoAllocateChunkSize - */ - function setUpReadableByteStreamController( + function pull1Algorithm() { + if (reading) { + readAgainForBranch1 = true; + return PromiseResolve(undefined); + } + reading = true; + const byobRequest = readableByteStreamControllerGetBYOBRequest( + branch1[_controller], + ); + if (byobRequest === null) { + pullWithDefaultReader(); + } else { + pullWithBYOBReader(byobRequest[_view], false); + } + return PromiseResolve(undefined); + } + + function pull2Algorithm() { + if (reading) { + readAgainForBranch2 = true; + return PromiseResolve(undefined); + } + reading = true; + const byobRequest = readableByteStreamControllerGetBYOBRequest( + branch2[_controller], + ); + if (byobRequest === null) { + pullWithDefaultReader(); + } else { + pullWithBYOBReader(byobRequest[_view], true); + } + return PromiseResolve(undefined); + } + + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } + + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } + + function startAlgorithm() { + return undefined; + } + + branch1 = createReadableByteStream( + startAlgorithm, + pull1Algorithm, + cancel1Algorithm, + ); + branch2 = createReadableByteStream( + startAlgorithm, + pull2Algorithm, + cancel2Algorithm, + ); + + branch1[_original] = stream; + branch2[_original] = stream; + + forwardReaderError(reader); + return [branch1, branch2]; +} + +/** + * @param {ReadableStream} stream + * @param {ReadableByteStreamController} controller + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {number | undefined} autoAllocateChunkSize + */ +function setUpReadableByteStreamController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + autoAllocateChunkSize, +) { + assert(stream[_controller] === undefined); + if (autoAllocateChunkSize !== undefined) { + assert(NumberIsInteger(autoAllocateChunkSize)); + assert(autoAllocateChunkSize >= 0); + } + controller[_stream] = stream; + controller[_pullAgain] = controller[_pulling] = false; + controller[_byobRequest] = null; + resetQueue(controller); + controller[_closeRequested] = controller[_started] = false; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + controller[_autoAllocateChunkSize] = autoAllocateChunkSize; + controller[_pendingPullIntos] = []; + stream[_controller] = controller; + const startResult = startAlgorithm(); + const startPromise = resolvePromiseWith(startResult); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + startPromise, + () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableByteStreamControllerCallPullIfNeeded(controller); + }, + (r) => { + readableByteStreamControllerError(controller, r); + }, + ), + ); +} + +/** + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + */ +function setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, +) { + const controller = webidl.createBranded(ReadableByteStreamController); + /** @type {() => void} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason: any) => Promise} */ + let cancelAlgorithm = (_reason) => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableByteStreamController'", + }, + ); + } + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); + } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); + } + const autoAllocateChunkSize = underlyingSourceDict["autoAllocateChunkSize"]; + if (autoAllocateChunkSize === 0) { + throw new TypeError("autoAllocateChunkSize must be greater than 0"); + } + setUpReadableByteStreamController( stream, controller, startAlgorithm, @@ -3098,127 +3205,117 @@ cancelAlgorithm, highWaterMark, autoAllocateChunkSize, - ) { - assert(stream[_controller] === undefined); - if (autoAllocateChunkSize !== undefined) { - assert(NumberIsInteger(autoAllocateChunkSize)); - assert(autoAllocateChunkSize >= 0); - } - controller[_stream] = stream; - controller[_pullAgain] = controller[_pulling] = false; - controller[_byobRequest] = null; - resetQueue(controller); - controller[_closeRequested] = controller[_started] = false; - controller[_strategyHWM] = highWaterMark; - controller[_pullAlgorithm] = pullAlgorithm; - controller[_cancelAlgorithm] = cancelAlgorithm; - controller[_autoAllocateChunkSize] = autoAllocateChunkSize; - controller[_pendingPullIntos] = []; - stream[_controller] = controller; - const startResult = startAlgorithm(); - const startPromise = resolvePromiseWith(startResult); - setPromiseIsHandledToTrue( - PromisePrototypeThen( - startPromise, - () => { - controller[_started] = true; - assert(controller[_pulling] === false); - assert(controller[_pullAgain] === false); - readableByteStreamControllerCallPullIfNeeded(controller); - }, - (r) => { - readableByteStreamControllerError(controller, r); - }, - ), - ); - } + ); +} - /** - * @param {ReadableStream} stream - * @param {UnderlyingSource} underlyingSource - * @param {UnderlyingSource} underlyingSourceDict - * @param {number} highWaterMark - */ - function setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ) { - const controller = webidl.createBranded(ReadableByteStreamController); - /** @type {() => void} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason: any) => Promise} */ - let cancelAlgorithm = (_reason) => resolvePromiseWith(undefined); - if (underlyingSourceDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.start, - [controller], - underlyingSource, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'ReadableByteStreamController'", - }, - ); - } - if (underlyingSourceDict.pull !== undefined) { - pullAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.pull, - [controller], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'cancelAlgorithm' on 'ReadableByteStreamController'", - returnsPromise: true, - }, - ); - } - const autoAllocateChunkSize = underlyingSourceDict["autoAllocateChunkSize"]; - if (autoAllocateChunkSize === 0) { - throw new TypeError("autoAllocateChunkSize must be greater than 0"); - } - setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - autoAllocateChunkSize, - ); - } +/** + * @template R + * @param {ReadableStream} stream + * @param {ReadableStreamDefaultController} controller + * @param {(controller: ReadableStreamDefaultController) => void | Promise} startAlgorithm + * @param {(controller: ReadableStreamDefaultController) => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ +function setUpReadableStreamDefaultController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(stream[_controller] === undefined); + controller[_stream] = stream; + resetQueue(controller); + controller[_started] = + controller[_closeRequested] = + controller[_pullAgain] = + controller[_pulling] = + false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + stream[_controller] = controller; + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableStreamDefaultControllerCallPullIfNeeded(controller); + }, (r) => { + readableStreamDefaultControllerError(controller, r); + }); +} - /** - * @template R - * @param {ReadableStream} stream - * @param {ReadableStreamDefaultController} controller - * @param {(controller: ReadableStreamDefaultController) => void | Promise} startAlgorithm - * @param {(controller: ReadableStreamDefaultController) => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultController( +/** + * @template R + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ +function setUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(ReadableStreamDefaultController); + /** @type {() => Promise} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let cancelAlgorithm = () => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableStreamDefaultController'", + }, + ); + } + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpReadableStreamDefaultController( stream, controller, startAlgorithm, @@ -3226,234 +3323,255 @@ cancelAlgorithm, highWaterMark, sizeAlgorithm, + ); +} + +/** + * @template R + * @param {ReadableStreamBYOBReader} reader + * @param {ReadableStream} stream + */ +function setUpReadableStreamBYOBReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); + } + if ( + !(ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + )) ) { - assert(stream[_controller] === undefined); - controller[_stream] = stream; - resetQueue(controller); - controller[_started] = - controller[_closeRequested] = - controller[_pullAgain] = - controller[_pulling] = - false; - controller[_strategySizeAlgorithm] = sizeAlgorithm; - controller[_strategyHWM] = highWaterMark; - controller[_pullAlgorithm] = pullAlgorithm; - controller[_cancelAlgorithm] = cancelAlgorithm; - stream[_controller] = controller; - const startResult = startAlgorithm(controller); - const startPromise = resolvePromiseWith(startResult); - uponPromise(startPromise, () => { - controller[_started] = true; - assert(controller[_pulling] === false); - assert(controller[_pullAgain] === false); - readableStreamDefaultControllerCallPullIfNeeded(controller); - }, (r) => { - readableStreamDefaultControllerError(controller, r); - }); + throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readIntoRequests] = []; +} - /** - * @template R - * @param {ReadableStream} stream - * @param {UnderlyingSource} underlyingSource - * @param {UnderlyingSource} underlyingSourceDict - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(ReadableStreamDefaultController); - /** @type {() => Promise} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise} */ - let cancelAlgorithm = () => resolvePromiseWith(undefined); - if (underlyingSourceDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.start, - [controller], - underlyingSource, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'ReadableStreamDefaultController'", - }, - ); - } - if (underlyingSourceDict.pull !== undefined) { - pullAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.pull, - [controller], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadableStream} stream + */ +function setUpReadableStreamDefaultReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readRequests] = []; +} - /** - * @template R - * @param {ReadableStreamBYOBReader} reader - * @param {ReadableStream} stream - */ - function setUpReadableStreamBYOBReader(reader, stream) { - if (isReadableStreamLocked(stream)) { - throw new TypeError("ReadableStream is locked."); +/** + * @template O + * @param {TransformStream} stream + * @param {TransformStreamDefaultController} controller + * @param {(chunk: O, controller: TransformStreamDefaultController) => Promise} transformAlgorithm + * @param {(controller: TransformStreamDefaultController) => Promise} flushAlgorithm + */ +function setUpTransformStreamDefaultController( + stream, + controller, + transformAlgorithm, + flushAlgorithm, +) { + assert(ObjectPrototypeIsPrototypeOf(TransformStreamPrototype, stream)); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + controller[_transformAlgorithm] = transformAlgorithm; + controller[_flushAlgorithm] = flushAlgorithm; +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Transformer} transformer + * @param {Transformer} transformerDict + */ +function setUpTransformStreamDefaultControllerFromTransformer( + stream, + transformer, + transformerDict, +) { + /** @type {TransformStreamDefaultController} */ + const controller = webidl.createBranded(TransformStreamDefaultController); + /** @type {(chunk: O, controller: TransformStreamDefaultController) => Promise} */ + let transformAlgorithm = (chunk) => { + try { + transformStreamDefaultControllerEnqueue(controller, chunk); + } catch (e) { + return PromiseReject(e); } - if ( - !(ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - )) - ) { - throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); - } - readableStreamReaderGenericInitialize(reader, stream); - reader[_readIntoRequests] = []; + return resolvePromiseWith(undefined); + }; + /** @type {(controller: TransformStreamDefaultController) => Promise} */ + let flushAlgorithm = () => resolvePromiseWith(undefined); + if (transformerDict.transform !== undefined) { + transformAlgorithm = (chunk, controller) => + webidl.invokeCallbackFunction( + transformerDict.transform, + [chunk, controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - * @param {ReadableStream} stream - */ - function setUpReadableStreamDefaultReader(reader, stream) { - if (isReadableStreamLocked(stream)) { - throw new TypeError("ReadableStream is locked."); - } - readableStreamReaderGenericInitialize(reader, stream); - reader[_readRequests] = []; + if (transformerDict.flush !== undefined) { + flushAlgorithm = (controller) => + webidl.invokeCallbackFunction( + transformerDict.flush, + [controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template O - * @param {TransformStream} stream - * @param {TransformStreamDefaultController} controller - * @param {(chunk: O, controller: TransformStreamDefaultController) => Promise} transformAlgorithm - * @param {(controller: TransformStreamDefaultController) => Promise} flushAlgorithm - */ - function setUpTransformStreamDefaultController( + setUpTransformStreamDefaultController( stream, controller, transformAlgorithm, flushAlgorithm, - ) { - assert(ObjectPrototypeIsPrototypeOf(TransformStreamPrototype, stream)); - assert(stream[_controller] === undefined); - controller[_stream] = stream; - stream[_controller] = controller; - controller[_transformAlgorithm] = transformAlgorithm; - controller[_flushAlgorithm] = flushAlgorithm; - } + ); +} - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {Transformer} transformer - * @param {Transformer} transformerDict - */ - function setUpTransformStreamDefaultControllerFromTransformer( - stream, - transformer, - transformerDict, - ) { - /** @type {TransformStreamDefaultController} */ - const controller = webidl.createBranded(TransformStreamDefaultController); - /** @type {(chunk: O, controller: TransformStreamDefaultController) => Promise} */ - let transformAlgorithm = (chunk) => { - try { - transformStreamDefaultControllerEnqueue(controller, chunk); - } catch (e) { - return PromiseReject(e); - } - return resolvePromiseWith(undefined); - }; - /** @type {(controller: TransformStreamDefaultController) => Promise} */ - let flushAlgorithm = () => resolvePromiseWith(undefined); - if (transformerDict.transform !== undefined) { - transformAlgorithm = (chunk, controller) => - webidl.invokeCallbackFunction( - transformerDict.transform, - [chunk, controller], - transformer, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (transformerDict.flush !== undefined) { - flushAlgorithm = (controller) => - webidl.invokeCallbackFunction( - transformerDict.flush, - [controller], - transformer, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpTransformStreamDefaultController( - stream, - controller, - transformAlgorithm, - flushAlgorithm, - ); - } +/** + * @template W + * @param {WritableStream} stream + * @param {WritableStreamDefaultController} controller + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W, controller: WritableStreamDefaultController) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason?: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ +function setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(isWritableStream(stream)); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + resetQueue(controller); + controller[_signal] = newSignal(); + controller[_started] = false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_writeAlgorithm] = writeAlgorithm; + controller[_closeAlgorithm] = closeAlgorithm; + controller[_abortAlgorithm] = abortAlgorithm; + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (r) => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDealWithRejection(stream, r); + }); +} - /** - * @template W - * @param {WritableStream} stream - * @param {WritableStreamDefaultController} controller - * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm - * @param {(chunk: W, controller: WritableStreamDefaultController) => Promise} writeAlgorithm - * @param {() => Promise} closeAlgorithm - * @param {(reason?: any) => Promise} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultController( +/** + * @template W + * @param {WritableStream} stream + * @param {UnderlyingSink} underlyingSink + * @param {UnderlyingSink} underlyingSinkDict + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ +function setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(WritableStreamDefaultController); + /** @type {(controller: WritableStreamDefaultController) => any} */ + let startAlgorithm = () => undefined; + /** @type {(chunk: W, controller: WritableStreamDefaultController) => Promise} */ + let writeAlgorithm = () => resolvePromiseWith(undefined); + let closeAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let abortAlgorithm = () => resolvePromiseWith(undefined); + + if (underlyingSinkDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.start, + [controller], + underlyingSink, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'WritableStreamDefaultController'", + }, + ); + } + if (underlyingSinkDict.write !== undefined) { + writeAlgorithm = (chunk) => + webidl.invokeCallbackFunction( + underlyingSinkDict.write, + [chunk, controller], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSinkDict.close !== undefined) { + closeAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.close, + [], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSinkDict.abort !== undefined) { + abortAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSinkDict.abort, + [reason], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpWritableStreamDefaultController( stream, controller, startAlgorithm, @@ -3462,627 +3580,477 @@ abortAlgorithm, highWaterMark, sizeAlgorithm, - ) { - assert(isWritableStream(stream)); - assert(stream[_controller] === undefined); - controller[_stream] = stream; - stream[_controller] = controller; - resetQueue(controller); - controller[_signal] = newSignal(); - controller[_started] = false; - controller[_strategySizeAlgorithm] = sizeAlgorithm; - controller[_strategyHWM] = highWaterMark; - controller[_writeAlgorithm] = writeAlgorithm; - controller[_closeAlgorithm] = closeAlgorithm; - controller[_abortAlgorithm] = abortAlgorithm; - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - const startResult = startAlgorithm(controller); - const startPromise = resolvePromiseWith(startResult); - uponPromise(startPromise, () => { - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - controller[_started] = true; - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, (r) => { - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - controller[_started] = true; - writableStreamDealWithRejection(stream, r); - }); + ); +} + +/** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {WritableStream} stream + */ +function setUpWritableStreamDefaultWriter(writer, stream) { + if (isWritableStreamLocked(stream) === true) { + throw new TypeError("The stream is already locked."); } - - /** - * @template W - * @param {WritableStream} stream - * @param {UnderlyingSink} underlyingSink - * @param {UnderlyingSink} underlyingSinkDict - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultControllerFromUnderlyingSink( - stream, - underlyingSink, - underlyingSinkDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(WritableStreamDefaultController); - /** @type {(controller: WritableStreamDefaultController) => any} */ - let startAlgorithm = () => undefined; - /** @type {(chunk: W, controller: WritableStreamDefaultController) => Promise} */ - let writeAlgorithm = () => resolvePromiseWith(undefined); - let closeAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise} */ - let abortAlgorithm = () => resolvePromiseWith(undefined); - - if (underlyingSinkDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSinkDict.start, - [controller], - underlyingSink, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'WritableStreamDefaultController'", - }, - ); - } - if (underlyingSinkDict.write !== undefined) { - writeAlgorithm = (chunk) => - webidl.invokeCallbackFunction( - underlyingSinkDict.write, - [chunk, controller], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.close !== undefined) { - closeAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSinkDict.close, - [], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.abort !== undefined) { - abortAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSinkDict.abort, - [reason], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - } - - /** - * @template W - * @param {WritableStreamDefaultWriter} writer - * @param {WritableStream} stream - */ - function setUpWritableStreamDefaultWriter(writer, stream) { - if (isWritableStreamLocked(stream) === true) { - throw new TypeError("The stream is already locked."); - } - writer[_stream] = stream; - stream[_writer] = writer; - const state = stream[_state]; - if (state === "writable") { - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - stream[_backpressure] === true - ) { - writer[_readyPromise] = new Deferred(); - } else { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].resolve(undefined); - } - writer[_closedPromise] = new Deferred(); - } else if (state === "erroring") { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(writer[_readyPromise].promise); - writer[_closedPromise] = new Deferred(); - } else if (state === "closed") { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].resolve(undefined); - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].resolve(undefined); - } else { - assert(state === "errored"); - const storedError = stream[_storedError]; - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(storedError); - setPromiseIsHandledToTrue(writer[_readyPromise].promise); - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].reject(storedError); - setPromiseIsHandledToTrue(writer[_closedPromise].promise); - } - } - - /** @param {TransformStreamDefaultController} controller */ - function transformStreamDefaultControllerClearAlgorithms(controller) { - controller[_transformAlgorithm] = undefined; - controller[_flushAlgorithm] = undefined; - } - - /** - * @template O - * @param {TransformStreamDefaultController} controller - * @param {O} chunk - */ - function transformStreamDefaultControllerEnqueue(controller, chunk) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue( - /** @type {ReadableStreamDefaultController} */ readableController, - ) === false - ) { - throw new TypeError("Readable stream is unavailable."); - } - try { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ readableController, - chunk, - ); - } catch (e) { - transformStreamErrorWritableAndUnblockWrite(stream, e); - throw stream[_readable][_storedError]; - } - const backpressure = readableStreamDefaultcontrollerHasBackpressure( - /** @type {ReadableStreamDefaultController} */ readableController, - ); - if (backpressure !== stream[_backpressure]) { - assert(backpressure === true); - transformStreamSetBackpressure(stream, true); - } - } - - /** - * @param {TransformStreamDefaultController} controller - * @param {any=} e - */ - function transformStreamDefaultControllerError(controller, e) { - transformStreamError(controller[_stream], e); - } - - /** - * @template O - * @param {TransformStreamDefaultController} controller - * @param {any} chunk - * @returns {Promise} - */ - function transformStreamDefaultControllerPerformTransform(controller, chunk) { - const transformPromise = controller[_transformAlgorithm](chunk, controller); - return transformPromiseWith(transformPromise, undefined, (r) => { - transformStreamError(controller[_stream], r); - throw r; - }); - } - - /** @param {TransformStreamDefaultController} controller */ - function transformStreamDefaultControllerTerminate(controller) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ readableController, - ); - const error = new TypeError("The stream has been terminated."); - transformStreamErrorWritableAndUnblockWrite(stream, error); - } - - /** - * @param {TransformStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { - transformStreamError(stream, reason); - return resolvePromiseWith(undefined); - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @returns {Promise} - */ - function transformStreamDefaultSinkCloseAlgorithm(stream) { - const readable = stream[_readable]; - const controller = stream[_controller]; - const flushPromise = controller[_flushAlgorithm](controller); - transformStreamDefaultControllerClearAlgorithms(controller); - return transformPromiseWith(flushPromise, () => { - if (readable[_state] === "errored") { - throw readable[_storedError]; - } - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ readable[_controller], - ); - }, (r) => { - transformStreamError(stream, r); - throw readable[_storedError]; - }); - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {I} chunk - * @returns {Promise} - */ - function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { - assert(stream[_writable][_state] === "writable"); - const controller = stream[_controller]; - if (stream[_backpressure] === true) { - const backpressureChangePromise = stream[_backpressureChangePromise]; - assert(backpressureChangePromise !== undefined); - return transformPromiseWith(backpressureChangePromise.promise, () => { - const writable = stream[_writable]; - const state = writable[_state]; - if (state === "erroring") { - throw writable[_storedError]; - } - assert(state === "writable"); - return transformStreamDefaultControllerPerformTransform( - controller, - chunk, - ); - }); - } - return transformStreamDefaultControllerPerformTransform(controller, chunk); - } - - /** - * @param {TransformStream} stream - * @returns {Promise} - */ - function transformStreamDefaultSourcePullAlgorithm(stream) { - assert(stream[_backpressure] === true); - assert(stream[_backpressureChangePromise] !== undefined); - transformStreamSetBackpressure(stream, false); - return stream[_backpressureChangePromise].promise; - } - - /** - * @param {TransformStream} stream - * @param {any=} e - */ - function transformStreamError(stream, e) { - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ stream[_readable][ - _controller - ], - e, - ); - transformStreamErrorWritableAndUnblockWrite(stream, e); - } - - /** - * @param {TransformStream} stream - * @param {any=} e - */ - function transformStreamErrorWritableAndUnblockWrite(stream, e) { - transformStreamDefaultControllerClearAlgorithms(stream[_controller]); - writableStreamDefaultControllerErrorIfNeeded( - stream[_writable][_controller], - e, - ); - if (stream[_backpressure] === true) { - transformStreamSetBackpressure(stream, false); - } - } - - /** - * @param {TransformStream} stream - * @param {boolean} backpressure - */ - function transformStreamSetBackpressure(stream, backpressure) { - assert(stream[_backpressure] !== backpressure); - if (stream[_backpressureChangePromise] !== undefined) { - stream[_backpressureChangePromise].resolve(undefined); - } - stream[_backpressureChangePromise] = new Deferred(); - stream[_backpressure] = backpressure; - } - - /** - * @param {WritableStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function writableStreamAbort(stream, reason) { - const state = stream[_state]; - if (state === "closed" || state === "errored") { - return resolvePromiseWith(undefined); - } - stream[_controller][_signal][signalAbort](reason); - if (state === "closed" || state === "errored") { - return resolvePromiseWith(undefined); - } - if (stream[_pendingAbortRequest] !== undefined) { - return stream[_pendingAbortRequest].deferred.promise; - } - assert(state === "writable" || state === "erroring"); - let wasAlreadyErroring = false; - if (state === "erroring") { - wasAlreadyErroring = true; - reason = undefined; - } - /** Deferred */ - const deferred = new Deferred(); - stream[_pendingAbortRequest] = { - deferred, - reason, - wasAlreadyErroring, - }; - if (wasAlreadyErroring === false) { - writableStreamStartErroring(stream, reason); - } - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {Promise} - */ - function writableStreamAddWriteRequest(stream) { - assert(isWritableStreamLocked(stream) === true); - assert(stream[_state] === "writable"); - /** @type {Deferred} */ - const deferred = new Deferred(); - ArrayPrototypePush(stream[_writeRequests], deferred); - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {Promise} - */ - function writableStreamClose(stream) { - const state = stream[_state]; - if (state === "closed" || state === "errored") { - return PromiseReject( - new TypeError("Writable stream is closed or errored."), - ); - } - assert(state === "writable" || state === "erroring"); - assert(writableStreamCloseQueuedOrInFlight(stream) === false); - /** @type {Deferred} */ - const deferred = new Deferred(); - stream[_closeRequest] = deferred; - const writer = stream[_writer]; - if ( - writer !== undefined && stream[_backpressure] === true && - state === "writable" - ) { - writer[_readyPromise].resolve(undefined); - } - writableStreamDefaultControllerClose(stream[_controller]); - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamCloseQueuedOrInFlight(stream) { - if ( - stream[_closeRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; - } - return true; - } - - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamDealWithRejection(stream, error) { - const state = stream[_state]; - if (state === "writable") { - writableStreamStartErroring(stream, error); - return; - } - assert(state === "erroring"); - writableStreamFinishErroring(stream); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - */ - function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { - const stream = controller[_stream]; - if (controller[_started] === false) { - return; - } - if (stream[_inFlightWriteRequest] !== undefined) { - return; - } - const state = stream[_state]; - assert(state !== "closed" && state !== "errored"); - if (state === "erroring") { - writableStreamFinishErroring(stream); - return; - } - if (controller[_queue].length === 0) { - return; - } - const value = peekQueueValue(controller); - if (value === _close) { - writableStreamDefaultControllerProcessClose(controller); - } else { - writableStreamDefaultControllerProcessWrite(controller, value); - } - } - - function writableStreamDefaultControllerClearAlgorithms(controller) { - controller[_writeAlgorithm] = undefined; - controller[_closeAlgorithm] = undefined; - controller[_abortAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; - } - - /** @param {WritableStreamDefaultController} controller */ - function writableStreamDefaultControllerClose(controller) { - enqueueValueWithSize(controller, _close, 0); - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } - - /** - * @param {WritableStreamDefaultController} controller - * @param {any} error - */ - function writableStreamDefaultControllerError(controller, error) { - const stream = controller[_stream]; - assert(stream[_state] === "writable"); - writableStreamDefaultControllerClearAlgorithms(controller); - writableStreamStartErroring(stream, error); - } - - /** - * @param {WritableStreamDefaultController} controller - * @param {any} error - */ - function writableStreamDefaultControllerErrorIfNeeded(controller, error) { - if (controller[_stream][_state] === "writable") { - writableStreamDefaultControllerError(controller, error); - } - } - - /** - * @param {WritableStreamDefaultController} controller - * @returns {boolean} - */ - function writableStreamDefaultControllerGetBackpressure(controller) { - const desiredSize = writableStreamDefaultControllerGetDesiredSize( - controller, - ); - return desiredSize <= 0; - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - * @returns {number} - */ - function writableStreamDefaultControllerGetChunkSize(controller, chunk) { - let value; - try { - value = controller[_strategySizeAlgorithm](chunk); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return 1; - } - return value; - } - - /** - * @param {WritableStreamDefaultController} controller - * @returns {number} - */ - function writableStreamDefaultControllerGetDesiredSize(controller) { - return controller[_strategyHWM] - controller[_queueTotalSize]; - } - - /** @param {WritableStreamDefaultController} controller */ - function writableStreamDefaultControllerProcessClose(controller) { - const stream = controller[_stream]; - writableStreamMarkCloseRequestInFlight(stream); - dequeueValue(controller); - assert(controller[_queue].length === 0); - const sinkClosePromise = controller[_closeAlgorithm](); - writableStreamDefaultControllerClearAlgorithms(controller); - uponPromise(sinkClosePromise, () => { - writableStreamFinishInFlightClose(stream); - }, (reason) => { - writableStreamFinishInFlightCloseWithError(stream, reason); - }); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - */ - function writableStreamDefaultControllerProcessWrite(controller, chunk) { - const stream = controller[_stream]; - writableStreamMarkFirstWriteRequestInFlight(stream); - const sinkWritePromise = controller[_writeAlgorithm](chunk, controller); - uponPromise(sinkWritePromise, () => { - writableStreamFinishInFlightWrite(stream); - const state = stream[_state]; - assert(state === "writable" || state === "erroring"); - dequeueValue(controller); - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - state === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, (reason) => { - if (stream[_state] === "writable") { - writableStreamDefaultControllerClearAlgorithms(controller); - } - writableStreamFinishInFlightWriteWithError(stream, reason); - }); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - * @param {number} chunkSize - */ - function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { - try { - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return; - } - const stream = controller[_stream]; + writer[_stream] = stream; + stream[_writer] = writer; + const state = stream[_state]; + if (state === "writable") { if ( writableStreamCloseQueuedOrInFlight(stream) === false && - stream[_state] === "writable" + stream[_backpressure] === true + ) { + writer[_readyPromise] = new Deferred(); + } else { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].resolve(undefined); + } + writer[_closedPromise] = new Deferred(); + } else if (state === "erroring") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + } else if (state === "closed") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].resolve(undefined); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].resolve(undefined); + } else { + assert(state === "errored"); + const storedError = stream[_storedError]; + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(storedError); + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(storedError); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); + } +} + +/** @param {TransformStreamDefaultController} controller */ +function transformStreamDefaultControllerClearAlgorithms(controller) { + controller[_transformAlgorithm] = undefined; + controller[_flushAlgorithm] = undefined; +} + +/** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {O} chunk + */ +function transformStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue( + /** @type {ReadableStreamDefaultController} */ readableController, + ) === false + ) { + throw new TypeError("Readable stream is unavailable."); + } + try { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ readableController, + chunk, + ); + } catch (e) { + transformStreamErrorWritableAndUnblockWrite(stream, e); + throw stream[_readable][_storedError]; + } + const backpressure = readableStreamDefaultcontrollerHasBackpressure( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + if (backpressure !== stream[_backpressure]) { + assert(backpressure === true); + transformStreamSetBackpressure(stream, true); + } +} + +/** + * @param {TransformStreamDefaultController} controller + * @param {any=} e + */ +function transformStreamDefaultControllerError(controller, e) { + transformStreamError(controller[_stream], e); +} + +/** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {any} chunk + * @returns {Promise} + */ +function transformStreamDefaultControllerPerformTransform(controller, chunk) { + const transformPromise = controller[_transformAlgorithm](chunk, controller); + return transformPromiseWith(transformPromise, undefined, (r) => { + transformStreamError(controller[_stream], r); + throw r; + }); +} + +/** @param {TransformStreamDefaultController} controller */ +function transformStreamDefaultControllerTerminate(controller) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + const error = new TypeError("The stream has been terminated."); + transformStreamErrorWritableAndUnblockWrite(stream, error); +} + +/** + * @param {TransformStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { + transformStreamError(stream, reason); + return resolvePromiseWith(undefined); +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @returns {Promise} + */ +function transformStreamDefaultSinkCloseAlgorithm(stream) { + const readable = stream[_readable]; + const controller = stream[_controller]; + const flushPromise = controller[_flushAlgorithm](controller); + transformStreamDefaultControllerClearAlgorithms(controller); + return transformPromiseWith(flushPromise, () => { + if (readable[_state] === "errored") { + throw readable[_storedError]; + } + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ readable[_controller], + ); + }, (r) => { + transformStreamError(stream, r); + throw readable[_storedError]; + }); +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {I} chunk + * @returns {Promise} + */ +function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { + assert(stream[_writable][_state] === "writable"); + const controller = stream[_controller]; + if (stream[_backpressure] === true) { + const backpressureChangePromise = stream[_backpressureChangePromise]; + assert(backpressureChangePromise !== undefined); + return transformPromiseWith(backpressureChangePromise.promise, () => { + const writable = stream[_writable]; + const state = writable[_state]; + if (state === "erroring") { + throw writable[_storedError]; + } + assert(state === "writable"); + return transformStreamDefaultControllerPerformTransform( + controller, + chunk, + ); + }); + } + return transformStreamDefaultControllerPerformTransform(controller, chunk); +} + +/** + * @param {TransformStream} stream + * @returns {Promise} + */ +function transformStreamDefaultSourcePullAlgorithm(stream) { + assert(stream[_backpressure] === true); + assert(stream[_backpressureChangePromise] !== undefined); + transformStreamSetBackpressure(stream, false); + return stream[_backpressureChangePromise].promise; +} + +/** + * @param {TransformStream} stream + * @param {any=} e + */ +function transformStreamError(stream, e) { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ stream[_readable][ + _controller + ], + e, + ); + transformStreamErrorWritableAndUnblockWrite(stream, e); +} + +/** + * @param {TransformStream} stream + * @param {any=} e + */ +function transformStreamErrorWritableAndUnblockWrite(stream, e) { + transformStreamDefaultControllerClearAlgorithms(stream[_controller]); + writableStreamDefaultControllerErrorIfNeeded( + stream[_writable][_controller], + e, + ); + if (stream[_backpressure] === true) { + transformStreamSetBackpressure(stream, false); + } +} + +/** + * @param {TransformStream} stream + * @param {boolean} backpressure + */ +function transformStreamSetBackpressure(stream, backpressure) { + assert(stream[_backpressure] !== backpressure); + if (stream[_backpressureChangePromise] !== undefined) { + stream[_backpressureChangePromise].resolve(undefined); + } + stream[_backpressureChangePromise] = new Deferred(); + stream[_backpressure] = backpressure; +} + +/** + * @param {WritableStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function writableStreamAbort(stream, reason) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); + } + stream[_controller][_signal][signalAbort](reason); + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); + } + if (stream[_pendingAbortRequest] !== undefined) { + return stream[_pendingAbortRequest].deferred.promise; + } + assert(state === "writable" || state === "erroring"); + let wasAlreadyErroring = false; + if (state === "erroring") { + wasAlreadyErroring = true; + reason = undefined; + } + /** Deferred */ + const deferred = new Deferred(); + stream[_pendingAbortRequest] = { + deferred, + reason, + wasAlreadyErroring, + }; + if (wasAlreadyErroring === false) { + writableStreamStartErroring(stream, reason); + } + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise} + */ +function writableStreamAddWriteRequest(stream) { + assert(isWritableStreamLocked(stream) === true); + assert(stream[_state] === "writable"); + /** @type {Deferred} */ + const deferred = new Deferred(); + ArrayPrototypePush(stream[_writeRequests], deferred); + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise} + */ +function writableStreamClose(stream) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return PromiseReject( + new TypeError("Writable stream is closed or errored."), + ); + } + assert(state === "writable" || state === "erroring"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + /** @type {Deferred} */ + const deferred = new Deferred(); + stream[_closeRequest] = deferred; + const writer = stream[_writer]; + if ( + writer !== undefined && stream[_backpressure] === true && + state === "writable" + ) { + writer[_readyPromise].resolve(undefined); + } + writableStreamDefaultControllerClose(stream[_controller]); + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function writableStreamCloseQueuedOrInFlight(stream) { + if ( + stream[_closeRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; + } + return true; +} + +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamDealWithRejection(stream, error) { + const state = stream[_state]; + if (state === "writable") { + writableStreamStartErroring(stream, error); + return; + } + assert(state === "erroring"); + writableStreamFinishErroring(stream); +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + */ +function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { + const stream = controller[_stream]; + if (controller[_started] === false) { + return; + } + if (stream[_inFlightWriteRequest] !== undefined) { + return; + } + const state = stream[_state]; + assert(state !== "closed" && state !== "errored"); + if (state === "erroring") { + writableStreamFinishErroring(stream); + return; + } + if (controller[_queue].length === 0) { + return; + } + const value = peekQueueValue(controller); + if (value === _close) { + writableStreamDefaultControllerProcessClose(controller); + } else { + writableStreamDefaultControllerProcessWrite(controller, value); + } +} + +function writableStreamDefaultControllerClearAlgorithms(controller) { + controller[_writeAlgorithm] = undefined; + controller[_closeAlgorithm] = undefined; + controller[_abortAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; +} + +/** @param {WritableStreamDefaultController} controller */ +function writableStreamDefaultControllerClose(controller) { + enqueueValueWithSize(controller, _close, 0); + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} + +/** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ +function writableStreamDefaultControllerError(controller, error) { + const stream = controller[_stream]; + assert(stream[_state] === "writable"); + writableStreamDefaultControllerClearAlgorithms(controller); + writableStreamStartErroring(stream, error); +} + +/** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ +function writableStreamDefaultControllerErrorIfNeeded(controller, error) { + if (controller[_stream][_state] === "writable") { + writableStreamDefaultControllerError(controller, error); + } +} + +/** + * @param {WritableStreamDefaultController} controller + * @returns {boolean} + */ +function writableStreamDefaultControllerGetBackpressure(controller) { + const desiredSize = writableStreamDefaultControllerGetDesiredSize( + controller, + ); + return desiredSize <= 0; +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @returns {number} + */ +function writableStreamDefaultControllerGetChunkSize(controller, chunk) { + let value; + try { + value = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return 1; + } + return value; +} + +/** + * @param {WritableStreamDefaultController} controller + * @returns {number} + */ +function writableStreamDefaultControllerGetDesiredSize(controller) { + return controller[_strategyHWM] - controller[_queueTotalSize]; +} + +/** @param {WritableStreamDefaultController} controller */ +function writableStreamDefaultControllerProcessClose(controller) { + const stream = controller[_stream]; + writableStreamMarkCloseRequestInFlight(stream); + dequeueValue(controller); + assert(controller[_queue].length === 0); + const sinkClosePromise = controller[_closeAlgorithm](); + writableStreamDefaultControllerClearAlgorithms(controller); + uponPromise(sinkClosePromise, () => { + writableStreamFinishInFlightClose(stream); + }, (reason) => { + writableStreamFinishInFlightCloseWithError(stream, reason); + }); +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + */ +function writableStreamDefaultControllerProcessWrite(controller, chunk) { + const stream = controller[_stream]; + writableStreamMarkFirstWriteRequestInFlight(stream); + const sinkWritePromise = controller[_writeAlgorithm](chunk, controller); + uponPromise(sinkWritePromise, () => { + writableStreamFinishInFlightWrite(stream); + const state = stream[_state]; + assert(state === "writable" || state === "erroring"); + dequeueValue(controller); + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + state === "writable" ) { const backpressure = writableStreamDefaultControllerGetBackpressure( controller, @@ -4090,712 +4058,781 @@ writableStreamUpdateBackpressure(stream, backpressure); } writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} reason - * @returns {Promise} - */ - function writableStreamDefaultWriterAbort(writer, reason) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamAbort(stream, reason); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {Promise} - */ - function writableStreamDefaultWriterClose(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamClose(stream); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {Promise} - */ - function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - const state = stream[_state]; - if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" - ) { - return resolvePromiseWith(undefined); + }, (reason) => { + if (stream[_state] === "writable") { + writableStreamDefaultControllerClearAlgorithms(controller); } - if (state === "errored") { - return PromiseReject(stream[_storedError]); - } - assert(state === "writable" || state === "erroring"); - return writableStreamDefaultWriterClose(writer); - } + writableStreamFinishInFlightWriteWithError(stream, reason); + }); +} - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} error - */ - function writableStreamDefaultWriterEnsureClosedPromiseRejected( - writer, - error, +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @param {number} chunkSize + */ +function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return; + } + const stream = controller[_stream]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + stream[_state] === "writable" ) { - if (writer[_closedPromise].state === "pending") { - writer[_closedPromise].reject(error); - } else { - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].reject(error); - } - setPromiseIsHandledToTrue(writer[_closedPromise].promise); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} error - */ - function writableStreamDefaultWriterEnsureReadyPromiseRejected( - writer, - error, - ) { - if (writer[_readyPromise].state === "pending") { - writer[_readyPromise].reject(error); - } else { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(error); - } - setPromiseIsHandledToTrue(writer[_readyPromise].promise); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {number | null} - */ - function writableStreamDefaultWriterGetDesiredSize(writer) { - const stream = writer[_stream]; - const state = stream[_state]; - if (state === "errored" || state === "erroring") { - return null; - } - if (state === "closed") { - return 0; - } - return writableStreamDefaultControllerGetDesiredSize(stream[_controller]); - } - - /** @param {WritableStreamDefaultWriter} writer */ - function writableStreamDefaultWriterRelease(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - assert(stream[_writer] === writer); - const releasedError = new TypeError( - "The writer has already been released.", - ); - writableStreamDefaultWriterEnsureReadyPromiseRejected( - writer, - releasedError, - ); - writableStreamDefaultWriterEnsureClosedPromiseRejected( - writer, - releasedError, - ); - stream[_writer] = undefined; - writer[_stream] = undefined; - } - - /** - * @template W - * @param {WritableStreamDefaultWriter} writer - * @param {W} chunk - * @returns {Promise} - */ - function writableStreamDefaultWriterWrite(writer, chunk) { - const stream = writer[_stream]; - assert(stream !== undefined); - const controller = stream[_controller]; - const chunkSize = writableStreamDefaultControllerGetChunkSize( + const backpressure = writableStreamDefaultControllerGetBackpressure( controller, - chunk, ); - if (stream !== writer[_stream]) { - return PromiseReject(new TypeError("Writer's stream is unexpected.")); - } - const state = stream[_state]; - if (state === "errored") { - return PromiseReject(stream[_storedError]); - } - if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" - ) { - return PromiseReject( - new TypeError("The stream is closing or is closed."), - ); - } - if (state === "erroring") { - return PromiseReject(stream[_storedError]); - } - assert(state === "writable"); - const promise = writableStreamAddWriteRequest(stream); - writableStreamDefaultControllerWrite(controller, chunk, chunkSize); - return promise; + writableStreamUpdateBackpressure(stream, backpressure); } + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} - /** @param {WritableStream} stream */ - function writableStreamFinishErroring(stream) { - assert(stream[_state] === "erroring"); - assert(writableStreamHasOperationMarkedInFlight(stream) === false); - stream[_state] = "errored"; - stream[_controller][_errorSteps](); - const storedError = stream[_storedError]; - const writeRequests = stream[_writeRequests]; - for (let i = 0; i < writeRequests.length; ++i) { - const writeRequest = writeRequests[i]; - writeRequest.reject(storedError); - } - stream[_writeRequests] = []; - if (stream[_pendingAbortRequest] === undefined) { - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const abortRequest = stream[_pendingAbortRequest]; - stream[_pendingAbortRequest] = undefined; - if (abortRequest.wasAlreadyErroring === true) { - abortRequest.deferred.reject(storedError); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const promise = stream[_controller][_abortSteps](abortRequest.reason); - uponPromise(promise, () => { - abortRequest.deferred.resolve(undefined); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, (reason) => { - abortRequest.deferred.reject(reason); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }); +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} reason + * @returns {Promise} + */ +function writableStreamDefaultWriterAbort(writer, reason) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamAbort(stream, reason); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ +function writableStreamDefaultWriterClose(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamClose(stream); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ +function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + const state = stream[_state]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return resolvePromiseWith(undefined); } - - /** @param {WritableStream} stream */ - function writableStreamFinishInFlightClose(stream) { - assert(stream[_inFlightCloseRequest] !== undefined); - stream[_inFlightCloseRequest].resolve(undefined); - stream[_inFlightCloseRequest] = undefined; - const state = stream[_state]; - assert(state === "writable" || state === "erroring"); - if (state === "erroring") { - stream[_storedError] = undefined; - if (stream[_pendingAbortRequest] !== undefined) { - stream[_pendingAbortRequest].deferred.resolve(undefined); - stream[_pendingAbortRequest] = undefined; - } - } - stream[_state] = "closed"; - const writer = stream[_writer]; - if (writer !== undefined) { - writer[_closedPromise].resolve(undefined); - } - assert(stream[_pendingAbortRequest] === undefined); - assert(stream[_storedError] === undefined); + if (state === "errored") { + return PromiseReject(stream[_storedError]); } + assert(state === "writable" || state === "erroring"); + return writableStreamDefaultWriterClose(writer); +} - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamFinishInFlightCloseWithError(stream, error) { - assert(stream[_inFlightCloseRequest] !== undefined); - stream[_inFlightCloseRequest].reject(error); - stream[_inFlightCloseRequest] = undefined; - assert(stream[_state] === "writable" || stream[_state] === "erroring"); +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ +function writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + error, +) { + if (writer[_closedPromise].state === "pending") { + writer[_closedPromise].reject(error); + } else { + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_closedPromise].promise); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ +function writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + error, +) { + if (writer[_readyPromise].state === "pending") { + writer[_readyPromise].reject(error); + } else { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_readyPromise].promise); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {number | null} + */ +function writableStreamDefaultWriterGetDesiredSize(writer) { + const stream = writer[_stream]; + const state = stream[_state]; + if (state === "errored" || state === "erroring") { + return null; + } + if (state === "closed") { + return 0; + } + return writableStreamDefaultControllerGetDesiredSize(stream[_controller]); +} + +/** @param {WritableStreamDefaultWriter} writer */ +function writableStreamDefaultWriterRelease(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + assert(stream[_writer] === writer); + const releasedError = new TypeError( + "The writer has already been released.", + ); + writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + releasedError, + ); + writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + releasedError, + ); + stream[_writer] = undefined; + writer[_stream] = undefined; +} + +/** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {W} chunk + * @returns {Promise} + */ +function writableStreamDefaultWriterWrite(writer, chunk) { + const stream = writer[_stream]; + assert(stream !== undefined); + const controller = stream[_controller]; + const chunkSize = writableStreamDefaultControllerGetChunkSize( + controller, + chunk, + ); + if (stream !== writer[_stream]) { + return PromiseReject(new TypeError("Writer's stream is unexpected.")); + } + const state = stream[_state]; + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return PromiseReject( + new TypeError("The stream is closing or is closed."), + ); + } + if (state === "erroring") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable"); + const promise = writableStreamAddWriteRequest(stream); + writableStreamDefaultControllerWrite(controller, chunk, chunkSize); + return promise; +} + +/** @param {WritableStream} stream */ +function writableStreamFinishErroring(stream) { + assert(stream[_state] === "erroring"); + assert(writableStreamHasOperationMarkedInFlight(stream) === false); + stream[_state] = "errored"; + stream[_controller][_errorSteps](); + const storedError = stream[_storedError]; + const writeRequests = stream[_writeRequests]; + for (let i = 0; i < writeRequests.length; ++i) { + const writeRequest = writeRequests[i]; + writeRequest.reject(storedError); + } + stream[_writeRequests] = []; + if (stream[_pendingAbortRequest] === undefined) { + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const abortRequest = stream[_pendingAbortRequest]; + stream[_pendingAbortRequest] = undefined; + if (abortRequest.wasAlreadyErroring === true) { + abortRequest.deferred.reject(storedError); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const promise = stream[_controller][_abortSteps](abortRequest.reason); + uponPromise(promise, () => { + abortRequest.deferred.resolve(undefined); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }, (reason) => { + abortRequest.deferred.reject(reason); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }); +} + +/** @param {WritableStream} stream */ +function writableStreamFinishInFlightClose(stream) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].resolve(undefined); + stream[_inFlightCloseRequest] = undefined; + const state = stream[_state]; + assert(state === "writable" || state === "erroring"); + if (state === "erroring") { + stream[_storedError] = undefined; if (stream[_pendingAbortRequest] !== undefined) { - stream[_pendingAbortRequest].deferred.reject(error); + stream[_pendingAbortRequest].deferred.resolve(undefined); stream[_pendingAbortRequest] = undefined; } - writableStreamDealWithRejection(stream, error); } - - /** @param {WritableStream} stream */ - function writableStreamFinishInFlightWrite(stream) { - assert(stream[_inFlightWriteRequest] !== undefined); - stream[_inFlightWriteRequest].resolve(undefined); - stream[_inFlightWriteRequest] = undefined; + stream[_state] = "closed"; + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].resolve(undefined); } + assert(stream[_pendingAbortRequest] === undefined); + assert(stream[_storedError] === undefined); +} - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamFinishInFlightWriteWithError(stream, error) { - assert(stream[_inFlightWriteRequest] !== undefined); - stream[_inFlightWriteRequest].reject(error); - stream[_inFlightWriteRequest] = undefined; - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - writableStreamDealWithRejection(stream, error); +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamFinishInFlightCloseWithError(stream, error) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].reject(error); + stream[_inFlightCloseRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + if (stream[_pendingAbortRequest] !== undefined) { + stream[_pendingAbortRequest].deferred.reject(error); + stream[_pendingAbortRequest] = undefined; } + writableStreamDealWithRejection(stream, error); +} - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamHasOperationMarkedInFlight(stream) { - if ( - stream[_inFlightWriteRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; - } - return true; +/** @param {WritableStream} stream */ +function writableStreamFinishInFlightWrite(stream) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].resolve(undefined); + stream[_inFlightWriteRequest] = undefined; +} + +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamFinishInFlightWriteWithError(stream, error) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].reject(error); + stream[_inFlightWriteRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + writableStreamDealWithRejection(stream, error); +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function writableStreamHasOperationMarkedInFlight(stream) { + if ( + stream[_inFlightWriteRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; } + return true; +} - /** @param {WritableStream} stream */ - function writableStreamMarkCloseRequestInFlight(stream) { +/** @param {WritableStream} stream */ +function writableStreamMarkCloseRequestInFlight(stream) { + assert(stream[_inFlightCloseRequest] === undefined); + assert(stream[_closeRequest] !== undefined); + stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest] = undefined; +} + +/** + * @template W + * @param {WritableStream} stream + */ +function writableStreamMarkFirstWriteRequestInFlight(stream) { + assert(stream[_inFlightWriteRequest] === undefined); + assert(stream[_writeRequests].length); + const writeRequest = stream[_writeRequests].shift(); + stream[_inFlightWriteRequest] = writeRequest; +} + +/** @param {WritableStream} stream */ +function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { + assert(stream[_state] === "errored"); + if (stream[_closeRequest] !== undefined) { assert(stream[_inFlightCloseRequest] === undefined); - assert(stream[_closeRequest] !== undefined); - stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest].reject(stream[_storedError]); stream[_closeRequest] = undefined; } - - /** - * @template W - * @param {WritableStream} stream - */ - function writableStreamMarkFirstWriteRequestInFlight(stream) { - assert(stream[_inFlightWriteRequest] === undefined); - assert(stream[_writeRequests].length); - const writeRequest = stream[_writeRequests].shift(); - stream[_inFlightWriteRequest] = writeRequest; + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); } +} - /** @param {WritableStream} stream */ - function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { - assert(stream[_state] === "errored"); - if (stream[_closeRequest] !== undefined) { - assert(stream[_inFlightCloseRequest] === undefined); - stream[_closeRequest].reject(stream[_storedError]); - stream[_closeRequest] = undefined; - } - const writer = stream[_writer]; - if (writer !== undefined) { - writer[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(writer[_closedPromise].promise); +/** + * @param {WritableStream} stream + * @param {any=} reason + */ +function writableStreamStartErroring(stream, reason) { + assert(stream[_storedError] === undefined); + assert(stream[_state] === "writable"); + const controller = stream[_controller]; + assert(controller !== undefined); + stream[_state] = "erroring"; + stream[_storedError] = reason; + const writer = stream[_writer]; + if (writer !== undefined) { + writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + } + if ( + writableStreamHasOperationMarkedInFlight(stream) === false && + controller[_started] === true + ) { + writableStreamFinishErroring(stream); + } +} + +/** + * @param {WritableStream} stream + * @param {boolean} backpressure + */ +function writableStreamUpdateBackpressure(stream, backpressure) { + assert(stream[_state] === "writable"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + const writer = stream[_writer]; + if (writer !== undefined && backpressure !== stream[_backpressure]) { + if (backpressure === true) { + writer[_readyPromise] = new Deferred(); + } else { + assert(backpressure === false); + writer[_readyPromise].resolve(undefined); } } + stream[_backpressure] = backpressure; +} - /** - * @param {WritableStream} stream - * @param {any=} reason - */ - function writableStreamStartErroring(stream, reason) { - assert(stream[_storedError] === undefined); - assert(stream[_state] === "writable"); - const controller = stream[_controller]; - assert(controller !== undefined); - stream[_state] = "erroring"; - stream[_storedError] = reason; - const writer = stream[_writer]; - if (writer !== undefined) { - writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); - } - if ( - writableStreamHasOperationMarkedInFlight(stream) === false && - controller[_started] === true - ) { - writableStreamFinishErroring(stream); - } - } +/** + * @template T + * @param {T} value + * @param {boolean} done + * @returns {IteratorResult} + */ +function createIteratorResult(value, done) { + const result = ObjectCreate(ObjectPrototype); + ObjectDefineProperties(result, { + value: { value, writable: true, enumerable: true, configurable: true }, + done: { + value: done, + writable: true, + enumerable: true, + configurable: true, + }, + }); + return result; +} - /** - * @param {WritableStream} stream - * @param {boolean} backpressure - */ - function writableStreamUpdateBackpressure(stream, backpressure) { - assert(stream[_state] === "writable"); - assert(writableStreamCloseQueuedOrInFlight(stream) === false); - const writer = stream[_writer]; - if (writer !== undefined && backpressure !== stream[_backpressure]) { - if (backpressure === true) { - writer[_readyPromise] = new Deferred(); - } else { - assert(backpressure === false); - writer[_readyPromise].resolve(undefined); +/** @type {AsyncIterator} */ +const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); + +const _iteratorNext = Symbol("[[iteratorNext]]"); +const _iteratorFinished = Symbol("[[iteratorFinished]]"); + +/** @type {AsyncIterator} */ +const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ + /** @returns {Promise>} */ + next() { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + function nextSteps() { + if (reader[_iteratorFinished]) { + return PromiseResolve(createIteratorResult(undefined, true)); } + + if (reader[_stream] === undefined) { + return PromiseReject( + new TypeError( + "Cannot get the next iteration result once the reader has been released.", + ), + ); + } + + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve(createIteratorResult(chunk, false)); + }, + closeSteps() { + readableStreamDefaultReaderRelease(reader); + promise.resolve(createIteratorResult(undefined, true)); + }, + errorSteps(e) { + readableStreamDefaultReaderRelease(reader); + promise.reject(e); + }, + }; + + readableStreamDefaultReaderRead(reader, readRequest); + return PromisePrototypeThen(promise.promise, (result) => { + reader[_iteratorNext] = null; + if (result.done === true) { + reader[_iteratorFinished] = true; + return createIteratorResult(undefined, true); + } + return result; + }, (reason) => { + reader[_iteratorNext] = null; + reader[_iteratorFinished] = true; + throw reason; + }); + } + + reader[_iteratorNext] = reader[_iteratorNext] + ? PromisePrototypeThen(reader[_iteratorNext], nextSteps, nextSteps) + : nextSteps(); + + return reader[_iteratorNext]; + }, + /** + * @param {unknown} arg + * @returns {Promise>} + */ + return(arg) { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + const returnSteps = () => { + if (reader[_iteratorFinished]) { + return PromiseResolve(createIteratorResult(arg, true)); + } + reader[_iteratorFinished] = true; + + if (reader[_stream] === undefined) { + return PromiseResolve(createIteratorResult(undefined, true)); + } + assert(reader[_readRequests].length === 0); + if (this[_preventCancel] === false) { + const result = readableStreamReaderGenericCancel(reader, arg); + readableStreamDefaultReaderRelease(reader); + return result; + } + readableStreamDefaultReaderRelease(reader); + return PromiseResolve(createIteratorResult(undefined, true)); + }; + + const returnPromise = reader[_iteratorNext] + ? PromisePrototypeThen(reader[_iteratorNext], returnSteps, returnSteps) + : returnSteps(); + return PromisePrototypeThen( + returnPromise, + () => createIteratorResult(arg, true), + ); + }, +}, asyncIteratorPrototype); + +class ByteLengthQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'ByteLengthQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = globalThis; + this[_highWaterMark] = init.highWaterMark; + } + + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + return this[_highWaterMark]; + } + + /** @returns {(chunk: ArrayBufferView) => number} */ + get size() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + initializeByteLengthSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ByteLengthQueuingStrategyPrototype, + this, + ), + keys: [ + "highWaterMark", + "size", + ], + })); + } +} + +webidl.configurePrototype(ByteLengthQueuingStrategy); +const ByteLengthQueuingStrategyPrototype = ByteLengthQueuingStrategy.prototype; + +/** @type {WeakMap number>} */ +const byteSizeFunctionWeakMap = new WeakMap(); + +function initializeByteLengthSizeFunction(globalObject) { + if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { + return; + } + const size = (chunk) => chunk.byteLength; + WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); +} + +class CountQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'CountQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = globalThis; + this[_highWaterMark] = init.highWaterMark; + } + + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, CountQueuingStrategyPrototype); + return this[_highWaterMark]; + } + + /** @returns {(chunk: any) => 1} */ + get size() { + webidl.assertBranded(this, CountQueuingStrategyPrototype); + initializeCountSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + CountQueuingStrategyPrototype, + this, + ), + keys: [ + "highWaterMark", + "size", + ], + })); + } +} + +webidl.configurePrototype(CountQueuingStrategy); +const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; + +/** @type {WeakMap 1>} */ +const countSizeFunctionWeakMap = new WeakMap(); + +/** @param {typeof globalThis} globalObject */ +function initializeCountSizeFunction(globalObject) { + if (WeakMapPrototypeHas(countSizeFunctionWeakMap, globalObject)) { + return; + } + const size = () => 1; + WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size); +} + +const _resourceBacking = Symbol("[[resourceBacking]]"); +// This distinction exists to prevent unrefable streams being used in +// regular fast streams that are unaware of refability +const _resourceBackingUnrefable = Symbol("[[resourceBackingUnrefable]]"); +/** @template R */ +class ReadableStream { + /** @type {ReadableStreamDefaultController | ReadableByteStreamController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {boolean} */ + [_disturbed]; + /** @type {ReadableStreamDefaultReader | ReadableStreamBYOBReader} */ + [_reader]; + /** @type {"readable" | "closed" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {{ rid: number, autoClose: boolean } | null} */ + [_resourceBacking] = null; + + /** + * @param {UnderlyingSource=} underlyingSource + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSource = undefined, strategy = undefined) { + const prefix = "Failed to construct 'ReadableStream'"; + if (underlyingSource !== undefined) { + underlyingSource = webidl.converters.object(underlyingSource, { + prefix, + context: "Argument 1", + }); + } else { + underlyingSource = null; + } + if (strategy !== undefined) { + strategy = webidl.converters.QueuingStrategy(strategy, { + prefix, + context: "Argument 2", + }); + } else { + strategy = {}; + } + this[webidl.brand] = webidl.brand; + let underlyingSourceDict = {}; + if (underlyingSource !== undefined) { + underlyingSourceDict = webidl.converters.UnderlyingSource( + underlyingSource, + { prefix, context: "underlyingSource" }, + ); + } + initializeReadableStream(this); + if (underlyingSourceDict.type === "bytes") { + if (strategy.size !== undefined) { + throw new RangeError( + `${prefix}: When underlying source is "bytes", strategy.size must be undefined.`, + ); + } + const highWaterMark = extractHighWaterMark(strategy, 0); + setUpReadableByteStreamControllerFromUnderlyingSource( + // @ts-ignore cannot easily assert this is ReadableStream + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + ); + } else { + assert(!(ReflectHas(underlyingSourceDict, "type"))); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpReadableStreamDefaultControllerFromUnderlyingSource( + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, + ); + } + } + + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, ReadableStreamPrototype); + return isReadableStreamLocked(this); + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); + } + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("Cannot cancel a locked ReadableStream."), + ); + } + return readableStreamCancel(this, reason); + } + + /** + * @param {ReadableStreamGetReaderOptions=} options + * @returns {ReadableStreamDefaultReader | ReadableStreamBYOBReader} + */ + getReader(options = undefined) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'getReader' on 'ReadableStream'"; + if (options !== undefined) { + options = webidl.converters.ReadableStreamGetReaderOptions(options, { + prefix, + context: "Argument 1", + }); + } else { + options = {}; + } + if (options.mode === undefined) { + return acquireReadableStreamDefaultReader(this); + } else { + assert(options.mode === "byob"); + return acquireReadableStreamBYOBReader(this); } - stream[_backpressure] = backpressure; } /** * @template T - * @param {T} value - * @param {boolean} done - * @returns {IteratorResult} + * @param {{ readable: ReadableStream, writable: WritableStream }} transform + * @param {PipeOptions=} options + * @returns {ReadableStream} */ - function createIteratorResult(value, done) { - const result = ObjectCreate(ObjectPrototype); - ObjectDefineProperties(result, { - value: { value, writable: true, enumerable: true, configurable: true }, - done: { - value: done, - writable: true, - enumerable: true, - configurable: true, - }, + pipeThrough(transform, options = {}) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + transform = webidl.converters.ReadableWritablePair(transform, { + prefix, + context: "Argument 1", }); - return result; + options = webidl.converters.StreamPipeOptions(options, { + prefix, + context: "Argument 2", + }); + const { readable, writable } = transform; + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + throw new TypeError("ReadableStream is already locked."); + } + if (isWritableStreamLocked(writable)) { + throw new TypeError("Target WritableStream is already locked."); + } + const promise = readableStreamPipeTo( + this, + writable, + preventClose, + preventAbort, + preventCancel, + signal, + ); + setPromiseIsHandledToTrue(promise); + return readable; } - /** @type {AsyncIterator} */ - const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); - - const _iteratorNext = Symbol("[[iteratorNext]]"); - const _iteratorFinished = Symbol("[[iteratorFinished]]"); - - /** @type {AsyncIterator} */ - const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ - /** @returns {Promise>} */ - next() { - /** @type {ReadableStreamDefaultReader} */ - const reader = this[_reader]; - function nextSteps() { - if (reader[_iteratorFinished]) { - return PromiseResolve(createIteratorResult(undefined, true)); - } - - if (reader[_stream] === undefined) { - return PromiseReject( - new TypeError( - "Cannot get the next iteration result once the reader has been released.", - ), - ); - } - - /** @type {Deferred>} */ - const promise = new Deferred(); - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - promise.resolve(createIteratorResult(chunk, false)); - }, - closeSteps() { - readableStreamDefaultReaderRelease(reader); - promise.resolve(createIteratorResult(undefined, true)); - }, - errorSteps(e) { - readableStreamDefaultReaderRelease(reader); - promise.reject(e); - }, - }; - - readableStreamDefaultReaderRead(reader, readRequest); - return PromisePrototypeThen(promise.promise, (result) => { - reader[_iteratorNext] = null; - if (result.done === true) { - reader[_iteratorFinished] = true; - return createIteratorResult(undefined, true); - } - return result; - }, (reason) => { - reader[_iteratorNext] = null; - reader[_iteratorFinished] = true; - throw reason; - }); - } - - reader[_iteratorNext] = reader[_iteratorNext] - ? PromisePrototypeThen(reader[_iteratorNext], nextSteps, nextSteps) - : nextSteps(); - - return reader[_iteratorNext]; - }, - /** - * @param {unknown} arg - * @returns {Promise>} - */ - return(arg) { - /** @type {ReadableStreamDefaultReader} */ - const reader = this[_reader]; - const returnSteps = () => { - if (reader[_iteratorFinished]) { - return PromiseResolve(createIteratorResult(arg, true)); - } - reader[_iteratorFinished] = true; - - if (reader[_stream] === undefined) { - return PromiseResolve(createIteratorResult(undefined, true)); - } - assert(reader[_readRequests].length === 0); - if (this[_preventCancel] === false) { - const result = readableStreamReaderGenericCancel(reader, arg); - readableStreamDefaultReaderRelease(reader); - return result; - } - readableStreamDefaultReaderRelease(reader); - return PromiseResolve(createIteratorResult(undefined, true)); - }; - - const returnPromise = reader[_iteratorNext] - ? PromisePrototypeThen(reader[_iteratorNext], returnSteps, returnSteps) - : returnSteps(); - return PromisePrototypeThen( - returnPromise, - () => createIteratorResult(arg, true), - ); - }, - }, asyncIteratorPrototype); - - class ByteLengthQueuingStrategy { - /** @param {{ highWaterMark: number }} init */ - constructor(init) { - const prefix = "Failed to construct 'ByteLengthQueuingStrategy'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - init = webidl.converters.QueuingStrategyInit(init, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - this[_globalObject] = window; - this[_highWaterMark] = init.highWaterMark; - } - - /** @returns {number} */ - get highWaterMark() { - webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); - return this[_highWaterMark]; - } - - /** @returns {(chunk: ArrayBufferView) => number} */ - get size() { - webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); - initializeByteLengthSizeFunction(this[_globalObject]); - return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ByteLengthQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); - } - } - - webidl.configurePrototype(ByteLengthQueuingStrategy); - const ByteLengthQueuingStrategyPrototype = - ByteLengthQueuingStrategy.prototype; - - /** @type {WeakMap number>} */ - const byteSizeFunctionWeakMap = new WeakMap(); - - function initializeByteLengthSizeFunction(globalObject) { - if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { - return; - } - const size = (chunk) => chunk.byteLength; - WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); - } - - class CountQueuingStrategy { - /** @param {{ highWaterMark: number }} init */ - constructor(init) { - const prefix = "Failed to construct 'CountQueuingStrategy'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - init = webidl.converters.QueuingStrategyInit(init, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - this[_globalObject] = window; - this[_highWaterMark] = init.highWaterMark; - } - - /** @returns {number} */ - get highWaterMark() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - return this[_highWaterMark]; - } - - /** @returns {(chunk: any) => 1} */ - get size() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - initializeCountSizeFunction(this[_globalObject]); - return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - CountQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); - } - } - - webidl.configurePrototype(CountQueuingStrategy); - const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; - - /** @type {WeakMap 1>} */ - const countSizeFunctionWeakMap = new WeakMap(); - - /** @param {typeof globalThis} globalObject */ - function initializeCountSizeFunction(globalObject) { - if (WeakMapPrototypeHas(countSizeFunctionWeakMap, globalObject)) { - return; - } - const size = () => 1; - WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size); - } - - const _resourceBacking = Symbol("[[resourceBacking]]"); - // This distinction exists to prevent unrefable streams being used in - // regular fast streams that are unaware of refability - const _resourceBackingUnrefable = Symbol("[[resourceBackingUnrefable]]"); - /** @template R */ - class ReadableStream { - /** @type {ReadableStreamDefaultController | ReadableByteStreamController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {boolean} */ - [_disturbed]; - /** @type {ReadableStreamDefaultReader | ReadableStreamBYOBReader} */ - [_reader]; - /** @type {"readable" | "closed" | "errored"} */ - [_state]; - /** @type {any} */ - [_storedError]; - /** @type {{ rid: number, autoClose: boolean } | null} */ - [_resourceBacking] = null; - - /** - * @param {UnderlyingSource=} underlyingSource - * @param {QueuingStrategy=} strategy - */ - constructor(underlyingSource = undefined, strategy = undefined) { - const prefix = "Failed to construct 'ReadableStream'"; - if (underlyingSource !== undefined) { - underlyingSource = webidl.converters.object(underlyingSource, { - prefix, - context: "Argument 1", - }); - } else { - underlyingSource = null; - } - if (strategy !== undefined) { - strategy = webidl.converters.QueuingStrategy(strategy, { - prefix, - context: "Argument 2", - }); - } else { - strategy = {}; - } - this[webidl.brand] = webidl.brand; - let underlyingSourceDict = {}; - if (underlyingSource !== undefined) { - underlyingSourceDict = webidl.converters.UnderlyingSource( - underlyingSource, - { prefix, context: "underlyingSource" }, - ); - } - initializeReadableStream(this); - if (underlyingSourceDict.type === "bytes") { - if (strategy.size !== undefined) { - throw new RangeError( - `${prefix}: When underlying source is "bytes", strategy.size must be undefined.`, - ); - } - const highWaterMark = extractHighWaterMark(strategy, 0); - setUpReadableByteStreamControllerFromUnderlyingSource( - // @ts-ignore cannot easily assert this is ReadableStream - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ); - } else { - assert(!(ReflectHas(underlyingSourceDict, "type"))); - const sizeAlgorithm = extractSizeAlgorithm(strategy); - const highWaterMark = extractHighWaterMark(strategy, 1); - setUpReadableStreamDefaultControllerFromUnderlyingSource( - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ); - } - } - - /** @returns {boolean} */ - get locked() { + /** + * @param {WritableStream} destination + * @param {PipeOptions=} options + * @returns {Promise} + */ + pipeTo(destination, options = {}) { + try { webidl.assertBranded(this, ReadableStreamPrototype); - return isReadableStreamLocked(this); - } - - /** - * @param {any=} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } - if (isReadableStreamLocked(this)) { - return PromiseReject( - new TypeError("Cannot cancel a locked ReadableStream."), - ); - } - return readableStreamCancel(this, reason); - } - - /** - * @param {ReadableStreamGetReaderOptions=} options - * @returns {ReadableStreamDefaultReader | ReadableStreamBYOBReader} - */ - getReader(options = undefined) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'getReader' on 'ReadableStream'"; - if (options !== undefined) { - options = webidl.converters.ReadableStreamGetReaderOptions(options, { - prefix, - context: "Argument 1", - }); - } else { - options = {}; - } - if (options.mode === undefined) { - return acquireReadableStreamDefaultReader(this); - } else { - assert(options.mode === "byob"); - return acquireReadableStreamBYOBReader(this); - } - } - - /** - * @template T - * @param {{ readable: ReadableStream, writable: WritableStream }} transform - * @param {PipeOptions=} options - * @returns {ReadableStream} - */ - pipeThrough(transform, options = {}) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'"; + const prefix = "Failed to execute 'pipeTo' on 'ReadableStream'"; webidl.requiredArguments(arguments.length, 1, { prefix }); - transform = webidl.converters.ReadableWritablePair(transform, { + destination = webidl.converters.WritableStream(destination, { prefix, context: "Argument 1", }); @@ -4803,1453 +4840,1411 @@ prefix, context: "Argument 2", }); - const { readable, writable } = transform; - const { preventClose, preventAbort, preventCancel, signal } = options; - if (isReadableStreamLocked(this)) { - throw new TypeError("ReadableStream is already locked."); - } - if (isWritableStreamLocked(writable)) { - throw new TypeError("Target WritableStream is already locked."); - } - const promise = readableStreamPipeTo( - this, - writable, - preventClose, - preventAbort, - preventCancel, - signal, - ); - setPromiseIsHandledToTrue(promise); - return readable; + } catch (err) { + return PromiseReject(err); } - - /** - * @param {WritableStream} destination - * @param {PipeOptions=} options - * @returns {Promise} - */ - pipeTo(destination, options = {}) { - try { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'pipeTo' on 'ReadableStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - destination = webidl.converters.WritableStream(destination, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.StreamPipeOptions(options, { - prefix, - context: "Argument 2", - }); - } catch (err) { - return PromiseReject(err); - } - const { preventClose, preventAbort, preventCancel, signal } = options; - if (isReadableStreamLocked(this)) { - return PromiseReject( - new TypeError("ReadableStream is already locked."), - ); - } - if (isWritableStreamLocked(destination)) { - return PromiseReject( - new TypeError("destination WritableStream is already locked."), - ); - } - return readableStreamPipeTo( - this, - destination, - preventClose, - preventAbort, - preventCancel, - signal, + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("ReadableStream is already locked."), ); } - - /** @returns {[ReadableStream, ReadableStream]} */ - tee() { - webidl.assertBranded(this, ReadableStreamPrototype); - return readableStreamTee(this, false); + if (isWritableStreamLocked(destination)) { + return PromiseReject( + new TypeError("destination WritableStream is already locked."), + ); } + return readableStreamPipeTo( + this, + destination, + preventClose, + preventAbort, + preventCancel, + signal, + ); + } - // TODO(lucacasonato): should be moved to webidl crate - /** - * @param {ReadableStreamIteratorOptions=} options - * @returns {AsyncIterableIterator} - */ - values(options = {}) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'values' on 'ReadableStream'"; - options = webidl.converters.ReadableStreamIteratorOptions(options, { - prefix, - context: "Argument 1", - }); - /** @type {AsyncIterableIterator} */ - const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); - const reader = acquireReadableStreamDefaultReader(this); - iterator[_reader] = reader; - iterator[_preventCancel] = options.preventCancel; - return iterator; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; - } + /** @returns {[ReadableStream, ReadableStream]} */ + tee() { + webidl.assertBranded(this, ReadableStreamPrototype); + return readableStreamTee(this, false); } // TODO(lucacasonato): should be moved to webidl crate - ReadableStream.prototype[SymbolAsyncIterator] = - ReadableStream.prototype.values; - ObjectDefineProperty(ReadableStream.prototype, SymbolAsyncIterator, { - writable: true, - enumerable: false, - configurable: true, - }); - - webidl.configurePrototype(ReadableStream); - const ReadableStreamPrototype = ReadableStream.prototype; - - function errorReadableStream(stream, e) { - readableStreamDefaultControllerError(stream[_controller], e); + /** + * @param {ReadableStreamIteratorOptions=} options + * @returns {AsyncIterableIterator} + */ + values(options = {}) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'values' on 'ReadableStream'"; + options = webidl.converters.ReadableStreamIteratorOptions(options, { + prefix, + context: "Argument 1", + }); + /** @type {AsyncIterableIterator} */ + const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); + const reader = acquireReadableStreamDefaultReader(this); + iterator[_reader] = reader; + iterator[_preventCancel] = options.preventCancel; + return iterator; } - /** @template R */ - class ReadableStreamDefaultReader { - /** @type {Deferred} */ - [_closedPromise]; - /** @type {ReadableStream | undefined} */ - [_stream]; - /** @type {ReadRequest[]} */ - [_readRequests]; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + } +} - /** @param {ReadableStream} stream */ - constructor(stream) { - const prefix = "Failed to construct 'ReadableStreamDefaultReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.ReadableStream(stream, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - setUpReadableStreamDefaultReader(this, stream); - } +// TODO(lucacasonato): should be moved to webidl crate +ReadableStream.prototype[SymbolAsyncIterator] = ReadableStream.prototype.values; +ObjectDefineProperty(ReadableStream.prototype, SymbolAsyncIterator, { + writable: true, + enumerable: false, + configurable: true, +}); - /** @returns {Promise>} */ - read() { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - /** @type {Deferred>} */ - const promise = new Deferred(); - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - promise.resolve({ value: chunk, done: false }); - }, - closeSteps() { - promise.resolve({ value: undefined, done: true }); - }, - errorSteps(e) { - promise.reject(e); - }, - }; - readableStreamDefaultReaderRead(this, readRequest); - return promise.promise; - } +webidl.configurePrototype(ReadableStream); +const ReadableStreamPrototype = ReadableStream.prototype; - /** @returns {void} */ - releaseLock() { +function errorReadableStream(stream, e) { + readableStreamDefaultControllerError(stream[_controller], e); +} + +/** @template R */ +class ReadableStreamDefaultReader { + /** @type {Deferred} */ + [_closedPromise]; + /** @type {ReadableStream | undefined} */ + [_stream]; + /** @type {ReadRequest[]} */ + [_readRequests]; + + /** @param {ReadableStream} stream */ + constructor(stream) { + const prefix = "Failed to construct 'ReadableStreamDefaultReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.ReadableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpReadableStreamDefaultReader(this, stream); + } + + /** @returns {Promise>} */ + read() { + try { webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - if (this[_stream] === undefined) { - return; - } - readableStreamDefaultReaderRelease(this); + } catch (err) { + return PromiseReject(err); } - - get closed() { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; - } - - /** - * @param {any} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } - - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - return readableStreamReaderGenericCancel(this, reason); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve({ value: chunk, done: false }); + }, + closeSteps() { + promise.resolve({ value: undefined, done: true }); + }, + errorSteps(e) { + promise.reject(e); + }, + }; + readableStreamDefaultReaderRead(this, readRequest); + return promise.promise; } - webidl.configurePrototype(ReadableStreamDefaultReader); - const ReadableStreamDefaultReaderPrototype = - ReadableStreamDefaultReader.prototype; + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (this[_stream] === undefined) { + return; + } + readableStreamDefaultReaderRelease(this); + } - /** @template R */ - class ReadableStreamBYOBReader { - /** @type {Deferred} */ - [_closedPromise]; - /** @type {ReadableStream | undefined} */ - [_stream]; - /** @type {ReadIntoRequest[]} */ - [_readIntoRequests]; + get closed() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + } catch (err) { + return PromiseReject(err); + } + return this[_closedPromise].promise; + } - /** @param {ReadableStream} stream */ - constructor(stream) { - const prefix = "Failed to construct 'ReadableStreamBYOBReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.ReadableStream(stream, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - setUpReadableStreamBYOBReader(this, stream); + /** + * @param {any} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); } - /** - * @param {ArrayBufferView} view - * @returns {Promise} - */ - read(view) { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'"; - view = webidl.converters.ArrayBufferView(view, { - prefix, - context: "Argument 1", - }); - } catch (err) { - return PromiseReject(err); - } - - if (view.byteLength === 0) { - return PromiseReject( - new TypeError("view must have non-zero byteLength"), - ); - } - if (view.buffer.byteLength === 0) { - return PromiseReject( - new TypeError("view's buffer must have non-zero byteLength"), - ); - } - if (isDetachedBuffer(view.buffer)) { - return PromiseReject( - new TypeError("view's buffer has been detached"), - ); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - /** @type {Deferred} */ - const promise = new Deferred(); - /** @type {ReadIntoRequest} */ - const readIntoRequest = { - chunkSteps(chunk) { - promise.resolve({ value: chunk, done: false }); - }, - closeSteps(chunk) { - promise.resolve({ value: chunk, done: true }); - }, - errorSteps(e) { - promise.reject(e); - }, - }; - readableStreamBYOBReaderRead(this, view, readIntoRequest); - return promise.promise; + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + return readableStreamReaderGenericCancel(this, reason); + } - /** @returns {void} */ - releaseLock() { + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + } +} + +webidl.configurePrototype(ReadableStreamDefaultReader); +const ReadableStreamDefaultReaderPrototype = + ReadableStreamDefaultReader.prototype; + +/** @template R */ +class ReadableStreamBYOBReader { + /** @type {Deferred} */ + [_closedPromise]; + /** @type {ReadableStream | undefined} */ + [_stream]; + /** @type {ReadIntoRequest[]} */ + [_readIntoRequests]; + + /** @param {ReadableStream} stream */ + constructor(stream) { + const prefix = "Failed to construct 'ReadableStreamBYOBReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.ReadableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpReadableStreamBYOBReader(this, stream); + } + + /** + * @param {ArrayBufferView} view + * @returns {Promise} + */ + read(view) { + try { webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - if (this[_stream] === undefined) { - return; - } - readableStreamBYOBReaderRelease(this); - } - - get closed() { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; - } - - /** - * @param {any} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } - - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - return readableStreamReaderGenericCancel(this, reason); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; - } - } - - webidl.configurePrototype(ReadableStreamBYOBReader); - const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; - - class ReadableStreamBYOBRequest { - /** @type {ReadableByteStreamController} */ - [_controller]; - /** @type {ArrayBufferView | null} */ - [_view]; - - /** @returns {ArrayBufferView | null} */ - get view() { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - return this[_view]; - } - - constructor() { - webidl.illegalConstructor(); - } - - respond(bytesWritten) { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - const prefix = - "Failed to execute 'respond' on 'ReadableStreamBYOBRequest'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - bytesWritten = webidl.converters["unsigned long long"](bytesWritten, { - enforceRange: true, - prefix, - context: "Argument 1", - }); - - if (this[_controller] === undefined) { - throw new TypeError("This BYOB request has been invalidated"); - } - if (isDetachedBuffer(this[_view].buffer)) { - throw new TypeError( - "The BYOB request's buffer has been detached and so cannot be used as a response", - ); - } - assert(this[_view].byteLength > 0); - assert(this[_view].buffer.byteLength > 0); - readableByteStreamControllerRespond(this[_controller], bytesWritten); - } - - respondWithNewView(view) { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - const prefix = - "Failed to execute 'respondWithNewView' on 'ReadableStreamBYOBRequest'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'"; view = webidl.converters.ArrayBufferView(view, { prefix, context: "Argument 1", }); - - if (this[_controller] === undefined) { - throw new TypeError("This BYOB request has been invalidated"); - } - if (isDetachedBuffer(view.buffer)) { - throw new TypeError( - "The given view's buffer has been detached and so cannot be used as a response", - ); - } - readableByteStreamControllerRespondWithNewView(this[_controller], view); + } catch (err) { + return PromiseReject(err); } + + if (view.byteLength === 0) { + return PromiseReject( + new TypeError("view must have non-zero byteLength"), + ); + } + if (view.buffer.byteLength === 0) { + return PromiseReject( + new TypeError("view's buffer must have non-zero byteLength"), + ); + } + if (isDetachedBuffer(view.buffer)) { + return PromiseReject( + new TypeError("view's buffer has been detached"), + ); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + /** @type {Deferred} */ + const promise = new Deferred(); + /** @type {ReadIntoRequest} */ + const readIntoRequest = { + chunkSteps(chunk) { + promise.resolve({ value: chunk, done: false }); + }, + closeSteps(chunk) { + promise.resolve({ value: chunk, done: true }); + }, + errorSteps(e) { + promise.reject(e); + }, + }; + readableStreamBYOBReaderRead(this, view, readIntoRequest); + return promise.promise; } - webidl.configurePrototype(ReadableStreamBYOBRequest); - const ReadableStreamBYOBRequestPrototype = - ReadableStreamBYOBRequest.prototype; - - class ReadableByteStreamController { - /** @type {number | undefined} */ - [_autoAllocateChunkSize]; - /** @type {ReadableStreamBYOBRequest | null} */ - [_byobRequest]; - /** @type {(reason: any) => Promise} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {PullIntoDescriptor[]} */ - [_pendingPullIntos]; - /** @type {ReadableByteStreamQueueEntry[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {ReadableStream} */ - [_stream]; - - constructor() { - webidl.illegalConstructor(); + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (this[_stream] === undefined) { + return; } + readableStreamBYOBReaderRelease(this); + } - /** @returns {ReadableStreamBYOBRequest | null} */ - get byobRequest() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - return readableByteStreamControllerGetBYOBRequest(this); + get closed() { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + } catch (err) { + return PromiseReject(err); } + return this[_closedPromise].promise; + } - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - return readableByteStreamControllerGetDesiredSize(this); - } - - /** @returns {void} */ - close() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - if (this[_closeRequested] === true) { - throw new TypeError("Closed already requested."); + /** + * @param {any} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - if (this[_stream][_state] !== "readable") { - throw new TypeError( - "ReadableByteStreamController's stream is not in a readable state.", - ); - } - readableByteStreamControllerClose(this); + } catch (err) { + return PromiseReject(err); } - /** - * @param {ArrayBufferView} chunk - * @returns {void} - */ - enqueue(chunk) { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - const prefix = - "Failed to execute 'enqueue' on 'ReadableByteStreamController'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - const arg1 = "Argument 1"; - chunk = webidl.converters.ArrayBufferView(chunk, { + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + return readableStreamReaderGenericCancel(this, reason); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + } +} + +webidl.configurePrototype(ReadableStreamBYOBReader); +const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; + +class ReadableStreamBYOBRequest { + /** @type {ReadableByteStreamController} */ + [_controller]; + /** @type {ArrayBufferView | null} */ + [_view]; + + /** @returns {ArrayBufferView | null} */ + get view() { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + return this[_view]; + } + + constructor() { + webidl.illegalConstructor(); + } + + respond(bytesWritten) { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + const prefix = "Failed to execute 'respond' on 'ReadableStreamBYOBRequest'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bytesWritten = webidl.converters["unsigned long long"](bytesWritten, { + enforceRange: true, + prefix, + context: "Argument 1", + }); + + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); + } + if (isDetachedBuffer(this[_view].buffer)) { + throw new TypeError( + "The BYOB request's buffer has been detached and so cannot be used as a response", + ); + } + assert(this[_view].byteLength > 0); + assert(this[_view].buffer.byteLength > 0); + readableByteStreamControllerRespond(this[_controller], bytesWritten); + } + + respondWithNewView(view) { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + const prefix = + "Failed to execute 'respondWithNewView' on 'ReadableStreamBYOBRequest'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + view = webidl.converters.ArrayBufferView(view, { + prefix, + context: "Argument 1", + }); + + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); + } + if (isDetachedBuffer(view.buffer)) { + throw new TypeError( + "The given view's buffer has been detached and so cannot be used as a response", + ); + } + readableByteStreamControllerRespondWithNewView(this[_controller], view); + } +} + +webidl.configurePrototype(ReadableStreamBYOBRequest); +const ReadableStreamBYOBRequestPrototype = ReadableStreamBYOBRequest.prototype; + +class ReadableByteStreamController { + /** @type {number | undefined} */ + [_autoAllocateChunkSize]; + /** @type {ReadableStreamBYOBRequest | null} */ + [_byobRequest]; + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {PullIntoDescriptor[]} */ + [_pendingPullIntos]; + /** @type {ReadableByteStreamQueueEntry[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {ReadableStreamBYOBRequest | null} */ + get byobRequest() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + return readableByteStreamControllerGetBYOBRequest(this); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + return readableByteStreamControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + if (this[_closeRequested] === true) { + throw new TypeError("Closed already requested."); + } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "ReadableByteStreamController's stream is not in a readable state.", + ); + } + readableByteStreamControllerClose(this); + } + + /** + * @param {ArrayBufferView} chunk + * @returns {void} + */ + enqueue(chunk) { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + const prefix = + "Failed to execute 'enqueue' on 'ReadableByteStreamController'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + const arg1 = "Argument 1"; + chunk = webidl.converters.ArrayBufferView(chunk, { + prefix, + context: arg1, + }); + if (chunk.byteLength === 0) { + throw webidl.makeException(TypeError, "length must be non-zero", { prefix, context: arg1, }); - if (chunk.byteLength === 0) { - throw webidl.makeException(TypeError, "length must be non-zero", { - prefix, - context: arg1, - }); - } - if (chunk.buffer.byteLength === 0) { - throw webidl.makeException( - TypeError, - "buffer length must be non-zero", - { prefix, context: arg1 }, - ); - } - if (this[_closeRequested] === true) { - throw new TypeError( - "Cannot enqueue chunk after a close has been requested.", - ); - } - if (this[_stream][_state] !== "readable") { - throw new TypeError( - "Cannot enqueue chunk when underlying stream is not readable.", - ); - } - return readableByteStreamControllerEnqueue(this, chunk); } - - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableByteStreamControllerError(this, e); + if (chunk.buffer.byteLength === 0) { + throw webidl.makeException( + TypeError, + "buffer length must be non-zero", + { prefix, context: arg1 }, + ); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - this, - ), - keys: ["desiredSize"], - })); + if (this[_closeRequested] === true) { + throw new TypeError( + "Cannot enqueue chunk after a close has been requested.", + ); } - - /** - * @param {any} reason - * @returns {Promise} - */ - [_cancelSteps](reason) { - readableByteStreamControllerClearPendingPullIntos(this); - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableByteStreamControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest} readRequest - * @returns {void} - */ - [_pullSteps](readRequest) { - /** @type {ReadableStream} */ - const stream = this[_stream]; - assert(readableStreamHasDefaultReader(stream)); - if (this[_queueTotalSize] > 0) { - assert(readableStreamGetNumReadRequests(stream) === 0); - readableByteStreamControllerFillReadRequestFromQueue(this, readRequest); - return; - } - const autoAllocateChunkSize = this[_autoAllocateChunkSize]; - if (autoAllocateChunkSize !== undefined) { - let buffer; - try { - buffer = new ArrayBuffer(autoAllocateChunkSize); - } catch (e) { - readRequest.errorSteps(e); - return; - } - /** @type {PullIntoDescriptor} */ - const pullIntoDescriptor = { - buffer, - bufferByteLength: autoAllocateChunkSize, - byteOffset: 0, - byteLength: autoAllocateChunkSize, - bytesFilled: 0, - elementSize: 1, - viewConstructor: Uint8Array, - readerType: "default", - }; - ArrayPrototypePush(this[_pendingPullIntos], pullIntoDescriptor); - } - readableStreamAddReadRequest(stream, readRequest); - readableByteStreamControllerCallPullIfNeeded(this); - } - - [_releaseSteps]() { - if (this[_pendingPullIntos].length !== 0) { - /** @type {PullIntoDescriptor} */ - const firstPendingPullInto = this[_pendingPullIntos][0]; - firstPendingPullInto.readerType = "none"; - this[_pendingPullIntos] = [firstPendingPullInto]; - } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "Cannot enqueue chunk when underlying stream is not readable.", + ); } + return readableByteStreamControllerEnqueue(this, chunk); } - webidl.configurePrototype(ReadableByteStreamController); - const ReadableByteStreamControllerPrototype = - ReadableByteStreamController.prototype; - - /** @template R */ - class ReadableStreamDefaultController { - /** @type {(reason: any) => Promise} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {Array>} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: R) => number} */ - [_strategySizeAlgorithm]; - /** @type {ReadableStream} */ - [_stream]; - - constructor() { - webidl.illegalConstructor(); - } - - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - return readableStreamDefaultControllerGetDesiredSize(this); - } - - /** @returns {void} */ - close() { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError("The stream controller cannot close or enqueue."); - } - readableStreamDefaultControllerClose(this); - } - - /** - * @param {R} chunk - * @returns {void} - */ - enqueue(chunk = undefined) { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError("The stream controller cannot close or enqueue."); - } - readableStreamDefaultControllerEnqueue(this, chunk); - } - - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableStreamDefaultControllerError(this, e); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableStreamDefaultController.prototype, - this, - ), - keys: ["desiredSize"], - })); - } - - /** - * @param {any} reason - * @returns {Promise} - */ - [_cancelSteps](reason) { - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest} readRequest - * @returns {void} - */ - [_pullSteps](readRequest) { - const stream = this[_stream]; - if (this[_queue].length) { - const chunk = dequeueValue(this); - if (this[_closeRequested] && this[_queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(this); - readableStreamClose(stream); - } else { - readableStreamDefaultControllerCallPullIfNeeded(this); - } - readRequest.chunkSteps(chunk); - } else { - readableStreamAddReadRequest(stream, readRequest); - readableStreamDefaultControllerCallPullIfNeeded(this); - } - } - - [_releaseSteps]() { - return; - } - } - - webidl.configurePrototype(ReadableStreamDefaultController); - const ReadableStreamDefaultControllerPrototype = - ReadableStreamDefaultController.prototype; - /** - * @template I - * @template O + * @param {any=} e + * @returns {void} */ - class TransformStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred} */ - [_backpressureChangePromise]; - /** @type {TransformStreamDefaultController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {ReadableStream} */ - [_readable]; - /** @type {WritableStream} */ - [_writable]; + error(e = undefined) { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); + } + readableByteStreamControllerError(this, e); + } - /** - * @param {Transformer} transformer - * @param {QueuingStrategy} writableStrategy - * @param {QueuingStrategy} readableStrategy - */ - constructor( - transformer = undefined, - writableStrategy = {}, - readableStrategy = {}, - ) { - const prefix = "Failed to construct 'TransformStream'"; - if (transformer !== undefined) { - transformer = webidl.converters.object(transformer, { - prefix, - context: "Argument 1", - }); - } - writableStrategy = webidl.converters.QueuingStrategy(writableStrategy, { - prefix, - context: "Argument 2", - }); - readableStrategy = webidl.converters.QueuingStrategy(readableStrategy, { - prefix, - context: "Argument 2", - }); - this[webidl.brand] = webidl.brand; - if (transformer === undefined) { - transformer = null; - } - const transformerDict = webidl.converters.Transformer(transformer, { - prefix, - context: "transformer", - }); - if (transformerDict.readableType !== undefined) { - throw new RangeError( - `${prefix}: readableType transformers not supported.`, - ); - } - if (transformerDict.writableType !== undefined) { - throw new RangeError( - `${prefix}: writableType transformers not supported.`, - ); - } - const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); - const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); - const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); - const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); - /** @type {Deferred} */ - const startPromise = new Deferred(); - initializeTransformStream( + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, this, - startPromise, - writableHighWaterMark, - writableSizeAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ); - setUpTransformStreamDefaultControllerFromTransformer( + ), + keys: ["desiredSize"], + })); + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + readableByteStreamControllerClearPendingPullIntos(this); + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableByteStreamControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + /** @type {ReadableStream} */ + const stream = this[_stream]; + assert(readableStreamHasDefaultReader(stream)); + if (this[_queueTotalSize] > 0) { + assert(readableStreamGetNumReadRequests(stream) === 0); + readableByteStreamControllerFillReadRequestFromQueue(this, readRequest); + return; + } + const autoAllocateChunkSize = this[_autoAllocateChunkSize]; + if (autoAllocateChunkSize !== undefined) { + let buffer; + try { + buffer = new ArrayBuffer(autoAllocateChunkSize); + } catch (e) { + readRequest.errorSteps(e); + return; + } + /** @type {PullIntoDescriptor} */ + const pullIntoDescriptor = { + buffer, + bufferByteLength: autoAllocateChunkSize, + byteOffset: 0, + byteLength: autoAllocateChunkSize, + bytesFilled: 0, + elementSize: 1, + viewConstructor: Uint8Array, + readerType: "default", + }; + ArrayPrototypePush(this[_pendingPullIntos], pullIntoDescriptor); + } + readableStreamAddReadRequest(stream, readRequest); + readableByteStreamControllerCallPullIfNeeded(this); + } + + [_releaseSteps]() { + if (this[_pendingPullIntos].length !== 0) { + /** @type {PullIntoDescriptor} */ + const firstPendingPullInto = this[_pendingPullIntos][0]; + firstPendingPullInto.readerType = "none"; + this[_pendingPullIntos] = [firstPendingPullInto]; + } + } +} + +webidl.configurePrototype(ReadableByteStreamController); +const ReadableByteStreamControllerPrototype = + ReadableByteStreamController.prototype; + +/** @template R */ +class ReadableStreamDefaultController { + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {Array>} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: R) => number} */ + [_strategySizeAlgorithm]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + return readableStreamDefaultControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); + } + readableStreamDefaultControllerClose(this); + } + + /** + * @param {R} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); + } + readableStreamDefaultControllerEnqueue(this, chunk); + } + + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); + } + readableStreamDefaultControllerError(this, e); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableStreamDefaultController.prototype, this, - transformer, - transformerDict, - ); - if (transformerDict.start) { - startPromise.resolve( - webidl.invokeCallbackFunction( - transformerDict.start, - [this[_controller]], - transformer, - webidl.converters.any, - { - prefix: - "Failed to call 'start' on 'TransformStreamDefaultController'", - }, - ), - ); + ), + keys: ["desiredSize"], + })); + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + const stream = this[_stream]; + if (this[_queue].length) { + const chunk = dequeueValue(this); + if (this[_closeRequested] && this[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(this); + readableStreamClose(stream); } else { - startPromise.resolve(undefined); + readableStreamDefaultControllerCallPullIfNeeded(this); } - } - - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_readable]; - } - - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_writable]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ readable: this.readable, writable: this.writable }) - }`; + readRequest.chunkSteps(chunk); + } else { + readableStreamAddReadRequest(stream, readRequest); + readableStreamDefaultControllerCallPullIfNeeded(this); } } - webidl.configurePrototype(TransformStream); - const TransformStreamPrototype = TransformStream.prototype; - - /** @template O */ - class TransformStreamDefaultController { - /** @type {(controller: this) => Promise} */ - [_flushAlgorithm]; - /** @type {TransformStream} */ - [_stream]; - /** @type {(chunk: O, controller: this) => Promise} */ - [_transformAlgorithm]; - - constructor() { - webidl.illegalConstructor(); - } - - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - const readableController = this[_stream][_readable][_controller]; - return readableStreamDefaultControllerGetDesiredSize( - /** @type {ReadableStreamDefaultController} */ readableController, - ); - } - - /** - * @param {O} chunk - * @returns {void} - */ - enqueue(chunk = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - transformStreamDefaultControllerEnqueue(this, chunk); - } - - /** - * @param {any=} reason - * @returns {void} - */ - error(reason = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - transformStreamDefaultControllerError(this, reason); - } - - /** @returns {void} */ - terminate() { - webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); - transformStreamDefaultControllerTerminate(this); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - TransformStreamDefaultController.prototype, - this, - ), - keys: ["desiredSize"], - })); - } + [_releaseSteps]() { + return; } +} - webidl.configurePrototype(TransformStreamDefaultController); - const TransformStreamDefaultControllerPrototype = - TransformStreamDefaultController.prototype; +webidl.configurePrototype(ReadableStreamDefaultController); +const ReadableStreamDefaultControllerPrototype = + ReadableStreamDefaultController.prototype; - /** @template W */ - class WritableStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred | undefined} */ - [_closeRequest]; - /** @type {WritableStreamDefaultController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {Deferred | undefined} */ - [_inFlightWriteRequest]; - /** @type {Deferred | undefined} */ - [_inFlightCloseRequest]; - /** @type {PendingAbortRequest | undefined} */ - [_pendingAbortRequest]; - /** @type {"writable" | "closed" | "erroring" | "errored"} */ - [_state]; - /** @type {any} */ - [_storedError]; - /** @type {WritableStreamDefaultWriter} */ - [_writer]; - /** @type {Deferred[]} */ - [_writeRequests]; +/** + * @template I + * @template O + */ +class TransformStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred} */ + [_backpressureChangePromise]; + /** @type {TransformStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {ReadableStream} */ + [_readable]; + /** @type {WritableStream} */ + [_writable]; - /** - * @param {UnderlyingSink=} underlyingSink - * @param {QueuingStrategy=} strategy - */ - constructor(underlyingSink = undefined, strategy = {}) { - const prefix = "Failed to construct 'WritableStream'"; - if (underlyingSink !== undefined) { - underlyingSink = webidl.converters.object(underlyingSink, { - prefix, - context: "Argument 1", - }); - } - strategy = webidl.converters.QueuingStrategy(strategy, { - prefix, - context: "Argument 2", - }); - this[webidl.brand] = webidl.brand; - if (underlyingSink === undefined) { - underlyingSink = null; - } - const underlyingSinkDict = webidl.converters.UnderlyingSink( - underlyingSink, - { prefix, context: "underlyingSink" }, - ); - if (underlyingSinkDict.type != null) { - throw new RangeError( - `${prefix}: WritableStream does not support 'type' in the underlying sink.`, - ); - } - initializeWritableStream(this); - const sizeAlgorithm = extractSizeAlgorithm(strategy); - const highWaterMark = extractHighWaterMark(strategy, 1); - setUpWritableStreamDefaultControllerFromUnderlyingSink( - this, - underlyingSink, - underlyingSinkDict, - highWaterMark, - sizeAlgorithm, - ); - } - - /** @returns {boolean} */ - get locked() { - webidl.assertBranded(this, WritableStreamPrototype); - return isWritableStreamLocked(this); - } - - /** - * @param {any=} reason - * @returns {Promise} - */ - abort(reason = undefined) { - try { - webidl.assertBranded(this, WritableStreamPrototype); - } catch (err) { - return PromiseReject(err); - } - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - if (isWritableStreamLocked(this)) { - return PromiseReject( - new TypeError( - "The writable stream is locked, therefore cannot be aborted.", - ), - ); - } - return writableStreamAbort(this, reason); - } - - /** @returns {Promise} */ - close() { - try { - webidl.assertBranded(this, WritableStreamPrototype); - } catch (err) { - return PromiseReject(err); - } - if (isWritableStreamLocked(this)) { - return PromiseReject( - new TypeError( - "The writable stream is locked, therefore cannot be closed.", - ), - ); - } - if (writableStreamCloseQueuedOrInFlight(this) === true) { - return PromiseReject( - new TypeError("The writable stream is already closing."), - ); - } - return writableStreamClose(this); - } - - /** @returns {WritableStreamDefaultWriter} */ - getWriter() { - webidl.assertBranded(this, WritableStreamPrototype); - return acquireWritableStreamDefaultWriter(this); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; - } - } - - webidl.configurePrototype(WritableStream); - const WritableStreamPrototype = WritableStream.prototype; - - /** @template W */ - class WritableStreamDefaultWriter { - /** @type {Deferred} */ - [_closedPromise]; - - /** @type {Deferred} */ - [_readyPromise]; - - /** @type {WritableStream} */ - [_stream]; - - /** - * @param {WritableStream} stream - */ - constructor(stream) { - const prefix = "Failed to construct 'WritableStreamDefaultWriter'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.WritableStream(stream, { + /** + * @param {Transformer} transformer + * @param {QueuingStrategy} writableStrategy + * @param {QueuingStrategy} readableStrategy + */ + constructor( + transformer = undefined, + writableStrategy = {}, + readableStrategy = {}, + ) { + const prefix = "Failed to construct 'TransformStream'"; + if (transformer !== undefined) { + transformer = webidl.converters.object(transformer, { prefix, context: "Argument 1", }); - this[webidl.brand] = webidl.brand; - setUpWritableStreamDefaultWriter(this, stream); } - - /** @returns {Promise} */ - get closed() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; + writableStrategy = webidl.converters.QueuingStrategy(writableStrategy, { + prefix, + context: "Argument 2", + }); + readableStrategy = webidl.converters.QueuingStrategy(readableStrategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (transformer === undefined) { + transformer = null; } - - /** @returns {number} */ - get desiredSize() { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - if (this[_stream] === undefined) { - throw new TypeError( - "A writable stream is not associated with the writer.", - ); - } - return writableStreamDefaultWriterGetDesiredSize(this); + const transformerDict = webidl.converters.Transformer(transformer, { + prefix, + context: "transformer", + }); + if (transformerDict.readableType !== undefined) { + throw new RangeError( + `${prefix}: readableType transformers not supported.`, + ); } - - /** @returns {Promise} */ - get ready() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_readyPromise].promise; + if (transformerDict.writableType !== undefined) { + throw new RangeError( + `${prefix}: writableType transformers not supported.`, + ); } - - /** - * @param {any} reason - * @returns {Promise} - */ - abort(reason = undefined) { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associated with the writer."), - ); - } - return writableStreamDefaultWriterAbort(this, reason); - } - - /** @returns {Promise} */ - close() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - const stream = this[_stream]; - if (stream === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associated with the writer."), - ); - } - if (writableStreamCloseQueuedOrInFlight(stream) === true) { - return PromiseReject( - new TypeError("The associated stream is already closing."), - ); - } - return writableStreamDefaultWriterClose(this); - } - - /** @returns {void} */ - releaseLock() { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - const stream = this[_stream]; - if (stream === undefined) { - return; - } - assert(stream[_writer] !== undefined); - writableStreamDefaultWriterRelease(this); - } - - /** - * @param {W} chunk - * @returns {Promise} - */ - write(chunk = undefined) { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - } catch (err) { - return PromiseReject(err); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associate with the writer."), - ); - } - return writableStreamDefaultWriterWrite(this, chunk); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - WritableStreamDefaultWriter.prototype, - this, + const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); + const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); + const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); + const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); + /** @type {Deferred} */ + const startPromise = new Deferred(); + initializeTransformStream( + this, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ); + setUpTransformStreamDefaultControllerFromTransformer( + this, + transformer, + transformerDict, + ); + if (transformerDict.start) { + startPromise.resolve( + webidl.invokeCallbackFunction( + transformerDict.start, + [this[_controller]], + transformer, + webidl.converters.any, + { + prefix: + "Failed to call 'start' on 'TransformStreamDefaultController'", + }, ), - keys: [ - "closed", - "desiredSize", - "ready", - ], - })); + ); + } else { + startPromise.resolve(undefined); } } - webidl.configurePrototype(WritableStreamDefaultWriter); - const WritableStreamDefaultWriterPrototype = - WritableStreamDefaultWriter.prototype; - - /** @template W */ - class WritableStreamDefaultController { - /** @type {(reason?: any) => Promise} */ - [_abortAlgorithm]; - /** @type {() => Promise} */ - [_closeAlgorithm]; - /** @type {ValueWithSize[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: W) => number} */ - [_strategySizeAlgorithm]; - /** @type {WritableStream} */ - [_stream]; - /** @type {(chunk: W, controller: this) => Promise} */ - [_writeAlgorithm]; - /** @type {AbortSignal} */ - [_signal]; - - get signal() { - webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); - return this[_signal]; - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - const state = this[_stream][_state]; - if (state !== "writable") { - return; - } - writableStreamDefaultControllerError(this, e); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - WritableStreamDefaultController.prototype, - this, - ), - keys: [], - })); - } - - /** - * @param {any=} reason - * @returns {Promise} - */ - [_abortSteps](reason) { - const result = this[_abortAlgorithm](reason); - writableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - [_errorSteps]() { - resetQueue(this); - } + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_readable]; } - webidl.configurePrototype(WritableStreamDefaultController); - const WritableStreamDefaultControllerPrototype = - WritableStreamDefaultController.prototype; + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_writable]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ readable: this.readable, writable: this.writable }) + }`; + } +} + +webidl.configurePrototype(TransformStream); +const TransformStreamPrototype = TransformStream.prototype; + +/** @template O */ +class TransformStreamDefaultController { + /** @type {(controller: this) => Promise} */ + [_flushAlgorithm]; + /** @type {TransformStream} */ + [_stream]; + /** @type {(chunk: O, controller: this) => Promise} */ + [_transformAlgorithm]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + const readableController = this[_stream][_readable][_controller]; + return readableStreamDefaultControllerGetDesiredSize( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + } /** - * @param {ReadableStream} stream + * @param {O} chunk + * @returns {void} */ - function createProxy(stream) { - return stream.pipeThrough(new TransformStream()); + enqueue(chunk = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + transformStreamDefaultControllerEnqueue(this, chunk); } - webidl.converters.ReadableStream = webidl - .createInterfaceConverter("ReadableStream", ReadableStream.prototype); - webidl.converters.WritableStream = webidl - .createInterfaceConverter("WritableStream", WritableStream.prototype); + /** + * @param {any=} reason + * @returns {void} + */ + error(reason = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + transformStreamDefaultControllerError(this, reason); + } - webidl.converters.ReadableStreamType = webidl.createEnumConverter( - "ReadableStreamType", - ["bytes"], - ); + /** @returns {void} */ + terminate() { + webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); + transformStreamDefaultControllerTerminate(this); + } - webidl.converters.UnderlyingSource = webidl - .createDictionaryConverter("UnderlyingSource", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "pull", - converter: webidl.converters.Function, - }, - { - key: "cancel", - converter: webidl.converters.Function, - }, - { - key: "type", - converter: webidl.converters.ReadableStreamType, - }, - { - key: "autoAllocateChunkSize", - converter: (V, opts) => - webidl.converters["unsigned long long"](V, { - ...opts, - enforceRange: true, - }), - }, - ]); - webidl.converters.UnderlyingSink = webidl - .createDictionaryConverter("UnderlyingSink", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "write", - converter: webidl.converters.Function, - }, - { - key: "close", - converter: webidl.converters.Function, - }, - { - key: "abort", - converter: webidl.converters.Function, - }, - { - key: "type", - converter: webidl.converters.any, - }, - ]); - webidl.converters.Transformer = webidl - .createDictionaryConverter("Transformer", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "transform", - converter: webidl.converters.Function, - }, - { - key: "flush", - converter: webidl.converters.Function, - }, - { - key: "readableType", - converter: webidl.converters.any, - }, - { - key: "writableType", - converter: webidl.converters.any, - }, - ]); - webidl.converters.QueuingStrategy = webidl - .createDictionaryConverter("QueuingStrategy", [ - { - key: "highWaterMark", - converter: webidl.converters["unrestricted double"], - }, - { - key: "size", - converter: webidl.converters.Function, - }, - ]); - webidl.converters.QueuingStrategyInit = webidl - .createDictionaryConverter("QueuingStrategyInit", [ - { - key: "highWaterMark", - converter: webidl.converters["unrestricted double"], - required: true, - }, - ]); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + TransformStreamDefaultController.prototype, + this, + ), + keys: ["desiredSize"], + })); + } +} - webidl.converters.ReadableStreamIteratorOptions = webidl - .createDictionaryConverter("ReadableStreamIteratorOptions", [ - { - key: "preventCancel", - defaultValue: false, - converter: webidl.converters.boolean, - }, - ]); +webidl.configurePrototype(TransformStreamDefaultController); +const TransformStreamDefaultControllerPrototype = + TransformStreamDefaultController.prototype; - webidl.converters.ReadableStreamReaderMode = webidl - .createEnumConverter("ReadableStreamReaderMode", ["byob"]); - webidl.converters.ReadableStreamGetReaderOptions = webidl - .createDictionaryConverter("ReadableStreamGetReaderOptions", [{ - key: "mode", - converter: webidl.converters.ReadableStreamReaderMode, - }]); +/** @template W */ +class WritableStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred | undefined} */ + [_closeRequest]; + /** @type {WritableStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {Deferred | undefined} */ + [_inFlightWriteRequest]; + /** @type {Deferred | undefined} */ + [_inFlightCloseRequest]; + /** @type {PendingAbortRequest | undefined} */ + [_pendingAbortRequest]; + /** @type {"writable" | "closed" | "erroring" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {WritableStreamDefaultWriter} */ + [_writer]; + /** @type {Deferred[]} */ + [_writeRequests]; - webidl.converters.ReadableWritablePair = webidl - .createDictionaryConverter("ReadableWritablePair", [ - { - key: "readable", - converter: webidl.converters.ReadableStream, - required: true, - }, - { - key: "writable", - converter: webidl.converters.WritableStream, - required: true, - }, - ]); - webidl.converters.StreamPipeOptions = webidl - .createDictionaryConverter("StreamPipeOptions", [ - { - key: "preventClose", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "preventAbort", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "preventCancel", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { key: "signal", converter: webidl.converters.AbortSignal }, - ]); + /** + * @param {UnderlyingSink=} underlyingSink + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSink = undefined, strategy = {}) { + const prefix = "Failed to construct 'WritableStream'"; + if (underlyingSink !== undefined) { + underlyingSink = webidl.converters.object(underlyingSink, { + prefix, + context: "Argument 1", + }); + } + strategy = webidl.converters.QueuingStrategy(strategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (underlyingSink === undefined) { + underlyingSink = null; + } + const underlyingSinkDict = webidl.converters.UnderlyingSink( + underlyingSink, + { prefix, context: "underlyingSink" }, + ); + if (underlyingSinkDict.type != null) { + throw new RangeError( + `${prefix}: WritableStream does not support 'type' in the underlying sink.`, + ); + } + initializeWritableStream(this); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpWritableStreamDefaultControllerFromUnderlyingSink( + this, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, + ); + } - window.__bootstrap.streams = { - // Non-Public - _state, - isReadableStreamDisturbed, - errorReadableStream, - createProxy, - writableStreamClose, - readableStreamClose, - readableStreamCollectIntoUint8Array, - readableStreamDisturb, - readableStreamForRid, - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - readableStreamThrowIfErrored, - getReadableStreamResourceBacking, - writableStreamForRid, - getWritableStreamResourceBacking, - Deferred, - // Exposed in global runtime scope - ByteLengthQueuingStrategy, - CountQueuingStrategy, - ReadableStream, - ReadableStreamPrototype, - ReadableStreamDefaultReader, - TransformStream, - WritableStream, - WritableStreamDefaultWriter, - WritableStreamDefaultController, - ReadableByteStreamController, - ReadableStreamBYOBReader, - ReadableStreamBYOBRequest, - ReadableStreamDefaultController, - TransformStreamDefaultController, - }; -})(this); + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, WritableStreamPrototype); + return isWritableStreamLocked(this); + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamPrototype); + } catch (err) { + return PromiseReject(err); + } + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be aborted.", + ), + ); + } + return writableStreamAbort(this, reason); + } + + /** @returns {Promise} */ + close() { + try { + webidl.assertBranded(this, WritableStreamPrototype); + } catch (err) { + return PromiseReject(err); + } + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be closed.", + ), + ); + } + if (writableStreamCloseQueuedOrInFlight(this) === true) { + return PromiseReject( + new TypeError("The writable stream is already closing."), + ); + } + return writableStreamClose(this); + } + + /** @returns {WritableStreamDefaultWriter} */ + getWriter() { + webidl.assertBranded(this, WritableStreamPrototype); + return acquireWritableStreamDefaultWriter(this); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + } +} + +webidl.configurePrototype(WritableStream); +const WritableStreamPrototype = WritableStream.prototype; + +/** @template W */ +class WritableStreamDefaultWriter { + /** @type {Deferred} */ + [_closedPromise]; + + /** @type {Deferred} */ + [_readyPromise]; + + /** @type {WritableStream} */ + [_stream]; + + /** + * @param {WritableStream} stream + */ + constructor(stream) { + const prefix = "Failed to construct 'WritableStreamDefaultWriter'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.WritableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpWritableStreamDefaultWriter(this, stream); + } + + /** @returns {Promise} */ + get closed() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); + } + return this[_closedPromise].promise; + } + + /** @returns {number} */ + get desiredSize() { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + if (this[_stream] === undefined) { + throw new TypeError( + "A writable stream is not associated with the writer.", + ); + } + return writableStreamDefaultWriterGetDesiredSize(this); + } + + /** @returns {Promise} */ + get ready() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); + } + return this[_readyPromise].promise; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); + } + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); + } + return writableStreamDefaultWriterAbort(this, reason); + } + + /** @returns {Promise} */ + close() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); + } + const stream = this[_stream]; + if (stream === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); + } + if (writableStreamCloseQueuedOrInFlight(stream) === true) { + return PromiseReject( + new TypeError("The associated stream is already closing."), + ); + } + return writableStreamDefaultWriterClose(this); + } + + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + const stream = this[_stream]; + if (stream === undefined) { + return; + } + assert(stream[_writer] !== undefined); + writableStreamDefaultWriterRelease(this); + } + + /** + * @param {W} chunk + * @returns {Promise} + */ + write(chunk = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + } catch (err) { + return PromiseReject(err); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associate with the writer."), + ); + } + return writableStreamDefaultWriterWrite(this, chunk); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + WritableStreamDefaultWriter.prototype, + this, + ), + keys: [ + "closed", + "desiredSize", + "ready", + ], + })); + } +} + +webidl.configurePrototype(WritableStreamDefaultWriter); +const WritableStreamDefaultWriterPrototype = + WritableStreamDefaultWriter.prototype; + +/** @template W */ +class WritableStreamDefaultController { + /** @type {(reason?: any) => Promise} */ + [_abortAlgorithm]; + /** @type {() => Promise} */ + [_closeAlgorithm]; + /** @type {ValueWithSize[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: W) => number} */ + [_strategySizeAlgorithm]; + /** @type {WritableStream} */ + [_stream]; + /** @type {(chunk: W, controller: this) => Promise} */ + [_writeAlgorithm]; + /** @type {AbortSignal} */ + [_signal]; + + get signal() { + webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); + return this[_signal]; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); + } + const state = this[_stream][_state]; + if (state !== "writable") { + return; + } + writableStreamDefaultControllerError(this, e); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + WritableStreamDefaultController.prototype, + this, + ), + keys: [], + })); + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + [_abortSteps](reason) { + const result = this[_abortAlgorithm](reason); + writableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + [_errorSteps]() { + resetQueue(this); + } +} + +webidl.configurePrototype(WritableStreamDefaultController); +const WritableStreamDefaultControllerPrototype = + WritableStreamDefaultController.prototype; + +/** + * @param {ReadableStream} stream + */ +function createProxy(stream) { + return stream.pipeThrough(new TransformStream()); +} + +webidl.converters.ReadableStream = webidl + .createInterfaceConverter("ReadableStream", ReadableStream.prototype); +webidl.converters.WritableStream = webidl + .createInterfaceConverter("WritableStream", WritableStream.prototype); + +webidl.converters.ReadableStreamType = webidl.createEnumConverter( + "ReadableStreamType", + ["bytes"], +); + +webidl.converters.UnderlyingSource = webidl + .createDictionaryConverter("UnderlyingSource", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "pull", + converter: webidl.converters.Function, + }, + { + key: "cancel", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.ReadableStreamType, + }, + { + key: "autoAllocateChunkSize", + converter: (V, opts) => + webidl.converters["unsigned long long"](V, { + ...opts, + enforceRange: true, + }), + }, + ]); +webidl.converters.UnderlyingSink = webidl + .createDictionaryConverter("UnderlyingSink", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "write", + converter: webidl.converters.Function, + }, + { + key: "close", + converter: webidl.converters.Function, + }, + { + key: "abort", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.any, + }, + ]); +webidl.converters.Transformer = webidl + .createDictionaryConverter("Transformer", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "transform", + converter: webidl.converters.Function, + }, + { + key: "flush", + converter: webidl.converters.Function, + }, + { + key: "readableType", + converter: webidl.converters.any, + }, + { + key: "writableType", + converter: webidl.converters.any, + }, + ]); +webidl.converters.QueuingStrategy = webidl + .createDictionaryConverter("QueuingStrategy", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + }, + { + key: "size", + converter: webidl.converters.Function, + }, + ]); +webidl.converters.QueuingStrategyInit = webidl + .createDictionaryConverter("QueuingStrategyInit", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + required: true, + }, + ]); + +webidl.converters.ReadableStreamIteratorOptions = webidl + .createDictionaryConverter("ReadableStreamIteratorOptions", [ + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + ]); + +webidl.converters.ReadableStreamReaderMode = webidl + .createEnumConverter("ReadableStreamReaderMode", ["byob"]); +webidl.converters.ReadableStreamGetReaderOptions = webidl + .createDictionaryConverter("ReadableStreamGetReaderOptions", [{ + key: "mode", + converter: webidl.converters.ReadableStreamReaderMode, + }]); + +webidl.converters.ReadableWritablePair = webidl + .createDictionaryConverter("ReadableWritablePair", [ + { + key: "readable", + converter: webidl.converters.ReadableStream, + required: true, + }, + { + key: "writable", + converter: webidl.converters.WritableStream, + required: true, + }, + ]); +webidl.converters.StreamPipeOptions = webidl + .createDictionaryConverter("StreamPipeOptions", [ + { + key: "preventClose", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventAbort", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { key: "signal", converter: webidl.converters.AbortSignal }, + ]); + +export { + // Non-Public + _state, + // Exposed in global runtime scope + ByteLengthQueuingStrategy, + CountQueuingStrategy, + createProxy, + Deferred, + errorReadableStream, + getReadableStreamResourceBacking, + getWritableStreamResourceBacking, + isReadableStreamDisturbed, + ReadableByteStreamController, + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + readableStreamClose, + readableStreamCollectIntoUint8Array, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + readableStreamDisturb, + readableStreamForRid, + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + ReadableStreamPrototype, + readableStreamThrowIfErrored, + TransformStream, + TransformStreamDefaultController, + WritableStream, + writableStreamClose, + WritableStreamDefaultController, + WritableStreamDefaultWriter, + writableStreamForRid, +}; diff --git a/ext/web/08_text_encoding.js b/ext/web/08_text_encoding.js index 8de7b949f2..f3ad966d09 100644 --- a/ext/web/08_text_encoding.js +++ b/ext/web/08_text_encoding.js @@ -9,437 +9,434 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + PromiseReject, + PromiseResolve, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + StringPrototypeCharCodeAt, + StringPrototypeSlice, + TypedArrayPrototypeSubarray, + Uint8Array, + ObjectPrototypeIsPrototypeOf, + ArrayBufferIsView, + Uint32Array, +} = primordials; -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - PromiseReject, - PromiseResolve, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - StringPrototypeCharCodeAt, - StringPrototypeSlice, - TypedArrayPrototypeSubarray, - Uint8Array, - ObjectPrototypeIsPrototypeOf, - ArrayBufferIsView, - Uint32Array, - } = window.__bootstrap.primordials; +class TextDecoder { + /** @type {string} */ + #encoding; + /** @type {boolean} */ + #fatal; + /** @type {boolean} */ + #ignoreBOM; + /** @type {boolean} */ + #utf8SinglePass; - class TextDecoder { - /** @type {string} */ - #encoding; - /** @type {boolean} */ - #fatal; - /** @type {boolean} */ - #ignoreBOM; - /** @type {boolean} */ - #utf8SinglePass; + /** @type {number | null} */ + #rid = null; - /** @type {number | null} */ - #rid = null; - - /** - * @param {string} label - * @param {TextDecoderOptions} options - */ - constructor(label = "utf-8", options = {}) { - const prefix = "Failed to construct 'TextDecoder'"; - label = webidl.converters.DOMString(label, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.TextDecoderOptions(options, { - prefix, - context: "Argument 2", - }); - const encoding = ops.op_encoding_normalize_label(label); - this.#encoding = encoding; - this.#fatal = options.fatal; - this.#ignoreBOM = options.ignoreBOM; - this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#encoding; - } - - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#fatal; - } - - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#ignoreBOM; - } - - /** - * @param {BufferSource} [input] - * @param {TextDecodeOptions} options - */ - decode(input = new Uint8Array(), options = undefined) { - webidl.assertBranded(this, TextDecoderPrototype); - const prefix = "Failed to execute 'decode' on 'TextDecoder'"; - if (input !== undefined) { - input = webidl.converters.BufferSource(input, { - prefix, - context: "Argument 1", - allowShared: true, - }); - } - let stream = false; - if (options !== undefined) { - options = webidl.converters.TextDecodeOptions(options, { - prefix, - context: "Argument 2", - }); - stream = options.stream; - } - - try { - // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. - // When doing so they will have to make sure that changes to input do not affect future calls to decode(). - if ( - ObjectPrototypeIsPrototypeOf( - // deno-lint-ignore prefer-primordials - SharedArrayBuffer.prototype, - input || input.buffer, - ) - ) { - // We clone the data into a non-shared ArrayBuffer so we can pass it - // to Rust. - // `input` is now a Uint8Array, and calling the TypedArray constructor - // with a TypedArray argument copies the data. - if (ArrayBufferIsView(input)) { - input = new Uint8Array( - input.buffer, - input.byteOffset, - input.byteLength, - ); - } else { - input = new Uint8Array(input); - } - } - - // Fast path for single pass encoding. - if (!stream && this.#rid === null) { - // Fast path for utf8 single pass encoding. - if (this.#utf8SinglePass) { - return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); - } - - return ops.op_encoding_decode_single( - input, - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } - - if (this.#rid === null) { - this.#rid = ops.op_encoding_new_decoder( - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } - return ops.op_encoding_decode(input, this.#rid, stream); - } finally { - if (!stream && this.#rid !== null) { - core.close(this.#rid); - this.#rid = null; - } - } - } + /** + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoder'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + const encoding = ops.op_encoding_normalize_label(label); + this.#encoding = encoding; + this.#fatal = options.fatal; + this.#ignoreBOM = options.ignoreBOM; + this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(TextDecoder); - const TextDecoderPrototype = TextDecoder.prototype; + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#encoding; + } - class TextEncoder { - constructor() { - this[webidl.brand] = webidl.brand; - } + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#fatal; + } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderPrototype); - return "utf-8"; - } + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#ignoreBOM; + } - /** - * @param {string} input - * @returns {Uint8Array} - */ - encode(input = "") { - webidl.assertBranded(this, TextEncoderPrototype); - const prefix = "Failed to execute 'encode' on 'TextEncoder'"; - // The WebIDL type of `input` is `USVString`, but `core.encode` already - // converts lone surrogates to the replacement character. - input = webidl.converters.DOMString(input, { + /** + * @param {BufferSource} [input] + * @param {TextDecodeOptions} options + */ + decode(input = new Uint8Array(), options = undefined) { + webidl.assertBranded(this, TextDecoderPrototype); + const prefix = "Failed to execute 'decode' on 'TextDecoder'"; + if (input !== undefined) { + input = webidl.converters.BufferSource(input, { prefix, context: "Argument 1", - }); - return core.encode(input); - } - - /** - * @param {string} source - * @param {Uint8Array} destination - * @returns {TextEncoderEncodeIntoResult} - */ - encodeInto(source, destination) { - webidl.assertBranded(this, TextEncoderPrototype); - const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'"; - // The WebIDL type of `source` is `USVString`, but the ops bindings - // already convert lone surrogates to the replacement character. - source = webidl.converters.DOMString(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.Uint8Array(destination, { - prefix, - context: "Argument 2", allowShared: true, }); - ops.op_encoding_encode_into(source, destination, encodeIntoBuf); - return { - read: encodeIntoBuf[0], - written: encodeIntoBuf[1], - }; } - } - - const encodeIntoBuf = new Uint32Array(2); - - webidl.configurePrototype(TextEncoder); - const TextEncoderPrototype = TextEncoder.prototype; - - class TextDecoderStream { - /** @type {TextDecoder} */ - #decoder; - /** @type {TransformStream} */ - #transform; - - /** - * @param {string} label - * @param {TextDecoderOptions} options - */ - constructor(label = "utf-8", options = {}) { - const prefix = "Failed to construct 'TextDecoderStream'"; - label = webidl.converters.DOMString(label, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.TextDecoderOptions(options, { + let stream = false; + if (options !== undefined) { + options = webidl.converters.TextDecodeOptions(options, { prefix, context: "Argument 2", }); - this.#decoder = new TextDecoder(label, options); - this.#transform = new TransformStream({ - // The transform and flush functions need access to TextDecoderStream's - // `this`, so they are defined as functions rather than methods. - transform: (chunk, controller) => { - try { - chunk = webidl.converters.BufferSource(chunk, { - allowShared: true, - }); - const decoded = this.#decoder.decode(chunk, { stream: true }); - if (decoded) { - controller.enqueue(decoded); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); - } - }, - flush: (controller) => { - try { - const final = this.#decoder.decode(); - if (final) { - controller.enqueue(final); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); - } - }, - }); - this[webidl.brand] = webidl.brand; + stream = options.stream; } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.encoding; - } + try { + // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. + // When doing so they will have to make sure that changes to input do not affect future calls to decode(). + if ( + ObjectPrototypeIsPrototypeOf( + // deno-lint-ignore prefer-primordials + SharedArrayBuffer.prototype, + input || input.buffer, + ) + ) { + // We clone the data into a non-shared ArrayBuffer so we can pass it + // to Rust. + // `input` is now a Uint8Array, and calling the TypedArray constructor + // with a TypedArray argument copies the data. + if (ArrayBufferIsView(input)) { + input = new Uint8Array( + input.buffer, + input.byteOffset, + input.byteLength, + ); + } else { + input = new Uint8Array(input); + } + } - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.fatal; - } + // Fast path for single pass encoding. + if (!stream && this.#rid === null) { + // Fast path for utf8 single pass encoding. + if (this.#utf8SinglePass) { + return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); + } - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.ignoreBOM; - } + return ops.op_encoding_decode_single( + input, + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.readable; - } - - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.writable; + if (this.#rid === null) { + this.#rid = ops.op_encoding_new_decoder( + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } + return ops.op_encoding_decode(input, this.#rid, stream); + } finally { + if (!stream && this.#rid !== null) { + core.close(this.#rid); + this.#rid = null; + } } } +} - webidl.configurePrototype(TextDecoderStream); - const TextDecoderStreamPrototype = TextDecoderStream.prototype; +webidl.configurePrototype(TextDecoder); +const TextDecoderPrototype = TextDecoder.prototype; - class TextEncoderStream { - /** @type {string | null} */ - #pendingHighSurrogate = null; - /** @type {TransformStream} */ - #transform; - - constructor() { - this.#transform = new TransformStream({ - // The transform and flush functions need access to TextEncoderStream's - // `this`, so they are defined as functions rather than methods. - transform: (chunk, controller) => { - try { - chunk = webidl.converters.DOMString(chunk); - if (chunk === "") { - return PromiseResolve(); - } - if (this.#pendingHighSurrogate !== null) { - chunk = this.#pendingHighSurrogate + chunk; - } - const lastCodeUnit = StringPrototypeCharCodeAt( - chunk, - chunk.length - 1, - ); - if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { - this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1); - chunk = StringPrototypeSlice(chunk, 0, -1); - } else { - this.#pendingHighSurrogate = null; - } - if (chunk) { - controller.enqueue(core.encode(chunk)); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); - } - }, - flush: (controller) => { - try { - if (this.#pendingHighSurrogate !== null) { - controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); - } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return "utf-8"; - } - - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.readable; - } - - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.writable; - } +class TextEncoder { + constructor() { + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(TextEncoderStream); - const TextEncoderStreamPrototype = TextEncoderStream.prototype; - - webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( - "TextDecoderOptions", - [ - { - key: "fatal", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "ignoreBOM", - converter: webidl.converters.boolean, - defaultValue: false, - }, - ], - ); - webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( - "TextDecodeOptions", - [ - { - key: "stream", - converter: webidl.converters.boolean, - defaultValue: false, - }, - ], - ); - - /** - * @param {Uint8Array} bytes - */ - function decode(bytes, encoding) { - const BOMEncoding = BOMSniff(bytes); - if (BOMEncoding !== null) { - encoding = BOMEncoding; - const start = BOMEncoding === "UTF-8" ? 3 : 2; - bytes = TypedArrayPrototypeSubarray(bytes, start); - } - return new TextDecoder(encoding).decode(bytes); + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderPrototype); + return "utf-8"; } /** - * @param {Uint8Array} bytes + * @param {string} input + * @returns {Uint8Array} */ - function BOMSniff(bytes) { - if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { - return "UTF-8"; - } - if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; - if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; - return null; + encode(input = "") { + webidl.assertBranded(this, TextEncoderPrototype); + const prefix = "Failed to execute 'encode' on 'TextEncoder'"; + // The WebIDL type of `input` is `USVString`, but `core.encode` already + // converts lone surrogates to the replacement character. + input = webidl.converters.DOMString(input, { + prefix, + context: "Argument 1", + }); + return core.encode(input); } - window.__bootstrap.encoding = { - TextEncoder, - TextDecoder, - TextEncoderStream, - TextDecoderStream, - decode, - }; -})(this); + /** + * @param {string} source + * @param {Uint8Array} destination + * @returns {TextEncoderEncodeIntoResult} + */ + encodeInto(source, destination) { + webidl.assertBranded(this, TextEncoderPrototype); + const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'"; + // The WebIDL type of `source` is `USVString`, but the ops bindings + // already convert lone surrogates to the replacement character. + source = webidl.converters.DOMString(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.Uint8Array(destination, { + prefix, + context: "Argument 2", + allowShared: true, + }); + ops.op_encoding_encode_into(source, destination, encodeIntoBuf); + return { + read: encodeIntoBuf[0], + written: encodeIntoBuf[1], + }; + } +} + +const encodeIntoBuf = new Uint32Array(2); + +webidl.configurePrototype(TextEncoder); +const TextEncoderPrototype = TextEncoder.prototype; + +class TextDecoderStream { + /** @type {TextDecoder} */ + #decoder; + /** @type {TransformStream} */ + #transform; + + /** + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoderStream'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + this.#decoder = new TextDecoder(label, options); + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextDecoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.BufferSource(chunk, { + allowShared: true, + }); + const decoded = this.#decoder.decode(chunk, { stream: true }); + if (decoded) { + controller.enqueue(decoded); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.encoding; + } + + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.fatal; + } + + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.ignoreBOM; + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.readable; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextDecoderStream); +const TextDecoderStreamPrototype = TextDecoderStream.prototype; + +class TextEncoderStream { + /** @type {string | null} */ + #pendingHighSurrogate = null; + /** @type {TransformStream} */ + #transform; + + constructor() { + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextEncoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.DOMString(chunk); + if (chunk === "") { + return PromiseResolve(); + } + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; + } + const lastCodeUnit = StringPrototypeCharCodeAt( + chunk, + chunk.length - 1, + ); + if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { + this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1); + chunk = StringPrototypeSlice(chunk, 0, -1); + } else { + this.#pendingHighSurrogate = null; + } + if (chunk) { + controller.enqueue(core.encode(chunk)); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + if (this.#pendingHighSurrogate !== null) { + controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return "utf-8"; + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.readable; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextEncoderStream); +const TextEncoderStreamPrototype = TextEncoderStream.prototype; + +webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( + "TextDecoderOptions", + [ + { + key: "fatal", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "ignoreBOM", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], +); +webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( + "TextDecodeOptions", + [ + { + key: "stream", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], +); + +/** + * @param {Uint8Array} bytes + */ +function decode(bytes, encoding) { + const BOMEncoding = BOMSniff(bytes); + if (BOMEncoding !== null) { + encoding = BOMEncoding; + const start = BOMEncoding === "UTF-8" ? 3 : 2; + bytes = TypedArrayPrototypeSubarray(bytes, start); + } + return new TextDecoder(encoding).decode(bytes); +} + +/** + * @param {Uint8Array} bytes + */ +function BOMSniff(bytes) { + if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { + return "UTF-8"; + } + if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; + if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; + return null; +} + +export { + decode, + TextDecoder, + TextDecoderStream, + TextEncoder, + TextEncoderStream, +}; diff --git a/ext/web/09_file.js b/ext/web/09_file.js index ecdce3e6a4..e1be3b4c21 100644 --- a/ext/web/09_file.js +++ b/ext/web/09_file.js @@ -9,630 +9,628 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayBufferPrototype, - ArrayBufferPrototypeSlice, - ArrayBufferIsView, - ArrayPrototypePush, - AsyncGeneratorPrototypeNext, - Date, - DatePrototypeGetTime, - FinalizationRegistry, - MathMax, - MathMin, - ObjectPrototypeIsPrototypeOf, - RegExpPrototypeTest, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - StringPrototypeCharAt, - StringPrototypeToLowerCase, - StringPrototypeSlice, - Symbol, - SymbolFor, - TypedArrayPrototypeSet, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - const consoleInternal = window.__bootstrap.console; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferPrototypeSlice, + ArrayBufferIsView, + ArrayPrototypePush, + AsyncGeneratorPrototypeNext, + Date, + DatePrototypeGetTime, + FinalizationRegistry, + MathMax, + MathMin, + ObjectPrototypeIsPrototypeOf, + RegExpPrototypeTest, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + StringPrototypeCharAt, + StringPrototypeToLowerCase, + StringPrototypeSlice, + Symbol, + SymbolFor, + TypedArrayPrototypeSet, + TypeError, + Uint8Array, +} = primordials; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; - // TODO(lucacasonato): this needs to not be hardcoded and instead depend on - // host os. - const isWindows = false; +// TODO(lucacasonato): this needs to not be hardcoded and instead depend on +// host os. +const isWindows = false; - /** - * @param {string} input - * @param {number} position - * @returns {{result: string, position: number}} - */ - function collectCodepointsNotCRLF(input, position) { - // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and - // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points - const start = position; - for ( - let c = StringPrototypeCharAt(input, position); - position < input.length && !(c === "\r" || c === "\n"); - c = StringPrototypeCharAt(input, ++position) - ); - return { result: StringPrototypeSlice(input, start, position), position }; - } - - /** - * @param {string} s - * @returns {string} - */ - function convertLineEndingsToNative(s) { - const nativeLineEnding = isWindows ? "\r\n" : "\n"; - - let { result, position } = collectCodepointsNotCRLF(s, 0); - - while (position < s.length) { - const codePoint = StringPrototypeCharAt(s, position); - if (codePoint === "\r") { - result += nativeLineEnding; - position++; - if ( - position < s.length && StringPrototypeCharAt(s, position) === "\n" - ) { - position++; - } - } else if (codePoint === "\n") { - position++; - result += nativeLineEnding; - } - const { result: token, position: newPosition } = collectCodepointsNotCRLF( - s, - position, - ); - position = newPosition; - result += token; - } - - return result; - } - - /** @param {(BlobReference | Blob)[]} parts */ - async function* toIterator(parts) { - for (let i = 0; i < parts.length; ++i) { - yield* parts[i].stream(); - } - } - - /** @typedef {BufferSource | Blob | string} BlobPart */ - - /** - * @param {BlobPart[]} parts - * @param {string} endings - * @returns {{ parts: (BlobReference|Blob)[], size: number }} - */ - function processBlobParts(parts, endings) { - /** @type {(BlobReference|Blob)[]} */ - const processedParts = []; - let size = 0; - for (let i = 0; i < parts.length; ++i) { - const element = parts[i]; - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, element)) { - const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0)); - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - size += element.byteLength; - } else if (ArrayBufferIsView(element)) { - const chunk = new Uint8Array( - element.buffer, - element.byteOffset, - element.byteLength, - ); - size += element.byteLength; - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, element)) { - ArrayPrototypePush(processedParts, element); - size += element.size; - } else if (typeof element === "string") { - const chunk = core.encode( - endings == "native" ? convertLineEndingsToNative(element) : element, - ); - size += chunk.byteLength; - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - } else { - throw new TypeError("Unreachable code (invalid element type)"); - } - } - return { parts: processedParts, size }; - } - - /** - * @param {string} str - * @returns {string} - */ - function normalizeType(str) { - let normalizedType = str; - if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { - normalizedType = ""; - } - return StringPrototypeToLowerCase(normalizedType); - } - - /** - * Get all Parts as a flat array containing all references - * @param {Blob} blob - * @param {string[]} bag - * @returns {string[]} - */ - function getParts(blob, bag = []) { - const parts = blob[_parts]; - for (let i = 0; i < parts.length; ++i) { - const part = parts[i]; - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) { - getParts(part, bag); - } else { - ArrayPrototypePush(bag, part._id); - } - } - return bag; - } - - const _type = Symbol("Type"); - const _size = Symbol("Size"); - const _parts = Symbol("Parts"); - - class Blob { - [_type] = ""; - [_size] = 0; - [_parts]; - - /** - * @param {BlobPart[]} blobParts - * @param {BlobPropertyBag} options - */ - constructor(blobParts = [], options = {}) { - const prefix = "Failed to construct 'Blob'"; - blobParts = webidl.converters["sequence"](blobParts, { - context: "Argument 1", - prefix, - }); - options = webidl.converters["BlobPropertyBag"](options, { - context: "Argument 2", - prefix, - }); - - this[webidl.brand] = webidl.brand; - - const { parts, size } = processBlobParts( - blobParts, - options.endings, - ); - - this[_parts] = parts; - this[_size] = size; - this[_type] = normalizeType(options.type); - } - - /** @returns {number} */ - get size() { - webidl.assertBranded(this, BlobPrototype); - return this[_size]; - } - - /** @returns {string} */ - get type() { - webidl.assertBranded(this, BlobPrototype); - return this[_type]; - } - - /** - * @param {number} [start] - * @param {number} [end] - * @param {string} [contentType] - * @returns {Blob} - */ - slice(start = undefined, end = undefined, contentType = undefined) { - webidl.assertBranded(this, BlobPrototype); - const prefix = "Failed to execute 'slice' on 'Blob'"; - if (start !== undefined) { - start = webidl.converters["long long"](start, { - clamp: true, - context: "Argument 1", - prefix, - }); - } - if (end !== undefined) { - end = webidl.converters["long long"](end, { - clamp: true, - context: "Argument 2", - prefix, - }); - } - if (contentType !== undefined) { - contentType = webidl.converters["DOMString"](contentType, { - context: "Argument 3", - prefix, - }); - } - - // deno-lint-ignore no-this-alias - const O = this; - /** @type {number} */ - let relativeStart; - if (start === undefined) { - relativeStart = 0; - } else { - if (start < 0) { - relativeStart = MathMax(O.size + start, 0); - } else { - relativeStart = MathMin(start, O.size); - } - } - /** @type {number} */ - let relativeEnd; - if (end === undefined) { - relativeEnd = O.size; - } else { - if (end < 0) { - relativeEnd = MathMax(O.size + end, 0); - } else { - relativeEnd = MathMin(end, O.size); - } - } - - const span = MathMax(relativeEnd - relativeStart, 0); - const blobParts = []; - let added = 0; - - const parts = this[_parts]; - for (let i = 0; i < parts.length; ++i) { - const part = parts[i]; - // don't add the overflow to new blobParts - if (added >= span) { - // Could maybe be possible to remove variable `added` - // and only use relativeEnd? - break; - } - const size = part.size; - if (relativeStart && size <= relativeStart) { - // Skip the beginning and change the relative - // start & end position as we skip the unwanted parts - relativeStart -= size; - relativeEnd -= size; - } else { - const chunk = part.slice( - relativeStart, - MathMin(part.size, relativeEnd), - ); - added += chunk.size; - relativeEnd -= part.size; - ArrayPrototypePush(blobParts, chunk); - relativeStart = 0; // All next sequential parts should start at 0 - } - } - - /** @type {string} */ - let relativeContentType; - if (contentType === undefined) { - relativeContentType = ""; - } else { - relativeContentType = normalizeType(contentType); - } - - const blob = new Blob([], { type: relativeContentType }); - blob[_parts] = blobParts; - blob[_size] = span; - return blob; - } - - /** - * @returns {ReadableStream} - */ - stream() { - webidl.assertBranded(this, BlobPrototype); - const partIterator = toIterator(this[_parts]); - const stream = new ReadableStream({ - type: "bytes", - /** @param {ReadableByteStreamController} controller */ - async pull(controller) { - while (true) { - const { value, done } = await AsyncGeneratorPrototypeNext( - partIterator, - ); - if (done) return controller.close(); - if (value.byteLength > 0) { - return controller.enqueue(value); - } - } - }, - }); - return stream; - } - - /** - * @returns {Promise} - */ - async text() { - webidl.assertBranded(this, BlobPrototype); - const buffer = await this.#u8Array(this.size); - return core.decode(buffer); - } - - async #u8Array(size) { - const bytes = new Uint8Array(size); - const partIterator = toIterator(this[_parts]); - let offset = 0; - while (true) { - const { value, done } = await AsyncGeneratorPrototypeNext( - partIterator, - ); - if (done) break; - const byteLength = value.byteLength; - if (byteLength > 0) { - TypedArrayPrototypeSet(bytes, value, offset); - offset += byteLength; - } - } - return bytes; - } - - /** - * @returns {Promise} - */ - async arrayBuffer() { - webidl.assertBranded(this, BlobPrototype); - const buf = await this.#u8Array(this.size); - return buf.buffer; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(BlobPrototype, this), - keys: [ - "size", - "type", - ], - })); - } - } - - webidl.configurePrototype(Blob); - const BlobPrototype = Blob.prototype; - - webidl.converters["Blob"] = webidl.createInterfaceConverter( - "Blob", - Blob.prototype, +/** + * @param {string} input + * @param {number} position + * @returns {{result: string, position: number}} + */ +function collectCodepointsNotCRLF(input, position) { + // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && !(c === "\r" || c === "\n"); + c = StringPrototypeCharAt(input, ++position) ); - webidl.converters["BlobPart"] = (V, opts) => { - // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) - if (typeof V == "object") { - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); - } + return { result: StringPrototypeSlice(input, start, position), position }; +} + +/** + * @param {string} s + * @returns {string} + */ +function convertLineEndingsToNative(s) { + const nativeLineEnding = isWindows ? "\r\n" : "\n"; + + let { result, position } = collectCodepointsNotCRLF(s, 0); + + while (position < s.length) { + const codePoint = StringPrototypeCharAt(s, position); + if (codePoint === "\r") { + result += nativeLineEnding; + position++; if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + position < s.length && StringPrototypeCharAt(s, position) === "\n" ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); + position++; } + } else if (codePoint === "\n") { + position++; + result += nativeLineEnding; } - // BlobPart is passed to processBlobParts after conversion, which calls core.encode() - // on the string. - // core.encode() is equivalent to USVString normalization. - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["sequence"] = webidl.createSequenceConverter( - webidl.converters["BlobPart"], - ); - webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ - "transparent", - "native", - ]); - const blobPropertyBagDictionary = [ - { - key: "type", - converter: webidl.converters["DOMString"], - defaultValue: "", - }, - { - key: "endings", - converter: webidl.converters["EndingType"], - defaultValue: "transparent", - }, - ]; - webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( - "BlobPropertyBag", - blobPropertyBagDictionary, - ); + const { result: token, position: newPosition } = collectCodepointsNotCRLF( + s, + position, + ); + position = newPosition; + result += token; + } - const _Name = Symbol("[[Name]]"); - const _LastModified = Symbol("[[LastModified]]"); + return result; +} - class File extends Blob { - /** @type {string} */ - [_Name]; - /** @type {number} */ - [_LastModified]; +/** @param {(BlobReference | Blob)[]} parts */ +async function* toIterator(parts) { + for (let i = 0; i < parts.length; ++i) { + yield* parts[i].stream(); + } +} - /** - * @param {BlobPart[]} fileBits - * @param {string} fileName - * @param {FilePropertyBag} options - */ - constructor(fileBits, fileName, options = {}) { - const prefix = "Failed to construct 'File'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); +/** @typedef {BufferSource | Blob | string} BlobPart */ - fileBits = webidl.converters["sequence"](fileBits, { +/** + * @param {BlobPart[]} parts + * @param {string} endings + * @returns {{ parts: (BlobReference|Blob)[], size: number }} + */ +function processBlobParts(parts, endings) { + /** @type {(BlobReference|Blob)[]} */ + const processedParts = []; + let size = 0; + for (let i = 0; i < parts.length; ++i) { + const element = parts[i]; + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, element)) { + const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0)); + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + size += element.byteLength; + } else if (ArrayBufferIsView(element)) { + const chunk = new Uint8Array( + element.buffer, + element.byteOffset, + element.byteLength, + ); + size += element.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, element)) { + ArrayPrototypePush(processedParts, element); + size += element.size; + } else if (typeof element === "string") { + const chunk = core.encode( + endings == "native" ? convertLineEndingsToNative(element) : element, + ); + size += chunk.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else { + throw new TypeError("Unreachable code (invalid element type)"); + } + } + return { parts: processedParts, size }; +} + +/** + * @param {string} str + * @returns {string} + */ +function normalizeType(str) { + let normalizedType = str; + if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { + normalizedType = ""; + } + return StringPrototypeToLowerCase(normalizedType); +} + +/** + * Get all Parts as a flat array containing all references + * @param {Blob} blob + * @param {string[]} bag + * @returns {string[]} + */ +function getParts(blob, bag = []) { + const parts = blob[_parts]; + for (let i = 0; i < parts.length; ++i) { + const part = parts[i]; + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) { + getParts(part, bag); + } else { + ArrayPrototypePush(bag, part._id); + } + } + return bag; +} + +const _type = Symbol("Type"); +const _size = Symbol("Size"); +const _parts = Symbol("Parts"); + +class Blob { + [_type] = ""; + [_size] = 0; + [_parts]; + + /** + * @param {BlobPart[]} blobParts + * @param {BlobPropertyBag} options + */ + constructor(blobParts = [], options = {}) { + const prefix = "Failed to construct 'Blob'"; + blobParts = webidl.converters["sequence"](blobParts, { + context: "Argument 1", + prefix, + }); + options = webidl.converters["BlobPropertyBag"](options, { + context: "Argument 2", + prefix, + }); + + this[webidl.brand] = webidl.brand; + + const { parts, size } = processBlobParts( + blobParts, + options.endings, + ); + + this[_parts] = parts; + this[_size] = size; + this[_type] = normalizeType(options.type); + } + + /** @returns {number} */ + get size() { + webidl.assertBranded(this, BlobPrototype); + return this[_size]; + } + + /** @returns {string} */ + get type() { + webidl.assertBranded(this, BlobPrototype); + return this[_type]; + } + + /** + * @param {number} [start] + * @param {number} [end] + * @param {string} [contentType] + * @returns {Blob} + */ + slice(start = undefined, end = undefined, contentType = undefined) { + webidl.assertBranded(this, BlobPrototype); + const prefix = "Failed to execute 'slice' on 'Blob'"; + if (start !== undefined) { + start = webidl.converters["long long"](start, { + clamp: true, context: "Argument 1", prefix, }); - fileName = webidl.converters["USVString"](fileName, { + } + if (end !== undefined) { + end = webidl.converters["long long"](end, { + clamp: true, context: "Argument 2", prefix, }); - options = webidl.converters["FilePropertyBag"](options, { + } + if (contentType !== undefined) { + contentType = webidl.converters["DOMString"](contentType, { context: "Argument 3", prefix, }); + } - super(fileBits, options); - - /** @type {string} */ - this[_Name] = fileName; - if (options.lastModified === undefined) { - /** @type {number} */ - this[_LastModified] = DatePrototypeGetTime(new Date()); + // deno-lint-ignore no-this-alias + const O = this; + /** @type {number} */ + let relativeStart; + if (start === undefined) { + relativeStart = 0; + } else { + if (start < 0) { + relativeStart = MathMax(O.size + start, 0); } else { - /** @type {number} */ - this[_LastModified] = options.lastModified; + relativeStart = MathMin(start, O.size); + } + } + /** @type {number} */ + let relativeEnd; + if (end === undefined) { + relativeEnd = O.size; + } else { + if (end < 0) { + relativeEnd = MathMax(O.size + end, 0); + } else { + relativeEnd = MathMin(end, O.size); } } - /** @returns {string} */ - get name() { - webidl.assertBranded(this, FilePrototype); - return this[_Name]; + const span = MathMax(relativeEnd - relativeStart, 0); + const blobParts = []; + let added = 0; + + const parts = this[_parts]; + for (let i = 0; i < parts.length; ++i) { + const part = parts[i]; + // don't add the overflow to new blobParts + if (added >= span) { + // Could maybe be possible to remove variable `added` + // and only use relativeEnd? + break; + } + const size = part.size; + if (relativeStart && size <= relativeStart) { + // Skip the beginning and change the relative + // start & end position as we skip the unwanted parts + relativeStart -= size; + relativeEnd -= size; + } else { + const chunk = part.slice( + relativeStart, + MathMin(part.size, relativeEnd), + ); + added += chunk.size; + relativeEnd -= part.size; + ArrayPrototypePush(blobParts, chunk); + relativeStart = 0; // All next sequential parts should start at 0 + } } - /** @returns {number} */ - get lastModified() { - webidl.assertBranded(this, FilePrototype); - return this[_LastModified]; - } - } - - webidl.configurePrototype(File); - const FilePrototype = File.prototype; - - webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( - "FilePropertyBag", - blobPropertyBagDictionary, - [ - { - key: "lastModified", - converter: webidl.converters["long long"], - }, - ], - ); - - // A finalization registry to deallocate a blob part when its JS reference is - // garbage collected. - const registry = new FinalizationRegistry((uuid) => { - ops.op_blob_remove_part(uuid); - }); - - // TODO(lucacasonato): get a better stream from Rust in BlobReference#stream - - /** - * An opaque reference to a blob part in Rust. This could be backed by a file, - * in memory storage, or something else. - */ - class BlobReference { - /** - * Don't use directly. Use `BlobReference.fromUint8Array`. - * @param {string} id - * @param {number} size - */ - constructor(id, size) { - this._id = id; - this.size = size; - registry.register(this, id); + /** @type {string} */ + let relativeContentType; + if (contentType === undefined) { + relativeContentType = ""; + } else { + relativeContentType = normalizeType(contentType); } - /** - * Create a new blob part from a Uint8Array. - * - * @param {Uint8Array} data - * @returns {BlobReference} - */ - static fromUint8Array(data) { - const id = ops.op_blob_create_part(data); - return new BlobReference(id, data.byteLength); - } - - /** - * Create a new BlobReference by slicing this BlobReference. This is a copy - * free operation - the sliced reference will still reference the original - * underlying bytes. - * - * @param {number} start - * @param {number} end - * @returns {BlobReference} - */ - slice(start, end) { - const size = end - start; - const id = ops.op_blob_slice_part(this._id, { - start, - len: size, - }); - return new BlobReference(id, size); - } - - /** - * Read the entire contents of the reference blob. - * @returns {AsyncGenerator} - */ - async *stream() { - yield core.opAsync("op_blob_read_part", this._id); - - // let position = 0; - // const end = this.size; - // while (position !== end) { - // const size = MathMin(end - position, 65536); - // const chunk = this.slice(position, position + size); - // position += chunk.size; - // yield core.opAsync("op_blob_read_part", chunk._id); - // } - } - } - - /** - * Construct a new Blob object from an object URL. - * - * This new object will not duplicate data in memory with the original Blob - * object from which this URL was created or with other Blob objects created - * from the same URL, but they will be different objects. - * - * The object returned from this function will not be a File object, even if - * the original object from which the object URL was constructed was one. This - * means that the `name` and `lastModified` properties are lost. - * - * @param {string} url - * @returns {Blob | null} - */ - function blobFromObjectUrl(url) { - const blobData = ops.op_blob_from_object_url(url); - if (blobData === null) { - return null; - } - - /** @type {BlobReference[]} */ - const parts = []; - let totalSize = 0; - - for (let i = 0; i < blobData.parts.length; ++i) { - const { uuid, size } = blobData.parts[i]; - ArrayPrototypePush(parts, new BlobReference(uuid, size)); - totalSize += size; - } - - const blob = webidl.createBranded(Blob); - blob[_type] = blobData.media_type; - blob[_size] = totalSize; - blob[_parts] = parts; + const blob = new Blob([], { type: relativeContentType }); + blob[_parts] = blobParts; + blob[_size] = span; return blob; } - window.__bootstrap.file = { - blobFromObjectUrl, - getParts, - Blob, - BlobPrototype, - File, - FilePrototype, - }; -})(this); + /** + * @returns {ReadableStream} + */ + stream() { + webidl.assertBranded(this, BlobPrototype); + const partIterator = toIterator(this[_parts]); + const stream = new ReadableStream({ + type: "bytes", + /** @param {ReadableByteStreamController} controller */ + async pull(controller) { + while (true) { + const { value, done } = await AsyncGeneratorPrototypeNext( + partIterator, + ); + if (done) return controller.close(); + if (value.byteLength > 0) { + return controller.enqueue(value); + } + } + }, + }); + return stream; + } + + /** + * @returns {Promise} + */ + async text() { + webidl.assertBranded(this, BlobPrototype); + const buffer = await this.#u8Array(this.size); + return core.decode(buffer); + } + + async #u8Array(size) { + const bytes = new Uint8Array(size); + const partIterator = toIterator(this[_parts]); + let offset = 0; + while (true) { + const { value, done } = await AsyncGeneratorPrototypeNext( + partIterator, + ); + if (done) break; + const byteLength = value.byteLength; + if (byteLength > 0) { + TypedArrayPrototypeSet(bytes, value, offset); + offset += byteLength; + } + } + return bytes; + } + + /** + * @returns {Promise} + */ + async arrayBuffer() { + webidl.assertBranded(this, BlobPrototype); + const buf = await this.#u8Array(this.size); + return buf.buffer; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(BlobPrototype, this), + keys: [ + "size", + "type", + ], + })); + } +} + +webidl.configurePrototype(Blob); +const BlobPrototype = Blob.prototype; + +webidl.converters["Blob"] = webidl.createInterfaceConverter( + "Blob", + Blob.prototype, +); +webidl.converters["BlobPart"] = (V, opts) => { + // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) + if (typeof V == "object") { + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); + } + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); + } + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); + } + } + // BlobPart is passed to processBlobParts after conversion, which calls core.encode() + // on the string. + // core.encode() is equivalent to USVString normalization. + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters["BlobPart"], +); +webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ + "transparent", + "native", +]); +const blobPropertyBagDictionary = [ + { + key: "type", + converter: webidl.converters["DOMString"], + defaultValue: "", + }, + { + key: "endings", + converter: webidl.converters["EndingType"], + defaultValue: "transparent", + }, +]; +webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( + "BlobPropertyBag", + blobPropertyBagDictionary, +); + +const _Name = Symbol("[[Name]]"); +const _LastModified = Symbol("[[LastModified]]"); + +class File extends Blob { + /** @type {string} */ + [_Name]; + /** @type {number} */ + [_LastModified]; + + /** + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} options + */ + constructor(fileBits, fileName, options = {}) { + const prefix = "Failed to construct 'File'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + fileBits = webidl.converters["sequence"](fileBits, { + context: "Argument 1", + prefix, + }); + fileName = webidl.converters["USVString"](fileName, { + context: "Argument 2", + prefix, + }); + options = webidl.converters["FilePropertyBag"](options, { + context: "Argument 3", + prefix, + }); + + super(fileBits, options); + + /** @type {string} */ + this[_Name] = fileName; + if (options.lastModified === undefined) { + /** @type {number} */ + this[_LastModified] = DatePrototypeGetTime(new Date()); + } else { + /** @type {number} */ + this[_LastModified] = options.lastModified; + } + } + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, FilePrototype); + return this[_Name]; + } + + /** @returns {number} */ + get lastModified() { + webidl.assertBranded(this, FilePrototype); + return this[_LastModified]; + } +} + +webidl.configurePrototype(File); +const FilePrototype = File.prototype; + +webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( + "FilePropertyBag", + blobPropertyBagDictionary, + [ + { + key: "lastModified", + converter: webidl.converters["long long"], + }, + ], +); + +// A finalization registry to deallocate a blob part when its JS reference is +// garbage collected. +const registry = new FinalizationRegistry((uuid) => { + ops.op_blob_remove_part(uuid); +}); + +// TODO(lucacasonato): get a better stream from Rust in BlobReference#stream + +/** + * An opaque reference to a blob part in Rust. This could be backed by a file, + * in memory storage, or something else. + */ +class BlobReference { + /** + * Don't use directly. Use `BlobReference.fromUint8Array`. + * @param {string} id + * @param {number} size + */ + constructor(id, size) { + this._id = id; + this.size = size; + registry.register(this, id); + } + + /** + * Create a new blob part from a Uint8Array. + * + * @param {Uint8Array} data + * @returns {BlobReference} + */ + static fromUint8Array(data) { + const id = ops.op_blob_create_part(data); + return new BlobReference(id, data.byteLength); + } + + /** + * Create a new BlobReference by slicing this BlobReference. This is a copy + * free operation - the sliced reference will still reference the original + * underlying bytes. + * + * @param {number} start + * @param {number} end + * @returns {BlobReference} + */ + slice(start, end) { + const size = end - start; + const id = ops.op_blob_slice_part(this._id, { + start, + len: size, + }); + return new BlobReference(id, size); + } + + /** + * Read the entire contents of the reference blob. + * @returns {AsyncGenerator} + */ + async *stream() { + yield core.opAsync("op_blob_read_part", this._id); + + // let position = 0; + // const end = this.size; + // while (position !== end) { + // const size = MathMin(end - position, 65536); + // const chunk = this.slice(position, position + size); + // position += chunk.size; + // yield core.opAsync("op_blob_read_part", chunk._id); + // } + } +} + +/** + * Construct a new Blob object from an object URL. + * + * This new object will not duplicate data in memory with the original Blob + * object from which this URL was created or with other Blob objects created + * from the same URL, but they will be different objects. + * + * The object returned from this function will not be a File object, even if + * the original object from which the object URL was constructed was one. This + * means that the `name` and `lastModified` properties are lost. + * + * @param {string} url + * @returns {Blob | null} + */ +function blobFromObjectUrl(url) { + const blobData = ops.op_blob_from_object_url(url); + if (blobData === null) { + return null; + } + + /** @type {BlobReference[]} */ + const parts = []; + let totalSize = 0; + + for (let i = 0; i < blobData.parts.length; ++i) { + const { uuid, size } = blobData.parts[i]; + ArrayPrototypePush(parts, new BlobReference(uuid, size)); + totalSize += size; + } + + const blob = webidl.createBranded(Blob); + blob[_type] = blobData.media_type; + blob[_size] = totalSize; + blob[_parts] = parts; + return blob; +} + +export { + Blob, + blobFromObjectUrl, + BlobPrototype, + File, + FilePrototype, + getParts, +}; diff --git a/ext/web/10_filereader.js b/ext/web/10_filereader.js index fb119f43ec..7a46dfa9ae 100644 --- a/ext/web/10_filereader.js +++ b/ext/web/10_filereader.js @@ -10,487 +10,482 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +import { forgivingBase64Encode } from "internal:ext/web/00_infra.js"; +import { EventTarget, ProgressEvent } from "internal:ext/web/02_event.js"; +import { decode, TextDecoder } from "internal:ext/web/08_text_encoding.js"; +import { parseMimeType } from "internal:ext/web/01_mimesniff.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const { + ArrayPrototypePush, + ArrayPrototypeReduce, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + queueMicrotask, + SafeArrayIterator, + Symbol, + TypedArrayPrototypeSet, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; -((window) => { - const core = window.Deno.core; - const webidl = window.__bootstrap.webidl; - const { forgivingBase64Encode } = window.__bootstrap.infra; - const { ProgressEvent } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { decode, TextDecoder } = window.__bootstrap.encoding; - const { parseMimeType } = window.__bootstrap.mimesniff; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayPrototypePush, - ArrayPrototypeReduce, - FunctionPrototypeCall, - Map, - MapPrototypeGet, - MapPrototypeSet, - ObjectDefineProperty, - ObjectPrototypeIsPrototypeOf, - queueMicrotask, - SafeArrayIterator, - Symbol, - TypedArrayPrototypeSet, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +const state = Symbol("[[state]]"); +const result = Symbol("[[result]]"); +const error = Symbol("[[error]]"); +const aborted = Symbol("[[aborted]]"); +const handlerSymbol = Symbol("eventHandlers"); - const state = Symbol("[[state]]"); - const result = Symbol("[[result]]"); - const error = Symbol("[[error]]"); - const aborted = Symbol("[[aborted]]"); - const handlerSymbol = Symbol("eventHandlers"); +class FileReader extends EventTarget { + /** @type {"empty" | "loading" | "done"} */ + [state] = "empty"; + /** @type {null | string | ArrayBuffer} */ + [result] = null; + /** @type {null | DOMException} */ + [error] = null; + /** @type {null | {aborted: boolean}} */ + [aborted] = null; - class FileReader extends EventTarget { - /** @type {"empty" | "loading" | "done"} */ - [state] = "empty"; - /** @type {null | string | ArrayBuffer} */ - [result] = null; - /** @type {null | DOMException} */ - [error] = null; - /** @type {null | {aborted: boolean}} */ - [aborted] = null; + /** + * @param {Blob} blob + * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype + */ + #readOperation(blob, readtype) { + // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. + if (this[state] === "loading") { + throw new DOMException( + "Invalid FileReader state.", + "InvalidStateError", + ); + } + // 2. Set fr’s state to "loading". + this[state] = "loading"; + // 3. Set fr’s result to null. + this[result] = null; + // 4. Set fr’s error to null. + this[error] = null; - /** - * @param {Blob} blob - * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype - */ - #readOperation(blob, readtype) { - // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. - if (this[state] === "loading") { - throw new DOMException( - "Invalid FileReader state.", - "InvalidStateError", - ); - } - // 2. Set fr’s state to "loading". - this[state] = "loading"; - // 3. Set fr’s result to null. - this[result] = null; - // 4. Set fr’s error to null. - this[error] = null; + // We set this[aborted] to a new object, and keep track of it in a + // separate variable, so if a new read operation starts while there are + // remaining tasks from a previous aborted operation, the new operation + // will run while the tasks from the previous one are still aborted. + const abortedState = this[aborted] = { aborted: false }; - // We set this[aborted] to a new object, and keep track of it in a - // separate variable, so if a new read operation starts while there are - // remaining tasks from a previous aborted operation, the new operation - // will run while the tasks from the previous one are still aborted. - const abortedState = this[aborted] = { aborted: false }; + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream*/ = blob.stream(); - // 5. Let stream be the result of calling get stream on blob. - const stream /*: ReadableStream*/ = blob.stream(); + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader(); - // 6. Let reader be the result of getting a reader from stream. - const reader = stream.getReader(); + // 7. Let bytes be an empty byte sequence. + /** @type {Uint8Array[]} */ + const chunks = []; - // 7. Let bytes be an empty byte sequence. - /** @type {Uint8Array[]} */ - const chunks = []; + // 8. Let chunkPromise be the result of reading a chunk from stream with reader. + let chunkPromise = reader.read(); - // 8. Let chunkPromise be the result of reading a chunk from stream with reader. - let chunkPromise = reader.read(); + // 9. Let isFirstChunk be true. + let isFirstChunk = true; - // 9. Let isFirstChunk be true. - let isFirstChunk = true; + // 10 in parallel while true + (async () => { + while (!abortedState.aborted) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const chunk = await chunkPromise; + if (abortedState.aborted) return; - // 10 in parallel while true - (async () => { - while (!abortedState.aborted) { - // 1. Wait for chunkPromise to be fulfilled or rejected. - try { - const chunk = await chunkPromise; - if (abortedState.aborted) return; - - // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. - if (isFirstChunk) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - // fire a progress event for loadstart - const ev = new ProgressEvent("loadstart", {}); - this.dispatchEvent(ev); - }); - } - // 3. Set isFirstChunk to false. - isFirstChunk = false; - - // 4. If chunkPromise is fulfilled with an object whose done property is false - // and whose value property is a Uint8Array object, run these steps: - if ( - !chunk.done && - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value) - ) { - ArrayPrototypePush(chunks, chunk.value); - - // TODO(bartlomieju): (only) If roughly 50ms have passed since last progress - { - const size = ArrayPrototypeReduce( - chunks, - (p, i) => p + i.byteLength, - 0, - ); - const ev = new ProgressEvent("progress", { - loaded: size, - }); - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - this.dispatchEvent(ev); - }); - } - - chunkPromise = reader.read(); - } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: - else if (chunk.done === true) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - // 1. Set fr’s state to "done". - this[state] = "done"; - // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. - const size = ArrayPrototypeReduce( - chunks, - (p, i) => p + i.byteLength, - 0, - ); - const bytes = new Uint8Array(size); - let offs = 0; - for (let i = 0; i < chunks.length; ++i) { - const chunk = chunks[i]; - TypedArrayPrototypeSet(bytes, chunk, offs); - offs += chunk.byteLength; - } - switch (readtype.kind) { - case "ArrayBuffer": { - this[result] = bytes.buffer; - break; - } - case "BinaryString": - this[result] = core.ops.op_encode_binary_string(bytes); - break; - case "Text": { - let decoder = undefined; - if (readtype.encoding) { - try { - decoder = new TextDecoder(readtype.encoding); - } catch { - // don't care about the error - } - } - if (decoder === undefined) { - const mimeType = parseMimeType(blob.type); - if (mimeType) { - const charset = MapPrototypeGet( - mimeType.parameters, - "charset", - ); - if (charset) { - try { - decoder = new TextDecoder(charset); - } catch { - // don't care about the error - } - } - } - } - if (decoder === undefined) { - decoder = new TextDecoder(); - } - this[result] = decode(bytes, decoder.encoding); - break; - } - case "DataUrl": { - const mediaType = blob.type || "application/octet-stream"; - this[result] = `data:${mediaType};base64,${ - forgivingBase64Encode(bytes) - }`; - break; - } - } - // 4.2 Fire a progress event called load at the fr. - { - const ev = new ProgressEvent("load", { - lengthComputable: true, - loaded: size, - total: size, - }); - this.dispatchEvent(ev); - } - - // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. - //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. - if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", { - lengthComputable: true, - loaded: size, - total: size, - }); - this.dispatchEvent(ev); - } - }); - break; - } - } catch (err) { + // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. + if (isFirstChunk) { // TODO(lucacasonato): this is wrong, should be HTML "queue a task" queueMicrotask(() => { if (abortedState.aborted) return; + // fire a progress event for loadstart + const ev = new ProgressEvent("loadstart", {}); + this.dispatchEvent(ev); + }); + } + // 3. Set isFirstChunk to false. + isFirstChunk = false; - // chunkPromise rejected + // 4. If chunkPromise is fulfilled with an object whose done property is false + // and whose value property is a Uint8Array object, run these steps: + if ( + !chunk.done && + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value) + ) { + ArrayPrototypePush(chunks, chunk.value); + + // TODO(bartlomieju): (only) If roughly 50ms have passed since last progress + { + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const ev = new ProgressEvent("progress", { + loaded: size, + }); + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + this.dispatchEvent(ev); + }); + } + + chunkPromise = reader.read(); + } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: + else if (chunk.done === true) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + // 1. Set fr’s state to "done". this[state] = "done"; - this[error] = err; - + // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const bytes = new Uint8Array(size); + let offs = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + TypedArrayPrototypeSet(bytes, chunk, offs); + offs += chunk.byteLength; + } + switch (readtype.kind) { + case "ArrayBuffer": { + this[result] = bytes.buffer; + break; + } + case "BinaryString": + this[result] = ops.op_encode_binary_string(bytes); + break; + case "Text": { + let decoder = undefined; + if (readtype.encoding) { + try { + decoder = new TextDecoder(readtype.encoding); + } catch { + // don't care about the error + } + } + if (decoder === undefined) { + const mimeType = parseMimeType(blob.type); + if (mimeType) { + const charset = MapPrototypeGet( + mimeType.parameters, + "charset", + ); + if (charset) { + try { + decoder = new TextDecoder(charset); + } catch { + // don't care about the error + } + } + } + } + if (decoder === undefined) { + decoder = new TextDecoder(); + } + this[result] = decode(bytes, decoder.encoding); + break; + } + case "DataUrl": { + const mediaType = blob.type || "application/octet-stream"; + this[result] = `data:${mediaType};base64,${ + forgivingBase64Encode(bytes) + }`; + break; + } + } + // 4.2 Fire a progress event called load at the fr. { - const ev = new ProgressEvent("error", {}); + const ev = new ProgressEvent("load", { + lengthComputable: true, + loaded: size, + total: size, + }); this.dispatchEvent(ev); } - //If fr’s state is not "loading", fire a progress event called loadend at fr. - //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. + //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", {}); + const ev = new ProgressEvent("loadend", { + lengthComputable: true, + loaded: size, + total: size, + }); this.dispatchEvent(ev); } }); break; } + } catch (err) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + + // chunkPromise rejected + this[state] = "done"; + this[error] = err; + + { + const ev = new ProgressEvent("error", {}); + this.dispatchEvent(ev); + } + + //If fr’s state is not "loading", fire a progress event called loadend at fr. + //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); + } + }); + break; } - })(); - } - - #getEventHandlerFor(name) { - webidl.assertBranded(this, FileReaderPrototype); - - const maybeMap = this[handlerSymbol]; - if (!maybeMap) return null; - - return MapPrototypeGet(maybeMap, name)?.handler ?? null; - } - - #setEventHandlerFor(name, value) { - webidl.assertBranded(this, FileReaderPrototype); - - if (!this[handlerSymbol]) { - this[handlerSymbol] = new Map(); - } - let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); - if (handlerWrapper) { - handlerWrapper.handler = value; - } else { - handlerWrapper = makeWrappedHandler(value); - this.addEventListener(name, handlerWrapper); } + })(); + } - MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); + #getEventHandlerFor(name) { + webidl.assertBranded(this, FileReaderPrototype); + + const maybeMap = this[handlerSymbol]; + if (!maybeMap) return null; + + return MapPrototypeGet(maybeMap, name)?.handler ?? null; + } + + #setEventHandlerFor(name, value) { + webidl.assertBranded(this, FileReaderPrototype); + + if (!this[handlerSymbol]) { + this[handlerSymbol] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else { + handlerWrapper = makeWrappedHandler(value); + this.addEventListener(name, handlerWrapper); } - constructor() { - super(); - this[webidl.brand] = webidl.brand; + MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); + } + + constructor() { + super(); + this[webidl.brand] = webidl.brand; + } + + /** @returns {number} */ + get readyState() { + webidl.assertBranded(this, FileReaderPrototype); + switch (this[state]) { + case "empty": + return FileReader.EMPTY; + case "loading": + return FileReader.LOADING; + case "done": + return FileReader.DONE; + default: + throw new TypeError("Invalid state"); + } + } + + get result() { + webidl.assertBranded(this, FileReaderPrototype); + return this[result]; + } + + get error() { + webidl.assertBranded(this, FileReaderPrototype); + return this[error]; + } + + abort() { + webidl.assertBranded(this, FileReaderPrototype); + // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. + if ( + this[state] === "empty" || + this[state] === "done" + ) { + this[result] = null; + return; + } + // If context object's state is "loading" set context object's state to "done" and set context object's result to null. + if (this[state] === "loading") { + this[state] = "done"; + this[result] = null; + } + // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. + // Terminate the algorithm for the read method being processed. + if (this[aborted] !== null) { + this[aborted].aborted = true; } - /** @returns {number} */ - get readyState() { - webidl.assertBranded(this, FileReaderPrototype); - switch (this[state]) { - case "empty": - return FileReader.EMPTY; - case "loading": - return FileReader.LOADING; - case "done": - return FileReader.DONE; - default: - throw new TypeError("Invalid state"); - } - } + // Fire a progress event called abort at the context object. + const ev = new ProgressEvent("abort", {}); + this.dispatchEvent(ev); - get result() { - webidl.assertBranded(this, FileReaderPrototype); - return this[result]; - } - - get error() { - webidl.assertBranded(this, FileReaderPrototype); - return this[error]; - } - - abort() { - webidl.assertBranded(this, FileReaderPrototype); - // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. - if ( - this[state] === "empty" || - this[state] === "done" - ) { - this[result] = null; - return; - } - // If context object's state is "loading" set context object's state to "done" and set context object's result to null. - if (this[state] === "loading") { - this[state] = "done"; - this[result] = null; - } - // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. - // Terminate the algorithm for the read method being processed. - if (this[aborted] !== null) { - this[aborted].aborted = true; - } - - // Fire a progress event called abort at the context object. - const ev = new ProgressEvent("abort", {}); + // If context object's state is not "loading", fire a progress event called loadend at the context object. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); this.dispatchEvent(ev); - - // If context object's state is not "loading", fire a progress event called loadend at the context object. - if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", {}); - this.dispatchEvent(ev); - } - } - - /** @param {Blob} blob */ - readAsArrayBuffer(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - this.#readOperation(blob, { kind: "ArrayBuffer" }); - } - - /** @param {Blob} blob */ - readAsBinaryString(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "BinaryString" }); - } - - /** @param {Blob} blob */ - readAsDataURL(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "DataUrl" }); - } - - /** - * @param {Blob} blob - * @param {string} [encoding] - */ - readAsText(blob, encoding = undefined) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsText' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - if (encoding !== undefined) { - encoding = webidl.converters["DOMString"](encoding, { - prefix, - context: "Argument 2", - }); - } - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "Text", encoding }); - } - - get onerror() { - return this.#getEventHandlerFor("error"); - } - set onerror(value) { - this.#setEventHandlerFor("error", value); - } - - get onloadstart() { - return this.#getEventHandlerFor("loadstart"); - } - set onloadstart(value) { - this.#setEventHandlerFor("loadstart", value); - } - - get onload() { - return this.#getEventHandlerFor("load"); - } - set onload(value) { - this.#setEventHandlerFor("load", value); - } - - get onloadend() { - return this.#getEventHandlerFor("loadend"); - } - set onloadend(value) { - this.#setEventHandlerFor("loadend", value); - } - - get onprogress() { - return this.#getEventHandlerFor("progress"); - } - set onprogress(value) { - this.#setEventHandlerFor("progress", value); - } - - get onabort() { - return this.#getEventHandlerFor("abort"); - } - set onabort(value) { - this.#setEventHandlerFor("abort", value); } } - webidl.configurePrototype(FileReader); - const FileReaderPrototype = FileReader.prototype; - - ObjectDefineProperty(FileReader, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - ObjectDefineProperty(FileReader, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - ObjectDefineProperty(FileReader, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 2, - }); - ObjectDefineProperty(FileReader.prototype, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - ObjectDefineProperty(FileReader.prototype, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - ObjectDefineProperty(FileReader.prototype, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 2, - }); - - function makeWrappedHandler(handler) { - function wrappedHandler(...args) { - if (typeof wrappedHandler.handler !== "function") { - return; - } - return FunctionPrototypeCall( - wrappedHandler.handler, - this, - ...new SafeArrayIterator(args), - ); - } - wrappedHandler.handler = handler; - return wrappedHandler; + /** @param {Blob} blob */ + readAsArrayBuffer(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + this.#readOperation(blob, { kind: "ArrayBuffer" }); } - window.__bootstrap.fileReader = { - FileReader, - }; -})(this); + /** @param {Blob} blob */ + readAsBinaryString(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "BinaryString" }); + } + + /** @param {Blob} blob */ + readAsDataURL(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "DataUrl" }); + } + + /** + * @param {Blob} blob + * @param {string} [encoding] + */ + readAsText(blob, encoding = undefined) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsText' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + if (encoding !== undefined) { + encoding = webidl.converters["DOMString"](encoding, { + prefix, + context: "Argument 2", + }); + } + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "Text", encoding }); + } + + get onerror() { + return this.#getEventHandlerFor("error"); + } + set onerror(value) { + this.#setEventHandlerFor("error", value); + } + + get onloadstart() { + return this.#getEventHandlerFor("loadstart"); + } + set onloadstart(value) { + this.#setEventHandlerFor("loadstart", value); + } + + get onload() { + return this.#getEventHandlerFor("load"); + } + set onload(value) { + this.#setEventHandlerFor("load", value); + } + + get onloadend() { + return this.#getEventHandlerFor("loadend"); + } + set onloadend(value) { + this.#setEventHandlerFor("loadend", value); + } + + get onprogress() { + return this.#getEventHandlerFor("progress"); + } + set onprogress(value) { + this.#setEventHandlerFor("progress", value); + } + + get onabort() { + return this.#getEventHandlerFor("abort"); + } + set onabort(value) { + this.#setEventHandlerFor("abort", value); + } +} + +webidl.configurePrototype(FileReader); +const FileReaderPrototype = FileReader.prototype; + +ObjectDefineProperty(FileReader, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, +}); +ObjectDefineProperty(FileReader, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, +}); +ObjectDefineProperty(FileReader, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, +}); +ObjectDefineProperty(FileReader.prototype, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, +}); +ObjectDefineProperty(FileReader.prototype, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, +}); +ObjectDefineProperty(FileReader.prototype, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, +}); + +function makeWrappedHandler(handler) { + function wrappedHandler(...args) { + if (typeof wrappedHandler.handler !== "function") { + return; + } + return FunctionPrototypeCall( + wrappedHandler.handler, + this, + ...new SafeArrayIterator(args), + ); + } + wrappedHandler.handler = handler; + return wrappedHandler; +} + +export { FileReader }; diff --git a/ext/web/11_blob_url.js b/ext/web/11_blob_url.js index a51a1e7185..02551fef69 100644 --- a/ext/web/11_blob_url.js +++ b/ext/web/11_blob_url.js @@ -10,50 +10,42 @@ /// /// /// -"use strict"; -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { getParts } = window.__bootstrap.file; - const { URL } = window.__bootstrap.url; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { getParts } from "internal:ext/web/09_file.js"; +import { URL } from "internal:ext/url/00_url.js"; - /** - * @param {Blob} blob - * @returns {string} - */ - function createObjectURL(blob) { - const prefix = "Failed to execute 'createObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - blob = webidl.converters["Blob"](blob, { - context: "Argument 1", - prefix, - }); +/** + * @param {Blob} blob + * @returns {string} + */ +function createObjectURL(blob) { + const prefix = "Failed to execute 'createObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + blob = webidl.converters["Blob"](blob, { + context: "Argument 1", + prefix, + }); - const url = ops.op_blob_create_object_url( - blob.type, - getParts(blob), - ); + return ops.op_blob_create_object_url(blob.type, getParts(blob)); +} - return url; - } +/** + * @param {string} url + * @returns {void} + */ +function revokeObjectURL(url) { + const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters["DOMString"](url, { + context: "Argument 1", + prefix, + }); - /** - * @param {string} url - * @returns {void} - */ - function revokeObjectURL(url) { - const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - url = webidl.converters["DOMString"](url, { - context: "Argument 1", - prefix, - }); + ops.op_blob_revoke_object_url(url); +} - ops.op_blob_revoke_object_url(url); - } - - URL.createObjectURL = createObjectURL; - URL.revokeObjectURL = revokeObjectURL; -})(globalThis); +URL.createObjectURL = createObjectURL; +URL.revokeObjectURL = revokeObjectURL; diff --git a/ext/web/12_location.js b/ext/web/12_location.js index 964ca591ec..da964eae85 100644 --- a/ext/web/12_location.js +++ b/ext/web/12_location.js @@ -1,403 +1,410 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const { URL } = window.__bootstrap.url; - const { DOMException } = window.__bootstrap.domException; - const { - Error, - ObjectDefineProperties, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; +import { URL } from "internal:ext/url/00_url.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectDefineProperties, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; - const locationConstructorKey = Symbol("locationConstuctorKey"); +const locationConstructorKey = Symbol("locationConstuctorKey"); - // The differences between the definitions of `Location` and `WorkerLocation` - // are because of the `LegacyUnforgeable` attribute only specified upon - // `Location`'s properties. See: - // - https://html.spec.whatwg.org/multipage/history.html#the-location-interface - // - https://heycam.github.io/webidl/#LegacyUnforgeable - class Location { - constructor(href = null, key = null) { - if (key != locationConstructorKey) { - throw new TypeError("Illegal constructor."); - } - const url = new URL(href); - url.username = ""; - url.password = ""; - ObjectDefineProperties(this, { - hash: { - get() { - return url.hash; - }, - set() { - throw new DOMException( - `Cannot set "location.hash".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - host: { - get() { - return url.host; - }, - set() { - throw new DOMException( - `Cannot set "location.host".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - hostname: { - get() { - return url.hostname; - }, - set() { - throw new DOMException( - `Cannot set "location.hostname".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - href: { - get() { - return url.href; - }, - set() { - throw new DOMException( - `Cannot set "location.href".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - origin: { - get() { - return url.origin; - }, - enumerable: true, - }, - pathname: { - get() { - return url.pathname; - }, - set() { - throw new DOMException( - `Cannot set "location.pathname".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - port: { - get() { - return url.port; - }, - set() { - throw new DOMException( - `Cannot set "location.port".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - protocol: { - get() { - return url.protocol; - }, - set() { - throw new DOMException( - `Cannot set "location.protocol".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - search: { - get() { - return url.search; - }, - set() { - throw new DOMException( - `Cannot set "location.search".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - ancestorOrigins: { - get() { - // TODO(nayeemrmn): Replace with a `DOMStringList` instance. - return { - length: 0, - item: () => null, - contains: () => false, - }; - }, - enumerable: true, - }, - assign: { - value: function assign() { - throw new DOMException( - `Cannot call "location.assign()".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - reload: { - value: function reload() { - throw new DOMException( - `Cannot call "location.reload()".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - replace: { - value: function replace() { - throw new DOMException( - `Cannot call "location.replace()".`, - "NotSupportedError", - ); - }, - enumerable: true, - }, - toString: { - value: function toString() { - return url.href; - }, - enumerable: true, - }, - [SymbolFor("Deno.privateCustomInspect")]: { - value: function (inspect) { - const object = { - hash: this.hash, - host: this.host, - hostname: this.hostname, - href: this.href, - origin: this.origin, - pathname: this.pathname, - port: this.port, - protocol: this.protocol, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object)}`; - }, - }, - }); +// The differences between the definitions of `Location` and `WorkerLocation` +// are because of the `LegacyUnforgeable` attribute only specified upon +// `Location`'s properties. See: +// - https://html.spec.whatwg.org/multipage/history.html#the-location-interface +// - https://heycam.github.io/webidl/#LegacyUnforgeable +class Location { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); } - } - - ObjectDefineProperties(Location.prototype, { - [SymbolToStringTag]: { - value: "Location", - configurable: true, - }, - }); - - const workerLocationUrls = new WeakMap(); - - class WorkerLocation { - constructor(href = null, key = null) { - if (key != locationConstructorKey) { - throw new TypeError("Illegal constructor."); - } - const url = new URL(href); - url.username = ""; - url.password = ""; - WeakMapPrototypeSet(workerLocationUrls, this, url); - } - } - - ObjectDefineProperties(WorkerLocation.prototype, { - hash: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.hash; - }, - configurable: true, - enumerable: true, - }, - host: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.host; - }, - configurable: true, - enumerable: true, - }, - hostname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.hostname; - }, - configurable: true, - enumerable: true, - }, - href: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.href; - }, - configurable: true, - enumerable: true, - }, - origin: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.origin; - }, - configurable: true, - enumerable: true, - }, - pathname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.pathname; - }, - configurable: true, - enumerable: true, - }, - port: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.port; - }, - configurable: true, - enumerable: true, - }, - protocol: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.protocol; - }, - configurable: true, - enumerable: true, - }, - search: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.search; - }, - configurable: true, - enumerable: true, - }, - toString: { - value: function toString() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.href; - }, - configurable: true, - enumerable: true, - writable: true, - }, - [SymbolToStringTag]: { - value: "WorkerLocation", - configurable: true, - }, - [SymbolFor("Deno.privateCustomInspect")]: { - value: function (inspect) { - const object = { - hash: this.hash, - host: this.host, - hostname: this.hostname, - href: this.href, - origin: this.origin, - pathname: this.pathname, - port: this.port, - protocol: this.protocol, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object)}`; - }, - }, - }); - - let location = undefined; - let workerLocation = undefined; - - function setLocationHref(href) { - location = new Location(href, locationConstructorKey); - workerLocation = new WorkerLocation(href, locationConstructorKey); - } - - window.__bootstrap.location = { - locationConstructorDescriptor: { - value: Location, - configurable: true, - writable: true, - }, - workerLocationConstructorDescriptor: { - value: WorkerLocation, - configurable: true, - writable: true, - }, - locationDescriptor: { - get() { - return location; - }, - set() { - throw new DOMException(`Cannot set "location".`, "NotSupportedError"); - }, - enumerable: true, - }, - workerLocationDescriptor: { - get() { - if (workerLocation == null) { - throw new Error( - `Assertion: "globalThis.location" must be defined in a worker.`, + const url = new URL(href); + url.username = ""; + url.password = ""; + ObjectDefineProperties(this, { + hash: { + get() { + return url.hash; + }, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", ); - } - return workerLocation; + }, + enumerable: true, }, - configurable: true, - enumerable: true, + host: { + get() { + return url.host; + }, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + hostname: { + get() { + return url.hostname; + }, + set() { + throw new DOMException( + `Cannot set "location.hostname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + href: { + get() { + return url.href; + }, + set() { + throw new DOMException( + `Cannot set "location.href".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + origin: { + get() { + return url.origin; + }, + enumerable: true, + }, + pathname: { + get() { + return url.pathname; + }, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + port: { + get() { + return url.port; + }, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + protocol: { + get() { + return url.protocol; + }, + set() { + throw new DOMException( + `Cannot set "location.protocol".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + search: { + get() { + return url.search; + }, + set() { + throw new DOMException( + `Cannot set "location.search".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + ancestorOrigins: { + get() { + // TODO(nayeemrmn): Replace with a `DOMStringList` instance. + return { + length: 0, + item: () => null, + contains: () => false, + }; + }, + enumerable: true, + }, + assign: { + value: function assign() { + throw new DOMException( + `Cannot call "location.assign()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + reload: { + value: function reload() { + throw new DOMException( + `Cannot call "location.reload()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + replace: { + value: function replace() { + throw new DOMException( + `Cannot call "location.replace()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + toString: { + value: function toString() { + return url.href; + }, + enumerable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + }, + }, + }); + } +} + +ObjectDefineProperties(Location.prototype, { + [SymbolToStringTag]: { + value: "Location", + configurable: true, + }, +}); + +const workerLocationUrls = new WeakMap(); + +class WorkerLocation { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + url.username = ""; + url.password = ""; + WeakMapPrototypeSet(workerLocationUrls, this, url); + } +} + +ObjectDefineProperties(WorkerLocation.prototype, { + hash: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hash; }, - setLocationHref, - getLocationHref() { - return location?.href; + configurable: true, + enumerable: true, + }, + host: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.host; }, - }; -})(this); + configurable: true, + enumerable: true, + }, + hostname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hostname; + }, + configurable: true, + enumerable: true, + }, + href: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + }, + origin: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.origin; + }, + configurable: true, + enumerable: true, + }, + pathname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.pathname; + }, + configurable: true, + enumerable: true, + }, + port: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.port; + }, + configurable: true, + enumerable: true, + }, + protocol: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.protocol; + }, + configurable: true, + enumerable: true, + }, + search: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.search; + }, + configurable: true, + enumerable: true, + }, + toString: { + value: function toString() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + writable: true, + }, + [SymbolToStringTag]: { + value: "WorkerLocation", + configurable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + }, + }, +}); + +let location = undefined; +let workerLocation = undefined; + +function setLocationHref(href) { + location = new Location(href, locationConstructorKey); + workerLocation = new WorkerLocation(href, locationConstructorKey); +} + +function getLocationHref() { + return location?.href; +} + +const locationConstructorDescriptor = { + value: Location, + configurable: true, + writable: true, +}; + +const workerLocationConstructorDescriptor = { + value: WorkerLocation, + configurable: true, + writable: true, +}; + +const locationDescriptor = { + get() { + return location; + }, + set() { + throw new DOMException(`Cannot set "location".`, "NotSupportedError"); + }, + enumerable: true, +}; +const workerLocationDescriptor = { + get() { + if (workerLocation == null) { + throw new Error( + `Assertion: "globalThis.location" must be defined in a worker.`, + ); + } + return workerLocation; + }, + configurable: true, + enumerable: true, +}; + +export { + getLocationHref, + locationConstructorDescriptor, + locationDescriptor, + setLocationHref, + workerLocationConstructorDescriptor, + workerLocationDescriptor, +}; diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js index 7ab2beb826..2a784bf3f0 100644 --- a/ext/web/13_message_port.js +++ b/ext/web/13_message_port.js @@ -6,338 +6,339 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const { InterruptedPrototype, ops } = core; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + defineEventHandler, + EventTarget, + MessageEvent, + setEventTargetData, +} from "internal:ext/web/02_event.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypePush, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Symbol, + SymbolFor, + SymbolIterator, + TypeError, +} = primordials; -((window) => { - const core = window.Deno.core; - const { InterruptedPrototype, ops } = core; - const webidl = window.__bootstrap.webidl; - const { EventTarget, setEventTargetData } = window.__bootstrap.eventTarget; - const { MessageEvent, defineEventHandler } = window.__bootstrap.event; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBufferPrototype, - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ArrayPrototypePush, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Symbol, - SymbolFor, - SymbolIterator, - TypeError, - } = window.__bootstrap.primordials; +class MessageChannel { + /** @type {MessagePort} */ + #port1; + /** @type {MessagePort} */ + #port2; - class MessageChannel { - /** @type {MessagePort} */ - #port1; - /** @type {MessagePort} */ - #port2; - - constructor() { - this[webidl.brand] = webidl.brand; - const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort(); - const port1 = createMessagePort(port1Id); - const port2 = createMessagePort(port2Id); - this.#port1 = port1; - this.#port2 = port2; - } - - get port1() { - webidl.assertBranded(this, MessageChannelPrototype); - return this.#port1; - } - - get port2() { - webidl.assertBranded(this, MessageChannelPrototype); - return this.#port2; - } - - [SymbolFor("Deno.inspect")](inspect) { - return `MessageChannel ${ - inspect({ port1: this.port1, port2: this.port2 }) - }`; - } + constructor() { + this[webidl.brand] = webidl.brand; + const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort(); + const port1 = createMessagePort(port1Id); + const port2 = createMessagePort(port2Id); + this.#port1 = port1; + this.#port2 = port2; } - webidl.configurePrototype(MessageChannel); - const MessageChannelPrototype = MessageChannel.prototype; - - const _id = Symbol("id"); - const _enabled = Symbol("enabled"); - - /** - * @param {number} id - * @returns {MessagePort} - */ - function createMessagePort(id) { - const port = core.createHostObject(); - ObjectSetPrototypeOf(port, MessagePortPrototype); - port[webidl.brand] = webidl.brand; - setEventTargetData(port); - port[_id] = id; - return port; + get port1() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port1; } - class MessagePort extends EventTarget { - /** @type {number | null} */ - [_id] = null; - /** @type {boolean} */ - [_enabled] = false; - - constructor() { - super(); - webidl.illegalConstructor(); - } - - /** - * @param {any} message - * @param {object[] | StructuredSerializeOptions} transferOrOptions - */ - postMessage(message, transferOrOptions = {}) { - webidl.assertBranded(this, MessagePortPrototype); - const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - if (ArrayPrototypeIncludes(transfer, this)) { - throw new DOMException("Can not tranfer self", "DataCloneError"); - } - const data = serializeJsMessageData(message, transfer); - if (this[_id] === null) return; - ops.op_message_port_post_message(this[_id], data); - } - - start() { - webidl.assertBranded(this, MessagePortPrototype); - if (this[_enabled]) return; - (async () => { - this[_enabled] = true; - while (true) { - if (this[_id] === null) break; - let data; - try { - data = await core.opAsync( - "op_message_port_recv_message", - this[_id], - ); - } catch (err) { - if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break; - throw err; - } - if (data === null) break; - let message, transferables; - try { - const v = deserializeJsMessageData(data); - message = v[0]; - transferables = v[1]; - } catch (err) { - const event = new MessageEvent("messageerror", { data: err }); - this.dispatchEvent(event); - return; - } - const event = new MessageEvent("message", { - data: message, - ports: ArrayPrototypeFilter( - transferables, - (t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t), - ), - }); - this.dispatchEvent(event); - } - this[_enabled] = false; - })(); - } - - close() { - webidl.assertBranded(this, MessagePortPrototype); - if (this[_id] !== null) { - core.close(this[_id]); - this[_id] = null; - } - } + get port2() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port2; } - defineEventHandler(MessagePort.prototype, "message", function (self) { - self.start(); - }); - defineEventHandler(MessagePort.prototype, "messageerror"); + [SymbolFor("Deno.inspect")](inspect) { + return `MessageChannel ${ + inspect({ port1: this.port1, port2: this.port2 }) + }`; + } +} - webidl.configurePrototype(MessagePort); - const MessagePortPrototype = MessagePort.prototype; +webidl.configurePrototype(MessageChannel); +const MessageChannelPrototype = MessageChannel.prototype; - /** - * @returns {[number, number]} - */ - function opCreateEntangledMessagePort() { - return ops.op_message_port_create_entangled(); +const _id = Symbol("id"); +const _enabled = Symbol("enabled"); + +/** + * @param {number} id + * @returns {MessagePort} + */ +function createMessagePort(id) { + const port = core.createHostObject(); + ObjectSetPrototypeOf(port, MessagePortPrototype); + port[webidl.brand] = webidl.brand; + setEventTargetData(port); + port[_id] = id; + return port; +} + +class MessagePort extends EventTarget { + /** @type {number | null} */ + [_id] = null; + /** @type {boolean} */ + [_enabled] = false; + + constructor() { + super(); + webidl.illegalConstructor(); } /** - * @param {globalThis.__bootstrap.messagePort.MessageData} messageData - * @returns {[any, object[]]} + * @param {any} message + * @param {object[] | StructuredSerializeOptions} transferOrOptions */ - function deserializeJsMessageData(messageData) { - /** @type {object[]} */ - const transferables = []; - const hostObjects = []; - const arrayBufferIdsInTransferables = []; - const transferredArrayBuffers = []; - - for (let i = 0; i < messageData.transferables.length; ++i) { - const transferable = messageData.transferables[i]; - switch (transferable.kind) { - case "messagePort": { - const port = createMessagePort(transferable.data); - ArrayPrototypePush(transferables, port); - ArrayPrototypePush(hostObjects, port); - break; - } - case "arrayBuffer": { - ArrayPrototypePush(transferredArrayBuffers, transferable.data); - const index = ArrayPrototypePush(transferables, null); - ArrayPrototypePush(arrayBufferIdsInTransferables, index); - break; - } - default: - throw new TypeError("Unreachable"); - } - } - - const data = core.deserialize(messageData.data, { - hostObjects, - transferredArrayBuffers, - }); - - for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { - const id = arrayBufferIdsInTransferables[i]; - transferables[id] = transferredArrayBuffers[i]; - } - - return [data, transferables]; - } - - /** - * @param {any} data - * @param {object[]} transferables - * @returns {globalThis.__bootstrap.messagePort.MessageData} - */ - function serializeJsMessageData(data, transferables) { - const transferredArrayBuffers = []; - for (let i = 0, j = 0; i < transferables.length; i++) { - const ab = transferables[i]; - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) { - if (ab.byteLength === 0 && core.ops.op_arraybuffer_was_detached(ab)) { - throw new DOMException( - `ArrayBuffer at index ${j} is already detached`, - "DataCloneError", - ); - } - j++; - transferredArrayBuffers.push(ab); - } - } - - const serializedData = core.serialize(data, { - hostObjects: ArrayPrototypeFilter( - transferables, - (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), - ), - transferredArrayBuffers, - }, (err) => { - throw new DOMException(err, "DataCloneError"); - }); - - /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */ - const serializedTransferables = []; - - let arrayBufferI = 0; - for (let i = 0; i < transferables.length; ++i) { - const transferable = transferables[i]; - if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) { - webidl.assertBranded(transferable, MessagePortPrototype); - const id = transferable[_id]; - if (id === null) { - throw new DOMException( - "Can not transfer disentangled message port", - "DataCloneError", - ); - } - transferable[_id] = null; - ArrayPrototypePush(serializedTransferables, { - kind: "messagePort", - data: id, - }); - } else if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable) - ) { - ArrayPrototypePush(serializedTransferables, { - kind: "arrayBuffer", - data: transferredArrayBuffers[arrayBufferI], - }); - arrayBufferI++; - } else { - throw new DOMException("Value not transferable", "DataCloneError"); - } - } - - return { - data: serializedData, - transferables: serializedTransferables, - }; - } - - webidl.converters.StructuredSerializeOptions = webidl - .createDictionaryConverter( - "StructuredSerializeOptions", - [ - { - key: "transfer", - converter: webidl.converters["sequence"], - get defaultValue() { - return []; - }, - }, - ], - ); - - function structuredClone(value, options) { - const prefix = "Failed to execute 'structuredClone'"; + postMessage(message, transferOrOptions = {}) { + webidl.assertBranded(this, MessagePortPrototype); + const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; webidl.requiredArguments(arguments.length, 1, { prefix }); - options = webidl.converters.StructuredSerializeOptions(options, { - prefix, - context: "Argument 2", - }); - const messageData = serializeJsMessageData(value, options.transfer); - return deserializeJsMessageData(messageData)[0]; + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); + } + const { transfer } = options; + if (ArrayPrototypeIncludes(transfer, this)) { + throw new DOMException("Can not tranfer self", "DataCloneError"); + } + const data = serializeJsMessageData(message, transfer); + if (this[_id] === null) return; + ops.op_message_port_post_message(this[_id], data); } - window.__bootstrap.messagePort = { - MessageChannel, - MessagePort, - MessagePortPrototype, - deserializeJsMessageData, - serializeJsMessageData, - structuredClone, + start() { + webidl.assertBranded(this, MessagePortPrototype); + if (this[_enabled]) return; + (async () => { + this[_enabled] = true; + while (true) { + if (this[_id] === null) break; + let data; + try { + data = await core.opAsync( + "op_message_port_recv_message", + this[_id], + ); + } catch (err) { + if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break; + throw err; + } + if (data === null) break; + let message, transferables; + try { + const v = deserializeJsMessageData(data); + message = v[0]; + transferables = v[1]; + } catch (err) { + const event = new MessageEvent("messageerror", { data: err }); + this.dispatchEvent(event); + return; + } + const event = new MessageEvent("message", { + data: message, + ports: ArrayPrototypeFilter( + transferables, + (t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t), + ), + }); + this.dispatchEvent(event); + } + this[_enabled] = false; + })(); + } + + close() { + webidl.assertBranded(this, MessagePortPrototype); + if (this[_id] !== null) { + core.close(this[_id]); + this[_id] = null; + } + } +} + +defineEventHandler(MessagePort.prototype, "message", function (self) { + self.start(); +}); +defineEventHandler(MessagePort.prototype, "messageerror"); + +webidl.configurePrototype(MessagePort); +const MessagePortPrototype = MessagePort.prototype; + +/** + * @returns {[number, number]} + */ +function opCreateEntangledMessagePort() { + return ops.op_message_port_create_entangled(); +} + +/** + * @param {messagePort.MessageData} messageData + * @returns {[any, object[]]} + */ +function deserializeJsMessageData(messageData) { + /** @type {object[]} */ + const transferables = []; + const hostObjects = []; + const arrayBufferIdsInTransferables = []; + const transferredArrayBuffers = []; + + for (let i = 0; i < messageData.transferables.length; ++i) { + const transferable = messageData.transferables[i]; + switch (transferable.kind) { + case "messagePort": { + const port = createMessagePort(transferable.data); + ArrayPrototypePush(transferables, port); + ArrayPrototypePush(hostObjects, port); + break; + } + case "arrayBuffer": { + ArrayPrototypePush(transferredArrayBuffers, transferable.data); + const index = ArrayPrototypePush(transferables, null); + ArrayPrototypePush(arrayBufferIdsInTransferables, index); + break; + } + default: + throw new TypeError("Unreachable"); + } + } + + const data = core.deserialize(messageData.data, { + hostObjects, + transferredArrayBuffers, + }); + + for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { + const id = arrayBufferIdsInTransferables[i]; + transferables[id] = transferredArrayBuffers[i]; + } + + return [data, transferables]; +} + +/** + * @param {any} data + * @param {object[]} transferables + * @returns {messagePort.MessageData} + */ +function serializeJsMessageData(data, transferables) { + const transferredArrayBuffers = []; + for (let i = 0, j = 0; i < transferables.length; i++) { + const ab = transferables[i]; + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) { + if (ab.byteLength === 0 && ops.op_arraybuffer_was_detached(ab)) { + throw new DOMException( + `ArrayBuffer at index ${j} is already detached`, + "DataCloneError", + ); + } + j++; + transferredArrayBuffers.push(ab); + } + } + + const serializedData = core.serialize(data, { + hostObjects: ArrayPrototypeFilter( + transferables, + (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), + ), + transferredArrayBuffers, + }, (err) => { + throw new DOMException(err, "DataCloneError"); + }); + + /** @type {messagePort.Transferable[]} */ + const serializedTransferables = []; + + let arrayBufferI = 0; + for (let i = 0; i < transferables.length; ++i) { + const transferable = transferables[i]; + if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) { + webidl.assertBranded(transferable, MessagePortPrototype); + const id = transferable[_id]; + if (id === null) { + throw new DOMException( + "Can not transfer disentangled message port", + "DataCloneError", + ); + } + transferable[_id] = null; + ArrayPrototypePush(serializedTransferables, { + kind: "messagePort", + data: id, + }); + } else if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable) + ) { + ArrayPrototypePush(serializedTransferables, { + kind: "arrayBuffer", + data: transferredArrayBuffers[arrayBufferI], + }); + arrayBufferI++; + } else { + throw new DOMException("Value not transferable", "DataCloneError"); + } + } + + return { + data: serializedData, + transferables: serializedTransferables, }; -})(globalThis); +} + +webidl.converters.StructuredSerializeOptions = webidl + .createDictionaryConverter( + "StructuredSerializeOptions", + [ + { + key: "transfer", + converter: webidl.converters["sequence"], + get defaultValue() { + return []; + }, + }, + ], + ); + +function structuredClone(value, options) { + const prefix = "Failed to execute 'structuredClone'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + options = webidl.converters.StructuredSerializeOptions(options, { + prefix, + context: "Argument 2", + }); + const messageData = serializeJsMessageData(value, options.transfer); + return deserializeJsMessageData(messageData)[0]; +} + +export { + deserializeJsMessageData, + MessageChannel, + MessagePort, + MessagePortPrototype, + serializeJsMessageData, + structuredClone, +}; diff --git a/ext/web/14_compression.js b/ext/web/14_compression.js index 338f8c8034..680da757e0 100644 --- a/ext/web/14_compression.js +++ b/ext/web/14_compression.js @@ -5,127 +5,120 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { TransformStream } from "internal:ext/web/06_streams.js"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { TransformStream } = window.__bootstrap.streams; +webidl.converters.CompressionFormat = webidl.createEnumConverter( + "CompressionFormat", + [ + "deflate", + "deflate-raw", + "gzip", + ], +); - webidl.converters.CompressionFormat = webidl.createEnumConverter( - "CompressionFormat", - [ - "deflate", - "deflate-raw", - "gzip", - ], - ); +class CompressionStream { + #transform; - class CompressionStream { - #transform; + constructor(format) { + const prefix = "Failed to construct 'CompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); - constructor(format) { - const prefix = "Failed to construct 'CompressionStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - format = webidl.converters.CompressionFormat(format, { - prefix, - context: "Argument 1", - }); + const rid = ops.op_compression_new(format, false); - const rid = ops.op_compression_new(format, false); + this.#transform = new TransformStream({ + transform(chunk, controller) { + chunk = webidl.converters.BufferSource(chunk, { + prefix, + context: "chunk", + }); + const output = ops.op_compression_write( + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = ops.op_compression_finish(rid); + maybeEnqueue(controller, output); + }, + }); - this.#transform = new TransformStream({ - transform(chunk, controller) { - chunk = webidl.converters.BufferSource(chunk, { - prefix, - context: "chunk", - }); - const output = ops.op_compression_write( - rid, - chunk, - ); - maybeEnqueue(controller, output); - }, - flush(controller) { - const output = ops.op_compression_finish(rid); - maybeEnqueue(controller, output); - }, - }); - - this[webidl.brand] = webidl.brand; - } - - get readable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.readable; - } - - get writable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.writable; - } + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(CompressionStream); - const CompressionStreamPrototype = CompressionStream.prototype; - - class DecompressionStream { - #transform; - - constructor(format) { - const prefix = "Failed to construct 'DecompressionStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - format = webidl.converters.CompressionFormat(format, { - prefix, - context: "Argument 1", - }); - - const rid = ops.op_compression_new(format, true); - - this.#transform = new TransformStream({ - transform(chunk, controller) { - chunk = webidl.converters.BufferSource(chunk, { - prefix, - context: "chunk", - }); - const output = ops.op_compression_write( - rid, - chunk, - ); - maybeEnqueue(controller, output); - }, - flush(controller) { - const output = ops.op_compression_finish(rid); - maybeEnqueue(controller, output); - }, - }); - - this[webidl.brand] = webidl.brand; - } - - get readable() { - webidl.assertBranded(this, DecompressionStreamPrototype); - return this.#transform.readable; - } - - get writable() { - webidl.assertBranded(this, DecompressionStreamPrototype); - return this.#transform.writable; - } + get readable() { + webidl.assertBranded(this, CompressionStreamPrototype); + return this.#transform.readable; } - function maybeEnqueue(controller, output) { - if (output && output.byteLength > 0) { - controller.enqueue(output); - } + get writable() { + webidl.assertBranded(this, CompressionStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(CompressionStream); +const CompressionStreamPrototype = CompressionStream.prototype; + +class DecompressionStream { + #transform; + + constructor(format) { + const prefix = "Failed to construct 'DecompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); + + const rid = ops.op_compression_new(format, true); + + this.#transform = new TransformStream({ + transform(chunk, controller) { + chunk = webidl.converters.BufferSource(chunk, { + prefix, + context: "chunk", + }); + const output = ops.op_compression_write( + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = ops.op_compression_finish(rid); + maybeEnqueue(controller, output); + }, + }); + + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(DecompressionStream); - const DecompressionStreamPrototype = DecompressionStream.prototype; + get readable() { + webidl.assertBranded(this, DecompressionStreamPrototype); + return this.#transform.readable; + } - window.__bootstrap.compression = { - CompressionStream, - DecompressionStream, - }; -})(globalThis); + get writable() { + webidl.assertBranded(this, DecompressionStreamPrototype); + return this.#transform.writable; + } +} + +function maybeEnqueue(controller, output) { + if (output && output.byteLength > 0) { + controller.enqueue(output); + } +} + +webidl.configurePrototype(DecompressionStream); +const DecompressionStreamPrototype = DecompressionStream.prototype; + +export { CompressionStream, DecompressionStream }; diff --git a/ext/web/15_performance.js b/ext/web/15_performance.js index 9107ce75b8..6a50f45f80 100644 --- a/ext/web/15_performance.js +++ b/ext/web/15_performance.js @@ -1,594 +1,594 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { - ArrayPrototypeFilter, - ArrayPrototypeFind, - ArrayPrototypePush, - ArrayPrototypeReverse, - ArrayPrototypeSlice, - ObjectKeys, - ObjectPrototypeIsPrototypeOf, - ReflectHas, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + ArrayPrototypeFind, + ArrayPrototypePush, + ArrayPrototypeReverse, + ArrayPrototypeSlice, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + ReflectHas, + Symbol, + SymbolFor, + TypeError, +} = primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { structuredClone } from "internal:ext/web/02_structured_clone.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +import { EventTarget } from "internal:ext/web/02_event.js"; +import { opNow } from "internal:ext/web/02_timers.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; - const { webidl, structuredClone } = window.__bootstrap; - const consoleInternal = window.__bootstrap.console; - const { EventTarget } = window.__bootstrap.eventTarget; - const { opNow } = window.__bootstrap.timers; - const { DOMException } = window.__bootstrap.domException; +const illegalConstructorKey = Symbol("illegalConstructorKey"); +const customInspect = SymbolFor("Deno.customInspect"); +let performanceEntries = []; +let timeOrigin; - const illegalConstructorKey = Symbol("illegalConstructorKey"); - const customInspect = SymbolFor("Deno.customInspect"); - let performanceEntries = []; - let timeOrigin; - - webidl.converters["PerformanceMarkOptions"] = webidl - .createDictionaryConverter( - "PerformanceMarkOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "startTime", - converter: webidl.converters.DOMHighResTimeStamp, - }, - ], - ); - - webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { - if (webidl.type(V) === "Number" && V !== null) { - return webidl.converters.DOMHighResTimeStamp(V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - webidl.converters["PerformanceMeasureOptions"] = webidl - .createDictionaryConverter( - "PerformanceMeasureOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "start", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - { - key: "duration", - converter: webidl.converters.DOMHighResTimeStamp, - }, - { - key: "end", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - ], - ); - - webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { - if (webidl.type(V) === "Object" && V !== null) { - return webidl.converters["PerformanceMeasureOptions"](V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - function setTimeOrigin(origin) { - timeOrigin = origin; - } - - function findMostRecent( - name, - type, - ) { - return ArrayPrototypeFind( - ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), - (entry) => entry.name === name && entry.entryType === type, - ); - } - - function convertMarkToTimestamp(mark) { - if (typeof mark === "string") { - const entry = findMostRecent(mark, "mark"); - if (!entry) { - throw new DOMException( - `Cannot find mark: "${mark}".`, - "SyntaxError", - ); - } - return entry.startTime; - } - if (mark < 0) { - throw new TypeError("Mark cannot be negative."); - } - return mark; - } - - function filterByNameType( - name, - type, - ) { - return ArrayPrototypeFilter( - performanceEntries, - (entry) => - (name ? entry.name === name : true) && - (type ? entry.entryType === type : true), - ); - } - - const now = opNow; - - const _name = Symbol("[[name]]"); - const _entryType = Symbol("[[entryType]]"); - const _startTime = Symbol("[[startTime]]"); - const _duration = Symbol("[[duration]]"); - class PerformanceEntry { - [_name] = ""; - [_entryType] = ""; - [_startTime] = 0; - [_duration] = 0; - - get name() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_name]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_entryType]; - } - - get startTime() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_startTime]; - } - - get duration() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_duration]; - } - - constructor( - name = null, - entryType = null, - startTime = null, - duration = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; - - this[_name] = name; - this[_entryType] = entryType; - this[_startTime] = startTime; - this[_duration] = duration; - } - - toJSON() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return { - name: this[_name], - entryType: this[_entryType], - startTime: this[_startTime], - duration: this[_duration], - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceEntryPrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - ], - })); - } - } - webidl.configurePrototype(PerformanceEntry); - const PerformanceEntryPrototype = PerformanceEntry.prototype; - - const _detail = Symbol("[[detail]]"); - class PerformanceMark extends PerformanceEntry { - [_detail] = null; - - get detail() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return this[_detail]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return "mark"; - } - - constructor( - name, - options = {}, - ) { - const prefix = "Failed to construct 'PerformanceMark'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); - - options = webidl.converters.PerformanceMarkOptions(options, { - prefix, - context: "Argument 2", - }); - - const { detail = null, startTime = now() } = options; - - super(name, "mark", startTime, 0, illegalConstructorKey); - this[webidl.brand] = webidl.brand; - if (startTime < 0) { - throw new TypeError("startTime cannot be negative"); - } - this[_detail] = structuredClone(detail); - } - - toJSON() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } - } - webidl.configurePrototype(PerformanceMark); - const PerformanceMarkPrototype = PerformanceMark.prototype; - class PerformanceMeasure extends PerformanceEntry { - [_detail] = null; - - get detail() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return this[_detail]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return "measure"; - } - - constructor( - name = null, - startTime = null, - duration = null, - detail = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - - super(name, "measure", startTime, duration, key); - this[webidl.brand] = webidl.brand; - this[_detail] = structuredClone(detail); - } - - toJSON() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceMeasurePrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } - } - webidl.configurePrototype(PerformanceMeasure); - const PerformanceMeasurePrototype = PerformanceMeasure.prototype; - class Performance extends EventTarget { - constructor(key = null) { - if (key != illegalConstructorKey) { - webidl.illegalConstructor(); - } - - super(); - this[webidl.brand] = webidl.brand; - } - - get timeOrigin() { - webidl.assertBranded(this, PerformancePrototype); - return timeOrigin; - } - - clearMarks(markName = undefined) { - webidl.assertBranded(this, PerformancePrototype); - if (markName !== undefined) { - markName = webidl.converters.DOMString(markName, { - prefix: "Failed to execute 'clearMarks' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => !(entry.name === markName && entry.entryType === "mark"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "mark", - ); - } - } - - clearMeasures(measureName = undefined) { - webidl.assertBranded(this, PerformancePrototype); - if (measureName !== undefined) { - measureName = webidl.converters.DOMString(measureName, { - prefix: "Failed to execute 'clearMeasures' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => - !(entry.name === measureName && entry.entryType === "measure"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "measure", - ); - } - } - - getEntries() { - webidl.assertBranded(this, PerformancePrototype); - return filterByNameType(); - } - - getEntriesByName( - name, - type = undefined, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); - - if (type !== undefined) { - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 2", - }); - } - - return filterByNameType(name, type); - } - - getEntriesByType(type) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 1", - }); - - return filterByNameType(undefined, type); - } - - mark( - markName, - markOptions = {}, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'mark' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - markName = webidl.converters.DOMString(markName, { - prefix, - context: "Argument 1", - }); - - markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { - prefix, - context: "Argument 2", - }); - - // 3.1.1.1 If the global object is a Window object and markName uses the - // same name as a read only attribute in the PerformanceTiming interface, - // throw a SyntaxError. - not implemented - const entry = new PerformanceMark(markName, markOptions); - // 3.1.1.7 Queue entry - not implemented - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - measure( - measureName, - startOrMeasureOptions = {}, - endMark = undefined, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'measure' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - measureName = webidl.converters.DOMString(measureName, { - prefix, - context: "Argument 1", - }); - - startOrMeasureOptions = webidl.converters - ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { - prefix, - context: "Argument 2", - }); - - if (endMark !== undefined) { - endMark = webidl.converters.DOMString(endMark, { - prefix, - context: "Argument 3", - }); - } - - if ( - startOrMeasureOptions && typeof startOrMeasureOptions === "object" && - ObjectKeys(startOrMeasureOptions).length > 0 - ) { - if (endMark) { - throw new TypeError("Options cannot be passed with endMark."); - } - if ( - !ReflectHas(startOrMeasureOptions, "start") && - !ReflectHas(startOrMeasureOptions, "end") - ) { - throw new TypeError( - "A start or end mark must be supplied in options.", - ); - } - if ( - ReflectHas(startOrMeasureOptions, "start") && - ReflectHas(startOrMeasureOptions, "duration") && - ReflectHas(startOrMeasureOptions, "end") - ) { - throw new TypeError( - "Cannot specify start, end, and duration together in options.", - ); - } - } - let endTime; - if (endMark) { - endTime = convertMarkToTimestamp(endMark); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "end") - ) { - endTime = convertMarkToTimestamp(startOrMeasureOptions.end); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "start") && - ReflectHas(startOrMeasureOptions, "duration") - ) { - const start = convertMarkToTimestamp(startOrMeasureOptions.start); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - endTime = start + duration; - } else { - endTime = now(); - } - let startTime; - if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "start") - ) { - startTime = convertMarkToTimestamp(startOrMeasureOptions.start); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "end") && - ReflectHas(startOrMeasureOptions, "duration") - ) { - const end = convertMarkToTimestamp(startOrMeasureOptions.end); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - startTime = end - duration; - } else if (typeof startOrMeasureOptions === "string") { - startTime = convertMarkToTimestamp(startOrMeasureOptions); - } else { - startTime = 0; - } - const entry = new PerformanceMeasure( - measureName, - startTime, - endTime - startTime, - typeof startOrMeasureOptions === "object" - ? startOrMeasureOptions.detail ?? null - : null, - illegalConstructorKey, - ); - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - now() { - webidl.assertBranded(this, PerformancePrototype); - return now(); - } - - toJSON() { - webidl.assertBranded(this, PerformancePrototype); - return { - timeOrigin: this.timeOrigin, - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this), - keys: [], - })); - } - } - webidl.configurePrototype(Performance); - const PerformancePrototype = Performance.prototype; - - webidl.converters["Performance"] = webidl.createInterfaceConverter( - "Performance", - PerformancePrototype, +webidl.converters["PerformanceMarkOptions"] = webidl + .createDictionaryConverter( + "PerformanceMarkOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "startTime", + converter: webidl.converters.DOMHighResTimeStamp, + }, + ], ); - window.__bootstrap.performance = { - PerformanceEntry, - PerformanceMark, - PerformanceMeasure, - Performance, - performance: new Performance(illegalConstructorKey), - setTimeOrigin, - }; -})(this); +webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { + if (webidl.type(V) === "Number" && V !== null) { + return webidl.converters.DOMHighResTimeStamp(V, opts); + } + return webidl.converters.DOMString(V, opts); +}; + +webidl.converters["PerformanceMeasureOptions"] = webidl + .createDictionaryConverter( + "PerformanceMeasureOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "start", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + { + key: "duration", + converter: webidl.converters.DOMHighResTimeStamp, + }, + { + key: "end", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + ], + ); + +webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { + if (webidl.type(V) === "Object" && V !== null) { + return webidl.converters["PerformanceMeasureOptions"](V, opts); + } + return webidl.converters.DOMString(V, opts); +}; + +function setTimeOrigin(origin) { + timeOrigin = origin; +} + +function findMostRecent( + name, + type, +) { + return ArrayPrototypeFind( + ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), + (entry) => entry.name === name && entry.entryType === type, + ); +} + +function convertMarkToTimestamp(mark) { + if (typeof mark === "string") { + const entry = findMostRecent(mark, "mark"); + if (!entry) { + throw new DOMException( + `Cannot find mark: "${mark}".`, + "SyntaxError", + ); + } + return entry.startTime; + } + if (mark < 0) { + throw new TypeError("Mark cannot be negative."); + } + return mark; +} + +function filterByNameType( + name, + type, +) { + return ArrayPrototypeFilter( + performanceEntries, + (entry) => + (name ? entry.name === name : true) && + (type ? entry.entryType === type : true), + ); +} + +const now = opNow; + +const _name = Symbol("[[name]]"); +const _entryType = Symbol("[[entryType]]"); +const _startTime = Symbol("[[startTime]]"); +const _duration = Symbol("[[duration]]"); +class PerformanceEntry { + [_name] = ""; + [_entryType] = ""; + [_startTime] = 0; + [_duration] = 0; + + get name() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_name]; + } + + get entryType() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_entryType]; + } + + get startTime() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_startTime]; + } + + get duration() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_duration]; + } + + constructor( + name = null, + entryType = null, + startTime = null, + duration = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); + } + this[webidl.brand] = webidl.brand; + + this[_name] = name; + this[_entryType] = entryType; + this[_startTime] = startTime; + this[_duration] = duration; + } + + toJSON() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return { + name: this[_name], + entryType: this[_entryType], + startTime: this[_startTime], + duration: this[_duration], + }; + } + + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PerformanceEntryPrototype, + this, + ), + keys: [ + "name", + "entryType", + "startTime", + "duration", + ], + })); + } +} +webidl.configurePrototype(PerformanceEntry); +const PerformanceEntryPrototype = PerformanceEntry.prototype; + +const _detail = Symbol("[[detail]]"); +class PerformanceMark extends PerformanceEntry { + [_detail] = null; + + get detail() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return this[_detail]; + } + + get entryType() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return "mark"; + } + + constructor( + name, + options = {}, + ) { + const prefix = "Failed to construct 'PerformanceMark'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); + + options = webidl.converters.PerformanceMarkOptions(options, { + prefix, + context: "Argument 2", + }); + + const { detail = null, startTime = now() } = options; + + super(name, "mark", startTime, 0, illegalConstructorKey); + this[webidl.brand] = webidl.brand; + if (startTime < 0) { + throw new TypeError("startTime cannot be negative"); + } + this[_detail] = structuredClone(detail); + } + + toJSON() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } + + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this), + keys: [ + "name", + "entryType", + "startTime", + "duration", + "detail", + ], + })); + } +} +webidl.configurePrototype(PerformanceMark); +const PerformanceMarkPrototype = PerformanceMark.prototype; +class PerformanceMeasure extends PerformanceEntry { + [_detail] = null; + + get detail() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return this[_detail]; + } + + get entryType() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return "measure"; + } + + constructor( + name = null, + startTime = null, + duration = null, + detail = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); + } + + super(name, "measure", startTime, duration, key); + this[webidl.brand] = webidl.brand; + this[_detail] = structuredClone(detail); + } + + toJSON() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } + + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PerformanceMeasurePrototype, + this, + ), + keys: [ + "name", + "entryType", + "startTime", + "duration", + "detail", + ], + })); + } +} +webidl.configurePrototype(PerformanceMeasure); +const PerformanceMeasurePrototype = PerformanceMeasure.prototype; +class Performance extends EventTarget { + constructor(key = null) { + if (key != illegalConstructorKey) { + webidl.illegalConstructor(); + } + + super(); + this[webidl.brand] = webidl.brand; + } + + get timeOrigin() { + webidl.assertBranded(this, PerformancePrototype); + return timeOrigin; + } + + clearMarks(markName = undefined) { + webidl.assertBranded(this, PerformancePrototype); + if (markName !== undefined) { + markName = webidl.converters.DOMString(markName, { + prefix: "Failed to execute 'clearMarks' on 'Performance'", + context: "Argument 1", + }); + + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => !(entry.name === markName && entry.entryType === "mark"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "mark", + ); + } + } + + clearMeasures(measureName = undefined) { + webidl.assertBranded(this, PerformancePrototype); + if (measureName !== undefined) { + measureName = webidl.converters.DOMString(measureName, { + prefix: "Failed to execute 'clearMeasures' on 'Performance'", + context: "Argument 1", + }); + + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => + !(entry.name === measureName && entry.entryType === "measure"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "measure", + ); + } + } + + getEntries() { + webidl.assertBranded(this, PerformancePrototype); + return filterByNameType(); + } + + getEntriesByName( + name, + type = undefined, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); + + if (type !== undefined) { + type = webidl.converters.DOMString(type, { + prefix, + context: "Argument 2", + }); + } + + return filterByNameType(name, type); + } + + getEntriesByType(type) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + type = webidl.converters.DOMString(type, { + prefix, + context: "Argument 1", + }); + + return filterByNameType(undefined, type); + } + + mark( + markName, + markOptions = {}, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'mark' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + markName = webidl.converters.DOMString(markName, { + prefix, + context: "Argument 1", + }); + + markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { + prefix, + context: "Argument 2", + }); + + // 3.1.1.1 If the global object is a Window object and markName uses the + // same name as a read only attribute in the PerformanceTiming interface, + // throw a SyntaxError. - not implemented + const entry = new PerformanceMark(markName, markOptions); + // 3.1.1.7 Queue entry - not implemented + ArrayPrototypePush(performanceEntries, entry); + return entry; + } + + measure( + measureName, + startOrMeasureOptions = {}, + endMark = undefined, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'measure' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + measureName = webidl.converters.DOMString(measureName, { + prefix, + context: "Argument 1", + }); + + startOrMeasureOptions = webidl.converters + ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { + prefix, + context: "Argument 2", + }); + + if (endMark !== undefined) { + endMark = webidl.converters.DOMString(endMark, { + prefix, + context: "Argument 3", + }); + } + + if ( + startOrMeasureOptions && typeof startOrMeasureOptions === "object" && + ObjectKeys(startOrMeasureOptions).length > 0 + ) { + if (endMark) { + throw new TypeError("Options cannot be passed with endMark."); + } + if ( + !ReflectHas(startOrMeasureOptions, "start") && + !ReflectHas(startOrMeasureOptions, "end") + ) { + throw new TypeError( + "A start or end mark must be supplied in options.", + ); + } + if ( + ReflectHas(startOrMeasureOptions, "start") && + ReflectHas(startOrMeasureOptions, "duration") && + ReflectHas(startOrMeasureOptions, "end") + ) { + throw new TypeError( + "Cannot specify start, end, and duration together in options.", + ); + } + } + let endTime; + if (endMark) { + endTime = convertMarkToTimestamp(endMark); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "end") + ) { + endTime = convertMarkToTimestamp(startOrMeasureOptions.end); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "start") && + ReflectHas(startOrMeasureOptions, "duration") + ) { + const start = convertMarkToTimestamp(startOrMeasureOptions.start); + const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); + endTime = start + duration; + } else { + endTime = now(); + } + let startTime; + if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "start") + ) { + startTime = convertMarkToTimestamp(startOrMeasureOptions.start); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "end") && + ReflectHas(startOrMeasureOptions, "duration") + ) { + const end = convertMarkToTimestamp(startOrMeasureOptions.end); + const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); + startTime = end - duration; + } else if (typeof startOrMeasureOptions === "string") { + startTime = convertMarkToTimestamp(startOrMeasureOptions); + } else { + startTime = 0; + } + const entry = new PerformanceMeasure( + measureName, + startTime, + endTime - startTime, + typeof startOrMeasureOptions === "object" + ? startOrMeasureOptions.detail ?? null + : null, + illegalConstructorKey, + ); + ArrayPrototypePush(performanceEntries, entry); + return entry; + } + + now() { + webidl.assertBranded(this, PerformancePrototype); + return now(); + } + + toJSON() { + webidl.assertBranded(this, PerformancePrototype); + return { + timeOrigin: this.timeOrigin, + }; + } + + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this), + keys: [], + })); + } +} +webidl.configurePrototype(Performance); +const PerformancePrototype = Performance.prototype; + +webidl.converters["Performance"] = webidl.createInterfaceConverter( + "Performance", + PerformancePrototype, +); + +const performance = new Performance(illegalConstructorKey); + +export { + Performance, + performance, + PerformanceEntry, + PerformanceMark, + PerformanceMeasure, + setTimeOrigin, +}; diff --git a/ext/web/benches/encoding.rs b/ext/web/benches/encoding.rs index 254ea4455a..f8ad57c4f6 100644 --- a/ext/web/benches/encoding.rs +++ b/ext/web/benches/encoding.rs @@ -29,11 +29,12 @@ fn setup() -> Vec { deno_console::init(), deno_web::init::(BlobStore::default(), None), Extension::builder("bench_setup") - .js(vec![( - "setup", + .esm(vec![( + "internal:setup", r#" - const { TextDecoder } = globalThis.__bootstrap.encoding; - const hello12k = Deno.core.encode("hello world\n".repeat(1e3)); + import { TextDecoder } from "internal:ext/web/08_text_encoding.js"; + globalThis.TextDecoder = TextDecoder; + globalThis.hello12k = Deno.core.encode("hello world\n".repeat(1e3)); "#, )]) .state(|state| { diff --git a/ext/web/benches/timers_ops.rs b/ext/web/benches/timers_ops.rs index b28b1ae1d7..a2af22982b 100644 --- a/ext/web/benches/timers_ops.rs +++ b/ext/web/benches/timers_ops.rs @@ -28,9 +28,10 @@ fn setup() -> Vec { deno_console::init(), deno_web::init::(BlobStore::default(), None), Extension::builder("bench_setup") - .js(vec![ - ("setup", r#" - const { setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; + .esm(vec![ + ("internal:setup", r#" + import { setTimeout, handleTimerMacrotask } from "internal:ext/web/02_timers.js"; + globalThis.setTimeout = setTimeout; Deno.core.setMacrotaskCallback(handleTimerMacrotask); "#), ]) diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index 9bb89d98e0..fe0c8ac077 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -1,120 +1,111 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-var - /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var infra: { - collectSequenceOfCodepoints( - input: string, - position: number, - condition: (char: string) => boolean, - ): { - result: string; - position: number; - }; - ASCII_DIGIT: string[]; - ASCII_UPPER_ALPHA: string[]; - ASCII_LOWER_ALPHA: string[]; - ASCII_ALPHA: string[]; - ASCII_ALPHANUMERIC: string[]; - HTTP_TAB_OR_SPACE: string[]; - HTTP_WHITESPACE: string[]; - HTTP_TOKEN_CODE_POINT: string[]; - HTTP_TOKEN_CODE_POINT_RE: RegExp; - HTTP_QUOTED_STRING_TOKEN_POINT: string[]; - HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; - HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; - HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; - HTTP_WHITESPACE_PREFIX_RE: RegExp; - HTTP_WHITESPACE_SUFFIX_RE: RegExp; - httpTrim(s: string): string; - regexMatcher(chars: string[]): string; - byteUpperCase(s: string): string; - byteLowerCase(s: string): string; - collectHttpQuotedString( - input: string, - position: number, - extractValue: boolean, - ): { - result: string; - position: number; - }; - forgivingBase64Encode(data: Uint8Array): string; - forgivingBase64Decode(data: string): Uint8Array; - serializeJSValueToJSONString(value: unknown): string; - }; +declare module "internal:ext/web/00_infra.js" { + function collectSequenceOfCodepoints( + input: string, + position: number, + condition: (char: string) => boolean, + ): { + result: string; + position: number; + }; + const ASCII_DIGIT: string[]; + const ASCII_UPPER_ALPHA: string[]; + const ASCII_LOWER_ALPHA: string[]; + const ASCII_ALPHA: string[]; + const ASCII_ALPHANUMERIC: string[]; + const HTTP_TAB_OR_SPACE: string[]; + const HTTP_WHITESPACE: string[]; + const HTTP_TOKEN_CODE_POINT: string[]; + const HTTP_TOKEN_CODE_POINT_RE: RegExp; + const HTTP_QUOTED_STRING_TOKEN_POINT: string[]; + const HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; + const HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; + const HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; + const HTTP_WHITESPACE_PREFIX_RE: RegExp; + const HTTP_WHITESPACE_SUFFIX_RE: RegExp; + function httpTrim(s: string): string; + function regexMatcher(chars: string[]): string; + function byteUpperCase(s: string): string; + function byteLowerCase(s: string): string; + function collectHttpQuotedString( + input: string, + position: number, + extractValue: boolean, + ): { + result: string; + position: number; + }; + function forgivingBase64Encode(data: Uint8Array): string; + function forgivingBase64Decode(data: string): Uint8Array; + function serializeJSValueToJSONString(value: unknown): string; +} - declare var domException: { - DOMException: typeof DOMException; - }; +declare module "internal:ext/web/01_dom_exception.js" { + export = DOMException; +} - declare namespace mimesniff { - declare interface MimeType { - type: string; - subtype: string; - parameters: Map; - } - declare function parseMimeType(input: string): MimeType | null; - declare function essence(mimeType: MimeType): string; - declare function serializeMimeType(mimeType: MimeType): string; - declare function extractMimeType( - headerValues: string[] | null, - ): MimeType | null; - } +declare module "internal:ext/web/01_mimesniff.js" { + interface MimeType { + type: string; + subtype: string; + parameters: Map; + } + function parseMimeType(input: string): MimeType | null; + function essence(mimeType: MimeType): string; + function serializeMimeType(mimeType: MimeType): string; + function extractMimeType( + headerValues: string[] | null, + ): MimeType | null; +} - declare var eventTarget: { - EventTarget: typeof EventTarget; - }; +declare module "internal:ext/web/02_event.js" { + const EventTarget: typeof EventTarget; + const Event: typeof event; + const ErrorEvent: typeof ErrorEvent; + const CloseEvent: typeof CloseEvent; + const MessageEvent: typeof MessageEvent; + const CustomEvent: typeof CustomEvent; + const ProgressEvent: typeof ProgressEvent; + const PromiseRejectionEvent: typeof PromiseRejectionEvent; + const reportError: typeof reportError; +} - declare var event: { - Event: typeof event; - ErrorEvent: typeof ErrorEvent; - CloseEvent: typeof CloseEvent; - MessageEvent: typeof MessageEvent; - CustomEvent: typeof CustomEvent; - ProgressEvent: typeof ProgressEvent; - PromiseRejectionEvent: typeof PromiseRejectionEvent; - reportError: typeof reportError; - }; +declare module "internal:ext/web/12_location.js" { + function getLocationHref(): string | undefined; +} - declare var location: { - getLocationHref(): string | undefined; - }; +declare module "internal:ext/web/05_base64.js" { + function atob(data: string): string; + function btoa(data: string): string; +} - declare var base64: { - atob(data: string): string; - btoa(data: string): string; - }; +declare module "internal:ext/web/09_file.js" { + function blobFromObjectUrl(url: string): Blob | null; + function getParts(blob: Blob): string[]; + const Blob: typeof Blob; + const File: typeof File; +} - declare var file: { - blobFromObjectUrl(url: string): Blob | null; - getParts(blob: Blob): string[]; - Blob: typeof Blob; - File: typeof File; - }; +declare module "internal:ext/web/06_streams.js" { + const ReadableStream: typeof ReadableStream; + function isReadableStreamDisturbed(stream: ReadableStream): boolean; + function createProxy(stream: ReadableStream): ReadableStream; +} - declare var streams: { - ReadableStream: typeof ReadableStream; - isReadableStreamDisturbed(stream: ReadableStream): boolean; - createProxy(stream: ReadableStream): ReadableStream; - }; - - declare namespace messagePort { - declare type Transferable = { - kind: "messagePort"; - data: number; - } | { - kind: "arrayBuffer"; - data: number; - }; - declare interface MessageData { - data: Uint8Array; - transferables: Transferable[]; - } - } +declare module "internal:ext/web/13_message_port.js" { + type Transferable = { + kind: "messagePort"; + data: number; + } | { + kind: "arrayBuffer"; + data: number; + }; + interface MessageData { + data: Uint8Array; + transferables: Transferable[]; } } diff --git a/ext/web/lib.rs b/ext/web/lib.rs index c677bb8e91..4fcc06ef4f 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -64,7 +64,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_console", "deno_url"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/web", "00_infra.js", "01_dom_exception.js", diff --git a/ext/webgpu/src/01_webgpu.js b/ext/webgpu/src/01_webgpu.js index 23e8b24f08..58b7130997 100644 --- a/ext/webgpu/src/01_webgpu.js +++ b/ext/webgpu/src/01_webgpu.js @@ -6,1914 +6,1979 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { EventTarget } from "internal:ext/web/02_event.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +const { + ArrayBuffer, + ArrayBufferIsView, + ArrayIsArray, + ArrayPrototypeFilter, + ArrayPrototypeMap, + ArrayPrototypePop, + ArrayPrototypePush, + Error, + MathMax, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + Promise, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + SafeArrayIterator, + SafePromiseAll, + Set, + SetPrototypeHas, + Symbol, + SymbolFor, + TypeError, + Uint32Array, + Uint32ArrayPrototype, + Uint8Array, + WeakRef, +} = primordials; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const eventTarget = window.__bootstrap.eventTarget; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBuffer, - ArrayBufferIsView, - ArrayIsArray, - ArrayPrototypeFilter, - ArrayPrototypeMap, - ArrayPrototypePop, - ArrayPrototypePush, - Error, - MathMax, - ObjectDefineProperty, - ObjectPrototypeIsPrototypeOf, - Promise, - PromisePrototypeCatch, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - SafeArrayIterator, - SafePromiseAll, - Set, - SetPrototypeHas, - Symbol, - SymbolFor, - TypeError, - Uint32Array, - Uint32ArrayPrototype, - Uint8Array, - WeakRef, - } = window.__bootstrap.primordials; +const _rid = Symbol("[[rid]]"); +const _size = Symbol("[[size]]"); +const _usage = Symbol("[[usage]]"); +const _state = Symbol("[[state]]"); +const _mappingRange = Symbol("[[mapping_range]]"); +const _mappedRanges = Symbol("[[mapped_ranges]]"); +const _mapMode = Symbol("[[map_mode]]"); +const _adapter = Symbol("[[adapter]]"); +const _cleanup = Symbol("[[cleanup]]"); +const _vendor = Symbol("[[vendor]]"); +const _architecture = Symbol("[[architecture]]"); +const _description = Symbol("[[description]]"); +const _limits = Symbol("[[limits]]"); +const _reason = Symbol("[[reason]]"); +const _message = Symbol("[[message]]"); +const _label = Symbol("[[label]]"); +const _device = Symbol("[[device]]"); +const _queue = Symbol("[[queue]]"); +const _views = Symbol("[[views]]"); +const _texture = Symbol("[[texture]]"); +const _encoders = Symbol("[[encoders]]"); +const _encoder = Symbol("[[encoder]]"); +const _descriptor = Symbol("[[descriptor]]"); +const _width = Symbol("[[width]]"); +const _height = Symbol("[[height]]"); +const _depthOrArrayLayers = Symbol("[[depthOrArrayLayers]]"); +const _mipLevelCount = Symbol("[[mipLevelCount]]"); +const _sampleCount = Symbol("[[sampleCount]]"); +const _dimension = Symbol("[[dimension]]"); +const _format = Symbol("[[format]]"); +const _type = Symbol("[[type]]"); +const _count = Symbol("[[count]]"); - const _rid = Symbol("[[rid]]"); - const _size = Symbol("[[size]]"); - const _usage = Symbol("[[usage]]"); - const _state = Symbol("[[state]]"); - const _mappingRange = Symbol("[[mapping_range]]"); - const _mappedRanges = Symbol("[[mapped_ranges]]"); - const _mapMode = Symbol("[[map_mode]]"); - const _adapter = Symbol("[[adapter]]"); - const _cleanup = Symbol("[[cleanup]]"); - const _vendor = Symbol("[[vendor]]"); - const _architecture = Symbol("[[architecture]]"); - const _description = Symbol("[[description]]"); - const _limits = Symbol("[[limits]]"); - const _reason = Symbol("[[reason]]"); - const _message = Symbol("[[message]]"); - const _label = Symbol("[[label]]"); - const _device = Symbol("[[device]]"); - const _queue = Symbol("[[queue]]"); - const _views = Symbol("[[views]]"); - const _texture = Symbol("[[texture]]"); - const _encoders = Symbol("[[encoders]]"); - const _encoder = Symbol("[[encoder]]"); - const _descriptor = Symbol("[[descriptor]]"); - const _width = Symbol("[[width]]"); - const _height = Symbol("[[height]]"); - const _depthOrArrayLayers = Symbol("[[depthOrArrayLayers]]"); - const _mipLevelCount = Symbol("[[mipLevelCount]]"); - const _sampleCount = Symbol("[[sampleCount]]"); - const _dimension = Symbol("[[dimension]]"); - const _format = Symbol("[[format]]"); - const _type = Symbol("[[type]]"); - const _count = Symbol("[[count]]"); - - /** - * @param {any} self - * @param {{prefix: string, context: string}} opts - * @returns {InnerGPUDevice & {rid: number}} - */ - function assertDevice(self, { prefix, context }) { - const device = self[_device]; - const deviceRid = device?.rid; - if (deviceRid === undefined) { - throw new DOMException( - `${prefix}: ${context} references an invalid or destroyed device.`, - "OperationError", - ); - } - return device; +/** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ +function assertDevice(self, { prefix, context }) { + const device = self[_device]; + const deviceRid = device?.rid; + if (deviceRid === undefined) { + throw new DOMException( + `${prefix}: ${context} references an invalid or destroyed device.`, + "OperationError", + ); } + return device; +} - /** - * @param {InnerGPUDevice} self - * @param {any} resource - * @param {{prefix: string, resourceContext: string, selfContext: string}} opts - * @returns {InnerGPUDevice & {rid: number}} - */ - function assertDeviceMatch( - self, - resource, - { prefix, resourceContext, selfContext }, - ) { - const resourceDevice = assertDevice(resource, { - prefix, - context: resourceContext, - }); - if (resourceDevice.rid !== self.rid) { - throw new DOMException( - `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, - "OperationError", - ); - } - return { ...resourceDevice, rid: resourceDevice.rid }; +/** + * @param {InnerGPUDevice} self + * @param {any} resource + * @param {{prefix: string, resourceContext: string, selfContext: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ +function assertDeviceMatch( + self, + resource, + { prefix, resourceContext, selfContext }, +) { + const resourceDevice = assertDevice(resource, { + prefix, + context: resourceContext, + }); + if (resourceDevice.rid !== self.rid) { + throw new DOMException( + `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, + "OperationError", + ); } + return { ...resourceDevice, rid: resourceDevice.rid }; +} - /** - * @param {any} self - * @param {{prefix: string, context: string}} opts - * @returns {number} - */ - function assertResource(self, { prefix, context }) { - const rid = self[_rid]; - if (rid === undefined) { - throw new DOMException( - `${prefix}: ${context} an invalid or destroyed resource.`, - "OperationError", - ); - } - return rid; +/** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {number} + */ +function assertResource(self, { prefix, context }) { + const rid = self[_rid]; + if (rid === undefined) { + throw new DOMException( + `${prefix}: ${context} an invalid or destroyed resource.`, + "OperationError", + ); } + return rid; +} - /** - * @param {number[] | GPUExtent3DDict} data - * @returns {GPUExtent3DDict} - */ - function normalizeGPUExtent3D(data) { - if (ArrayIsArray(data)) { - return { - width: data[0], - height: data[1], - depthOrArrayLayers: data[2], - }; - } else { - return data; - } - } - - /** - * @param {number[] | GPUOrigin3DDict} data - * @returns {GPUOrigin3DDict} - */ - function normalizeGPUOrigin3D(data) { - if (ArrayIsArray(data)) { - return { - x: data[0], - y: data[1], - z: data[2], - }; - } else { - return data; - } - } - - /** - * @param {number[] | GPUColor} data - * @returns {GPUColor} - */ - function normalizeGPUColor(data) { - if (ArrayIsArray(data)) { - return { - r: data[0], - g: data[1], - b: data[2], - a: data[3], - }; - } else { - return data; - } - } - - const illegalConstructorKey = Symbol("illegalConstructorKey"); - class GPUError extends Error { - constructor(key = null) { - super(); - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - } - - [_message]; - get message() { - webidl.assertBranded(this, GPUErrorPrototype); - return this[_message]; - } - } - const GPUErrorPrototype = GPUError.prototype; - - class GPUValidationError extends GPUError { - name = "GPUValidationError"; - /** @param {string} message */ - constructor(message) { - const prefix = "Failed to construct 'GPUValidationError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); - super(illegalConstructorKey); - this[webidl.brand] = webidl.brand; - this[_message] = message; - } - } - const GPUValidationErrorPrototype = GPUValidationError.prototype; - - class GPUOutOfMemoryError extends GPUError { - name = "GPUOutOfMemoryError"; - constructor(message) { - const prefix = "Failed to construct 'GPUOutOfMemoryError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); - super(illegalConstructorKey); - this[webidl.brand] = webidl.brand; - this[_message] = message; - } - } - const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; - - class GPU { - [webidl.brand] = webidl.brand; - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPURequestAdapterOptions} options - */ - async requestAdapter(options = {}) { - webidl.assertBranded(this, GPUPrototype); - options = webidl.converters.GPURequestAdapterOptions(options, { - prefix: "Failed to execute 'requestAdapter' on 'GPU'", - context: "Argument 1", - }); - - const { err, ...data } = await core.opAsync( - "op_webgpu_request_adapter", - options.powerPreference, - options.forceFallbackAdapter, - ); - - if (err) { - return null; - } else { - return createGPUAdapter(data); - } - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } - } - const GPUPrototype = GPU.prototype; - - /** - * @typedef InnerGPUAdapter - * @property {number} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits - * @property {boolean} isFallbackAdapter - */ - - /** - * @param {InnerGPUAdapter} inner - * @returns {GPUAdapter} - */ - function createGPUAdapter(inner) { - /** @type {GPUAdapter} */ - const adapter = webidl.createBranded(GPUAdapter); - adapter[_adapter] = { - ...inner, - features: createGPUSupportedFeatures(inner.features), - limits: createGPUSupportedLimits(inner.limits), +/** + * @param {number[] | GPUExtent3DDict} data + * @returns {GPUExtent3DDict} + */ +function normalizeGPUExtent3D(data) { + if (ArrayIsArray(data)) { + return { + width: data[0], + height: data[1], + depthOrArrayLayers: data[2], }; - return adapter; + } else { + return data; } +} - class GPUAdapter { - /** @type {InnerGPUAdapter} */ - [_adapter]; +/** + * @param {number[] | GPUOrigin3DDict} data + * @returns {GPUOrigin3DDict} + */ +function normalizeGPUOrigin3D(data) { + if (ArrayIsArray(data)) { + return { + x: data[0], + y: data[1], + z: data[2], + }; + } else { + return data; + } +} - /** @returns {GPUSupportedFeatures} */ - get features() { - webidl.assertBranded(this, GPUAdapterPrototype); - return this[_adapter].features; - } - /** @returns {GPUSupportedLimits} */ - get limits() { - webidl.assertBranded(this, GPUAdapterPrototype); - return this[_adapter].limits; - } - /** @returns {boolean} */ - get isFallbackAdapter() { - return this[_adapter].isFallbackAdapter; - } +/** + * @param {number[] | GPUColor} data + * @returns {GPUColor} + */ +function normalizeGPUColor(data) { + if (ArrayIsArray(data)) { + return { + r: data[0], + g: data[1], + b: data[2], + a: data[3], + }; + } else { + return data; + } +} - constructor() { +const illegalConstructorKey = Symbol("illegalConstructorKey"); +class GPUError extends Error { + constructor(key = null) { + super(); + if (key !== illegalConstructorKey) { webidl.illegalConstructor(); } - - /** - * @param {GPUDeviceDescriptor} descriptor - * @returns {Promise} - */ - async requestDevice(descriptor = {}) { - webidl.assertBranded(this, GPUAdapterPrototype); - const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; - descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const requiredFeatures = descriptor.requiredFeatures ?? []; - for (let i = 0; i < requiredFeatures.length; ++i) { - const feature = requiredFeatures[i]; - if ( - !SetPrototypeHas( - this[_adapter].features[webidl.setlikeInner], - feature, - ) - ) { - throw new TypeError( - `${prefix}: requiredFeatures must be a subset of the adapter features.`, - ); - } - } - - const { rid, features, limits } = await core.opAsync( - "op_webgpu_request_device", - this[_adapter].rid, - descriptor.label, - requiredFeatures, - descriptor.requiredLimits, - ); - - const inner = new InnerGPUDevice({ - rid, - adapter: this, - features: createGPUSupportedFeatures(features), - limits: createGPUSupportedLimits(limits), - }); - return createGPUDevice( - descriptor.label, - inner, - createGPUQueue(descriptor.label, inner), - ); - } - - /** - * @param {string[]} unmaskHints - * @returns {Promise} - */ - async requestAdapterInfo(unmaskHints = []) { - webidl.assertBranded(this, GPUAdapterPrototype); - const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; - unmaskHints = webidl.converters["sequence"](unmaskHints, { - prefix, - context: "Argument 1", - }); - - const { - vendor, - architecture, - device, - description, - } = await core.opAsync( - "op_webgpu_request_adapter_info", - this[_adapter].rid, - ); - - const adapterInfo = webidl.createBranded(GPUAdapterInfo); - adapterInfo[_vendor] = unmaskHints.includes("vendor") ? vendor : ""; - adapterInfo[_architecture] = unmaskHints.includes("architecture") - ? architecture - : ""; - adapterInfo[_device] = unmaskHints.includes("device") ? device : ""; - adapterInfo[_description] = unmaskHints.includes("description") - ? description - : ""; - return adapterInfo; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - limits: this.limits, - }) - }`; - } } - const GPUAdapterPrototype = GPUAdapter.prototype; - class GPUAdapterInfo { - /** @type {string} */ - [_vendor]; - /** @returns {string} */ - get vendor() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_vendor]; - } - - /** @type {string} */ - [_architecture]; - /** @returns {string} */ - get architecture() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_architecture]; - } - - /** @type {string} */ - [_device]; - /** @returns {string} */ - get device() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_device]; - } - - /** @type {string} */ - [_description]; - /** @returns {string} */ - get description() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_description]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - vendor: this.vendor, - architecture: this.architecture, - device: this.device, - description: this.description, - }) - }`; - } + [_message]; + get message() { + webidl.assertBranded(this, GPUErrorPrototype); + return this[_message]; } - const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; +} +const GPUErrorPrototype = GPUError.prototype; - function createGPUSupportedLimits(limits) { - /** @type {GPUSupportedLimits} */ - const adapterFeatures = webidl.createBranded(GPUSupportedLimits); - adapterFeatures[_limits] = limits; - return adapterFeatures; +class GPUValidationError extends GPUError { + name = "GPUValidationError"; + /** @param {string} message */ + constructor(message) { + const prefix = "Failed to construct 'GPUValidationError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; + } +} +const GPUValidationErrorPrototype = GPUValidationError.prototype; + +class GPUOutOfMemoryError extends GPUError { + name = "GPUOutOfMemoryError"; + constructor(message) { + const prefix = "Failed to construct 'GPUOutOfMemoryError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; + } +} +const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; + +class GPU { + [webidl.brand] = webidl.brand; + + constructor() { + webidl.illegalConstructor(); } /** - * @typedef InnerAdapterLimits - * @property {number} maxTextureDimension1D - * @property {number} maxTextureDimension2D - * @property {number} maxTextureDimension3D - * @property {number} maxTextureArrayLayers - * @property {number} maxBindGroups - * @property {number} maxDynamicUniformBuffersPerPipelineLayout - * @property {number} maxDynamicStorageBuffersPerPipelineLayout - * @property {number} maxSampledTexturesPerShaderStage - * @property {number} maxSamplersPerShaderStage - * @property {number} maxStorageBuffersPerShaderStage - * @property {number} maxStorageTexturesPerShaderStage - * @property {number} maxUniformBuffersPerShaderStage - * @property {number} maxUniformBufferBindingSize - * @property {number} maxStorageBufferBindingSize - * @property {number} minUniformBufferOffsetAlignment - * @property {number} minStorageBufferOffsetAlignment - * @property {number} maxVertexBuffers - * @property {number} maxVertexAttributes - * @property {number} maxVertexBufferArrayStride - * @property {number} maxInterStageShaderComponents - * @property {number} maxComputeWorkgroupStorageSize - * @property {number} maxComputeInvocationsPerWorkgroup - * @property {number} maxComputeWorkgroupSizeX - * @property {number} maxComputeWorkgroupSizeY - * @property {number} maxComputeWorkgroupSizeZ - * @property {number} maxComputeWorkgroupsPerDimension + * @param {GPURequestAdapterOptions} options */ + async requestAdapter(options = {}) { + webidl.assertBranded(this, GPUPrototype); + options = webidl.converters.GPURequestAdapterOptions(options, { + prefix: "Failed to execute 'requestAdapter' on 'GPU'", + context: "Argument 1", + }); - class GPUSupportedLimits { - /** @type {InnerAdapterLimits} */ - [_limits]; - constructor() { - webidl.illegalConstructor(); - } + const { err, ...data } = await core.opAsync( + "op_webgpu_request_adapter", + options.powerPreference, + options.forceFallbackAdapter, + ); - get maxTextureDimension1D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension1D; - } - get maxTextureDimension2D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension2D; - } - get maxTextureDimension3D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension3D; - } - get maxTextureArrayLayers() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureArrayLayers; - } - get maxBindGroups() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindGroups; - } - get maxBindingsPerBindGroup() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindingsPerBindGroup; - } - get maxBufferSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBufferSize; - } - get maxDynamicUniformBuffersPerPipelineLayout() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; - } - get maxDynamicStorageBuffersPerPipelineLayout() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; - } - get maxSampledTexturesPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSampledTexturesPerShaderStage; - } - get maxSamplersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSamplersPerShaderStage; - } - get maxStorageBuffersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBuffersPerShaderStage; - } - get maxStorageTexturesPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageTexturesPerShaderStage; - } - get maxUniformBuffersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBuffersPerShaderStage; - } - get maxUniformBufferBindingSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBufferBindingSize; - } - get maxStorageBufferBindingSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBufferBindingSize; - } - get minUniformBufferOffsetAlignment() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minUniformBufferOffsetAlignment; - } - get minStorageBufferOffsetAlignment() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minStorageBufferOffsetAlignment; - } - get maxVertexBuffers() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBuffers; - } - get maxVertexAttributes() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexAttributes; - } - get maxVertexBufferArrayStride() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBufferArrayStride; - } - get maxInterStageShaderComponents() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxInterStageShaderComponents; - } - get maxComputeWorkgroupStorageSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupStorageSize; - } - get maxComputeInvocationsPerWorkgroup() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeInvocationsPerWorkgroup; - } - get maxComputeWorkgroupSizeX() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeX; - } - get maxComputeWorkgroupSizeY() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeY; - } - get maxComputeWorkgroupSizeZ() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeZ; - } - get maxComputeWorkgroupsPerDimension() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupsPerDimension; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect(this[_limits])}`; + if (err) { + return null; + } else { + return createGPUAdapter(data); } } - const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; - function createGPUSupportedFeatures(features) { - /** @type {GPUSupportedFeatures} */ - const supportedFeatures = webidl.createBranded(GPUSupportedFeatures); - supportedFeatures[webidl.setlikeInner] = new Set(features); - return webidl.setlike( - supportedFeatures, - GPUSupportedFeaturesPrototype, - true, + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} +const GPUPrototype = GPU.prototype; + +/** + * @typedef InnerGPUAdapter + * @property {number} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + * @property {boolean} isFallbackAdapter + */ + +/** + * @param {InnerGPUAdapter} inner + * @returns {GPUAdapter} + */ +function createGPUAdapter(inner) { + /** @type {GPUAdapter} */ + const adapter = webidl.createBranded(GPUAdapter); + adapter[_adapter] = { + ...inner, + features: createGPUSupportedFeatures(inner.features), + limits: createGPUSupportedLimits(inner.limits), + }; + return adapter; +} + +class GPUAdapter { + /** @type {InnerGPUAdapter} */ + [_adapter]; + + /** @returns {GPUSupportedFeatures} */ + get features() { + webidl.assertBranded(this, GPUAdapterPrototype); + return this[_adapter].features; + } + /** @returns {GPUSupportedLimits} */ + get limits() { + webidl.assertBranded(this, GPUAdapterPrototype); + return this[_adapter].limits; + } + /** @returns {boolean} */ + get isFallbackAdapter() { + return this[_adapter].isFallbackAdapter; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUDeviceDescriptor} descriptor + * @returns {Promise} + */ + async requestDevice(descriptor = {}) { + webidl.assertBranded(this, GPUAdapterPrototype); + const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; + descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const requiredFeatures = descriptor.requiredFeatures ?? []; + for (let i = 0; i < requiredFeatures.length; ++i) { + const feature = requiredFeatures[i]; + if ( + !SetPrototypeHas( + this[_adapter].features[webidl.setlikeInner], + feature, + ) + ) { + throw new TypeError( + `${prefix}: requiredFeatures must be a subset of the adapter features.`, + ); + } + } + + const { rid, features, limits } = await core.opAsync( + "op_webgpu_request_device", + this[_adapter].rid, + descriptor.label, + requiredFeatures, + descriptor.requiredLimits, + ); + + const inner = new InnerGPUDevice({ + rid, + adapter: this, + features: createGPUSupportedFeatures(features), + limits: createGPUSupportedLimits(limits), + }); + return createGPUDevice( + descriptor.label, + inner, + createGPUQueue(descriptor.label, inner), ); } - class GPUSupportedFeatures { - constructor() { - webidl.illegalConstructor(); - } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect([...new SafeArrayIterator(this.values())]) - }`; - } - } - - const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; - /** - * @param {string | undefined} reason - * @param {string} message - * @returns {GPUDeviceLostInfo} + * @param {string[]} unmaskHints + * @returns {Promise} */ - function createGPUDeviceLostInfo(reason, message) { - /** @type {GPUDeviceLostInfo} */ - const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); - deviceLostInfo[_reason] = reason; - deviceLostInfo[_message] = message; - return deviceLostInfo; - } - - class GPUDeviceLostInfo { - /** @type {string | undefined} */ - [_reason]; - /** @type {string} */ - [_message]; - - constructor() { - webidl.illegalConstructor(); - } - - get reason() { - webidl.assertBranded(this, GPUDeviceLostInfoPrototype); - return this[_reason]; - } - get message() { - webidl.assertBranded(this, GPUDeviceLostInfoPrototype); - return this[_message]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ reason: this[_reason], message: this[_message] }) - }`; - } - } - - const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; - - /** - * @param {string} name - * @param {any} type - */ - function GPUObjectBaseMixin(name, type) { - type.prototype[_label] = null; - ObjectDefineProperty(type.prototype, "label", { - /** - * @return {string | null} - */ - get() { - webidl.assertBranded(this, type.prototype); - return this[_label]; - }, - /** - * @param {string | null} label - */ - set(label) { - webidl.assertBranded(this, type.prototype); - label = webidl.converters["UVString?"](label, { - prefix: `Failed to set 'label' on '${name}'`, - context: "Argument 1", - }); - this[_label] = label; - }, + async requestAdapterInfo(unmaskHints = []) { + webidl.assertBranded(this, GPUAdapterPrototype); + const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; + unmaskHints = webidl.converters["sequence"](unmaskHints, { + prefix, + context: "Argument 1", }); + + const { + vendor, + architecture, + device, + description, + } = await core.opAsync( + "op_webgpu_request_adapter_info", + this[_adapter].rid, + ); + + const adapterInfo = webidl.createBranded(GPUAdapterInfo); + adapterInfo[_vendor] = unmaskHints.includes("vendor") ? vendor : ""; + adapterInfo[_architecture] = unmaskHints.includes("architecture") + ? architecture + : ""; + adapterInfo[_device] = unmaskHints.includes("device") ? device : ""; + adapterInfo[_description] = unmaskHints.includes("description") + ? description + : ""; + return adapterInfo; } - /** - * @typedef ErrorScope - * @property {string} filter - * @property {Promise[]} operations - */ + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + limits: this.limits, + }) + }`; + } +} +const GPUAdapterPrototype = GPUAdapter.prototype; - /** - * @typedef InnerGPUDeviceOptions - * @property {GPUAdapter} adapter - * @property {number | undefined} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits - */ +class GPUAdapterInfo { + /** @type {string} */ + [_vendor]; + /** @returns {string} */ + get vendor() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_vendor]; + } - class InnerGPUDevice { - /** @type {GPUAdapter} */ - adapter; - /** @type {number | undefined} */ - rid; - /** @type {GPUSupportedFeatures} */ - features; - /** @type {GPUSupportedLimits} */ - limits; - /** @type {WeakRef[]} */ - resources; - /** @type {boolean} */ - isLost; - /** @type {Promise} */ - lost; - /** @type {(info: GPUDeviceLostInfo) => void} */ - resolveLost; - /** @type {ErrorScope[]} */ - errorScopeStack; + /** @type {string} */ + [_architecture]; + /** @returns {string} */ + get architecture() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_architecture]; + } + /** @type {string} */ + [_device]; + /** @returns {string} */ + get device() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_device]; + } + + /** @type {string} */ + [_description]; + /** @returns {string} */ + get description() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_description]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + vendor: this.vendor, + architecture: this.architecture, + device: this.device, + description: this.description, + }) + }`; + } +} +const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; + +function createGPUSupportedLimits(limits) { + /** @type {GPUSupportedLimits} */ + const adapterFeatures = webidl.createBranded(GPUSupportedLimits); + adapterFeatures[_limits] = limits; + return adapterFeatures; +} + +/** + * @typedef InnerAdapterLimits + * @property {number} maxTextureDimension1D + * @property {number} maxTextureDimension2D + * @property {number} maxTextureDimension3D + * @property {number} maxTextureArrayLayers + * @property {number} maxBindGroups + * @property {number} maxDynamicUniformBuffersPerPipelineLayout + * @property {number} maxDynamicStorageBuffersPerPipelineLayout + * @property {number} maxSampledTexturesPerShaderStage + * @property {number} maxSamplersPerShaderStage + * @property {number} maxStorageBuffersPerShaderStage + * @property {number} maxStorageTexturesPerShaderStage + * @property {number} maxUniformBuffersPerShaderStage + * @property {number} maxUniformBufferBindingSize + * @property {number} maxStorageBufferBindingSize + * @property {number} minUniformBufferOffsetAlignment + * @property {number} minStorageBufferOffsetAlignment + * @property {number} maxVertexBuffers + * @property {number} maxVertexAttributes + * @property {number} maxVertexBufferArrayStride + * @property {number} maxInterStageShaderComponents + * @property {number} maxComputeWorkgroupStorageSize + * @property {number} maxComputeInvocationsPerWorkgroup + * @property {number} maxComputeWorkgroupSizeX + * @property {number} maxComputeWorkgroupSizeY + * @property {number} maxComputeWorkgroupSizeZ + * @property {number} maxComputeWorkgroupsPerDimension + */ + +class GPUSupportedLimits { + /** @type {InnerAdapterLimits} */ + [_limits]; + constructor() { + webidl.illegalConstructor(); + } + + get maxTextureDimension1D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension1D; + } + get maxTextureDimension2D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension2D; + } + get maxTextureDimension3D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension3D; + } + get maxTextureArrayLayers() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureArrayLayers; + } + get maxBindGroups() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBindGroups; + } + get maxBindingsPerBindGroup() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBindingsPerBindGroup; + } + get maxBufferSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBufferSize; + } + get maxDynamicUniformBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + } + get maxDynamicStorageBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + } + get maxSampledTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxSampledTexturesPerShaderStage; + } + get maxSamplersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxSamplersPerShaderStage; + } + get maxStorageBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageBuffersPerShaderStage; + } + get maxStorageTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageTexturesPerShaderStage; + } + get maxUniformBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxUniformBuffersPerShaderStage; + } + get maxUniformBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxUniformBufferBindingSize; + } + get maxStorageBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageBufferBindingSize; + } + get minUniformBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].minUniformBufferOffsetAlignment; + } + get minStorageBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].minStorageBufferOffsetAlignment; + } + get maxVertexBuffers() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexBuffers; + } + get maxVertexAttributes() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexAttributes; + } + get maxVertexBufferArrayStride() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexBufferArrayStride; + } + get maxInterStageShaderComponents() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxInterStageShaderComponents; + } + get maxComputeWorkgroupStorageSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupStorageSize; + } + get maxComputeInvocationsPerWorkgroup() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeInvocationsPerWorkgroup; + } + get maxComputeWorkgroupSizeX() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeX; + } + get maxComputeWorkgroupSizeY() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeY; + } + get maxComputeWorkgroupSizeZ() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeZ; + } + get maxComputeWorkgroupsPerDimension() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupsPerDimension; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect(this[_limits])}`; + } +} +const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; + +function createGPUSupportedFeatures(features) { + /** @type {GPUSupportedFeatures} */ + const supportedFeatures = webidl.createBranded(GPUSupportedFeatures); + supportedFeatures[webidl.setlikeInner] = new Set(features); + return webidl.setlike( + supportedFeatures, + GPUSupportedFeaturesPrototype, + true, + ); +} + +class GPUSupportedFeatures { + constructor() { + webidl.illegalConstructor(); + } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect([...new SafeArrayIterator(this.values())]) + }`; + } +} + +const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; + +/** + * @param {string | undefined} reason + * @param {string} message + * @returns {GPUDeviceLostInfo} + */ +function createGPUDeviceLostInfo(reason, message) { + /** @type {GPUDeviceLostInfo} */ + const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); + deviceLostInfo[_reason] = reason; + deviceLostInfo[_message] = message; + return deviceLostInfo; +} + +class GPUDeviceLostInfo { + /** @type {string | undefined} */ + [_reason]; + /** @type {string} */ + [_message]; + + constructor() { + webidl.illegalConstructor(); + } + + get reason() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_reason]; + } + get message() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_message]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ reason: this[_reason], message: this[_message] }) + }`; + } +} + +const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; + +/** + * @param {string} name + * @param {any} type + */ +function GPUObjectBaseMixin(name, type) { + type.prototype[_label] = null; + ObjectDefineProperty(type.prototype, "label", { /** - * @param {InnerGPUDeviceOptions} options + * @return {string | null} */ - constructor(options) { - this.adapter = options.adapter; - this.rid = options.rid; - this.features = options.features; - this.limits = options.limits; - this.resources = []; - this.isLost = false; - this.resolveLost = () => {}; - this.lost = new Promise((resolve) => { - this.resolveLost = resolve; + get() { + webidl.assertBranded(this, type.prototype); + return this[_label]; + }, + /** + * @param {string | null} label + */ + set(label) { + webidl.assertBranded(this, type.prototype); + label = webidl.converters["UVString?"](label, { + prefix: `Failed to set 'label' on '${name}'`, + context: "Argument 1", }); - this.errorScopeStack = []; - } + this[_label] = label; + }, + }); +} - /** @param {any} resource */ - trackResource(resource) { - ArrayPrototypePush(this.resources, new WeakRef(resource)); - } +/** + * @typedef ErrorScope + * @property {string} filter + * @property {Promise[]} operations + */ - /** @param {{ type: string, value: string | null } | undefined} err */ - pushError(err) { - this.pushErrorPromise(PromiseResolve(err)); - } +/** + * @typedef InnerGPUDeviceOptions + * @property {GPUAdapter} adapter + * @property {number | undefined} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + */ - /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ - pushErrorPromise(promise) { - const operation = PromisePrototypeThen(promise, (err) => { - if (err) { - switch (err.type) { - case "lost": - this.isLost = true; - this.resolveLost( - createGPUDeviceLostInfo(undefined, "device was lost"), - ); - break; - case "validation": - return PromiseReject( - new GPUValidationError(err.value ?? "validation error"), - ); - case "out-of-memory": - return PromiseReject(new GPUOutOfMemoryError()); - } +class InnerGPUDevice { + /** @type {GPUAdapter} */ + adapter; + /** @type {number | undefined} */ + rid; + /** @type {GPUSupportedFeatures} */ + features; + /** @type {GPUSupportedLimits} */ + limits; + /** @type {WeakRef[]} */ + resources; + /** @type {boolean} */ + isLost; + /** @type {Promise} */ + lost; + /** @type {(info: GPUDeviceLostInfo) => void} */ + resolveLost; + /** @type {ErrorScope[]} */ + errorScopeStack; + + /** + * @param {InnerGPUDeviceOptions} options + */ + constructor(options) { + this.adapter = options.adapter; + this.rid = options.rid; + this.features = options.features; + this.limits = options.limits; + this.resources = []; + this.isLost = false; + this.resolveLost = () => {}; + this.lost = new Promise((resolve) => { + this.resolveLost = resolve; + }); + this.errorScopeStack = []; + } + + /** @param {any} resource */ + trackResource(resource) { + ArrayPrototypePush(this.resources, new WeakRef(resource)); + } + + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + this.pushErrorPromise(PromiseResolve(err)); + } + + /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ + pushErrorPromise(promise) { + const operation = PromisePrototypeThen(promise, (err) => { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + return PromiseReject( + new GPUValidationError(err.value ?? "validation error"), + ); + case "out-of-memory": + return PromiseReject(new GPUOutOfMemoryError()); } - }); - - const validationStack = ArrayPrototypeFilter( - this.errorScopeStack, - ({ filter }) => filter == "validation", - ); - const validationScope = validationStack[validationStack.length - 1]; - const validationFilteredPromise = PromisePrototypeCatch( - operation, - (err) => { - if (ObjectPrototypeIsPrototypeOf(GPUValidationErrorPrototype, err)) { - return PromiseReject(err); - } - return PromiseResolve(); - }, - ); - if (validationScope) { - ArrayPrototypePush( - validationScope.operations, - validationFilteredPromise, - ); - } else { - PromisePrototypeCatch(validationFilteredPromise, () => { - // TODO(lucacasonato): emit an UncapturedErrorEvent - }); } - // prevent uncaptured promise rejections - PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + }); - const oomStack = ArrayPrototypeFilter( - this.errorScopeStack, - ({ filter }) => filter == "out-of-memory", - ); - const oomScope = oomStack[oomStack.length - 1]; - const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { - if (ObjectPrototypeIsPrototypeOf(GPUOutOfMemoryErrorPrototype, err)) { + const validationStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "validation", + ); + const validationScope = validationStack[validationStack.length - 1]; + const validationFilteredPromise = PromisePrototypeCatch( + operation, + (err) => { + if (ObjectPrototypeIsPrototypeOf(GPUValidationErrorPrototype, err)) { return PromiseReject(err); } return PromiseResolve(); + }, + ); + if (validationScope) { + ArrayPrototypePush( + validationScope.operations, + validationFilteredPromise, + ); + } else { + PromisePrototypeCatch(validationFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent }); - if (oomScope) { - ArrayPrototypePush(oomScope.operations, oomFilteredPromise); - } else { - PromisePrototypeCatch(oomFilteredPromise, () => { - // TODO(lucacasonato): emit an UncapturedErrorEvent - }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + + const oomStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "out-of-memory", + ); + const oomScope = oomStack[oomStack.length - 1]; + const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { + if (ObjectPrototypeIsPrototypeOf(GPUOutOfMemoryErrorPrototype, err)) { + return PromiseReject(err); } - // prevent uncaptured promise rejections - PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); - } - } - - /** - * @param {string | null} label - * @param {InnerGPUDevice} inner - * @param {GPUQueue} queue - * @returns {GPUDevice} - */ - function createGPUDevice(label, inner, queue) { - /** @type {GPUDevice} */ - const device = webidl.createBranded(GPUDevice); - device[_label] = label; - device[_device] = inner; - device[_queue] = queue; - return device; - } - - class GPUDevice extends eventTarget.EventTarget { - /** @type {InnerGPUDevice} */ - [_device]; - - /** @type {GPUQueue} */ - [_queue]; - - [_cleanup]() { - const device = this[_device]; - const resources = device.resources; - while (resources.length > 0) { - const resource = ArrayPrototypePop(resources)?.deref(); - if (resource) { - resource[_cleanup](); - } - } - const rid = device.rid; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - device.rid = undefined; - } - } - - get features() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_device].features; - } - get limits() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_device].limits; - } - get queue() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_queue]; - } - - constructor() { - webidl.illegalConstructor(); - super(); - } - - destroy() { - webidl.assertBranded(this, GPUDevicePrototype); - this[_cleanup](); - } - - /** - * @param {GPUBufferDescriptor} descriptor - * @returns {GPUBuffer} - */ - createBuffer(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_buffer( - device.rid, - descriptor.label, - descriptor.size, - descriptor.usage, - descriptor.mappedAtCreation, - ); - device.pushError(err); - /** @type {CreateGPUBufferOptions} */ - let options; - if (descriptor.mappedAtCreation) { - options = { - mapping: new ArrayBuffer(descriptor.size), - mappingRange: [0, descriptor.size], - mappedRanges: [], - state: "mapped at creation", - }; - } else { - options = { - mapping: null, - mappedRanges: null, - mappingRange: null, - state: "unmapped", - }; - } - const buffer = createGPUBuffer( - descriptor.label, - device, - rid, - descriptor.size, - descriptor.usage, - options, - ); - device.trackResource(buffer); - return buffer; - } - - /** - * @param {GPUTextureDescriptor} descriptor - * @returns {GPUTexture} - */ - createTexture(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture({ - deviceRid: device.rid, - ...descriptor, - size: normalizeGPUExtent3D(descriptor.size), - }); - device.pushError(err); - - const texture = createGPUTexture( - descriptor, - device, - rid, - ); - device.trackResource(texture); - return texture; - } - - /** - * @param {GPUSamplerDescriptor} descriptor - * @returns {GPUSampler} - */ - createSampler(descriptor = {}) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; - descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const sampler = createGPUSampler( - descriptor.label, - device, - rid, - ); - device.trackResource(sampler); - return sampler; - } - - /** - * @param {GPUBindGroupLayoutDescriptor} descriptor - * @returns {GPUBindGroupLayout} - */ - createBindGroupLayout(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - for (let i = 0; i < descriptor.entries.length; ++i) { - const entry = descriptor.entries[i]; - - let j = 0; - if (entry.buffer) j++; - if (entry.sampler) j++; - if (entry.texture) j++; - if (entry.storageTexture) j++; - - if (j !== 1) { - throw new Error(); // TODO(@crowlKats): correct error - } - } - - const { rid, err } = ops.op_webgpu_create_bind_group_layout( - device.rid, - descriptor.label, - descriptor.entries, - ); - device.pushError(err); - - const bindGroupLayout = createGPUBindGroupLayout( - descriptor.label, - device, - rid, - ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; - } - - /** - * @param {GPUPipelineLayoutDescriptor} descriptor - * @returns {GPUPipelineLayout} - */ - createPipelineLayout(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bindGroupLayouts = ArrayPrototypeMap( - descriptor.bindGroupLayouts, - (layout, i) => { - const context = `bind group layout ${i + 1}`; - const rid = assertResource(layout, { prefix, context }); - assertDeviceMatch(device, layout, { - prefix, - selfContext: "this", - resourceContext: context, - }); - return rid; - }, - ); - const { rid, err } = ops.op_webgpu_create_pipeline_layout( - device.rid, - descriptor.label, - bindGroupLayouts, - ); - device.pushError(err); - - const pipelineLayout = createGPUPipelineLayout( - descriptor.label, - device, - rid, - ); - device.trackResource(pipelineLayout); - return pipelineLayout; - } - - /** - * @param {GPUBindGroupDescriptor} descriptor - * @returns {GPUBindGroup} - */ - createBindGroup(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const layout = assertResource(descriptor.layout, { - prefix, - context: "layout", - }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: "layout", - selfContext: "this", - }); - const entries = ArrayPrototypeMap(descriptor.entries, (entry, i) => { - const context = `entry ${i + 1}`; - const resource = entry.resource; - if (ObjectPrototypeIsPrototypeOf(GPUSamplerPrototype, resource)) { - const rid = assertResource(resource, { - prefix, - context, - }); - assertDeviceMatch(device, resource, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUSampler", - resource: rid, - }; - } else if ( - ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, resource) - ) { - const rid = assertResource(resource, { - prefix, - context, - }); - assertResource(resource[_texture], { - prefix, - context, - }); - assertDeviceMatch(device, resource[_texture], { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUTextureView", - resource: rid, - }; - } else { - const rid = assertResource(resource.buffer, { prefix, context }); - assertDeviceMatch(device, resource.buffer, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUBufferBinding", - resource: rid, - offset: entry.resource.offset, - size: entry.resource.size, - }; - } - }); - - const { rid, err } = ops.op_webgpu_create_bind_group( - device.rid, - descriptor.label, - layout, - entries, - ); - device.pushError(err); - - const bindGroup = createGPUBindGroup( - descriptor.label, - device, - rid, - ); - device.trackResource(bindGroup); - return bindGroup; - } - - /** - * @param {GPUShaderModuleDescriptor} descriptor - */ - createShaderModule(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_shader_module( - device.rid, - descriptor.label, - descriptor.code, - ); - device.pushError(err); - - const shaderModule = createGPUShaderModule( - descriptor.label, - device, - rid, - ); - device.trackResource(shaderModule); - return shaderModule; - } - - /** - * @param {GPUComputePipelineDescriptor} descriptor - * @returns {GPUComputePipeline} - */ - createComputePipeline(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - let layout = descriptor.layout; - if (typeof descriptor.layout !== "string") { - const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: context, - selfContext: "this", - }); - } - const module = assertResource(descriptor.compute.module, { - prefix, - context: "compute shader module", - }); - assertDeviceMatch(device, descriptor.compute.module, { - prefix, - resourceContext: "compute shader module", - selfContext: "this", - }); - - const { rid, err } = ops.op_webgpu_create_compute_pipeline( - device.rid, - descriptor.label, - layout, - { - module, - entryPoint: descriptor.compute.entryPoint, - constants: descriptor.compute.constants, - }, - ); - device.pushError(err); - - const computePipeline = createGPUComputePipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(computePipeline); - return computePipeline; - } - - /** - * @param {GPURenderPipelineDescriptor} descriptor - * @returns {GPURenderPipeline} - */ - createRenderPipeline(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - let layout = descriptor.layout; - if (typeof descriptor.layout !== "string") { - const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: context, - selfContext: "this", - }); - } - const module = assertResource(descriptor.vertex.module, { - prefix, - context: "vertex shader module", - }); - assertDeviceMatch(device, descriptor.vertex.module, { - prefix, - resourceContext: "vertex shader module", - selfContext: "this", - }); - let fragment = undefined; - if (descriptor.fragment) { - const module = assertResource(descriptor.fragment.module, { - prefix, - context: "fragment shader module", - }); - assertDeviceMatch(device, descriptor.fragment.module, { - prefix, - resourceContext: "fragment shader module", - selfContext: "this", - }); - fragment = { - module, - entryPoint: descriptor.fragment.entryPoint, - targets: descriptor.fragment.targets, - }; - } - - const { rid, err } = ops.op_webgpu_create_render_pipeline({ - deviceRid: device.rid, - label: descriptor.label, - layout, - vertex: { - module, - entryPoint: descriptor.vertex.entryPoint, - buffers: descriptor.vertex.buffers, - }, - primitive: descriptor.primitive, - depthStencil: descriptor.depthStencil, - multisample: descriptor.multisample, - fragment, - }); - device.pushError(err); - - const renderPipeline = createGPURenderPipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(renderPipeline); - return renderPipeline; - } - - createComputePipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createComputePipeline(descriptor)); - } - - createRenderPipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createRenderPipeline(descriptor)); - } - - /** - * @param {GPUCommandEncoderDescriptor} descriptor - * @returns {GPUCommandEncoder} - */ - createCommandEncoder(descriptor = {}) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; - descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_command_encoder( - device.rid, - descriptor.label, - ); - device.pushError(err); - - const commandEncoder = createGPUCommandEncoder( - descriptor.label, - device, - rid, - ); - device.trackResource(commandEncoder); - return commandEncoder; - } - - /** - * @param {GPURenderBundleEncoderDescriptor} descriptor - * @returns {GPURenderBundleEncoder} - */ - createRenderBundleEncoder(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = - "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( - descriptor, - { - prefix, - context: "Argument 1", - }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const renderBundleEncoder = createGPURenderBundleEncoder( - descriptor.label, - device, - rid, - ); - device.trackResource(renderBundleEncoder); - return renderBundleEncoder; - } - - /** - * @param {GPUQuerySetDescriptor} descriptor - * @returns {GPUQuerySet} - */ - createQuerySet(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUQuerySetDescriptor( - descriptor, - { - prefix, - context: "Argument 1", - }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_query_set({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const querySet = createGPUQuerySet( - descriptor.label, - device, - rid, - descriptor, - ); - device.trackResource(querySet); - return querySet; - } - - get lost() { - webidl.assertBranded(this, GPUDevicePrototype); - const device = this[_device]; - if (!device) { - return PromiseResolve(true); - } - if (device.rid === undefined) { - return PromiseResolve(true); - } - return device.lost; - } - - /** - * @param {GPUErrorFilter} filter - */ - pushErrorScope(filter) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - filter = webidl.converters.GPUErrorFilter(filter, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); - } - - /** - * @returns {Promise} - */ - // deno-lint-ignore require-await - async popErrorScope() { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; - const device = assertDevice(this, { prefix, context: "this" }); - if (device.isLost) { - throw new DOMException("Device has been lost.", "OperationError"); - } - const scope = ArrayPrototypePop(device.errorScopeStack); - if (!scope) { - throw new DOMException( - "There are no error scopes on the error scope stack.", - "OperationError", - ); - } - const operations = SafePromiseAll(scope.operations); - return PromisePrototypeThen( - operations, - () => PromiseResolve(null), - (err) => PromiseResolve(err), - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - label: this.label, - limits: this.limits, - queue: this.queue, - }) - }`; - } - } - GPUObjectBaseMixin("GPUDevice", GPUDevice); - const GPUDevicePrototype = GPUDevice.prototype; - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @returns {GPUQueue} - */ - function createGPUQueue(label, device) { - /** @type {GPUQueue} */ - const queue = webidl.createBranded(GPUQueue); - queue[_label] = label; - queue[_device] = device; - return queue; - } - - class GPUQueue { - /** @type {InnerGPUDevice} */ - [_device]; - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUCommandBuffer[]} commandBuffers - */ - submit(commandBuffers) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'submit' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - commandBuffers = webidl.converters["sequence"]( - commandBuffers, - { prefix, context: "Argument 1" }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const commandBufferRids = ArrayPrototypeMap( - commandBuffers, - (buffer, i) => { - const context = `command buffer ${i + 1}`; - const rid = assertResource(buffer, { prefix, context }); - assertDeviceMatch(device, buffer, { - prefix, - selfContext: "this", - resourceContext: context, - }); - return rid; - }, - ); - const { err } = ops.op_webgpu_queue_submit(device.rid, commandBufferRids); - for (let i = 0; i < commandBuffers.length; ++i) { - commandBuffers[i][_rid] = undefined; - } - device.pushError(err); - } - - onSubmittedWorkDone() { - webidl.assertBranded(this, GPUQueuePrototype); return PromiseResolve(); + }); + if (oomScope) { + ArrayPrototypePush(oomScope.operations, oomFilteredPromise); + } else { + PromisePrototypeCatch(oomFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); } + // prevent uncaptured promise rejections + PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); + } +} - /** - * @param {GPUBuffer} buffer - * @param {number} bufferOffset - * @param {BufferSource} data - * @param {number} [dataOffset] - * @param {number} [size] - */ - writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters["GPUBuffer"](buffer, { - prefix, - context: "Argument 1", - }); - bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); - dataOffset = webidl.converters["GPUSize64"](dataOffset, { - prefix, - context: "Argument 4", - }); - size = size === undefined - ? undefined - : webidl.converters["GPUSize64"](size, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - selfContext: "this", - resourceContext: "Argument 1", - }); - const { err } = ops.op_webgpu_write_buffer( - device.rid, - bufferRid, - bufferOffset, - dataOffset, - size, - new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), - ); - device.pushError(err); +/** + * @param {string | null} label + * @param {InnerGPUDevice} inner + * @param {GPUQueue} queue + * @returns {GPUDevice} + */ +function createGPUDevice(label, inner, queue) { + /** @type {GPUDevice} */ + const device = webidl.createBranded(GPUDevice); + device[_label] = label; + device[_device] = inner; + device[_queue] = queue; + return device; +} + +class GPUDevice extends EventTarget { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {GPUQueue} */ + [_queue]; + + [_cleanup]() { + const device = this[_device]; + const resources = device.resources; + while (resources.length > 0) { + const resource = ArrayPrototypePop(resources)?.deref(); + if (resource) { + resource[_cleanup](); + } } - - /** - * @param {GPUImageCopyTexture} destination - * @param {BufferSource} data - * @param {GPUImageDataLayout} dataLayout - * @param {GPUExtent3D} size - */ - writeTexture(destination, data, dataLayout, size) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 1", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 2", - }); - dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUExtent3D(size, { - prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(destination.texture, { - prefix, - context: "texture", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - selfContext: "this", - resourceContext: "texture", - }); - const { err } = ops.op_webgpu_write_texture( - device.rid, - { - texture: textureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: destination.aspect, - }, - dataLayout, - normalizeGPUExtent3D(size), - new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), - ); - device.pushError(err); - } - - copyImageBitmapToTexture(_source, _destination, _copySize) { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; } } - GPUObjectBaseMixin("GPUQueue", GPUQueue); - const GPUQueuePrototype = GPUQueue.prototype; + + get features() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_device].features; + } + get limits() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_device].limits; + } + get queue() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_queue]; + } + + constructor() { + webidl.illegalConstructor(); + super(); + } + + destroy() { + webidl.assertBranded(this, GPUDevicePrototype); + this[_cleanup](); + } /** - * @typedef CreateGPUBufferOptions - * @property {ArrayBuffer | null} mapping - * @property {number[] | null} mappingRange - * @property {[ArrayBuffer, number, number][] | null} mappedRanges - * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state - */ - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @param {number} size - * @param {number} usage - * @param {CreateGPUBufferOptions} options + * @param {GPUBufferDescriptor} descriptor * @returns {GPUBuffer} */ - function createGPUBuffer(label, device, rid, size, usage, options) { - /** @type {GPUBuffer} */ - const buffer = webidl.createBranded(GPUBuffer); - buffer[_label] = label; - buffer[_device] = device; - buffer[_rid] = rid; - buffer[_size] = size; - buffer[_usage] = usage; - buffer[_mappingRange] = options.mappingRange; - buffer[_mappedRanges] = options.mappedRanges; - buffer[_state] = options.state; + createBuffer(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_buffer( + device.rid, + descriptor.label, + descriptor.size, + descriptor.usage, + descriptor.mappedAtCreation, + ); + device.pushError(err); + /** @type {CreateGPUBufferOptions} */ + let options; + if (descriptor.mappedAtCreation) { + options = { + mapping: new ArrayBuffer(descriptor.size), + mappingRange: [0, descriptor.size], + mappedRanges: [], + state: "mapped at creation", + }; + } else { + options = { + mapping: null, + mappedRanges: null, + mappingRange: null, + state: "unmapped", + }; + } + const buffer = createGPUBuffer( + descriptor.label, + device, + rid, + descriptor.size, + descriptor.usage, + options, + ); + device.trackResource(buffer); return buffer; } - class GPUBuffer { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number} */ - [_rid]; - /** @type {number} */ - [_size]; - /** @type {number} */ - [_usage]; - /** @type {"mapped" | "mapped at creation" | "pending" | "unmapped" | "destroy"} */ - [_state]; - /** @type {[number, number] | null} */ - [_mappingRange]; - /** @type {[ArrayBuffer, number, number][] | null} */ - [_mappedRanges]; - /** @type {number} */ - [_mapMode]; + /** + * @param {GPUTextureDescriptor} descriptor + * @returns {GPUTexture} + */ + createTexture(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_texture({ + deviceRid: device.rid, + ...descriptor, + size: normalizeGPUExtent3D(descriptor.size), + }); + device.pushError(err); - [_cleanup]() { - const mappedRanges = this[_mappedRanges]; - if (mappedRanges) { - while (mappedRanges.length > 0) { - const mappedRange = ArrayPrototypePop(mappedRanges); - if (mappedRange !== undefined) { - core.close(mappedRange[1]); - } - } + const texture = createGPUTexture( + descriptor, + device, + rid, + ); + device.trackResource(texture); + return texture; + } + + /** + * @param {GPUSamplerDescriptor} descriptor + * @returns {GPUSampler} + */ + createSampler(descriptor = {}) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; + descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_texture({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const sampler = createGPUSampler( + descriptor.label, + device, + rid, + ); + device.trackResource(sampler); + return sampler; + } + + /** + * @param {GPUBindGroupLayoutDescriptor} descriptor + * @returns {GPUBindGroupLayout} + */ + createBindGroupLayout(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + for (let i = 0; i < descriptor.entries.length; ++i) { + const entry = descriptor.entries[i]; + + let j = 0; + if (entry.buffer) j++; + if (entry.sampler) j++; + if (entry.texture) j++; + if (entry.storageTexture) j++; + + if (j !== 1) { + throw new Error(); // TODO(@crowlKats): correct error } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - this[_state] = "destroy"; } - constructor() { - webidl.illegalConstructor(); - } + const { rid, err } = ops.op_webgpu_create_bind_group_layout( + device.rid, + descriptor.label, + descriptor.entries, + ); + device.pushError(err); - get size() { - webidl.assertBranded(this, GPUBufferPrototype); - return this[_size]; - } + const bindGroupLayout = createGPUBindGroupLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } - get usage() { - webidl.assertBranded(this, GPUBufferPrototype); - return this[_usage]; - } + /** + * @param {GPUPipelineLayoutDescriptor} descriptor + * @returns {GPUPipelineLayout} + */ + createPipelineLayout(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bindGroupLayouts = ArrayPrototypeMap( + descriptor.bindGroupLayouts, + (layout, i) => { + const context = `bind group layout ${i + 1}`; + const rid = assertResource(layout, { prefix, context }); + assertDeviceMatch(device, layout, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { rid, err } = ops.op_webgpu_create_pipeline_layout( + device.rid, + descriptor.label, + bindGroupLayouts, + ); + device.pushError(err); - get mapState() { - webidl.assertBranded(this, GPUBufferPrototype); - const state = this[_state]; - if (state === "mapped at creation") { - return "mapped"; + const pipelineLayout = createGPUPipelineLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(pipelineLayout); + return pipelineLayout; + } + + /** + * @param {GPUBindGroupDescriptor} descriptor + * @returns {GPUBindGroup} + */ + createBindGroup(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const layout = assertResource(descriptor.layout, { + prefix, + context: "layout", + }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: "layout", + selfContext: "this", + }); + const entries = ArrayPrototypeMap(descriptor.entries, (entry, i) => { + const context = `entry ${i + 1}`; + const resource = entry.resource; + if (ObjectPrototypeIsPrototypeOf(GPUSamplerPrototype, resource)) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertDeviceMatch(device, resource, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUSampler", + resource: rid, + }; + } else if ( + ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, resource) + ) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertResource(resource[_texture], { + prefix, + context, + }); + assertDeviceMatch(device, resource[_texture], { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUTextureView", + resource: rid, + }; } else { - return state; + const rid = assertResource(resource.buffer, { prefix, context }); + assertDeviceMatch(device, resource.buffer, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUBufferBinding", + resource: rid, + offset: entry.resource.offset, + size: entry.resource.size, + }; } + }); + + const { rid, err } = ops.op_webgpu_create_bind_group( + device.rid, + descriptor.label, + layout, + entries, + ); + device.pushError(err); + + const bindGroup = createGPUBindGroup( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroup); + return bindGroup; + } + + /** + * @param {GPUShaderModuleDescriptor} descriptor + */ + createShaderModule(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_shader_module( + device.rid, + descriptor.label, + descriptor.code, + ); + device.pushError(err); + + const shaderModule = createGPUShaderModule( + descriptor.label, + device, + rid, + ); + device.trackResource(shaderModule); + return shaderModule; + } + + /** + * @param {GPUComputePipelineDescriptor} descriptor + * @returns {GPUComputePipeline} + */ + createComputePipeline(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = descriptor.layout; + if (typeof descriptor.layout !== "string") { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.compute.module, { + prefix, + context: "compute shader module", + }); + assertDeviceMatch(device, descriptor.compute.module, { + prefix, + resourceContext: "compute shader module", + selfContext: "this", + }); + + const { rid, err } = ops.op_webgpu_create_compute_pipeline( + device.rid, + descriptor.label, + layout, + { + module, + entryPoint: descriptor.compute.entryPoint, + constants: descriptor.compute.constants, + }, + ); + device.pushError(err); + + const computePipeline = createGPUComputePipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(computePipeline); + return computePipeline; + } + + /** + * @param {GPURenderPipelineDescriptor} descriptor + * @returns {GPURenderPipeline} + */ + createRenderPipeline(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = descriptor.layout; + if (typeof descriptor.layout !== "string") { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.vertex.module, { + prefix, + context: "vertex shader module", + }); + assertDeviceMatch(device, descriptor.vertex.module, { + prefix, + resourceContext: "vertex shader module", + selfContext: "this", + }); + let fragment = undefined; + if (descriptor.fragment) { + const module = assertResource(descriptor.fragment.module, { + prefix, + context: "fragment shader module", + }); + assertDeviceMatch(device, descriptor.fragment.module, { + prefix, + resourceContext: "fragment shader module", + selfContext: "this", + }); + fragment = { + module, + entryPoint: descriptor.fragment.entryPoint, + targets: descriptor.fragment.targets, + }; } - /** - * @param {number} mode - * @param {number} offset - * @param {number} [size] - */ - async mapAsync(mode, offset = 0, size) { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - mode = webidl.converters.GPUMapModeFlags(mode, { + const { rid, err } = ops.op_webgpu_create_render_pipeline({ + deviceRid: device.rid, + label: descriptor.label, + layout, + vertex: { + module, + entryPoint: descriptor.vertex.entryPoint, + buffers: descriptor.vertex.buffers, + }, + primitive: descriptor.primitive, + depthStencil: descriptor.depthStencil, + multisample: descriptor.multisample, + fragment, + }); + device.pushError(err); + + const renderPipeline = createGPURenderPipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(renderPipeline); + return renderPipeline; + } + + createComputePipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createComputePipeline(descriptor)); + } + + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createRenderPipeline(descriptor)); + } + + /** + * @param {GPUCommandEncoderDescriptor} descriptor + * @returns {GPUCommandEncoder} + */ + createCommandEncoder(descriptor = {}) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; + descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_command_encoder( + device.rid, + descriptor.label, + ); + device.pushError(err); + + const commandEncoder = createGPUCommandEncoder( + descriptor.label, + device, + rid, + ); + device.trackResource(commandEncoder); + return commandEncoder; + } + + /** + * @param {GPURenderBundleEncoderDescriptor} descriptor + * @returns {GPURenderBundleEncoder} + */ + createRenderBundleEncoder(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = + "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( + descriptor, + { prefix, context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const renderBundleEncoder = createGPURenderBundleEncoder( + descriptor.label, + device, + rid, + ); + device.trackResource(renderBundleEncoder); + return renderBundleEncoder; + } + + /** + * @param {GPUQuerySetDescriptor} descriptor + * @returns {GPUQuerySet} + */ + createQuerySet(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUQuerySetDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_query_set({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const querySet = createGPUQuerySet( + descriptor.label, + device, + rid, + descriptor, + ); + device.trackResource(querySet); + return querySet; + } + + get lost() { + webidl.assertBranded(this, GPUDevicePrototype); + const device = this[_device]; + if (!device) { + return PromiseResolve(true); + } + if (device.rid === undefined) { + return PromiseResolve(true); + } + return device.lost; + } + + /** + * @param {GPUErrorFilter} filter + */ + pushErrorScope(filter) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + filter = webidl.converters.GPUErrorFilter(filter, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); + } + + /** + * @returns {Promise} + */ + // deno-lint-ignore require-await + async popErrorScope() { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; + const device = assertDevice(this, { prefix, context: "this" }); + if (device.isLost) { + throw new DOMException("Device has been lost.", "OperationError"); + } + const scope = ArrayPrototypePop(device.errorScopeStack); + if (!scope) { + throw new DOMException( + "There are no error scopes on the error scope stack.", + "OperationError", + ); + } + const operations = SafePromiseAll(scope.operations); + return PromisePrototypeThen( + operations, + () => PromiseResolve(null), + (err) => PromiseResolve(err), + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + label: this.label, + limits: this.limits, + queue: this.queue, + }) + }`; + } +} +GPUObjectBaseMixin("GPUDevice", GPUDevice); +const GPUDevicePrototype = GPUDevice.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @returns {GPUQueue} + */ +function createGPUQueue(label, device) { + /** @type {GPUQueue} */ + const queue = webidl.createBranded(GPUQueue); + queue[_label] = label; + queue[_device] = device; + return queue; +} + +class GPUQueue { + /** @type {InnerGPUDevice} */ + [_device]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUCommandBuffer[]} commandBuffers + */ + submit(commandBuffers) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'submit' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + commandBuffers = webidl.converters["sequence"]( + commandBuffers, + { prefix, context: "Argument 1" }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const commandBufferRids = ArrayPrototypeMap( + commandBuffers, + (buffer, i) => { + const context = `command buffer ${i + 1}`; + const rid = assertResource(buffer, { prefix, context }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { err } = ops.op_webgpu_queue_submit(device.rid, commandBufferRids); + for (let i = 0; i < commandBuffers.length; ++i) { + commandBuffers[i][_rid] = undefined; + } + device.pushError(err); + } + + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueuePrototype); + return PromiseResolve(); + } + + /** + * @param {GPUBuffer} buffer + * @param {number} bufferOffset + * @param {BufferSource} data + * @param {number} [dataOffset] + * @param {number} [size] + */ + writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters["GPUBuffer"](buffer, { + prefix, + context: "Argument 1", + }); + bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); + dataOffset = webidl.converters["GPUSize64"](dataOffset, { + prefix, + context: "Argument 4", + }); + size = size === undefined + ? undefined + : webidl.converters["GPUSize64"](size, { + prefix, + context: "Argument 5", }); - offset = webidl.converters.GPUSize64(offset, { + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: "Argument 1", + }); + const { err } = ops.op_webgpu_write_buffer( + device.rid, + bufferRid, + bufferOffset, + dataOffset, + size, + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} destination + * @param {BufferSource} data + * @param {GPUImageDataLayout} dataLayout + * @param {GPUExtent3D} size + */ + writeTexture(destination, data, dataLayout, size) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 1", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); + dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUExtent3D(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(destination.texture, { + prefix, + context: "texture", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + selfContext: "this", + resourceContext: "texture", + }); + const { err } = ops.op_webgpu_write_texture( + device.rid, + { + texture: textureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: destination.aspect, + }, + dataLayout, + normalizeGPUExtent3D(size), + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + copyImageBitmapToTexture(_source, _destination, _copySize) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUQueue", GPUQueue); +const GPUQueuePrototype = GPUQueue.prototype; + +/** + * @typedef CreateGPUBufferOptions + * @property {ArrayBuffer | null} mapping + * @property {number[] | null} mappingRange + * @property {[ArrayBuffer, number, number][] | null} mappedRanges + * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state + */ + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @param {number} size + * @param {number} usage + * @param {CreateGPUBufferOptions} options + * @returns {GPUBuffer} + */ +function createGPUBuffer(label, device, rid, size, usage, options) { + /** @type {GPUBuffer} */ + const buffer = webidl.createBranded(GPUBuffer); + buffer[_label] = label; + buffer[_device] = device; + buffer[_rid] = rid; + buffer[_size] = size; + buffer[_usage] = usage; + buffer[_mappingRange] = options.mappingRange; + buffer[_mappedRanges] = options.mappedRanges; + buffer[_state] = options.state; + return buffer; +} + +class GPUBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number} */ + [_rid]; + /** @type {number} */ + [_size]; + /** @type {number} */ + [_usage]; + /** @type {"mapped" | "mapped at creation" | "pending" | "unmapped" | "destroy"} */ + [_state]; + /** @type {[number, number] | null} */ + [_mappingRange]; + /** @type {[ArrayBuffer, number, number][] | null} */ + [_mappedRanges]; + /** @type {number} */ + [_mapMode]; + + [_cleanup]() { + const mappedRanges = this[_mappedRanges]; + if (mappedRanges) { + while (mappedRanges.length > 0) { + const mappedRange = ArrayPrototypePop(mappedRanges); + if (mappedRange !== undefined) { + core.close(mappedRange[1]); + } + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + this[_state] = "destroy"; + } + + constructor() { + webidl.illegalConstructor(); + } + + get size() { + webidl.assertBranded(this, GPUBufferPrototype); + return this[_size]; + } + + get usage() { + webidl.assertBranded(this, GPUBufferPrototype); + return this[_usage]; + } + + get mapState() { + webidl.assertBranded(this, GPUBufferPrototype); + const state = this[_state]; + if (state === "mapped at creation") { + return "mapped"; + } else { + return state; + } + } + + /** + * @param {number} mode + * @param {number} offset + * @param {number} [size] + */ + async mapAsync(mode, offset = 0, size) { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + mode = webidl.converters.GPUMapModeFlags(mode, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = size === undefined ? undefined : webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if ((offset % 8) !== 0) { + throw new DOMException( + `${prefix}: offset must be a multiple of 8.`, + "OperationError", + ); + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", + ); + } + if ((offset + rangeSize) > this[_size]) { + throw new DOMException( + `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + "OperationError", + ); + } + if (this[_state] !== "unmapped") { + throw new DOMException( + `${prefix}: GPUBuffer is not currently unmapped.`, + "OperationError", + ); + } + const readMode = (mode & 0x0001) === 0x0001; + const writeMode = (mode & 0x0002) === 0x0002; + if ((readMode && writeMode) || (!readMode && !writeMode)) { + throw new DOMException( + `${prefix}: exactly one of READ or WRITE map mode must be set.`, + "OperationError", + ); + } + if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { + throw new DOMException( + `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, + "OperationError", + ); + } + if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { + throw new DOMException( + `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + "OperationError", + ); + } + + this[_mapMode] = mode; + this[_state] = "pending"; + const promise = PromisePrototypeThen( + core.opAsync( + "op_webgpu_buffer_get_map_async", + bufferRid, + device.rid, + mode, + offset, + rangeSize, + ), + ({ err }) => err, + ); + device.pushErrorPromise(promise); + const err = await promise; + if (err) { + throw new DOMException("validation error occured", "OperationError"); + } + this[_state] = "mapped"; + this[_mappingRange] = [offset, offset + rangeSize]; + /** @type {[ArrayBuffer, number, number][] | null} */ + this[_mappedRanges] = []; + } + + /** + * @param {number} offset + * @param {number} size + */ + getMappedRange(offset = 0, size) { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 1", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { prefix, context: "Argument 2", }); - size = size === undefined - ? undefined - : webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - /** @type {number} */ - let rangeSize; - if (size === undefined) { - rangeSize = MathMax(0, this[_size] - offset); - } else { - rangeSize = this[_size]; - } - if ((offset % 8) !== 0) { - throw new DOMException( - `${prefix}: offset must be a multiple of 8.`, - "OperationError", - ); - } - if ((rangeSize % 4) !== 0) { - throw new DOMException( - `${prefix}: rangeSize must be a multiple of 4.`, - "OperationError", - ); - } - if ((offset + rangeSize) > this[_size]) { - throw new DOMException( - `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, - "OperationError", - ); - } - if (this[_state] !== "unmapped") { - throw new DOMException( - `${prefix}: GPUBuffer is not currently unmapped.`, - "OperationError", - ); - } - const readMode = (mode & 0x0001) === 0x0001; - const writeMode = (mode & 0x0002) === 0x0002; - if ((readMode && writeMode) || (!readMode && !writeMode)) { - throw new DOMException( - `${prefix}: exactly one of READ or WRITE map mode must be set.`, - "OperationError", - ); - } - if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { - throw new DOMException( - `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, - "OperationError", - ); - } - if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { - throw new DOMException( - `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, - "OperationError", - ); - } - - this[_mapMode] = mode; - this[_state] = "pending"; - const promise = PromisePrototypeThen( - core.opAsync( - "op_webgpu_buffer_get_map_async", - bufferRid, - device.rid, - mode, - offset, - rangeSize, - ), - ({ err }) => err, - ); - device.pushErrorPromise(promise); - const err = await promise; - if (err) { - throw new DOMException("validation error occured", "OperationError"); - } - this[_state] = "mapped"; - this[_mappingRange] = [offset, offset + rangeSize]; - /** @type {[ArrayBuffer, number, number][] | null} */ - this[_mappedRanges] = []; + } + assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = size; } - /** - * @param {number} offset - * @param {number} size - */ - getMappedRange(offset = 0, size) { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 1", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 2", - }); + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (let i = 0; i < mappedRanges.length; ++i) { + const { 0: buffer, /* 1: rid, */ 2: start } = mappedRanges[i]; + // TODO(lucacasonato): is this logic correct? + const end = start + buffer.byteLength; + if ( + (start >= offset && start < (offset + rangeSize)) || + (end >= offset && end < (offset + rangeSize)) + ) { + throw new DOMException( + `${prefix}: requested buffer overlaps with another mapped range.`, + "OperationError", + ); } - assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - /** @type {number} */ - let rangeSize; - if (size === undefined) { - rangeSize = MathMax(0, this[_size] - offset); - } else { - rangeSize = size; + } + + const buffer = new ArrayBuffer(rangeSize); + const { rid } = ops.op_webgpu_buffer_get_mapped_range( + bufferRid, + offset, + size, + new Uint8Array(buffer), + ); + + ArrayPrototypePush(mappedRanges, [buffer, rid, offset]); + + return buffer; + } + + unmap() { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + if (this[_state] === "unmapped" || this[_state] === "destroyed") { + throw new DOMException( + `${prefix}: buffer is not ready to be unmapped.`, + "OperationError", + ); + } + if (this[_state] === "pending") { + // TODO(lucacasonato): this is not spec compliant. + throw new DOMException( + `${prefix}: can not unmap while mapping. This is a Deno limitation.`, + "OperationError", + ); + } else if ( + this[_state] === "mapped" || this[_state] === "mapped at creation" + ) { + /** @type {boolean} */ + let write = false; + if (this[_state] === "mapped at creation") { + write = true; + } else if (this[_state] === "mapped") { + const mapMode = this[_mapMode]; + if (mapMode === undefined) { + throw new DOMException( + `${prefix}: invalid state.`, + "OperationError", + ); + } + if ((mapMode & 0x0002) === 0x0002) { + write = true; + } } const mappedRanges = this[_mappedRanges]; @@ -1921,3345 +1986,3261 @@ throw new DOMException(`${prefix}: invalid state.`, "OperationError"); } for (let i = 0; i < mappedRanges.length; ++i) { - const { 0: buffer, /* 1: rid, */ 2: start } = mappedRanges[i]; - // TODO(lucacasonato): is this logic correct? - const end = start + buffer.byteLength; - if ( - (start >= offset && start < (offset + rangeSize)) || - (end >= offset && end < (offset + rangeSize)) - ) { - throw new DOMException( - `${prefix}: requested buffer overlaps with another mapped range.`, - "OperationError", - ); - } - } - - const buffer = new ArrayBuffer(rangeSize); - const { rid } = ops.op_webgpu_buffer_get_mapped_range( - bufferRid, - offset, - size, - new Uint8Array(buffer), - ); - - ArrayPrototypePush(mappedRanges, [buffer, rid, offset]); - - return buffer; - } - - unmap() { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - if (this[_state] === "unmapped" || this[_state] === "destroyed") { - throw new DOMException( - `${prefix}: buffer is not ready to be unmapped.`, - "OperationError", + const { 0: buffer, 1: mappedRid } = mappedRanges[i]; + const { err } = ops.op_webgpu_buffer_unmap( + bufferRid, + mappedRid, + ...new SafeArrayIterator(write ? [new Uint8Array(buffer)] : []), ); + device.pushError(err); + if (err) return; } - if (this[_state] === "pending") { - // TODO(lucacasonato): this is not spec compliant. - throw new DOMException( - `${prefix}: can not unmap while mapping. This is a Deno limitation.`, - "OperationError", - ); - } else if ( - this[_state] === "mapped" || this[_state] === "mapped at creation" - ) { - /** @type {boolean} */ - let write = false; - if (this[_state] === "mapped at creation") { - write = true; - } else if (this[_state] === "mapped") { - const mapMode = this[_mapMode]; - if (mapMode === undefined) { - throw new DOMException( - `${prefix}: invalid state.`, - "OperationError", - ); - } - if ((mapMode & 0x0002) === 0x0002) { - write = true; - } - } - - const mappedRanges = this[_mappedRanges]; - if (!mappedRanges) { - throw new DOMException(`${prefix}: invalid state.`, "OperationError"); - } - for (let i = 0; i < mappedRanges.length; ++i) { - const { 0: buffer, 1: mappedRid } = mappedRanges[i]; - const { err } = ops.op_webgpu_buffer_unmap( - bufferRid, - mappedRid, - ...new SafeArrayIterator(write ? [new Uint8Array(buffer)] : []), - ); - device.pushError(err); - if (err) return; - } - this[_mappingRange] = null; - this[_mappedRanges] = null; - } - - this[_state] = "unmapped"; + this[_mappingRange] = null; + this[_mappedRanges] = null; } - destroy() { - webidl.assertBranded(this, GPUBufferPrototype); - this[_cleanup](); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + this[_state] = "unmapped"; } - GPUObjectBaseMixin("GPUBuffer", GPUBuffer); - const GPUBufferPrototype = GPUBuffer.prototype; - class GPUBufferUsage { - constructor() { - webidl.illegalConstructor(); - } + destroy() { + webidl.assertBranded(this, GPUBufferPrototype); + this[_cleanup](); + } - static get MAP_READ() { - return 0x0001; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBuffer", GPUBuffer); +const GPUBufferPrototype = GPUBuffer.prototype; + +class GPUBufferUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get MAP_READ() { + return 0x0001; + } + static get MAP_WRITE() { + return 0x0002; + } + static get COPY_SRC() { + return 0x0004; + } + static get COPY_DST() { + return 0x0008; + } + static get INDEX() { + return 0x0010; + } + static get VERTEX() { + return 0x0020; + } + static get UNIFORM() { + return 0x0040; + } + static get STORAGE() { + return 0x0080; + } + static get INDIRECT() { + return 0x0100; + } + static get QUERY_RESOLVE() { + return 0x0200; + } +} + +class GPUMapMode { + constructor() { + webidl.illegalConstructor(); + } + + static get READ() { + return 0x0001; + } + static get WRITE() { + return 0x0002; + } +} + +/** + * @param {GPUTextureDescriptor} descriptor + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUTexture} + */ +function createGPUTexture(descriptor, device, rid) { + /** @type {GPUTexture} */ + const texture = webidl.createBranded(GPUTexture); + texture[_label] = descriptor.label; + texture[_device] = device; + texture[_rid] = rid; + texture[_views] = []; + texture[_width] = descriptor.size.width; + texture[_height] = descriptor.size.height; + texture[_depthOrArrayLayers] = descriptor.size.depthOrArrayLayers; + texture[_mipLevelCount] = descriptor.mipLevelCount; + texture[_sampleCount] = descriptor.sampleCount; + texture[_dimension] = descriptor.dimension; + texture[_format] = descriptor.format; + texture[_usage] = descriptor.usage; + return texture; +} + +class GPUTexture { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_views]; + + /** @type {number} */ + [_width]; + /** @type {number} */ + [_height]; + /** @type {number} */ + [_depthOrArrayLayers]; + /** @type {number} */ + [_mipLevelCount]; + /** @type {number} */ + [_sampleCount]; + /** @type {GPUTextureDimension} */ + [_dimension]; + /** @type {GPUTextureFormat} */ + [_format]; + /** @type {number} */ + [_usage]; + + [_cleanup]() { + const views = this[_views]; + while (views.length > 0) { + const view = ArrayPrototypePop(views)?.deref(); + if (view) { + view[_cleanup](); + } } - static get MAP_WRITE() { - return 0x0002; - } - static get COPY_SRC() { - return 0x0004; - } - static get COPY_DST() { - return 0x0008; - } - static get INDEX() { - return 0x0010; - } - static get VERTEX() { - return 0x0020; - } - static get UNIFORM() { - return 0x0040; - } - static get STORAGE() { - return 0x0080; - } - static get INDIRECT() { - return 0x0100; - } - static get QUERY_RESOLVE() { - return 0x0200; + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } } - class GPUMapMode { - constructor() { - webidl.illegalConstructor(); - } - - static get READ() { - return 0x0001; - } - static get WRITE() { - return 0x0002; - } + constructor() { + webidl.illegalConstructor(); } /** - * @param {GPUTextureDescriptor} descriptor - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUTexture} + * @param {GPUTextureViewDescriptor} descriptor */ - function createGPUTexture(descriptor, device, rid) { - /** @type {GPUTexture} */ - const texture = webidl.createBranded(GPUTexture); - texture[_label] = descriptor.label; - texture[_device] = device; - texture[_rid] = rid; - texture[_views] = []; - texture[_width] = descriptor.size.width; - texture[_height] = descriptor.size.height; - texture[_depthOrArrayLayers] = descriptor.size.depthOrArrayLayers; - texture[_mipLevelCount] = descriptor.mipLevelCount; - texture[_sampleCount] = descriptor.sampleCount; - texture[_dimension] = descriptor.dimension; - texture[_format] = descriptor.format; - texture[_usage] = descriptor.usage; - return texture; - } + createView(descriptor = {}) { + webidl.assertBranded(this, GPUTexturePrototype); + const prefix = "Failed to execute 'createView' on 'GPUTexture'"; + webidl.requiredArguments(arguments.length, 0, { prefix }); + descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_texture_view({ + textureRid, + ...descriptor, + }); + device.pushError(err); - class GPUTexture { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {WeakRef[]} */ - [_views]; - - /** @type {number} */ - [_width]; - /** @type {number} */ - [_height]; - /** @type {number} */ - [_depthOrArrayLayers]; - /** @type {number} */ - [_mipLevelCount]; - /** @type {number} */ - [_sampleCount]; - /** @type {GPUTextureDimension} */ - [_dimension]; - /** @type {GPUTextureFormat} */ - [_format]; - /** @type {number} */ - [_usage]; - - [_cleanup]() { - const views = this[_views]; - while (views.length > 0) { - const view = ArrayPrototypePop(views)?.deref(); - if (view) { - view[_cleanup](); - } - } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUTextureViewDescriptor} descriptor - */ - createView(descriptor = {}) { - webidl.assertBranded(this, GPUTexturePrototype); - const prefix = "Failed to execute 'createView' on 'GPUTexture'"; - webidl.requiredArguments(arguments.length, 0, { prefix }); - descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture_view({ - textureRid, - ...descriptor, - }); - device.pushError(err); - - const textureView = createGPUTextureView( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_views], new WeakRef(textureView)); - return textureView; - } - - destroy() { - webidl.assertBranded(this, GPUTexturePrototype); - this[_cleanup](); - } - - get width() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_width]; - } - - get height() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_height]; - } - - get depthOrArrayLayers() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_depthOrArrayLayers]; - } - - get mipLevelCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_mipLevelCount]; - } - - get sampleCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_sampleCount]; - } - - get dimension() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_dimension]; - } - - get format() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_format]; - } - - get usage() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_usage]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUTexture", GPUTexture); - const GPUTexturePrototype = GPUTexture.prototype; - - class GPUTextureUsage { - constructor() { - webidl.illegalConstructor(); - } - - static get COPY_SRC() { - return 0x01; - } - static get COPY_DST() { - return 0x02; - } - static get TEXTURE_BINDING() { - return 0x04; - } - static get STORAGE_BINDING() { - return 0x08; - } - static get RENDER_ATTACHMENT() { - return 0x10; - } - } - - /** - * @param {string | null} label - * @param {GPUTexture} texture - * @param {number} rid - * @returns {GPUTextureView} - */ - function createGPUTextureView(label, texture, rid) { - /** @type {GPUTextureView} */ - const textureView = webidl.createBranded(GPUTextureView); - textureView[_label] = label; - textureView[_texture] = texture; - textureView[_rid] = rid; + const textureView = createGPUTextureView( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_views], new WeakRef(textureView)); return textureView; } - class GPUTextureView { - /** @type {GPUTexture} */ - [_texture]; - /** @type {number | undefined} */ - [_rid]; - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } + destroy() { + webidl.assertBranded(this, GPUTexturePrototype); + this[_cleanup](); + } - constructor() { - webidl.illegalConstructor(); - } + get width() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_width]; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + get height() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_height]; + } + + get depthOrArrayLayers() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_depthOrArrayLayers]; + } + + get mipLevelCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_mipLevelCount]; + } + + get sampleCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_sampleCount]; + } + + get dimension() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_dimension]; + } + + get format() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_format]; + } + + get usage() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_usage]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUTexture", GPUTexture); +const GPUTexturePrototype = GPUTexture.prototype; + +class GPUTextureUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get COPY_SRC() { + return 0x01; + } + static get COPY_DST() { + return 0x02; + } + static get TEXTURE_BINDING() { + return 0x04; + } + static get STORAGE_BINDING() { + return 0x08; + } + static get RENDER_ATTACHMENT() { + return 0x10; + } +} + +/** + * @param {string | null} label + * @param {GPUTexture} texture + * @param {number} rid + * @returns {GPUTextureView} + */ +function createGPUTextureView(label, texture, rid) { + /** @type {GPUTextureView} */ + const textureView = webidl.createBranded(GPUTextureView); + textureView[_label] = label; + textureView[_texture] = texture; + textureView[_rid] = rid; + return textureView; +} +class GPUTextureView { + /** @type {GPUTexture} */ + [_texture]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } } - GPUObjectBaseMixin("GPUTextureView", GPUTextureView); - const GPUTextureViewPrototype = GPUTextureView.prototype; + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUTextureView", GPUTextureView); +const GPUTextureViewPrototype = GPUTextureView.prototype; +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUSampler} + */ +function createGPUSampler(label, device, rid) { + /** @type {GPUSampler} */ + const sampler = webidl.createBranded(GPUSampler); + sampler[_label] = label; + sampler[_device] = device; + sampler[_rid] = rid; + return sampler; +} +class GPUSampler { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUSampler", GPUSampler); +const GPUSamplerPrototype = GPUSampler.prototype; +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroupLayout} + */ +function createGPUBindGroupLayout(label, device, rid) { + /** @type {GPUBindGroupLayout} */ + const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); + bindGroupLayout[_label] = label; + bindGroupLayout[_device] = device; + bindGroupLayout[_rid] = rid; + return bindGroupLayout; +} +class GPUBindGroupLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUPipelineLayout} + */ +function createGPUPipelineLayout(label, device, rid) { + /** @type {GPUPipelineLayout} */ + const pipelineLayout = webidl.createBranded(GPUPipelineLayout); + pipelineLayout[_label] = label; + pipelineLayout[_device] = device; + pipelineLayout[_rid] = rid; + return pipelineLayout; +} +class GPUPipelineLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroup} + */ +function createGPUBindGroup(label, device, rid) { + /** @type {GPUBindGroup} */ + const bindGroup = webidl.createBranded(GPUBindGroup); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; +} +class GPUBindGroup { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUShaderModule} + */ +function createGPUShaderModule(label, device, rid) { + /** @type {GPUShaderModule} */ + const bindGroup = webidl.createBranded(GPUShaderModule); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; +} +class GPUShaderModule { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + compilationInfo() { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); + +class GPUShaderStage { + constructor() { + webidl.illegalConstructor(); + } + + static get VERTEX() { + return 0x1; + } + + static get FRAGMENT() { + return 0x2; + } + + static get COMPUTE() { + return 0x4; + } +} + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUComputePipeline} + */ +function createGPUComputePipeline(label, device, rid) { + /** @type {GPUComputePipeline} */ + const pipeline = webidl.createBranded(GPUComputePipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; +} +class GPUComputePipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUSampler} - */ - function createGPUSampler(label, device, rid) { - /** @type {GPUSampler} */ - const sampler = webidl.createBranded(GPUSampler); - sampler[_label] = label; - sampler[_device] = device; - sampler[_rid] = rid; - return sampler; - } - class GPUSampler { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUSampler", GPUSampler); - const GPUSamplerPrototype = GPUSampler.prototype; - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid + * @param {number} index * @returns {GPUBindGroupLayout} */ - function createGPUBindGroupLayout(label, device, rid) { - /** @type {GPUBindGroupLayout} */ - const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); - bindGroupLayout[_label] = label; - bindGroupLayout[_device] = device; - bindGroupLayout[_rid] = rid; + getBindGroupLayout(index) { + webidl.assertBranded(this, GPUComputePipelinePrototype); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const computePipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = ops + .op_webgpu_compute_pipeline_get_bind_group_layout( + computePipelineRid, + index, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource(bindGroupLayout); return bindGroupLayout; } - class GPUBindGroupLayout { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); +const GPUComputePipelinePrototype = GPUComputePipeline.prototype; - constructor() { - webidl.illegalConstructor(); - } +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderPipeline} + */ +function createGPURenderPipeline(label, device, rid) { + /** @type {GPURenderPipeline} */ + const pipeline = webidl.createBranded(GPURenderPipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; +} +class GPURenderPipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } } - GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUPipelineLayout} - */ - function createGPUPipelineLayout(label, device, rid) { - /** @type {GPUPipelineLayout} */ - const pipelineLayout = webidl.createBranded(GPUPipelineLayout); - pipelineLayout[_label] = label; - pipelineLayout[_device] = device; - pipelineLayout[_rid] = rid; - return pipelineLayout; - } - class GPUPipelineLayout { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUBindGroup} - */ - function createGPUBindGroup(label, device, rid) { - /** @type {GPUBindGroup} */ - const bindGroup = webidl.createBranded(GPUBindGroup); - bindGroup[_label] = label; - bindGroup[_device] = device; - bindGroup[_rid] = rid; - return bindGroup; - } - class GPUBindGroup { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUShaderModule} - */ - function createGPUShaderModule(label, device, rid) { - /** @type {GPUShaderModule} */ - const bindGroup = webidl.createBranded(GPUShaderModule); - bindGroup[_label] = label; - bindGroup[_device] = device; - bindGroup[_rid] = rid; - return bindGroup; - } - class GPUShaderModule { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - compilationInfo() { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); - - class GPUShaderStage { - constructor() { - webidl.illegalConstructor(); - } - - static get VERTEX() { - return 0x1; - } - - static get FRAGMENT() { - return 0x2; - } - - static get COMPUTE() { - return 0x4; - } + constructor() { + webidl.illegalConstructor(); } /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUComputePipeline} + * @param {number} index */ - function createGPUComputePipeline(label, device, rid) { - /** @type {GPUComputePipeline} */ - const pipeline = webidl.createBranded(GPUComputePipeline); - pipeline[_label] = label; - pipeline[_device] = device; - pipeline[_rid] = rid; - return pipeline; - } - class GPUComputePipeline { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {number} index - * @returns {GPUBindGroupLayout} - */ - getBindGroupLayout(index) { - webidl.assertBranded(this, GPUComputePipelinePrototype); - const prefix = - "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const computePipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = ops - .op_webgpu_compute_pipeline_get_bind_group_layout( - computePipelineRid, - index, - ); - device.pushError(err); - - const bindGroupLayout = createGPUBindGroupLayout( - label, - device, - rid, + getBindGroupLayout(index) { + webidl.assertBranded(this, GPURenderPipelinePrototype); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderPipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = ops + .op_webgpu_render_pipeline_get_bind_group_layout( + renderPipelineRid, + index, ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; - } + device.pushError(err); - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; } - GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); - const GPUComputePipelinePrototype = GPUComputePipeline.prototype; - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderPipeline} - */ - function createGPURenderPipeline(label, device, rid) { - /** @type {GPURenderPipeline} */ - const pipeline = webidl.createBranded(GPURenderPipeline); - pipeline[_label] = label; - pipeline[_device] = device; - pipeline[_rid] = rid; - return pipeline; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; } - class GPURenderPipeline { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; +} +GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); +const GPURenderPipelinePrototype = GPURenderPipeline.prototype; - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; +class GPUColorWrite { + constructor() { + webidl.illegalConstructor(); + } + + static get RED() { + return 0x1; + } + static get GREEN() { + return 0x2; + } + static get BLUE() { + return 0x4; + } + static get ALPHA() { + return 0x8; + } + static get ALL() { + return 0xF; + } +} + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandEncoder} + */ +function createGPUCommandEncoder(label, device, rid) { + /** @type {GPUCommandEncoder} */ + const encoder = webidl.createBranded(GPUCommandEncoder); + encoder[_label] = label; + encoder[_device] = device; + encoder[_rid] = rid; + encoder[_encoders] = []; + return encoder; +} +class GPUCommandEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_encoders]; + + [_cleanup]() { + const encoders = this[_encoders]; + while (encoders.length > 0) { + const encoder = ArrayPrototypePop(encoders)?.deref(); + if (encoder) { + encoder[_cleanup](); } } - - constructor() { - webidl.illegalConstructor(); + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } + } - /** - * @param {number} index - */ - getBindGroupLayout(index) { - webidl.assertBranded(this, GPURenderPipelinePrototype); - const prefix = - "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderPipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = ops - .op_webgpu_render_pipeline_get_bind_group_layout( - renderPipelineRid, - index, - ); - device.pushError(err); + constructor() { + webidl.illegalConstructor(); + } - const bindGroupLayout = createGPUBindGroupLayout( - label, - device, - rid, + /** + * @param {GPURenderPassDescriptor} descriptor + * @return {GPURenderPassEncoder} + */ + beginRenderPass(descriptor) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + if (this[_rid] === undefined) { + throw new DOMException( + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", + "OperationError", ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); - const GPURenderPipelinePrototype = GPURenderPipeline.prototype; - - class GPUColorWrite { - constructor() { - webidl.illegalConstructor(); - } - - static get RED() { - return 0x1; - } - static get GREEN() { - return 0x2; - } - static get BLUE() { - return 0x4; - } - static get ALPHA() { - return 0x8; - } - static get ALL() { - return 0xF; - } - } - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUCommandEncoder} - */ - function createGPUCommandEncoder(label, device, rid) { - /** @type {GPUCommandEncoder} */ - const encoder = webidl.createBranded(GPUCommandEncoder); - encoder[_label] = label; - encoder[_device] = device; - encoder[_rid] = rid; - encoder[_encoders] = []; - return encoder; - } - class GPUCommandEncoder { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {WeakRef[]} */ - [_encoders]; - - [_cleanup]() { - const encoders = this[_encoders]; - while (encoders.length > 0) { - const encoder = ArrayPrototypePop(encoders)?.deref(); - if (encoder) { - encoder[_cleanup](); - } - } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPURenderPassDescriptor} descriptor - * @return {GPURenderPassEncoder} - */ - beginRenderPass(descriptor) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { + let depthStencilAttachment; + if (descriptor.depthStencilAttachment) { + const view = assertResource(descriptor.depthStencilAttachment.view, { prefix, - context: "Argument 1", + context: "texture view for depth stencil attachment", }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - - if (this[_rid] === undefined) { - throw new DOMException( - "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", - "OperationError", - ); - } - - let depthStencilAttachment; - if (descriptor.depthStencilAttachment) { - const view = assertResource(descriptor.depthStencilAttachment.view, { + assertDeviceMatch( + device, + descriptor.depthStencilAttachment.view[_texture], + { prefix, - context: "texture view for depth stencil attachment", + resourceContext: "texture view for depth stencil attachment", + selfContext: "this", + }, + ); + + depthStencilAttachment = { + ...descriptor.depthStencilAttachment, + view, + }; + } + const colorAttachments = ArrayPrototypeMap( + descriptor.colorAttachments, + (colorAttachment, i) => { + const context = `color attachment ${i + 1}`; + const view = assertResource(colorAttachment.view, { + prefix, + context: `texture view for ${context}`, + }); + assertResource(colorAttachment.view[_texture], { + prefix, + context: `texture backing texture view for ${context}`, }); assertDeviceMatch( device, - descriptor.depthStencilAttachment.view[_texture], + colorAttachment.view[_texture], { prefix, - resourceContext: "texture view for depth stencil attachment", + resourceContext: `texture view for ${context}`, selfContext: "this", }, ); - - depthStencilAttachment = { - ...descriptor.depthStencilAttachment, - view, - }; - } - const colorAttachments = ArrayPrototypeMap( - descriptor.colorAttachments, - (colorAttachment, i) => { - const context = `color attachment ${i + 1}`; - const view = assertResource(colorAttachment.view, { + let resolveTarget; + if (colorAttachment.resolveTarget) { + resolveTarget = assertResource( + colorAttachment.resolveTarget, + { + prefix, + context: `resolve target texture view for ${context}`, + }, + ); + assertResource(colorAttachment.resolveTarget[_texture], { prefix, - context: `texture view for ${context}`, - }); - assertResource(colorAttachment.view[_texture], { - prefix, - context: `texture backing texture view for ${context}`, + context: + `texture backing resolve target texture view for ${context}`, }); assertDeviceMatch( device, - colorAttachment.view[_texture], + colorAttachment.resolveTarget[_texture], { prefix, - resourceContext: `texture view for ${context}`, + resourceContext: `resolve target texture view for ${context}`, selfContext: "this", }, ); - let resolveTarget; - if (colorAttachment.resolveTarget) { - resolveTarget = assertResource( - colorAttachment.resolveTarget, - { - prefix, - context: `resolve target texture view for ${context}`, - }, - ); - assertResource(colorAttachment.resolveTarget[_texture], { - prefix, - context: - `texture backing resolve target texture view for ${context}`, - }); - assertDeviceMatch( - device, - colorAttachment.resolveTarget[_texture], - { - prefix, - resourceContext: `resolve target texture view for ${context}`, - selfContext: "this", - }, - ); - } - return { - view: view, - resolveTarget, - storeOp: colorAttachment.storeOp, - loadOp: colorAttachment.loadOp, - clearValue: normalizeGPUColor(colorAttachment.clearValue), - }; - }, - ); + } + return { + view: view, + resolveTarget, + storeOp: colorAttachment.storeOp, + loadOp: colorAttachment.loadOp, + clearValue: normalizeGPUColor(colorAttachment.clearValue), + }; + }, + ); - const { rid } = ops.op_webgpu_command_encoder_begin_render_pass( - commandEncoderRid, - descriptor.label, - colorAttachments, - depthStencilAttachment, - ); + const { rid } = ops.op_webgpu_command_encoder_begin_render_pass( + commandEncoderRid, + descriptor.label, + colorAttachments, + depthStencilAttachment, + ); - const renderPassEncoder = createGPURenderPassEncoder( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder)); - return renderPassEncoder; - } - - /** - * @param {GPUComputePassDescriptor} descriptor - */ - beginComputePass(descriptor = {}) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - - assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - - const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass( - commandEncoderRid, - descriptor.label, - ); - - const computePassEncoder = createGPUComputePassEncoder( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder)); - return computePassEncoder; - } - - /** - * @param {GPUBuffer} source - * @param {number} sourceOffset - * @param {GPUBuffer} destination - * @param {number} destinationOffset - * @param {number} size - */ - copyBufferToBuffer( - source, - sourceOffset, - destination, - destinationOffset, - size, - ) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - source = webidl.converters.GPUBuffer(source, { - prefix, - context: "Argument 1", - }); - sourceOffset = webidl.converters.GPUSize64(sourceOffset, { - prefix, - context: "Argument 2", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 3", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 4", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceRid = assertResource(source, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, source, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); - assertDeviceMatch(device, destination, { - prefix, - resourceContext: "Argument 3", - selfContext: "this", - }); - - const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_buffer( - commandEncoderRid, - sourceRid, - sourceOffset, - destinationRid, - destinationOffset, - size, - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyBuffer} source - * @param {GPUImageCopyTexture} destination - * @param {GPUExtent3D} copySize - */ - copyBufferToTexture(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyBuffer(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceBufferRid = assertResource(source.buffer, { - prefix, - context: "source in Argument 1", - }); - assertDeviceMatch(device, source.buffer, { - prefix, - resourceContext: "source in Argument 1", - selfContext: "this", - }); - const destinationTextureRid = assertResource(destination.texture, { - prefix, - context: "texture in Argument 2", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - resourceContext: "texture in Argument 2", - selfContext: "this", - }); - - const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_texture( - commandEncoderRid, - { - ...source, - buffer: sourceBufferRid, - }, - { - texture: destinationTextureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: destination.aspect, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyTexture} source - * @param {GPUImageCopyBuffer} destination - * @param {GPUExtent3D} copySize - */ - copyTextureToBuffer(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyBuffer(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { - prefix, - context: "texture in Argument 1", - }); - assertDeviceMatch(device, source.texture, { - prefix, - resourceContext: "texture in Argument 1", - selfContext: "this", - }); - const destinationBufferRid = assertResource(destination.buffer, { - prefix, - context: "buffer in Argument 2", - }); - assertDeviceMatch(device, destination.buffer, { - prefix, - resourceContext: "buffer in Argument 2", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_copy_texture_to_buffer( - commandEncoderRid, - { - texture: sourceTextureRid, - mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, - aspect: source.aspect, - }, - { - ...destination, - buffer: destinationBufferRid, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyTexture} source - * @param {GPUImageCopyTexture} destination - * @param {GPUExtent3D} copySize - */ - copyTextureToTexture(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { - prefix, - context: "texture in Argument 1", - }); - assertDeviceMatch(device, source.texture, { - prefix, - resourceContext: "texture in Argument 1", - selfContext: "this", - }); - const destinationTextureRid = assertResource(destination.texture, { - prefix, - context: "texture in Argument 2", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - resourceContext: "texture in Argument 2", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_copy_texture_to_texture( - commandEncoderRid, - { - texture: sourceTextureRid, - mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, - aspect: source.aspect, - }, - { - texture: destinationTextureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: source.aspect, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUBuffer} buffer - * @param {GPUSize64} offset - * @param {GPUSize64} size - */ - clearBuffer(buffer, offset = 0, size = undefined) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'clearBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 2", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - const { err } = ops.op_webgpu_command_encoder_clear_buffer( - commandEncoderRid, - bufferRid, - offset, - size, - ); - device.pushError(err); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_push_debug_group( - commandEncoderRid, - groupLabel, - ); - device.pushError(err); - } - - popDebugGroup() { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_pop_debug_group( - commandEncoderRid, - ); - device.pushError(err); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_insert_debug_marker( - commandEncoderRid, - markerLabel, - ); - device.pushError(err); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_write_timestamp( - commandEncoderRid, - querySetRid, - queryIndex, - ); - device.pushError(err); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} firstQuery - * @param {number} queryCount - * @param {GPUBuffer} destination - * @param {number} destinationOffset - */ - resolveQuerySet( - querySet, - firstQuery, - queryCount, - destination, - destinationOffset, - ) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - firstQuery = webidl.converters.GPUSize32(firstQuery, { - prefix, - context: "Argument 2", - }); - queryCount = webidl.converters.GPUSize32(queryCount, { - prefix, - context: "Argument 3", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 4", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); - assertDeviceMatch(device, destination, { - prefix, - resourceContext: "Argument 3", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_resolve_query_set( - commandEncoderRid, - querySetRid, - firstQuery, - queryCount, - destinationRid, - destinationOffset, - ); - device.pushError(err); - } - - /** - * @param {GPUCommandBufferDescriptor} descriptor - * @returns {GPUCommandBuffer} - */ - finish(descriptor = {}) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, err } = ops.op_webgpu_command_encoder_finish( - commandEncoderRid, - descriptor.label, - ); - device.pushError(err); - /** @type {number | undefined} */ - this[_rid] = undefined; - - const commandBuffer = createGPUCommandBuffer( - descriptor.label, - device, - rid, - ); - device.trackResource(commandBuffer); - return commandBuffer; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + const renderPassEncoder = createGPURenderPassEncoder( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder)); + return renderPassEncoder; } - GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); - const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPURenderPassEncoder} + * @param {GPUComputePassDescriptor} descriptor */ - function createGPURenderPassEncoder(label, encoder, rid) { - /** @type {GPURenderPassEncoder} */ - const passEncoder = webidl.createBranded(GPURenderPassEncoder); - passEncoder[_label] = label; - passEncoder[_encoder] = encoder; - passEncoder[_rid] = rid; - return passEncoder; - } + beginComputePass(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); - class GPURenderPassEncoder { - /** @type {GPUCommandEncoder} */ - [_encoder]; - /** @type {number | undefined} */ - [_rid]; + assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } + const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass( + commandEncoderRid, + descriptor.label, + ); - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {number} minDepth - * @param {number} maxDepth - */ - setViewport(x, y, width, height, minDepth, maxDepth) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 6, { prefix }); - x = webidl.converters.float(x, { prefix, context: "Argument 1" }); - y = webidl.converters.float(y, { prefix, context: "Argument 2" }); - width = webidl.converters.float(width, { prefix, context: "Argument 3" }); - height = webidl.converters.float(height, { - prefix, - context: "Argument 4", - }); - minDepth = webidl.converters.float(minDepth, { - prefix, - context: "Argument 5", - }); - maxDepth = webidl.converters.float(maxDepth, { - prefix, - context: "Argument 6", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_viewport({ - renderPassRid, - x, - y, - width, - height, - minDepth, - maxDepth, - }); - } - - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - */ - setScissorRect(x, y, width, height) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - x = webidl.converters.GPUIntegerCoordinate(x, { - prefix, - context: "Argument 1", - }); - y = webidl.converters.GPUIntegerCoordinate(y, { - prefix, - context: "Argument 2", - }); - width = webidl.converters.GPUIntegerCoordinate(width, { - prefix, - context: "Argument 3", - }); - height = webidl.converters.GPUIntegerCoordinate(height, { - prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_scissor_rect( - renderPassRid, - x, - y, - width, - height, - ); - } - - /** - * @param {GPUColor} color - */ - setBlendConstant(color) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - color = webidl.converters.GPUColor(color, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_blend_constant( - renderPassRid, - normalizeGPUColor(color), - ); - } - - /** - * @param {number} reference - */ - setStencilReference(reference) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reference = webidl.converters.GPUStencilValue(reference, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_stencil_reference( - renderPassRid, - reference, - ); - } - - beginOcclusionQuery(_queryIndex) { - throw new Error("Not yet implemented"); - } - - endOcclusionQuery() { - throw new Error("Not yet implemented"); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - beginPipelineStatisticsQuery(querySet, queryIndex) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_begin_pipeline_statistics_query( - renderPassRid, - querySetRid, - queryIndex, - ); - } - - endPipelineStatisticsQuery() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_end_pipeline_statistics_query(renderPassRid); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_write_timestamp( - renderPassRid, - querySetRid, - queryIndex, - ); - } - - /** - * @param {GPURenderBundle[]} bundles - */ - executeBundles(bundles) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - bundles = webidl.converters["sequence"](bundles, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { - const context = `bundle ${i + 1}`; - const rid = assertResource(bundle, { prefix, context }); - assertDeviceMatch(device, bundle, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return rid; - }); - ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids); - } - - end() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = "Failed to execute 'end' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const { err } = ops.op_webgpu_render_pass_end( - commandEncoderRid, - renderPassRid, - ); - device.pushError(err); - this[_rid] = undefined; - } - - // TODO(lucacasonato): has an overload - setBindGroup( - index, - bindGroup, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_render_pass_set_bind_group( - renderPassRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel); - } - - popDebugGroup() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_pop_debug_group(renderPassRid); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel); - } - - /** - * @param {GPURenderPipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid); - } - - /** - * @param {GPUBuffer} buffer - * @param {GPUIndexFormat} indexFormat - * @param {number} offset - * @param {number} size - */ - setIndexBuffer(buffer, indexFormat, offset = 0, size) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - } - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_index_buffer( - renderPassRid, - bufferRid, - indexFormat, - offset, - size, - ); - } - - /** - * @param {number} slot - * @param {GPUBuffer} buffer - * @param {number} offset - * @param {number} size - */ - setVertexBuffer(slot, buffer, offset = 0, size) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 2", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - } - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_vertex_buffer( - renderPassRid, - slot, - bufferRid, - offset, - size, - ); - } - - /** - * @param {number} vertexCount - * @param {number} instanceCount - * @param {number} firstVertex - * @param {number} firstInstance - */ - draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { - prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_draw( - renderPassRid, - vertexCount, - instanceCount, - firstVertex, - firstInstance, - ); - } - - /** - * @param {number} indexCount - * @param {number} instanceCount - * @param {number} firstIndex - * @param {number} baseVertex - * @param {number} firstInstance - */ - drawIndexed( - indexCount, - instanceCount = 1, - firstIndex = 0, - baseVertex = 0, - firstInstance = 0, - ) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { - prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 5", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_draw_indexed( - renderPassRid, - indexCount, - instanceCount, - firstIndex, - baseVertex, - firstInstance, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_draw_indirect( - renderPassRid, - indirectBufferRid, - indirectOffset, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndexedIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_draw_indexed_indirect( - renderPassRid, - indirectBufferRid, - indirectOffset, - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); - const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; - - /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPUComputePassEncoder} - */ - function createGPUComputePassEncoder(label, encoder, rid) { - /** @type {GPUComputePassEncoder} */ - const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); - computePassEncoder[_label] = label; - computePassEncoder[_encoder] = encoder; - computePassEncoder[_rid] = rid; + const computePassEncoder = createGPUComputePassEncoder( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder)); return computePassEncoder; } - class GPUComputePassEncoder { - /** @type {GPUCommandEncoder} */ - [_encoder]; + /** + * @param {GPUBuffer} source + * @param {number} sourceOffset + * @param {GPUBuffer} destination + * @param {number} destinationOffset + * @param {number} size + */ + copyBufferToBuffer( + source, + sourceOffset, + destination, + destinationOffset, + size, + ) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + source = webidl.converters.GPUBuffer(source, { + prefix, + context: "Argument 1", + }); + sourceOffset = webidl.converters.GPUSize64(sourceOffset, { + prefix, + context: "Argument 2", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 3", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 4", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceRid = assertResource(source, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, source, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUComputePipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPUComputePipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); - } - - /** - * @param {number} workgroupCountX - * @param {number} workgroupCountY - * @param {number} workgroupCountZ - */ - dispatchWorkgroups( - workgroupCountX, - workgroupCountY = 1, - workgroupCountZ = 1, - ) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - workgroupCountX = webidl.converters.GPUSize32(workgroupCountX, { - prefix, - context: "Argument 1", - }); - workgroupCountY = webidl.converters.GPUSize32(workgroupCountY, { - prefix, - context: "Argument 2", - }); - workgroupCountZ = webidl.converters.GPUSize32(workgroupCountZ, { - prefix, - context: "Argument 3", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_dispatch_workgroups( - computePassRid, - workgroupCountX, - workgroupCountY, - workgroupCountZ, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( - computePassRid, - indirectBufferRid, - indirectOffset, - ); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - beginPipelineStatisticsQuery(querySet, queryIndex) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_begin_pipeline_statistics_query( - computePassRid, - querySetRid, - queryIndex, - ); - } - - endPipelineStatisticsQuery() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_end_pipeline_statistics_query(computePassRid); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_write_timestamp( - computePassRid, - querySetRid, - queryIndex, - ); - } - - end() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = "Failed to execute 'end' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const { err } = ops.op_webgpu_compute_pass_end( - commandEncoderRid, - computePassRid, - ); - device.pushError(err); - this[_rid] = undefined; - } - - // TODO(lucacasonato): has an overload - setBindGroup( - index, - bindGroup, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_compute_pass_set_bind_group( - computePassRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel); - } - - popDebugGroup() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_pop_debug_group(computePassRid); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_insert_debug_marker( - computePassRid, - markerLabel, - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_buffer( + commandEncoderRid, + sourceRid, + sourceOffset, + destinationRid, + destinationOffset, + size, + ); + device.pushError(err); } - GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); - const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid + * @param {GPUImageCopyBuffer} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyBufferToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyBuffer(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceBufferRid = assertResource(source.buffer, { + prefix, + context: "source in Argument 1", + }); + assertDeviceMatch(device, source.buffer, { + prefix, + resourceContext: "source in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + + const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_texture( + commandEncoderRid, + { + ...source, + buffer: sourceBufferRid, + }, + { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: destination.aspect, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyBuffer} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToBuffer(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyBuffer(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationBufferRid = assertResource(destination.buffer, { + prefix, + context: "buffer in Argument 2", + }); + assertDeviceMatch(device, destination.buffer, { + prefix, + resourceContext: "buffer in Argument 2", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_copy_texture_to_buffer( + commandEncoderRid, + { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, + aspect: source.aspect, + }, + { + ...destination, + buffer: destinationBufferRid, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_copy_texture_to_texture( + commandEncoderRid, + { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, + aspect: source.aspect, + }, + { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: source.aspect, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUSize64} offset + * @param {GPUSize64} size + */ + clearBuffer(buffer, offset = 0, size = undefined) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'clearBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + const { err } = ops.op_webgpu_command_encoder_clear_buffer( + commandEncoderRid, + bufferRid, + offset, + size, + ); + device.pushError(err); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_push_debug_group( + commandEncoderRid, + groupLabel, + ); + device.pushError(err); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_pop_debug_group( + commandEncoderRid, + ); + device.pushError(err); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_insert_debug_marker( + commandEncoderRid, + markerLabel, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_write_timestamp( + commandEncoderRid, + querySetRid, + queryIndex, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} firstQuery + * @param {number} queryCount + * @param {GPUBuffer} destination + * @param {number} destinationOffset + */ + resolveQuerySet( + querySet, + firstQuery, + queryCount, + destination, + destinationOffset, + ) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + firstQuery = webidl.converters.GPUSize32(firstQuery, { + prefix, + context: "Argument 2", + }); + queryCount = webidl.converters.GPUSize32(queryCount, { + prefix, + context: "Argument 3", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 4", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_resolve_query_set( + commandEncoderRid, + querySetRid, + firstQuery, + queryCount, + destinationRid, + destinationOffset, + ); + device.pushError(err); + } + + /** + * @param {GPUCommandBufferDescriptor} descriptor * @returns {GPUCommandBuffer} */ - function createGPUCommandBuffer(label, device, rid) { - /** @type {GPUCommandBuffer} */ - const commandBuffer = webidl.createBranded(GPUCommandBuffer); - commandBuffer[_label] = label; - commandBuffer[_device] = device; - commandBuffer[_rid] = rid; + finish(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = ops.op_webgpu_command_encoder_finish( + commandEncoderRid, + descriptor.label, + ); + device.pushError(err); + /** @type {number | undefined} */ + this[_rid] = undefined; + + const commandBuffer = createGPUCommandBuffer( + descriptor.label, + device, + rid, + ); + device.trackResource(commandBuffer); return commandBuffer; } - class GPUCommandBuffer { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); +const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } +/** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPURenderPassEncoder} + */ +function createGPURenderPassEncoder(label, encoder, rid) { + /** @type {GPURenderPassEncoder} */ + const passEncoder = webidl.createBranded(GPURenderPassEncoder); + passEncoder[_label] = label; + passEncoder[_encoder] = encoder; + passEncoder[_rid] = rid; + return passEncoder; +} - constructor() { - webidl.illegalConstructor(); - } +class GPURenderPassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + /** @type {number | undefined} */ + [_rid]; - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } } - GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + + constructor() { + webidl.illegalConstructor(); + } /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundleEncoder} + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth */ - function createGPURenderBundleEncoder(label, device, rid) { - /** @type {GPURenderBundleEncoder} */ - const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); - bundleEncoder[_label] = label; - bundleEncoder[_device] = device; - bundleEncoder[_rid] = rid; - return bundleEncoder; + setViewport(x, y, width, height, minDepth, maxDepth) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 6, { prefix }); + x = webidl.converters.float(x, { prefix, context: "Argument 1" }); + y = webidl.converters.float(y, { prefix, context: "Argument 2" }); + width = webidl.converters.float(width, { prefix, context: "Argument 3" }); + height = webidl.converters.float(height, { + prefix, + context: "Argument 4", + }); + minDepth = webidl.converters.float(minDepth, { + prefix, + context: "Argument 5", + }); + maxDepth = webidl.converters.float(maxDepth, { + prefix, + context: "Argument 6", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_viewport({ + renderPassRid, + x, + y, + width, + height, + minDepth, + maxDepth, + }); } - class GPURenderBundleEncoder { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + setScissorRect(x, y, width, height) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + x = webidl.converters.GPUIntegerCoordinate(x, { + prefix, + context: "Argument 1", + }); + y = webidl.converters.GPUIntegerCoordinate(y, { + prefix, + context: "Argument 2", + }); + width = webidl.converters.GPUIntegerCoordinate(width, { + prefix, + context: "Argument 3", + }); + height = webidl.converters.GPUIntegerCoordinate(height, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_scissor_rect( + renderPassRid, + x, + y, + width, + height, + ); + } - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } + /** + * @param {GPUColor} color + */ + setBlendConstant(color) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + color = webidl.converters.GPUColor(color, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_blend_constant( + renderPassRid, + normalizeGPUColor(color), + ); + } - constructor() { - webidl.illegalConstructor(); - } + /** + * @param {number} reference + */ + setStencilReference(reference) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reference = webidl.converters.GPUStencilValue(reference, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_stencil_reference( + renderPassRid, + reference, + ); + } - /** - * @param {GPURenderBundleDescriptor} descriptor - */ - finish(descriptor = {}) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; - descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { + beginOcclusionQuery(_queryIndex) { + throw new Error("Not yet implemented"); + } + + endOcclusionQuery() { + throw new Error("Not yet implemented"); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_begin_pipeline_statistics_query( + renderPassRid, + querySetRid, + queryIndex, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_end_pipeline_statistics_query(renderPassRid); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_write_timestamp( + renderPassRid, + querySetRid, + queryIndex, + ); + } + + /** + * @param {GPURenderBundle[]} bundles + */ + executeBundles(bundles) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bundles = webidl.converters["sequence"](bundles, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { + const context = `bundle ${i + 1}`; + const rid = assertResource(bundle, { prefix, context }); + assertDeviceMatch(device, bundle, { prefix, - context: "Argument 1", + resourceContext: context, + selfContext: "this", }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish( - renderBundleEncoderRid, - descriptor.label, - ); - device.pushError(err); - this[_rid] = undefined; + return rid; + }); + ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids); + } - const renderBundle = createGPURenderBundle( - descriptor.label, - device, - rid, - ); - device.trackResource(renderBundle); - return renderBundle; + end() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'end' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const { err } = ops.op_webgpu_render_pass_end( + commandEncoderRid, + renderPassRid, + ); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) + ) { + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; } - - // TODO(lucacasonato): has an overload - setBindGroup( + ops.op_webgpu_render_pass_set_bind_group( + renderPassRid, index, - bindGroup, + bindGroupRid, dynamicOffsetsData, dynamicOffsetsDataStart, dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_render_bundle_encoder_set_bind_group( - renderBundleEncoderRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } + ); + } - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_push_debug_group( - renderBundleEncoderRid, - groupLabel, - ); - } + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel); + } - popDebugGroup() { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_pop_debug_group( - renderBundleEncoderRid, - ); - } + popDebugGroup() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_pop_debug_group(renderPassRid); + } - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_insert_debug_marker( - renderBundleEncoderRid, - markerLabel, - ); - } + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel); + } - /** - * @param {GPURenderPipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_pipeline( - renderBundleEncoderRid, - pipelineRid, - ); - } + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid); + } - /** - * @param {GPUBuffer} buffer - * @param {GPUIndexFormat} indexFormat - * @param {number} offset - * @param {number} size - */ - setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { size = webidl.converters.GPUSize64(size, { prefix, context: "Argument 4", }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_index_buffer( - renderBundleEncoderRid, - bufferRid, - indexFormat, - offset, - size, - ); } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_index_buffer( + renderPassRid, + bufferRid, + indexFormat, + offset, + size, + ); + } - /** - * @param {number} slot - * @param {GPUBuffer} buffer - * @param {number} offset - * @param {number} size - */ - setVertexBuffer(slot, buffer, offset = 0, size = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 2", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { size = webidl.converters.GPUSize64(size, { prefix, context: "Argument 4", }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_vertex_buffer( - renderBundleEncoderRid, - slot, - bufferRid, - offset, - size, - ); } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_vertex_buffer( + renderPassRid, + slot, + bufferRid, + offset, + size, + ); + } - /** - * @param {number} vertexCount - * @param {number} instanceCount - * @param {number} firstVertex - * @param {number} firstInstance - */ - draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { - prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 4", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw( - renderBundleEncoderRid, - vertexCount, - instanceCount, - firstVertex, - firstInstance, - ); - } + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_draw( + renderPassRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + ); + } - /** - * @param {number} indexCount - * @param {number} instanceCount - * @param {number} firstIndex - * @param {number} baseVertex - * @param {number} firstInstance - */ - drawIndexed( + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_draw_indexed( + renderPassRid, indexCount, - instanceCount = 1, - firstIndex = 0, - baseVertex = 0, - firstInstance = 0, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_draw_indirect( + renderPassRid, + indirectBufferRid, + indirectOffset, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndexedIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_draw_indexed_indirect( + renderPassRid, + indirectBufferRid, + indirectOffset, + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); +const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; + +/** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPUComputePassEncoder} + */ +function createGPUComputePassEncoder(label, encoder, rid) { + /** @type {GPUComputePassEncoder} */ + const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); + computePassEncoder[_label] = label; + computePassEncoder[_encoder] = encoder; + computePassEncoder[_rid] = rid; + return computePassEncoder; +} + +class GPUComputePassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUComputePipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPUComputePipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); + } + + /** + * @param {number} workgroupCountX + * @param {number} workgroupCountY + * @param {number} workgroupCountZ + */ + dispatchWorkgroups( + workgroupCountX, + workgroupCountY = 1, + workgroupCountZ = 1, + ) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + workgroupCountX = webidl.converters.GPUSize32(workgroupCountX, { + prefix, + context: "Argument 1", + }); + workgroupCountY = webidl.converters.GPUSize32(workgroupCountY, { + prefix, + context: "Argument 2", + }); + workgroupCountZ = webidl.converters.GPUSize32(workgroupCountZ, { + prefix, + context: "Argument 3", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_dispatch_workgroups( + computePassRid, + workgroupCountX, + workgroupCountY, + workgroupCountZ, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( + computePassRid, + indirectBufferRid, + indirectOffset, + ); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_begin_pipeline_statistics_query( + computePassRid, + querySetRid, + queryIndex, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_end_pipeline_statistics_query(computePassRid); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_write_timestamp( + computePassRid, + querySetRid, + queryIndex, + ); + } + + end() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = "Failed to execute 'end' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const { err } = ops.op_webgpu_compute_pass_end( + commandEncoderRid, + computePassRid, + ); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) ) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { - prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 5", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw_indexed( - renderBundleEncoderRid, - indexCount, - instanceCount, - firstIndex, - baseVertex, - firstInstance, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw_indirect( - renderBundleEncoderRid, - indirectBufferRid, - indirectOffset, - ); - } - - drawIndexedIndirect(_indirectBuffer, _indirectOffset) { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; } + ops.op_webgpu_compute_pass_set_bind_group( + computePassRid, + index, + bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ); } - GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); - const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundle} + * @param {string} groupLabel */ - function createGPURenderBundle(label, device, rid) { - /** @type {GPURenderBundle} */ - const bundle = webidl.createBranded(GPURenderBundle); - bundle[_label] = label; - bundle[_device] = device; - bundle[_rid] = rid; - return bundle; + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel); } - class GPURenderBundle { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + popDebugGroup() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_pop_debug_group(computePassRid); } - GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUQuerySet} + * @param {string} markerLabel */ - function createGPUQuerySet(label, device, rid, descriptor) { - /** @type {GPUQuerySet} */ - const queue = webidl.createBranded(GPUQuerySet); - queue[_label] = label; - queue[_device] = device; - queue[_rid] = rid; - queue[_descriptor] = descriptor; - return queue; + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_insert_debug_marker( + computePassRid, + markerLabel, + ); } - class GPUQuerySet { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {GPUQuerySetDescriptor} */ - [_descriptor]; - /** @type {GPUQueryType} */ - [_type]; - /** @type {number} */ - [_count]; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); +const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandBuffer} + */ +function createGPUCommandBuffer(label, device, rid) { + /** @type {GPUCommandBuffer} */ + const commandBuffer = webidl.createBranded(GPUCommandBuffer); + commandBuffer[_label] = label; + commandBuffer[_device] = device; + commandBuffer[_rid] = rid; + return commandBuffer; +} - constructor() { - webidl.illegalConstructor(); - } +class GPUCommandBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; - destroy() { - webidl.assertBranded(this, GPUQuerySetPrototype); - this[_cleanup](); - } - - get type() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_type](); - } - - get count() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_count](); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } } - GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); - const GPUQuerySetPrototype = GPUQuerySet.prototype; - window.__bootstrap.webgpu = { - _device, - assertDevice, - createGPUTexture, - gpu: webidl.createBranded(GPU), - GPU, - GPUAdapter, - GPUAdapterInfo, - GPUSupportedLimits, - GPUSupportedFeatures, - GPUDevice, - GPUDeviceLostInfo, - GPUQueue, - GPUBuffer, - GPUBufferUsage, - GPUMapMode, - GPUTextureUsage, - GPUTexture, - GPUTextureView, - GPUSampler, - GPUBindGroupLayout, - GPUPipelineLayout, - GPUBindGroup, - GPUShaderModule, - GPUShaderStage, - GPUComputePipeline, - GPURenderPipeline, - GPUColorWrite, - GPUCommandEncoder, - GPURenderPassEncoder, - GPUComputePassEncoder, - GPUCommandBuffer, - GPURenderBundleEncoder, - GPURenderBundle, - GPUQuerySet, - GPUError, - GPUValidationError, - GPUOutOfMemoryError, - }; -})(this); + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundleEncoder} + */ +function createGPURenderBundleEncoder(label, device, rid) { + /** @type {GPURenderBundleEncoder} */ + const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); + bundleEncoder[_label] = label; + bundleEncoder[_device] = device; + bundleEncoder[_rid] = rid; + return bundleEncoder; +} + +class GPURenderBundleEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderBundleDescriptor} descriptor + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; + descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish( + renderBundleEncoderRid, + descriptor.label, + ); + device.pushError(err); + this[_rid] = undefined; + + const renderBundle = createGPURenderBundle( + descriptor.label, + device, + rid, + ); + device.trackResource(renderBundle); + return renderBundle; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) + ) { + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; + } + ops.op_webgpu_render_bundle_encoder_set_bind_group( + renderBundleEncoderRid, + index, + bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_push_debug_group( + renderBundleEncoderRid, + groupLabel, + ); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_pop_debug_group( + renderBundleEncoderRid, + ); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_insert_debug_marker( + renderBundleEncoderRid, + markerLabel, + ); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_pipeline( + renderBundleEncoderRid, + pipelineRid, + ); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_index_buffer( + renderBundleEncoderRid, + bufferRid, + indexFormat, + offset, + size, + ); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_vertex_buffer( + renderBundleEncoderRid, + slot, + bufferRid, + offset, + size, + ); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw( + renderBundleEncoderRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + ); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw_indexed( + renderBundleEncoderRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw_indirect( + renderBundleEncoderRid, + indirectBufferRid, + indirectOffset, + ); + } + + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); +const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundle} + */ +function createGPURenderBundle(label, device, rid) { + /** @type {GPURenderBundle} */ + const bundle = webidl.createBranded(GPURenderBundle); + bundle[_label] = label; + bundle[_device] = device; + bundle[_rid] = rid; + return bundle; +} + +class GPURenderBundle { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUQuerySet} + */ +function createGPUQuerySet(label, device, rid, descriptor) { + /** @type {GPUQuerySet} */ + const queue = webidl.createBranded(GPUQuerySet); + queue[_label] = label; + queue[_device] = device; + queue[_rid] = rid; + queue[_descriptor] = descriptor; + return queue; +} + +class GPUQuerySet { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {GPUQuerySetDescriptor} */ + [_descriptor]; + /** @type {GPUQueryType} */ + [_type]; + /** @type {number} */ + [_count]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + destroy() { + webidl.assertBranded(this, GPUQuerySetPrototype); + this[_cleanup](); + } + + get type() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_type](); + } + + get count() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_count](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); +const GPUQuerySetPrototype = GPUQuerySet.prototype; + +const gpu = webidl.createBranded(GPU); +export { + _device, + assertDevice, + createGPUTexture, + GPU, + gpu, + GPUAdapter, + GPUAdapterInfo, + GPUBindGroup, + GPUBindGroupLayout, + GPUBuffer, + GPUBufferUsage, + GPUColorWrite, + GPUCommandBuffer, + GPUCommandEncoder, + GPUComputePassEncoder, + GPUComputePipeline, + GPUDevice, + GPUDeviceLostInfo, + GPUError, + GPUMapMode, + GPUOutOfMemoryError, + GPUPipelineLayout, + GPUQuerySet, + GPUQueue, + GPURenderBundle, + GPURenderBundleEncoder, + GPURenderPassEncoder, + GPURenderPipeline, + GPUSampler, + GPUShaderModule, + GPUShaderStage, + GPUSupportedFeatures, + GPUSupportedLimits, + GPUTexture, + GPUTextureUsage, + GPUTextureView, + GPUValidationError, +}; diff --git a/ext/webgpu/src/02_idl_types.js b/ext/webgpu/src/02_idl_types.js index c927f10a5a..daafdfa4b2 100644 --- a/ext/webgpu/src/02_idl_types.js +++ b/ext/webgpu/src/02_idl_types.js @@ -3,2038 +3,2034 @@ // @ts-check /// -"use strict"; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { + GPU, + GPUAdapter, + GPUBindGroup, + GPUBindGroupLayout, + GPUBuffer, + GPUBufferUsage, + GPUColorWrite, + GPUCommandBuffer, + GPUCommandEncoder, + GPUComputePassEncoder, + GPUComputePipeline, + GPUDevice, + GPUMapMode, + GPUOutOfMemoryError, + GPUPipelineLayout, + GPUQuerySet, + GPUQueue, + GPURenderBundle, + GPURenderBundleEncoder, + GPURenderPassEncoder, + GPURenderPipeline, + GPUSampler, + GPUShaderModule, + GPUShaderStage, + GPUSupportedFeatures, + GPUSupportedLimits, + GPUTexture, + GPUTextureUsage, + GPUTextureView, + GPUValidationError, +} from "internal:ext/webgpu/01_webgpu.js"; +const primordials = globalThis.__bootstrap.primordials; +const { SymbolIterator, TypeError } = primordials; -((window) => { - const webidl = window.__bootstrap.webidl; - const { - GPU, - GPUAdapter, - GPUSupportedLimits, - GPUSupportedFeatures, - GPUDevice, - GPUQueue, - GPUBuffer, - GPUBufferUsage, - GPUMapMode, - GPUTextureUsage, - GPUTexture, - GPUTextureView, - GPUSampler, - GPUBindGroupLayout, - GPUPipelineLayout, - GPUBindGroup, - GPUShaderModule, - GPUShaderStage, - GPUComputePipeline, - GPURenderPipeline, - GPUColorWrite, - GPUCommandEncoder, - GPURenderPassEncoder, - GPUComputePassEncoder, - GPUCommandBuffer, - GPURenderBundleEncoder, - GPURenderBundle, - GPUQuerySet, - GPUOutOfMemoryError, - GPUValidationError, - } = window.__bootstrap.webgpu; - const { SymbolIterator, TypeError } = window.__bootstrap.primordials; - - // This needs to be initialized after all of the base classes are implemented, - // otherwise their converters might not be available yet. - // DICTIONARY: GPUObjectDescriptorBase - const dictMembersGPUObjectDescriptorBase = [ - { key: "label", converter: webidl.converters["USVString"] }, - ]; - webidl.converters["GPUObjectDescriptorBase"] = webidl - .createDictionaryConverter( - "GPUObjectDescriptorBase", - dictMembersGPUObjectDescriptorBase, - ); - - // INTERFACE: GPUSupportedLimits - webidl.converters.GPUSupportedLimits = webidl.createInterfaceConverter( - "GPUSupportedLimits", - GPUSupportedLimits.prototype, - ); - - // INTERFACE: GPUSupportedFeatures - webidl.converters.GPUSupportedFeatures = webidl.createInterfaceConverter( - "GPUSupportedFeatures", - GPUSupportedFeatures.prototype, - ); - - // INTERFACE: GPU - webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU.prototype); - - // ENUM: GPUPowerPreference - webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( - "GPUPowerPreference", - [ - "low-power", - "high-performance", - ], - ); - - // DICTIONARY: GPURequestAdapterOptions - const dictMembersGPURequestAdapterOptions = [ - { - key: "powerPreference", - converter: webidl.converters["GPUPowerPreference"], - }, - { - key: "forceFallbackAdapter", - converter: webidl.converters.boolean, - defaultValue: false, - }, - ]; - webidl.converters["GPURequestAdapterOptions"] = webidl - .createDictionaryConverter( - "GPURequestAdapterOptions", - dictMembersGPURequestAdapterOptions, - ); - - // INTERFACE: GPUAdapter - webidl.converters.GPUAdapter = webidl.createInterfaceConverter( - "GPUAdapter", - GPUAdapter.prototype, - ); - - // ENUM: GPUFeatureName - webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( - "GPUFeatureName", - [ - "depth-clip-control", - "depth32float-stencil8", - "pipeline-statistics-query", - "texture-compression-bc", - "texture-compression-etc2", - "texture-compression-astc", - "timestamp-query", - "indirect-first-instance", - "shader-f16", - // extended from spec - "mappable-primary-buffers", - "texture-binding-array", - "buffer-binding-array", - "storage-resource-binding-array", - "sampled-texture-and-storage-buffer-array-non-uniform-indexing", - "uniform-buffer-and-storage-buffer-texture-non-uniform-indexing", - "unsized-binding-array", - "multi-draw-indirect", - "multi-draw-indirect-count", - "push-constants", - "address-mode-clamp-to-border", - "texture-adapter-specific-format-features", - "shader-float64", - "vertex-attribute-64bit", - "conservative-rasterization", - "vertex-writable-storage", - "clear-commands", - "spirv-shader-passthrough", - "shader-primitive-index", - ], - ); - - // TYPEDEF: GPUSize32 - webidl.converters["GPUSize32"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUSize64 - webidl.converters["GPUSize64"] = (V, opts) => - webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUDeviceDescriptor - const dictMembersGPUDeviceDescriptor = [ - { - key: "requiredFeatures", - converter: webidl.createSequenceConverter( - webidl.converters["GPUFeatureName"], - ), - get defaultValue() { - return []; - }, - }, - { - key: "requiredLimits", - converter: webidl.createRecordConverter( - webidl.converters["DOMString"], - webidl.converters["GPUSize64"], - ), - }, - ]; - webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( - "GPUDeviceDescriptor", +// This needs to be initialized after all of the base classes are implemented, +// otherwise their converters might not be available yet. +// DICTIONARY: GPUObjectDescriptorBase +const dictMembersGPUObjectDescriptorBase = [ + { key: "label", converter: webidl.converters["USVString"] }, +]; +webidl.converters["GPUObjectDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUObjectDescriptorBase", dictMembersGPUObjectDescriptorBase, - dictMembersGPUDeviceDescriptor, ); - // INTERFACE: GPUDevice - webidl.converters.GPUDevice = webidl.createInterfaceConverter( - "GPUDevice", - GPUDevice.prototype, +// INTERFACE: GPUSupportedLimits +webidl.converters.GPUSupportedLimits = webidl.createInterfaceConverter( + "GPUSupportedLimits", + GPUSupportedLimits.prototype, +); + +// INTERFACE: GPUSupportedFeatures +webidl.converters.GPUSupportedFeatures = webidl.createInterfaceConverter( + "GPUSupportedFeatures", + GPUSupportedFeatures.prototype, +); + +// INTERFACE: GPU +webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU.prototype); + +// ENUM: GPUPowerPreference +webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( + "GPUPowerPreference", + [ + "low-power", + "high-performance", + ], +); + +// DICTIONARY: GPURequestAdapterOptions +const dictMembersGPURequestAdapterOptions = [ + { + key: "powerPreference", + converter: webidl.converters["GPUPowerPreference"], + }, + { + key: "forceFallbackAdapter", + converter: webidl.converters.boolean, + defaultValue: false, + }, +]; +webidl.converters["GPURequestAdapterOptions"] = webidl + .createDictionaryConverter( + "GPURequestAdapterOptions", + dictMembersGPURequestAdapterOptions, ); - // INTERFACE: GPUBuffer - webidl.converters.GPUBuffer = webidl.createInterfaceConverter( - "GPUBuffer", - GPUBuffer.prototype, - ); +// INTERFACE: GPUAdapter +webidl.converters.GPUAdapter = webidl.createInterfaceConverter( + "GPUAdapter", + GPUAdapter.prototype, +); - // TYPEDEF: GPUBufferUsageFlags - webidl.converters["GPUBufferUsageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); +// ENUM: GPUFeatureName +webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( + "GPUFeatureName", + [ + "depth-clip-control", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "texture-compression-etc2", + "texture-compression-astc", + "timestamp-query", + "indirect-first-instance", + "shader-f16", + // extended from spec + "mappable-primary-buffers", + "texture-binding-array", + "buffer-binding-array", + "storage-resource-binding-array", + "sampled-texture-and-storage-buffer-array-non-uniform-indexing", + "uniform-buffer-and-storage-buffer-texture-non-uniform-indexing", + "unsized-binding-array", + "multi-draw-indirect", + "multi-draw-indirect-count", + "push-constants", + "address-mode-clamp-to-border", + "texture-adapter-specific-format-features", + "shader-float64", + "vertex-attribute-64bit", + "conservative-rasterization", + "vertex-writable-storage", + "clear-commands", + "spirv-shader-passthrough", + "shader-primitive-index", + ], +); - // DICTIONARY: GPUBufferDescriptor - const dictMembersGPUBufferDescriptor = [ - { key: "size", converter: webidl.converters["GPUSize64"], required: true }, - { - key: "usage", - converter: webidl.converters["GPUBufferUsageFlags"], - required: true, +// TYPEDEF: GPUSize32 +webidl.converters["GPUSize32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUSize64 +webidl.converters["GPUSize64"] = (V, opts) => + webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUDeviceDescriptor +const dictMembersGPUDeviceDescriptor = [ + { + key: "requiredFeatures", + converter: webidl.createSequenceConverter( + webidl.converters["GPUFeatureName"], + ), + get defaultValue() { + return []; }, - { - key: "mappedAtCreation", - converter: webidl.converters["boolean"], - defaultValue: false, + }, + { + key: "requiredLimits", + converter: webidl.createRecordConverter( + webidl.converters["DOMString"], + webidl.converters["GPUSize64"], + ), + }, +]; +webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( + "GPUDeviceDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUDeviceDescriptor, +); + +// INTERFACE: GPUDevice +webidl.converters.GPUDevice = webidl.createInterfaceConverter( + "GPUDevice", + GPUDevice.prototype, +); + +// INTERFACE: GPUBuffer +webidl.converters.GPUBuffer = webidl.createInterfaceConverter( + "GPUBuffer", + GPUBuffer.prototype, +); + +// TYPEDEF: GPUBufferUsageFlags +webidl.converters["GPUBufferUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUBufferDescriptor +const dictMembersGPUBufferDescriptor = [ + { key: "size", converter: webidl.converters["GPUSize64"], required: true }, + { + key: "usage", + converter: webidl.converters["GPUBufferUsageFlags"], + required: true, + }, + { + key: "mappedAtCreation", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( + "GPUBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBufferDescriptor, +); + +// INTERFACE: GPUBufferUsage +webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( + "GPUBufferUsage", + GPUBufferUsage.prototype, +); + +// TYPEDEF: GPUMapModeFlags +webidl.converters["GPUMapModeFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// INTERFACE: GPUMapMode +webidl.converters.GPUMapMode = webidl.createInterfaceConverter( + "GPUMapMode", + GPUMapMode.prototype, +); + +// INTERFACE: GPUTexture +webidl.converters.GPUTexture = webidl.createInterfaceConverter( + "GPUTexture", + GPUTexture.prototype, +); + +// TYPEDEF: GPUIntegerCoordinate +webidl.converters["GPUIntegerCoordinate"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPUIntegerCoordinate"]); + +// DICTIONARY: GPUExtent3DDict +const dictMembersGPUExtent3DDict = [ + { + key: "width", + converter: webidl.converters["GPUIntegerCoordinate"], + required: true, + }, + { + key: "height", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "depthOrArrayLayers", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, +]; +webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( + "GPUExtent3DDict", + dictMembersGPUExtent3DDict, +); + +// TYPEDEF: GPUExtent3D +webidl.converters["GPUExtent3D"] = (V, opts) => { + // Union for (sequence or GPUExtent3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUExtent3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUExtent3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUExtent3DDict.", + opts, + ); +}; + +// ENUM: GPUTextureDimension +webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( + "GPUTextureDimension", + [ + "1d", + "2d", + "3d", + ], +); + +// ENUM: GPUTextureFormat +webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( + "GPUTextureFormat", + [ + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + "rgba32uint", + "rgba32sint", + "rgba32float", + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + "depth32float-stencil8", + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb", + "etc2-rgb8unorm", + "etc2-rgb8unorm-srgb", + "etc2-rgb8a1unorm", + "etc2-rgb8a1unorm-srgb", + "etc2-rgba8unorm", + "etc2-rgba8unorm-srgb", + "eac-r11unorm", + "eac-r11snorm", + "eac-rg11unorm", + "eac-rg11snorm", + "astc-4x4-unorm", + "astc-4x4-unorm-srgb", + "astc-5x4-unorm", + "astc-5x4-unorm-srgb", + "astc-5x5-unorm", + "astc-5x5-unorm-srgb", + "astc-6x5-unorm", + "astc-6x5-unorm-srgb", + "astc-6x6-unorm", + "astc-6x6-unorm-srgb", + "astc-8x5-unorm", + "astc-8x5-unorm-srgb", + "astc-8x6-unorm", + "astc-8x6-unorm-srgb", + "astc-8x8-unorm", + "astc-8x8-unorm-srgb", + "astc-10x5-unorm", + "astc-10x5-unorm-srgb", + "astc-10x6-unorm", + "astc-10x6-unorm-srgb", + "astc-10x8-unorm", + "astc-10x8-unorm-srgb", + "astc-10x10-unorm", + "astc-10x10-unorm-srgb", + "astc-12x10-unorm", + "astc-12x10-unorm-srgb", + "astc-12x12-unorm", + "astc-12x12-unorm-srgb", + ], +); + +// TYPEDEF: GPUTextureUsageFlags +webidl.converters["GPUTextureUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUTextureDescriptor +const dictMembersGPUTextureDescriptor = [ + { + key: "size", + converter: webidl.converters["GPUExtent3D"], + required: true, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "dimension", + converter: webidl.converters["GPUTextureDimension"], + defaultValue: "2d", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + required: true, + }, + { + key: "viewFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + get defaultValue() { + return []; }, - ]; - webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( - "GPUBufferDescriptor", + }, +]; +webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( + "GPUTextureDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureDescriptor, +); + +// INTERFACE: GPUTextureUsage +webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( + "GPUTextureUsage", + GPUTextureUsage.prototype, +); + +// INTERFACE: GPUTextureView +webidl.converters.GPUTextureView = webidl.createInterfaceConverter( + "GPUTextureView", + GPUTextureView.prototype, +); + +// ENUM: GPUTextureViewDimension +webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( + "GPUTextureViewDimension", + [ + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d", + ], +); + +// ENUM: GPUTextureAspect +webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( + "GPUTextureAspect", + [ + "all", + "stencil-only", + "depth-only", + ], +); + +// DICTIONARY: GPUTextureViewDescriptor +const dictMembersGPUTextureViewDescriptor = [ + { key: "format", converter: webidl.converters["GPUTextureFormat"] }, + { + key: "dimension", + converter: webidl.converters["GPUTextureViewDimension"], + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + { + key: "baseMipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + { + key: "baseArrayLayer", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "arrayLayerCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, +]; +webidl.converters["GPUTextureViewDescriptor"] = webidl + .createDictionaryConverter( + "GPUTextureViewDescriptor", dictMembersGPUObjectDescriptorBase, - dictMembersGPUBufferDescriptor, + dictMembersGPUTextureViewDescriptor, ); - // INTERFACE: GPUBufferUsage - webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( - "GPUBufferUsage", - GPUBufferUsage.prototype, +// INTERFACE: GPUSampler +webidl.converters.GPUSampler = webidl.createInterfaceConverter( + "GPUSampler", + GPUSampler.prototype, +); + +// ENUM: GPUAddressMode +webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( + "GPUAddressMode", + [ + "clamp-to-edge", + "repeat", + "mirror-repeat", + ], +); + +// ENUM: GPUFilterMode +webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( + "GPUFilterMode", + [ + "nearest", + "linear", + ], +); + +// ENUM: GPUMipmapFilterMode +webidl.converters["GPUMipmapFilterMode"] = webidl.createEnumConverter( + "GPUMipmapFilterMode", + [ + "nearest", + "linear", + ], +); + +// ENUM: GPUCompareFunction +webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( + "GPUCompareFunction", + [ + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always", + ], +); + +// DICTIONARY: GPUSamplerDescriptor +const dictMembersGPUSamplerDescriptor = [ + { + key: "addressModeU", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeV", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeW", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "magFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "minFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "mipmapFilter", + converter: webidl.converters["GPUMipmapFilterMode"], + defaultValue: "nearest", + }, + { + key: "lodMinClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "lodMaxClamp", + converter: webidl.converters["float"], + defaultValue: 0xffffffff, + }, + { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, + { + key: "maxAnisotropy", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, clamp: true }), + defaultValue: 1, + }, +]; +webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( + "GPUSamplerDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUSamplerDescriptor, +); + +// INTERFACE: GPUBindGroupLayout +webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( + "GPUBindGroupLayout", + GPUBindGroupLayout.prototype, +); + +// TYPEDEF: GPUIndex32 +webidl.converters["GPUIndex32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUShaderStageFlags +webidl.converters["GPUShaderStageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// ENUM: GPUBufferBindingType +webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( + "GPUBufferBindingType", + [ + "uniform", + "storage", + "read-only-storage", + ], +); + +// DICTIONARY: GPUBufferBindingLayout +const dictMembersGPUBufferBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUBufferBindingType"], + defaultValue: "uniform", + }, + { + key: "hasDynamicOffset", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "minBindingSize", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, +]; +webidl.converters["GPUBufferBindingLayout"] = webidl + .createDictionaryConverter( + "GPUBufferBindingLayout", + dictMembersGPUBufferBindingLayout, ); - // TYPEDEF: GPUMapModeFlags - webidl.converters["GPUMapModeFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); +// ENUM: GPUSamplerBindingType +webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( + "GPUSamplerBindingType", + [ + "filtering", + "non-filtering", + "comparison", + ], +); - // INTERFACE: GPUMapMode - webidl.converters.GPUMapMode = webidl.createInterfaceConverter( - "GPUMapMode", - GPUMapMode.prototype, +// DICTIONARY: GPUSamplerBindingLayout +const dictMembersGPUSamplerBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUSamplerBindingType"], + defaultValue: "filtering", + }, +]; +webidl.converters["GPUSamplerBindingLayout"] = webidl + .createDictionaryConverter( + "GPUSamplerBindingLayout", + dictMembersGPUSamplerBindingLayout, ); - // INTERFACE: GPUTexture - webidl.converters.GPUTexture = webidl.createInterfaceConverter( - "GPUTexture", - GPUTexture.prototype, +// ENUM: GPUTextureSampleType +webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( + "GPUTextureSampleType", + [ + "float", + "unfilterable-float", + "depth", + "sint", + "uint", + ], +); + +// DICTIONARY: GPUTextureBindingLayout +const dictMembersGPUTextureBindingLayout = [ + { + key: "sampleType", + converter: webidl.converters["GPUTextureSampleType"], + defaultValue: "float", + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + { + key: "multisampled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUTextureBindingLayout", + dictMembersGPUTextureBindingLayout, ); - // TYPEDEF: GPUIntegerCoordinate - webidl.converters["GPUIntegerCoordinate"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPUIntegerCoordinate"]); +// ENUM: GPUStorageTextureAccess +webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( + "GPUStorageTextureAccess", + [ + "write-only", + ], +); - // DICTIONARY: GPUExtent3DDict - const dictMembersGPUExtent3DDict = [ - { - key: "width", - converter: webidl.converters["GPUIntegerCoordinate"], - required: true, - }, - { - key: "height", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - { - key: "depthOrArrayLayers", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - ]; - webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( - "GPUExtent3DDict", - dictMembersGPUExtent3DDict, +// DICTIONARY: GPUStorageTextureBindingLayout +const dictMembersGPUStorageTextureBindingLayout = [ + { + key: "access", + converter: webidl.converters["GPUStorageTextureAccess"], + defaultValue: "write-only", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, +]; +webidl.converters["GPUStorageTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUStorageTextureBindingLayout", + dictMembersGPUStorageTextureBindingLayout, ); - // TYPEDEF: GPUExtent3D - webidl.converters["GPUExtent3D"] = (V, opts) => { - // Union for (sequence or GPUExtent3DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUExtent3DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUExtent3DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUExtent3DDict.", - opts, - ); - }; - - // ENUM: GPUTextureDimension - webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( - "GPUTextureDimension", - [ - "1d", - "2d", - "3d", - ], +// DICTIONARY: GPUBindGroupLayoutEntry +const dictMembersGPUBindGroupLayoutEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "visibility", + converter: webidl.converters["GPUShaderStageFlags"], + required: true, + }, + { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, + { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, + { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, + { + key: "storageTexture", + converter: webidl.converters["GPUStorageTextureBindingLayout"], + }, +]; +webidl.converters["GPUBindGroupLayoutEntry"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutEntry", + dictMembersGPUBindGroupLayoutEntry, ); - // ENUM: GPUTextureFormat - webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( - "GPUTextureFormat", - [ - "r8unorm", - "r8snorm", - "r8uint", - "r8sint", - "r16uint", - "r16sint", - "r16float", - "rg8unorm", - "rg8snorm", - "rg8uint", - "rg8sint", - "r32uint", - "r32sint", - "r32float", - "rg16uint", - "rg16sint", - "rg16float", - "rgba8unorm", - "rgba8unorm-srgb", - "rgba8snorm", - "rgba8uint", - "rgba8sint", - "bgra8unorm", - "bgra8unorm-srgb", - "rgb9e5ufloat", - "rgb10a2unorm", - "rg11b10ufloat", - "rg32uint", - "rg32sint", - "rg32float", - "rgba16uint", - "rgba16sint", - "rgba16float", - "rgba32uint", - "rgba32sint", - "rgba32float", - "stencil8", - "depth16unorm", - "depth24plus", - "depth24plus-stencil8", - "depth32float", - "depth32float-stencil8", - "bc1-rgba-unorm", - "bc1-rgba-unorm-srgb", - "bc2-rgba-unorm", - "bc2-rgba-unorm-srgb", - "bc3-rgba-unorm", - "bc3-rgba-unorm-srgb", - "bc4-r-unorm", - "bc4-r-snorm", - "bc5-rg-unorm", - "bc5-rg-snorm", - "bc6h-rgb-ufloat", - "bc6h-rgb-float", - "bc7-rgba-unorm", - "bc7-rgba-unorm-srgb", - "etc2-rgb8unorm", - "etc2-rgb8unorm-srgb", - "etc2-rgb8a1unorm", - "etc2-rgb8a1unorm-srgb", - "etc2-rgba8unorm", - "etc2-rgba8unorm-srgb", - "eac-r11unorm", - "eac-r11snorm", - "eac-rg11unorm", - "eac-rg11snorm", - "astc-4x4-unorm", - "astc-4x4-unorm-srgb", - "astc-5x4-unorm", - "astc-5x4-unorm-srgb", - "astc-5x5-unorm", - "astc-5x5-unorm-srgb", - "astc-6x5-unorm", - "astc-6x5-unorm-srgb", - "astc-6x6-unorm", - "astc-6x6-unorm-srgb", - "astc-8x5-unorm", - "astc-8x5-unorm-srgb", - "astc-8x6-unorm", - "astc-8x6-unorm-srgb", - "astc-8x8-unorm", - "astc-8x8-unorm-srgb", - "astc-10x5-unorm", - "astc-10x5-unorm-srgb", - "astc-10x6-unorm", - "astc-10x6-unorm-srgb", - "astc-10x8-unorm", - "astc-10x8-unorm-srgb", - "astc-10x10-unorm", - "astc-10x10-unorm-srgb", - "astc-12x10-unorm", - "astc-12x10-unorm-srgb", - "astc-12x12-unorm", - "astc-12x12-unorm-srgb", - ], - ); - - // TYPEDEF: GPUTextureUsageFlags - webidl.converters["GPUTextureUsageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUTextureDescriptor - const dictMembersGPUTextureDescriptor = [ - { - key: "size", - converter: webidl.converters["GPUExtent3D"], - required: true, - }, - { - key: "mipLevelCount", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - { - key: "sampleCount", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - { - key: "dimension", - converter: webidl.converters["GPUTextureDimension"], - defaultValue: "2d", - }, - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "usage", - converter: webidl.converters["GPUTextureUsageFlags"], - required: true, - }, - { - key: "viewFormats", - converter: webidl.createSequenceConverter( - webidl.converters["GPUTextureFormat"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( - "GPUTextureDescriptor", +// DICTIONARY: GPUBindGroupLayoutDescriptor +const dictMembersGPUBindGroupLayoutDescriptor = [ + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayoutEntry"], + ), + required: true, + }, +]; +webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutDescriptor", dictMembersGPUObjectDescriptorBase, - dictMembersGPUTextureDescriptor, + dictMembersGPUBindGroupLayoutDescriptor, ); - // INTERFACE: GPUTextureUsage - webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( - "GPUTextureUsage", - GPUTextureUsage.prototype, - ); +// INTERFACE: GPUShaderStage +webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( + "GPUShaderStage", + GPUShaderStage.prototype, +); - // INTERFACE: GPUTextureView - webidl.converters.GPUTextureView = webidl.createInterfaceConverter( - "GPUTextureView", - GPUTextureView.prototype, - ); +// INTERFACE: GPUBindGroup +webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( + "GPUBindGroup", + GPUBindGroup.prototype, +); - // ENUM: GPUTextureViewDimension - webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( - "GPUTextureViewDimension", - [ - "1d", - "2d", - "2d-array", - "cube", - "cube-array", - "3d", - ], - ); +// DICTIONARY: GPUBufferBinding +const dictMembersGPUBufferBinding = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "size", converter: webidl.converters["GPUSize64"] }, +]; +webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( + "GPUBufferBinding", + dictMembersGPUBufferBinding, +); - // ENUM: GPUTextureAspect - webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( - "GPUTextureAspect", - [ - "all", - "stencil-only", - "depth-only", - ], - ); +// TYPEDEF: GPUBindingResource +webidl.converters["GPUBindingResource"] = + webidl.converters.any /** put union here! **/; - // DICTIONARY: GPUTextureViewDescriptor - const dictMembersGPUTextureViewDescriptor = [ - { key: "format", converter: webidl.converters["GPUTextureFormat"] }, - { - key: "dimension", - converter: webidl.converters["GPUTextureViewDimension"], - }, - { - key: "aspect", - converter: webidl.converters["GPUTextureAspect"], - defaultValue: "all", - }, - { - key: "baseMipLevel", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "mipLevelCount", - converter: webidl.converters["GPUIntegerCoordinate"], - }, - { - key: "baseArrayLayer", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "arrayLayerCount", - converter: webidl.converters["GPUIntegerCoordinate"], - }, - ]; - webidl.converters["GPUTextureViewDescriptor"] = webidl - .createDictionaryConverter( - "GPUTextureViewDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUTextureViewDescriptor, - ); +// DICTIONARY: GPUBindGroupEntry +const dictMembersGPUBindGroupEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "resource", + converter: webidl.converters["GPUBindingResource"], + required: true, + }, +]; +webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( + "GPUBindGroupEntry", + dictMembersGPUBindGroupEntry, +); - // INTERFACE: GPUSampler - webidl.converters.GPUSampler = webidl.createInterfaceConverter( - "GPUSampler", - GPUSampler.prototype, - ); - - // ENUM: GPUAddressMode - webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( - "GPUAddressMode", - [ - "clamp-to-edge", - "repeat", - "mirror-repeat", - ], - ); - - // ENUM: GPUFilterMode - webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( - "GPUFilterMode", - [ - "nearest", - "linear", - ], - ); - - // ENUM: GPUMipmapFilterMode - webidl.converters["GPUMipmapFilterMode"] = webidl.createEnumConverter( - "GPUMipmapFilterMode", - [ - "nearest", - "linear", - ], - ); - - // ENUM: GPUCompareFunction - webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( - "GPUCompareFunction", - [ - "never", - "less", - "equal", - "less-equal", - "greater", - "not-equal", - "greater-equal", - "always", - ], - ); - - // DICTIONARY: GPUSamplerDescriptor - const dictMembersGPUSamplerDescriptor = [ - { - key: "addressModeU", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "addressModeV", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "addressModeW", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "magFilter", - converter: webidl.converters["GPUFilterMode"], - defaultValue: "nearest", - }, - { - key: "minFilter", - converter: webidl.converters["GPUFilterMode"], - defaultValue: "nearest", - }, - { - key: "mipmapFilter", - converter: webidl.converters["GPUMipmapFilterMode"], - defaultValue: "nearest", - }, - { - key: "lodMinClamp", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "lodMaxClamp", - converter: webidl.converters["float"], - defaultValue: 0xffffffff, - }, - { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, - { - key: "maxAnisotropy", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, clamp: true }), - defaultValue: 1, - }, - ]; - webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( - "GPUSamplerDescriptor", +// DICTIONARY: GPUBindGroupDescriptor +const dictMembersGPUBindGroupDescriptor = [ + { + key: "layout", + converter: webidl.converters["GPUBindGroupLayout"], + required: true, + }, + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupEntry"], + ), + required: true, + }, +]; +webidl.converters["GPUBindGroupDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupDescriptor", dictMembersGPUObjectDescriptorBase, - dictMembersGPUSamplerDescriptor, + dictMembersGPUBindGroupDescriptor, ); - // INTERFACE: GPUBindGroupLayout - webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( - "GPUBindGroupLayout", - GPUBindGroupLayout.prototype, - ); - - // TYPEDEF: GPUIndex32 - webidl.converters["GPUIndex32"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUShaderStageFlags - webidl.converters["GPUShaderStageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // ENUM: GPUBufferBindingType - webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( - "GPUBufferBindingType", - [ - "uniform", - "storage", - "read-only-storage", - ], - ); - - // DICTIONARY: GPUBufferBindingLayout - const dictMembersGPUBufferBindingLayout = [ - { - key: "type", - converter: webidl.converters["GPUBufferBindingType"], - defaultValue: "uniform", - }, - { - key: "hasDynamicOffset", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "minBindingSize", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUBufferBindingLayout"] = webidl - .createDictionaryConverter( - "GPUBufferBindingLayout", - dictMembersGPUBufferBindingLayout, - ); - - // ENUM: GPUSamplerBindingType - webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( - "GPUSamplerBindingType", - [ - "filtering", - "non-filtering", - "comparison", - ], - ); - - // DICTIONARY: GPUSamplerBindingLayout - const dictMembersGPUSamplerBindingLayout = [ - { - key: "type", - converter: webidl.converters["GPUSamplerBindingType"], - defaultValue: "filtering", - }, - ]; - webidl.converters["GPUSamplerBindingLayout"] = webidl - .createDictionaryConverter( - "GPUSamplerBindingLayout", - dictMembersGPUSamplerBindingLayout, - ); - - // ENUM: GPUTextureSampleType - webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( - "GPUTextureSampleType", - [ - "float", - "unfilterable-float", - "depth", - "sint", - "uint", - ], - ); - - // DICTIONARY: GPUTextureBindingLayout - const dictMembersGPUTextureBindingLayout = [ - { - key: "sampleType", - converter: webidl.converters["GPUTextureSampleType"], - defaultValue: "float", - }, - { - key: "viewDimension", - converter: webidl.converters["GPUTextureViewDimension"], - defaultValue: "2d", - }, - { - key: "multisampled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUTextureBindingLayout"] = webidl - .createDictionaryConverter( - "GPUTextureBindingLayout", - dictMembersGPUTextureBindingLayout, - ); - - // ENUM: GPUStorageTextureAccess - webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( - "GPUStorageTextureAccess", - [ - "write-only", - ], - ); - - // DICTIONARY: GPUStorageTextureBindingLayout - const dictMembersGPUStorageTextureBindingLayout = [ - { - key: "access", - converter: webidl.converters["GPUStorageTextureAccess"], - defaultValue: "write-only", - }, - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "viewDimension", - converter: webidl.converters["GPUTextureViewDimension"], - defaultValue: "2d", - }, - ]; - webidl.converters["GPUStorageTextureBindingLayout"] = webidl - .createDictionaryConverter( - "GPUStorageTextureBindingLayout", - dictMembersGPUStorageTextureBindingLayout, - ); - - // DICTIONARY: GPUBindGroupLayoutEntry - const dictMembersGPUBindGroupLayoutEntry = [ - { - key: "binding", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - { - key: "visibility", - converter: webidl.converters["GPUShaderStageFlags"], - required: true, - }, - { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, - { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, - { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, - { - key: "storageTexture", - converter: webidl.converters["GPUStorageTextureBindingLayout"], - }, - ]; - webidl.converters["GPUBindGroupLayoutEntry"] = webidl - .createDictionaryConverter( - "GPUBindGroupLayoutEntry", - dictMembersGPUBindGroupLayoutEntry, - ); - - // DICTIONARY: GPUBindGroupLayoutDescriptor - const dictMembersGPUBindGroupLayoutDescriptor = [ - { - key: "entries", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupLayoutEntry"], - ), - required: true, - }, - ]; - webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl - .createDictionaryConverter( - "GPUBindGroupLayoutDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUBindGroupLayoutDescriptor, - ); - - // INTERFACE: GPUShaderStage - webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( - "GPUShaderStage", - GPUShaderStage.prototype, - ); - - // INTERFACE: GPUBindGroup - webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( - "GPUBindGroup", - GPUBindGroup.prototype, - ); - - // DICTIONARY: GPUBufferBinding - const dictMembersGPUBufferBinding = [ - { - key: "buffer", - converter: webidl.converters["GPUBuffer"], - required: true, - }, - { - key: "offset", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - { key: "size", converter: webidl.converters["GPUSize64"] }, - ]; - webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( - "GPUBufferBinding", - dictMembersGPUBufferBinding, - ); - - // TYPEDEF: GPUBindingResource - webidl.converters["GPUBindingResource"] = - webidl.converters.any /** put union here! **/; - - // DICTIONARY: GPUBindGroupEntry - const dictMembersGPUBindGroupEntry = [ - { - key: "binding", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - { - key: "resource", - converter: webidl.converters["GPUBindingResource"], - required: true, - }, - ]; - webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( - "GPUBindGroupEntry", - dictMembersGPUBindGroupEntry, - ); - - // DICTIONARY: GPUBindGroupDescriptor - const dictMembersGPUBindGroupDescriptor = [ - { - key: "layout", - converter: webidl.converters["GPUBindGroupLayout"], - required: true, - }, - { - key: "entries", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupEntry"], - ), - required: true, - }, - ]; - webidl.converters["GPUBindGroupDescriptor"] = webidl - .createDictionaryConverter( - "GPUBindGroupDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUBindGroupDescriptor, - ); - - // INTERFACE: GPUPipelineLayout - webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( - "GPUPipelineLayout", - GPUPipelineLayout.prototype, - ); - - // DICTIONARY: GPUPipelineLayoutDescriptor - const dictMembersGPUPipelineLayoutDescriptor = [ - { - key: "bindGroupLayouts", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupLayout"], - ), - required: true, - }, - ]; - webidl.converters["GPUPipelineLayoutDescriptor"] = webidl - .createDictionaryConverter( - "GPUPipelineLayoutDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineLayoutDescriptor, - ); - - // INTERFACE: GPUShaderModule - webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( - "GPUShaderModule", - GPUShaderModule.prototype, - ); - - // DICTIONARY: GPUShaderModuleDescriptor - const dictMembersGPUShaderModuleDescriptor = [ - { - key: "code", - converter: webidl.converters["DOMString"], - required: true, - }, - ]; - webidl.converters["GPUShaderModuleDescriptor"] = webidl - .createDictionaryConverter( - "GPUShaderModuleDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUShaderModuleDescriptor, - ); - - // // ENUM: GPUCompilationMessageType - // webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( - // "GPUCompilationMessageType", - // [ - // "error", - // "warning", - // "info", - // ], - // ); - - // // INTERFACE: GPUCompilationMessage - // webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( - // "GPUCompilationMessage", - // GPUCompilationMessage.prototype, - // ); - - // // INTERFACE: GPUCompilationInfo - // webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( - // "GPUCompilationInfo", - // GPUCompilationInfo.prototype, - // ); - - webidl.converters["GPUAutoLayoutMode"] = webidl.createEnumConverter( - "GPUAutoLayoutMode", - [ - "auto", - ], - ); - - webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"] = (V, opts) => { - if (typeof V === "object") { - return webidl.converters["GPUPipelineLayout"](V, opts); - } - return webidl.converters["GPUAutoLayoutMode"](V, opts); - }; - - // DICTIONARY: GPUPipelineDescriptorBase - const dictMembersGPUPipelineDescriptorBase = [ - { - key: "layout", - converter: webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"], - }, - ]; - webidl.converters["GPUPipelineDescriptorBase"] = webidl - .createDictionaryConverter( - "GPUPipelineDescriptorBase", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - ); - - // TYPEDEF: GPUPipelineConstantValue - webidl.converters.GPUPipelineConstantValue = webidl.converters.double; - - webidl.converters["record"] = webidl - .createRecordConverter( - webidl.converters.USVString, - webidl.converters.GPUPipelineConstantValue, - ); - - // DICTIONARY: GPUProgrammableStage - const dictMembersGPUProgrammableStage = [ - { - key: "module", - converter: webidl.converters["GPUShaderModule"], - required: true, - }, - { - key: "entryPoint", - converter: webidl.converters["USVString"], - required: true, - }, - { - key: "constants", - converter: - webidl.converters["record"], - }, - ]; - webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( - "GPUProgrammableStage", - dictMembersGPUProgrammableStage, - ); - - // INTERFACE: GPUComputePipeline - webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( - "GPUComputePipeline", - GPUComputePipeline.prototype, - ); - - // DICTIONARY: GPUComputePipelineDescriptor - const dictMembersGPUComputePipelineDescriptor = [ - { - key: "compute", - converter: webidl.converters["GPUProgrammableStage"], - required: true, - }, - ]; - webidl.converters["GPUComputePipelineDescriptor"] = webidl - .createDictionaryConverter( - "GPUComputePipelineDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - dictMembersGPUComputePipelineDescriptor, - ); - - // INTERFACE: GPURenderPipeline - webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( - "GPURenderPipeline", - GPURenderPipeline.prototype, - ); - - // ENUM: GPUVertexStepMode - webidl.converters["GPUVertexStepMode"] = webidl.createEnumConverter( - "GPUVertexStepMode", - [ - "vertex", - "instance", - ], - ); - - // ENUM: GPUVertexFormat - webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( - "GPUVertexFormat", - [ - "uint8x2", - "uint8x4", - "sint8x2", - "sint8x4", - "unorm8x2", - "unorm8x4", - "snorm8x2", - "snorm8x4", - "uint16x2", - "uint16x4", - "sint16x2", - "sint16x4", - "unorm16x2", - "unorm16x4", - "snorm16x2", - "snorm16x4", - "float16x2", - "float16x4", - "float32", - "float32x2", - "float32x3", - "float32x4", - "uint32", - "uint32x2", - "uint32x3", - "uint32x4", - "sint32", - "sint32x2", - "sint32x3", - "sint32x4", - ], - ); - - // DICTIONARY: GPUVertexAttribute - const dictMembersGPUVertexAttribute = [ - { - key: "format", - converter: webidl.converters["GPUVertexFormat"], - required: true, - }, - { - key: "offset", - converter: webidl.converters["GPUSize64"], - required: true, - }, - { - key: "shaderLocation", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - ]; - webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( - "GPUVertexAttribute", - dictMembersGPUVertexAttribute, - ); - - // DICTIONARY: GPUVertexBufferLayout - const dictMembersGPUVertexBufferLayout = [ - { - key: "arrayStride", - converter: webidl.converters["GPUSize64"], - required: true, - }, - { - key: "stepMode", - converter: webidl.converters["GPUVertexStepMode"], - defaultValue: "vertex", - }, - { - key: "attributes", - converter: webidl.createSequenceConverter( - webidl.converters["GPUVertexAttribute"], - ), - required: true, - }, - ]; - webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( - "GPUVertexBufferLayout", - dictMembersGPUVertexBufferLayout, - ); - - // DICTIONARY: GPUVertexState - const dictMembersGPUVertexState = [ - { - key: "buffers", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPUVertexBufferLayout"], - ), - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( - "GPUVertexState", - dictMembersGPUProgrammableStage, - dictMembersGPUVertexState, - ); - - // ENUM: GPUPrimitiveTopology - webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( - "GPUPrimitiveTopology", - [ - "point-list", - "line-list", - "line-strip", - "triangle-list", - "triangle-strip", - ], - ); - - // ENUM: GPUIndexFormat - webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( - "GPUIndexFormat", - [ - "uint16", - "uint32", - ], - ); - - // ENUM: GPUFrontFace - webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( - "GPUFrontFace", - [ - "ccw", - "cw", - ], - ); - - // ENUM: GPUCullMode - webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ - "none", - "front", - "back", - ]); - - // DICTIONARY: GPUPrimitiveState - const dictMembersGPUPrimitiveState = [ - { - key: "topology", - converter: webidl.converters["GPUPrimitiveTopology"], - defaultValue: "triangle-list", - }, - { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, - { - key: "frontFace", - converter: webidl.converters["GPUFrontFace"], - defaultValue: "ccw", - }, - { - key: "cullMode", - converter: webidl.converters["GPUCullMode"], - defaultValue: "none", - }, - { - key: "unclippedDepth", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( - "GPUPrimitiveState", - dictMembersGPUPrimitiveState, - ); - - // ENUM: GPUStencilOperation - webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( - "GPUStencilOperation", - [ - "keep", - "zero", - "replace", - "invert", - "increment-clamp", - "decrement-clamp", - "increment-wrap", - "decrement-wrap", - ], - ); - - // DICTIONARY: GPUStencilFaceState - const dictMembersGPUStencilFaceState = [ - { - key: "compare", - converter: webidl.converters["GPUCompareFunction"], - defaultValue: "always", - }, - { - key: "failOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - { - key: "depthFailOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - { - key: "passOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - ]; - webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( - "GPUStencilFaceState", - dictMembersGPUStencilFaceState, - ); - - // TYPEDEF: GPUStencilValue - webidl.converters["GPUStencilValue"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUDepthBias - webidl.converters["GPUDepthBias"] = (V, opts) => - webidl.converters["long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUDepthStencilState - const dictMembersGPUDepthStencilState = [ - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "depthWriteEnabled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "depthCompare", - converter: webidl.converters["GPUCompareFunction"], - defaultValue: "always", - }, - { - key: "stencilFront", - converter: webidl.converters["GPUStencilFaceState"], - get defaultValue() { - return {}; - }, - }, - { - key: "stencilBack", - converter: webidl.converters["GPUStencilFaceState"], - get defaultValue() { - return {}; - }, - }, - { - key: "stencilReadMask", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "stencilWriteMask", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "depthBias", - converter: webidl.converters["GPUDepthBias"], - defaultValue: 0, - }, - { - key: "depthBiasSlopeScale", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "depthBiasClamp", - converter: webidl.converters["float"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( - "GPUDepthStencilState", - dictMembersGPUDepthStencilState, - ); - - // TYPEDEF: GPUSampleMask - webidl.converters["GPUSampleMask"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUMultisampleState - const dictMembersGPUMultisampleState = [ - { - key: "count", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - { - key: "mask", - converter: webidl.converters["GPUSampleMask"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "alphaToCoverageEnabled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( - "GPUMultisampleState", - dictMembersGPUMultisampleState, - ); - - // ENUM: GPUBlendFactor - webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( - "GPUBlendFactor", - [ - "zero", - "one", - "src", - "one-minus-src", - "src-alpha", - "one-minus-src-alpha", - "dst", - "one-minus-dst", - "dst-alpha", - "one-minus-dst-alpha", - "src-alpha-saturated", - "constant", - "one-minus-constant", - ], - ); - - // ENUM: GPUBlendOperation - webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( - "GPUBlendOperation", - [ - "add", - "subtract", - "reverse-subtract", - "min", - "max", - ], - ); - - // DICTIONARY: GPUBlendComponent - const dictMembersGPUBlendComponent = [ - { - key: "srcFactor", - converter: webidl.converters["GPUBlendFactor"], - defaultValue: "one", - }, - { - key: "dstFactor", - converter: webidl.converters["GPUBlendFactor"], - defaultValue: "zero", - }, - { - key: "operation", - converter: webidl.converters["GPUBlendOperation"], - defaultValue: "add", - }, - ]; - webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( - "GPUBlendComponent", - dictMembersGPUBlendComponent, - ); - - // DICTIONARY: GPUBlendState - const dictMembersGPUBlendState = [ - { - key: "color", - converter: webidl.converters["GPUBlendComponent"], - required: true, - }, - { - key: "alpha", - converter: webidl.converters["GPUBlendComponent"], - required: true, - }, - ]; - webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( - "GPUBlendState", - dictMembersGPUBlendState, - ); - - // TYPEDEF: GPUColorWriteFlags - webidl.converters["GPUColorWriteFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUColorTargetState - const dictMembersGPUColorTargetState = [ - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { key: "blend", converter: webidl.converters["GPUBlendState"] }, - { - key: "writeMask", - converter: webidl.converters["GPUColorWriteFlags"], - defaultValue: 0xF, - }, - ]; - webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( - "GPUColorTargetState", - dictMembersGPUColorTargetState, - ); - - // DICTIONARY: GPUFragmentState - const dictMembersGPUFragmentState = [ - { - key: "targets", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPUColorTargetState"], - ), - ), - required: true, - }, - ]; - webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( - "GPUFragmentState", - dictMembersGPUProgrammableStage, - dictMembersGPUFragmentState, - ); - - // DICTIONARY: GPURenderPipelineDescriptor - const dictMembersGPURenderPipelineDescriptor = [ - { - key: "vertex", - converter: webidl.converters["GPUVertexState"], - required: true, - }, - { - key: "primitive", - converter: webidl.converters["GPUPrimitiveState"], - get defaultValue() { - return {}; - }, - }, - { - key: "depthStencil", - converter: webidl.converters["GPUDepthStencilState"], - }, - { - key: "multisample", - converter: webidl.converters["GPUMultisampleState"], - get defaultValue() { - return {}; - }, - }, - { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, - ]; - webidl.converters["GPURenderPipelineDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderPipelineDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - dictMembersGPURenderPipelineDescriptor, - ); - - // INTERFACE: GPUColorWrite - webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( - "GPUColorWrite", - GPUColorWrite.prototype, - ); - - // INTERFACE: GPUCommandBuffer - webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( - "GPUCommandBuffer", - GPUCommandBuffer.prototype, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); - - // DICTIONARY: GPUCommandBufferDescriptor - const dictMembersGPUCommandBufferDescriptor = []; - webidl.converters["GPUCommandBufferDescriptor"] = webidl - .createDictionaryConverter( - "GPUCommandBufferDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUCommandBufferDescriptor, - ); - - // INTERFACE: GPUCommandEncoder - webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( - "GPUCommandEncoder", - GPUCommandEncoder.prototype, - ); - - // DICTIONARY: GPUCommandEncoderDescriptor - const dictMembersGPUCommandEncoderDescriptor = []; - webidl.converters["GPUCommandEncoderDescriptor"] = webidl - .createDictionaryConverter( - "GPUCommandEncoderDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUCommandEncoderDescriptor, - ); - - // DICTIONARY: GPUImageDataLayout - const dictMembersGPUImageDataLayout = [ - { - key: "offset", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, - { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, - ]; - webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( - "GPUImageDataLayout", - dictMembersGPUImageDataLayout, - ); - - // DICTIONARY: GPUImageCopyBuffer - const dictMembersGPUImageCopyBuffer = [ - { - key: "buffer", - converter: webidl.converters["GPUBuffer"], - required: true, - }, - ]; - webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( - "GPUImageCopyBuffer", - dictMembersGPUImageDataLayout, - dictMembersGPUImageCopyBuffer, - ); - - // DICTIONARY: GPUOrigin3DDict - const dictMembersGPUOrigin3DDict = [ - { - key: "x", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "y", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "z", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( - "GPUOrigin3DDict", - dictMembersGPUOrigin3DDict, - ); - - // TYPEDEF: GPUOrigin3D - webidl.converters["GPUOrigin3D"] = (V, opts) => { - // Union for (sequence or GPUOrigin3DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUOrigin3DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUOrigin3DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUOrigin3DDict.", - opts, - ); - }; - - // DICTIONARY: GPUImageCopyTexture - const dictMembersGPUImageCopyTexture = [ - { - key: "texture", - converter: webidl.converters["GPUTexture"], - required: true, - }, - { - key: "mipLevel", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "origin", - converter: webidl.converters["GPUOrigin3D"], - get defaultValue() { - return {}; - }, - }, - { - key: "aspect", - converter: webidl.converters["GPUTextureAspect"], - defaultValue: "all", - }, - ]; - webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( - "GPUImageCopyTexture", - dictMembersGPUImageCopyTexture, - ); - - // DICTIONARY: GPUOrigin2DDict - const dictMembersGPUOrigin2DDict = [ - { - key: "x", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "y", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( - "GPUOrigin2DDict", - dictMembersGPUOrigin2DDict, - ); - - // TYPEDEF: GPUOrigin2D - webidl.converters["GPUOrigin2D"] = (V, opts) => { - // Union for (sequence or GPUOrigin2DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUOrigin2DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUOrigin2DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUOrigin2DDict.", - opts, - ); - }; - - // INTERFACE: GPUComputePassEncoder - webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( - "GPUComputePassEncoder", - GPUComputePassEncoder.prototype, - ); - - // DICTIONARY: GPUComputePassDescriptor - const dictMembersGPUComputePassDescriptor = []; - webidl.converters["GPUComputePassDescriptor"] = webidl - .createDictionaryConverter( - "GPUComputePassDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUComputePassDescriptor, - ); - - // INTERFACE: GPURenderPassEncoder - webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( - "GPURenderPassEncoder", - GPURenderPassEncoder.prototype, - ); - - // ENUM: GPULoadOp - webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ - "load", - "clear", - ]); - - // DICTIONARY: GPUColorDict - const dictMembersGPUColorDict = [ - { key: "r", converter: webidl.converters["double"], required: true }, - { key: "g", converter: webidl.converters["double"], required: true }, - { key: "b", converter: webidl.converters["double"], required: true }, - { key: "a", converter: webidl.converters["double"], required: true }, - ]; - webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( - "GPUColorDict", - dictMembersGPUColorDict, - ); - - // TYPEDEF: GPUColor - webidl.converters["GPUColor"] = (V, opts) => { - // Union for (sequence or GPUColorDict) - if (V === null || V === undefined) { - return webidl.converters["GPUColorDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUColorDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUColorDict.", - opts, - ); - }; - - // ENUM: GPUStoreOp - webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ - "store", - "discard", - ]); - - // DICTIONARY: GPURenderPassColorAttachment - const dictMembersGPURenderPassColorAttachment = [ - { - key: "view", - converter: webidl.converters["GPUTextureView"], - required: true, - }, - { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, - { - key: "clearValue", - converter: webidl.converters["GPUColor"], - }, - { - key: "loadOp", - converter: webidl.converters["GPULoadOp"], - required: true, - }, - { - key: "storeOp", - converter: webidl.converters["GPUStoreOp"], - required: true, - }, - ]; - webidl.converters["GPURenderPassColorAttachment"] = webidl - .createDictionaryConverter( - "GPURenderPassColorAttachment", - dictMembersGPURenderPassColorAttachment, - ); - - // DICTIONARY: GPURenderPassDepthStencilAttachment - const dictMembersGPURenderPassDepthStencilAttachment = [ - { - key: "view", - converter: webidl.converters["GPUTextureView"], - required: true, - }, - { - key: "depthClearValue", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "depthLoadOp", - converter: webidl.converters["GPULoadOp"], - }, - { - key: "depthStoreOp", - converter: webidl.converters["GPUStoreOp"], - }, - { - key: "depthReadOnly", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "stencilClearValue", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0, - }, - { - key: "stencilLoadOp", - converter: webidl.converters["GPULoadOp"], - }, - { - key: "stencilStoreOp", - converter: webidl.converters["GPUStoreOp"], - }, - { - key: "stencilReadOnly", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl - .createDictionaryConverter( - "GPURenderPassDepthStencilAttachment", - dictMembersGPURenderPassDepthStencilAttachment, - ); - - // INTERFACE: GPUQuerySet - webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( - "GPUQuerySet", - GPUQuerySet.prototype, - ); - - // DICTIONARY: GPURenderPassDescriptor - const dictMembersGPURenderPassDescriptor = [ - { - key: "colorAttachments", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPURenderPassColorAttachment"], - ), - ), - required: true, - }, - { - key: "depthStencilAttachment", - converter: webidl.converters["GPURenderPassDepthStencilAttachment"], - }, - ]; - webidl.converters["GPURenderPassDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderPassDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassDescriptor, - ); - - // INTERFACE: GPURenderBundle - webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( - "GPURenderBundle", - GPURenderBundle.prototype, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPURenderBundle"]); - - // DICTIONARY: GPURenderBundleDescriptor - const dictMembersGPURenderBundleDescriptor = []; - webidl.converters["GPURenderBundleDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderBundleDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderBundleDescriptor, - ); - - // INTERFACE: GPURenderBundleEncoder - webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( - "GPURenderBundleEncoder", - GPURenderBundleEncoder.prototype, - ); - - // DICTIONARY: GPURenderPassLayout - const dictMembersGPURenderPassLayout = [ - { - key: "colorFormats", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter(webidl.converters["GPUTextureFormat"]), - ), - required: true, - }, - { - key: "depthStencilFormat", - converter: webidl.converters["GPUTextureFormat"], - }, - { - key: "sampleCount", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - ]; - webidl.converters["GPURenderPassLayout"] = webidl - .createDictionaryConverter( - "GPURenderPassLayout", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassLayout, - ); - - // DICTIONARY: GPURenderBundleEncoderDescriptor - const dictMembersGPURenderBundleEncoderDescriptor = [ - { - key: "depthReadOnly", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "stencilReadOnly", - converter: webidl.converters.boolean, - defaultValue: false, - }, - ]; - webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderBundleEncoderDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassLayout, - dictMembersGPURenderBundleEncoderDescriptor, - ); - - // INTERFACE: GPUQueue - webidl.converters.GPUQueue = webidl.createInterfaceConverter( - "GPUQueue", - GPUQueue.prototype, - ); - - // ENUM: GPUQueryType - webidl.converters["GPUQueryType"] = webidl.createEnumConverter( - "GPUQueryType", - [ - "occlusion", - "pipeline-statistics", - "timestamp", - ], - ); - - // ENUM: GPUPipelineStatisticName - webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( - "GPUPipelineStatisticName", - [ - "vertex-shader-invocations", - "clipper-invocations", - "clipper-primitives-out", - "fragment-shader-invocations", - "compute-shader-invocations", - ], - ); - - // DICTIONARY: GPUQuerySetDescriptor - const dictMembersGPUQuerySetDescriptor = [ - { - key: "type", - converter: webidl.converters["GPUQueryType"], - required: true, - }, - { key: "count", converter: webidl.converters["GPUSize32"], required: true }, - { - key: "pipelineStatistics", - converter: webidl.createSequenceConverter( - webidl.converters["GPUPipelineStatisticName"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( - "GPUQuerySetDescriptor", +// INTERFACE: GPUPipelineLayout +webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( + "GPUPipelineLayout", + GPUPipelineLayout.prototype, +); + +// DICTIONARY: GPUPipelineLayoutDescriptor +const dictMembersGPUPipelineLayoutDescriptor = [ + { + key: "bindGroupLayouts", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayout"], + ), + required: true, + }, +]; +webidl.converters["GPUPipelineLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUPipelineLayoutDescriptor", dictMembersGPUObjectDescriptorBase, - dictMembersGPUQuerySetDescriptor, + dictMembersGPUPipelineLayoutDescriptor, ); - // ENUM: GPUDeviceLostReason - webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( - "GPUDeviceLostReason", - [ - "destroyed", - ], +// INTERFACE: GPUShaderModule +webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( + "GPUShaderModule", + GPUShaderModule.prototype, +); + +// DICTIONARY: GPUShaderModuleDescriptor +const dictMembersGPUShaderModuleDescriptor = [ + { + key: "code", + converter: webidl.converters["DOMString"], + required: true, + }, +]; +webidl.converters["GPUShaderModuleDescriptor"] = webidl + .createDictionaryConverter( + "GPUShaderModuleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUShaderModuleDescriptor, ); - // // INTERFACE: GPUDeviceLostInfo - // webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( - // "GPUDeviceLostInfo", - // GPUDeviceLostInfo.prototype, - // ); +// // ENUM: GPUCompilationMessageType +// webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( +// "GPUCompilationMessageType", +// [ +// "error", +// "warning", +// "info", +// ], +// ); - // ENUM: GPUErrorFilter - webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( - "GPUErrorFilter", - [ - "out-of-memory", - "validation", - ], +// // INTERFACE: GPUCompilationMessage +// webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( +// "GPUCompilationMessage", +// GPUCompilationMessage.prototype, +// ); + +// // INTERFACE: GPUCompilationInfo +// webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( +// "GPUCompilationInfo", +// GPUCompilationInfo.prototype, +// ); + +webidl.converters["GPUAutoLayoutMode"] = webidl.createEnumConverter( + "GPUAutoLayoutMode", + [ + "auto", + ], +); + +webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"] = (V, opts) => { + if (typeof V === "object") { + return webidl.converters["GPUPipelineLayout"](V, opts); + } + return webidl.converters["GPUAutoLayoutMode"](V, opts); +}; + +// DICTIONARY: GPUPipelineDescriptorBase +const dictMembersGPUPipelineDescriptorBase = [ + { + key: "layout", + converter: webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"], + }, +]; +webidl.converters["GPUPipelineDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUPipelineDescriptorBase", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, ); - // INTERFACE: GPUOutOfMemoryError - webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( - "GPUOutOfMemoryError", - GPUOutOfMemoryError.prototype, +// TYPEDEF: GPUPipelineConstantValue +webidl.converters.GPUPipelineConstantValue = webidl.converters.double; + +webidl.converters["record"] = webidl + .createRecordConverter( + webidl.converters.USVString, + webidl.converters.GPUPipelineConstantValue, ); - // INTERFACE: GPUValidationError - webidl.converters.GPUValidationError = webidl.createInterfaceConverter( - "GPUValidationError", - GPUValidationError.prototype, +// DICTIONARY: GPUProgrammableStage +const dictMembersGPUProgrammableStage = [ + { + key: "module", + converter: webidl.converters["GPUShaderModule"], + required: true, + }, + { + key: "entryPoint", + converter: webidl.converters["USVString"], + required: true, + }, + { + key: "constants", + converter: webidl.converters["record"], + }, +]; +webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( + "GPUProgrammableStage", + dictMembersGPUProgrammableStage, +); + +// INTERFACE: GPUComputePipeline +webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( + "GPUComputePipeline", + GPUComputePipeline.prototype, +); + +// DICTIONARY: GPUComputePipelineDescriptor +const dictMembersGPUComputePipelineDescriptor = [ + { + key: "compute", + converter: webidl.converters["GPUProgrammableStage"], + required: true, + }, +]; +webidl.converters["GPUComputePipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePipelineDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + dictMembersGPUComputePipelineDescriptor, ); - // TYPEDEF: GPUError - webidl.converters["GPUError"] = webidl.converters.any /** put union here! **/; +// INTERFACE: GPURenderPipeline +webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( + "GPURenderPipeline", + GPURenderPipeline.prototype, +); - // // INTERFACE: GPUUncapturedErrorEvent - // webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( - // "GPUUncapturedErrorEvent", - // GPUUncapturedErrorEvent.prototype, - // ); +// ENUM: GPUVertexStepMode +webidl.converters["GPUVertexStepMode"] = webidl.createEnumConverter( + "GPUVertexStepMode", + [ + "vertex", + "instance", + ], +); - // DICTIONARY: GPUUncapturedErrorEventInit - const dictMembersGPUUncapturedErrorEventInit = [ - { key: "error", converter: webidl.converters["GPUError"], required: true }, - ]; - webidl.converters["GPUUncapturedErrorEventInit"] = webidl - .createDictionaryConverter( - "GPUUncapturedErrorEventInit", - // dictMembersEventInit, - dictMembersGPUUncapturedErrorEventInit, - ); +// ENUM: GPUVertexFormat +webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( + "GPUVertexFormat", + [ + "uint8x2", + "uint8x4", + "sint8x2", + "sint8x4", + "unorm8x2", + "unorm8x4", + "snorm8x2", + "snorm8x4", + "uint16x2", + "uint16x4", + "sint16x2", + "sint16x4", + "unorm16x2", + "unorm16x4", + "snorm16x2", + "snorm16x4", + "float16x2", + "float16x4", + "float32", + "float32x2", + "float32x3", + "float32x4", + "uint32", + "uint32x2", + "uint32x3", + "uint32x4", + "sint32", + "sint32x2", + "sint32x3", + "sint32x4", + ], +); - // TYPEDEF: GPUBufferDynamicOffset - webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); +// DICTIONARY: GPUVertexAttribute +const dictMembersGPUVertexAttribute = [ + { + key: "format", + converter: webidl.converters["GPUVertexFormat"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "shaderLocation", + converter: webidl.converters["GPUIndex32"], + required: true, + }, +]; +webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( + "GPUVertexAttribute", + dictMembersGPUVertexAttribute, +); - // TYPEDEF: GPUSignedOffset32 - webidl.converters["GPUSignedOffset32"] = (V, opts) => - webidl.converters["long"](V, { ...opts, enforceRange: true }); +// DICTIONARY: GPUVertexBufferLayout +const dictMembersGPUVertexBufferLayout = [ + { + key: "arrayStride", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "stepMode", + converter: webidl.converters["GPUVertexStepMode"], + defaultValue: "vertex", + }, + { + key: "attributes", + converter: webidl.createSequenceConverter( + webidl.converters["GPUVertexAttribute"], + ), + required: true, + }, +]; +webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( + "GPUVertexBufferLayout", + dictMembersGPUVertexBufferLayout, +); - // TYPEDEF: GPUFlagsConstant - webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; -})(this); +// DICTIONARY: GPUVertexState +const dictMembersGPUVertexState = [ + { + key: "buffers", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUVertexBufferLayout"], + ), + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( + "GPUVertexState", + dictMembersGPUProgrammableStage, + dictMembersGPUVertexState, +); + +// ENUM: GPUPrimitiveTopology +webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( + "GPUPrimitiveTopology", + [ + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip", + ], +); + +// ENUM: GPUIndexFormat +webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( + "GPUIndexFormat", + [ + "uint16", + "uint32", + ], +); + +// ENUM: GPUFrontFace +webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( + "GPUFrontFace", + [ + "ccw", + "cw", + ], +); + +// ENUM: GPUCullMode +webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ + "none", + "front", + "back", +]); + +// DICTIONARY: GPUPrimitiveState +const dictMembersGPUPrimitiveState = [ + { + key: "topology", + converter: webidl.converters["GPUPrimitiveTopology"], + defaultValue: "triangle-list", + }, + { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, + { + key: "frontFace", + converter: webidl.converters["GPUFrontFace"], + defaultValue: "ccw", + }, + { + key: "cullMode", + converter: webidl.converters["GPUCullMode"], + defaultValue: "none", + }, + { + key: "unclippedDepth", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( + "GPUPrimitiveState", + dictMembersGPUPrimitiveState, +); + +// ENUM: GPUStencilOperation +webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( + "GPUStencilOperation", + [ + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap", + ], +); + +// DICTIONARY: GPUStencilFaceState +const dictMembersGPUStencilFaceState = [ + { + key: "compare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "failOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "depthFailOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "passOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, +]; +webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( + "GPUStencilFaceState", + dictMembersGPUStencilFaceState, +); + +// TYPEDEF: GPUStencilValue +webidl.converters["GPUStencilValue"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUDepthBias +webidl.converters["GPUDepthBias"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUDepthStencilState +const dictMembersGPUDepthStencilState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "depthWriteEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "depthCompare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "stencilFront", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilBack", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilReadMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "stencilWriteMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "depthBias", + converter: webidl.converters["GPUDepthBias"], + defaultValue: 0, + }, + { + key: "depthBiasSlopeScale", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthBiasClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, +]; +webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( + "GPUDepthStencilState", + dictMembersGPUDepthStencilState, +); + +// TYPEDEF: GPUSampleMask +webidl.converters["GPUSampleMask"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUMultisampleState +const dictMembersGPUMultisampleState = [ + { + key: "count", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "mask", + converter: webidl.converters["GPUSampleMask"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "alphaToCoverageEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( + "GPUMultisampleState", + dictMembersGPUMultisampleState, +); + +// ENUM: GPUBlendFactor +webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( + "GPUBlendFactor", + [ + "zero", + "one", + "src", + "one-minus-src", + "src-alpha", + "one-minus-src-alpha", + "dst", + "one-minus-dst", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "constant", + "one-minus-constant", + ], +); + +// ENUM: GPUBlendOperation +webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( + "GPUBlendOperation", + [ + "add", + "subtract", + "reverse-subtract", + "min", + "max", + ], +); + +// DICTIONARY: GPUBlendComponent +const dictMembersGPUBlendComponent = [ + { + key: "srcFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "one", + }, + { + key: "dstFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "zero", + }, + { + key: "operation", + converter: webidl.converters["GPUBlendOperation"], + defaultValue: "add", + }, +]; +webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( + "GPUBlendComponent", + dictMembersGPUBlendComponent, +); + +// DICTIONARY: GPUBlendState +const dictMembersGPUBlendState = [ + { + key: "color", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + { + key: "alpha", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, +]; +webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( + "GPUBlendState", + dictMembersGPUBlendState, +); + +// TYPEDEF: GPUColorWriteFlags +webidl.converters["GPUColorWriteFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUColorTargetState +const dictMembersGPUColorTargetState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { key: "blend", converter: webidl.converters["GPUBlendState"] }, + { + key: "writeMask", + converter: webidl.converters["GPUColorWriteFlags"], + defaultValue: 0xF, + }, +]; +webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( + "GPUColorTargetState", + dictMembersGPUColorTargetState, +); + +// DICTIONARY: GPUFragmentState +const dictMembersGPUFragmentState = [ + { + key: "targets", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUColorTargetState"], + ), + ), + required: true, + }, +]; +webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( + "GPUFragmentState", + dictMembersGPUProgrammableStage, + dictMembersGPUFragmentState, +); + +// DICTIONARY: GPURenderPipelineDescriptor +const dictMembersGPURenderPipelineDescriptor = [ + { + key: "vertex", + converter: webidl.converters["GPUVertexState"], + required: true, + }, + { + key: "primitive", + converter: webidl.converters["GPUPrimitiveState"], + get defaultValue() { + return {}; + }, + }, + { + key: "depthStencil", + converter: webidl.converters["GPUDepthStencilState"], + }, + { + key: "multisample", + converter: webidl.converters["GPUMultisampleState"], + get defaultValue() { + return {}; + }, + }, + { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, +]; +webidl.converters["GPURenderPipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPipelineDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + dictMembersGPURenderPipelineDescriptor, + ); + +// INTERFACE: GPUColorWrite +webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( + "GPUColorWrite", + GPUColorWrite.prototype, +); + +// INTERFACE: GPUCommandBuffer +webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( + "GPUCommandBuffer", + GPUCommandBuffer.prototype, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + +// DICTIONARY: GPUCommandBufferDescriptor +const dictMembersGPUCommandBufferDescriptor = []; +webidl.converters["GPUCommandBufferDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandBufferDescriptor, + ); + +// INTERFACE: GPUCommandEncoder +webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( + "GPUCommandEncoder", + GPUCommandEncoder.prototype, +); + +// DICTIONARY: GPUCommandEncoderDescriptor +const dictMembersGPUCommandEncoderDescriptor = []; +webidl.converters["GPUCommandEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandEncoderDescriptor, + ); + +// DICTIONARY: GPUImageDataLayout +const dictMembersGPUImageDataLayout = [ + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, + { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, +]; +webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( + "GPUImageDataLayout", + dictMembersGPUImageDataLayout, +); + +// DICTIONARY: GPUImageCopyBuffer +const dictMembersGPUImageCopyBuffer = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, +]; +webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( + "GPUImageCopyBuffer", + dictMembersGPUImageDataLayout, + dictMembersGPUImageCopyBuffer, +); + +// DICTIONARY: GPUOrigin3DDict +const dictMembersGPUOrigin3DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "z", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, +]; +webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( + "GPUOrigin3DDict", + dictMembersGPUOrigin3DDict, +); + +// TYPEDEF: GPUOrigin3D +webidl.converters["GPUOrigin3D"] = (V, opts) => { + // Union for (sequence or GPUOrigin3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUOrigin3DDict.", + opts, + ); +}; + +// DICTIONARY: GPUImageCopyTexture +const dictMembersGPUImageCopyTexture = [ + { + key: "texture", + converter: webidl.converters["GPUTexture"], + required: true, + }, + { + key: "mipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "origin", + converter: webidl.converters["GPUOrigin3D"], + get defaultValue() { + return {}; + }, + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, +]; +webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( + "GPUImageCopyTexture", + dictMembersGPUImageCopyTexture, +); + +// DICTIONARY: GPUOrigin2DDict +const dictMembersGPUOrigin2DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, +]; +webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( + "GPUOrigin2DDict", + dictMembersGPUOrigin2DDict, +); + +// TYPEDEF: GPUOrigin2D +webidl.converters["GPUOrigin2D"] = (V, opts) => { + // Union for (sequence or GPUOrigin2DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUOrigin2DDict.", + opts, + ); +}; + +// INTERFACE: GPUComputePassEncoder +webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( + "GPUComputePassEncoder", + GPUComputePassEncoder.prototype, +); + +// DICTIONARY: GPUComputePassDescriptor +const dictMembersGPUComputePassDescriptor = []; +webidl.converters["GPUComputePassDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUComputePassDescriptor, + ); + +// INTERFACE: GPURenderPassEncoder +webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( + "GPURenderPassEncoder", + GPURenderPassEncoder.prototype, +); + +// ENUM: GPULoadOp +webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ + "load", + "clear", +]); + +// DICTIONARY: GPUColorDict +const dictMembersGPUColorDict = [ + { key: "r", converter: webidl.converters["double"], required: true }, + { key: "g", converter: webidl.converters["double"], required: true }, + { key: "b", converter: webidl.converters["double"], required: true }, + { key: "a", converter: webidl.converters["double"], required: true }, +]; +webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( + "GPUColorDict", + dictMembersGPUColorDict, +); + +// TYPEDEF: GPUColor +webidl.converters["GPUColor"] = (V, opts) => { + // Union for (sequence or GPUColorDict) + if (V === null || V === undefined) { + return webidl.converters["GPUColorDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUColorDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUColorDict.", + opts, + ); +}; + +// ENUM: GPUStoreOp +webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ + "store", + "discard", +]); + +// DICTIONARY: GPURenderPassColorAttachment +const dictMembersGPURenderPassColorAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, + { + key: "clearValue", + converter: webidl.converters["GPUColor"], + }, + { + key: "loadOp", + converter: webidl.converters["GPULoadOp"], + required: true, + }, + { + key: "storeOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, +]; +webidl.converters["GPURenderPassColorAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassColorAttachment", + dictMembersGPURenderPassColorAttachment, + ); + +// DICTIONARY: GPURenderPassDepthStencilAttachment +const dictMembersGPURenderPassDepthStencilAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { + key: "depthClearValue", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthLoadOp", + converter: webidl.converters["GPULoadOp"], + }, + { + key: "depthStoreOp", + converter: webidl.converters["GPUStoreOp"], + }, + { + key: "depthReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "stencilClearValue", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0, + }, + { + key: "stencilLoadOp", + converter: webidl.converters["GPULoadOp"], + }, + { + key: "stencilStoreOp", + converter: webidl.converters["GPUStoreOp"], + }, + { + key: "stencilReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassDepthStencilAttachment", + dictMembersGPURenderPassDepthStencilAttachment, + ); + +// INTERFACE: GPUQuerySet +webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( + "GPUQuerySet", + GPUQuerySet.prototype, +); + +// DICTIONARY: GPURenderPassDescriptor +const dictMembersGPURenderPassDescriptor = [ + { + key: "colorAttachments", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPURenderPassColorAttachment"], + ), + ), + required: true, + }, + { + key: "depthStencilAttachment", + converter: webidl.converters["GPURenderPassDepthStencilAttachment"], + }, +]; +webidl.converters["GPURenderPassDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassDescriptor, + ); + +// INTERFACE: GPURenderBundle +webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + GPURenderBundle.prototype, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPURenderBundle"]); + +// DICTIONARY: GPURenderBundleDescriptor +const dictMembersGPURenderBundleDescriptor = []; +webidl.converters["GPURenderBundleDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleDescriptor, + ); + +// INTERFACE: GPURenderBundleEncoder +webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( + "GPURenderBundleEncoder", + GPURenderBundleEncoder.prototype, +); + +// DICTIONARY: GPURenderPassLayout +const dictMembersGPURenderPassLayout = [ + { + key: "colorFormats", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter(webidl.converters["GPUTextureFormat"]), + ), + required: true, + }, + { + key: "depthStencilFormat", + converter: webidl.converters["GPUTextureFormat"], + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, +]; +webidl.converters["GPURenderPassLayout"] = webidl + .createDictionaryConverter( + "GPURenderPassLayout", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassLayout, + ); + +// DICTIONARY: GPURenderBundleEncoderDescriptor +const dictMembersGPURenderBundleEncoderDescriptor = [ + { + key: "depthReadOnly", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "stencilReadOnly", + converter: webidl.converters.boolean, + defaultValue: false, + }, +]; +webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassLayout, + dictMembersGPURenderBundleEncoderDescriptor, + ); + +// INTERFACE: GPUQueue +webidl.converters.GPUQueue = webidl.createInterfaceConverter( + "GPUQueue", + GPUQueue.prototype, +); + +// ENUM: GPUQueryType +webidl.converters["GPUQueryType"] = webidl.createEnumConverter( + "GPUQueryType", + [ + "occlusion", + "pipeline-statistics", + "timestamp", + ], +); + +// ENUM: GPUPipelineStatisticName +webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( + "GPUPipelineStatisticName", + [ + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations", + ], +); + +// DICTIONARY: GPUQuerySetDescriptor +const dictMembersGPUQuerySetDescriptor = [ + { + key: "type", + converter: webidl.converters["GPUQueryType"], + required: true, + }, + { key: "count", converter: webidl.converters["GPUSize32"], required: true }, + { + key: "pipelineStatistics", + converter: webidl.createSequenceConverter( + webidl.converters["GPUPipelineStatisticName"], + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( + "GPUQuerySetDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUQuerySetDescriptor, +); + +// ENUM: GPUDeviceLostReason +webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( + "GPUDeviceLostReason", + [ + "destroyed", + ], +); + +// // INTERFACE: GPUDeviceLostInfo +// webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( +// "GPUDeviceLostInfo", +// GPUDeviceLostInfo.prototype, +// ); + +// ENUM: GPUErrorFilter +webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( + "GPUErrorFilter", + [ + "out-of-memory", + "validation", + ], +); + +// INTERFACE: GPUOutOfMemoryError +webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( + "GPUOutOfMemoryError", + GPUOutOfMemoryError.prototype, +); + +// INTERFACE: GPUValidationError +webidl.converters.GPUValidationError = webidl.createInterfaceConverter( + "GPUValidationError", + GPUValidationError.prototype, +); + +// TYPEDEF: GPUError +webidl.converters["GPUError"] = webidl.converters.any /** put union here! **/; + +// // INTERFACE: GPUUncapturedErrorEvent +// webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( +// "GPUUncapturedErrorEvent", +// GPUUncapturedErrorEvent.prototype, +// ); + +// DICTIONARY: GPUUncapturedErrorEventInit +const dictMembersGPUUncapturedErrorEventInit = [ + { key: "error", converter: webidl.converters["GPUError"], required: true }, +]; +webidl.converters["GPUUncapturedErrorEventInit"] = webidl + .createDictionaryConverter( + "GPUUncapturedErrorEventInit", + // dictMembersEventInit, + dictMembersGPUUncapturedErrorEventInit, + ); + +// TYPEDEF: GPUBufferDynamicOffset +webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUSignedOffset32 +webidl.converters["GPUSignedOffset32"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUFlagsConstant +webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; diff --git a/ext/webgpu/src/03_surface.js b/ext/webgpu/src/03_surface.js index f9e422e825..b46db047c5 100644 --- a/ext/webgpu/src/03_surface.js +++ b/ext/webgpu/src/03_surface.js @@ -6,144 +6,141 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { Symbol } = primordials; +import { + _device, + assertDevice, + createGPUTexture, +} from "internal:ext/webgpu/01_webgpu.js"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { Symbol } = window.__bootstrap.primordials; - const { _device, assertDevice, createGPUTexture } = window.__bootstrap.webgpu; +const _surfaceRid = Symbol("[[surfaceRid]]"); +const _configuration = Symbol("[[configuration]]"); +const _canvas = Symbol("[[canvas]]"); +const _currentTexture = Symbol("[[currentTexture]]"); +class GPUCanvasContext { + /** @type {number} */ + [_surfaceRid]; + /** @type {InnerGPUDevice} */ + [_device]; + [_configuration]; + [_canvas]; + /** @type {GPUTexture | undefined} */ + [_currentTexture]; - const _surfaceRid = Symbol("[[surfaceRid]]"); - const _configuration = Symbol("[[configuration]]"); - const _canvas = Symbol("[[canvas]]"); - const _currentTexture = Symbol("[[currentTexture]]"); - class GPUCanvasContext { - /** @type {number} */ - [_surfaceRid]; - /** @type {InnerGPUDevice} */ - [_device]; - [_configuration]; - [_canvas]; - /** @type {GPUTexture | undefined} */ - [_currentTexture]; + get canvas() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + return this[_canvas]; + } - get canvas() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - return this[_canvas]; - } + constructor() { + webidl.illegalConstructor(); + } - constructor() { - webidl.illegalConstructor(); - } + configure(configuration) { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + configuration = webidl.converters.GPUCanvasConfiguration(configuration, { + prefix, + context: "Argument 1", + }); - configure(configuration) { - webidl.assertBranded(this, GPUCanvasContextPrototype); - const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - configuration = webidl.converters.GPUCanvasConfiguration(configuration, { - prefix, - context: "Argument 1", - }); + this[_device] = configuration.device[_device]; + this[_configuration] = configuration; + const device = assertDevice(this, { + prefix, + context: "configuration.device", + }); - this[_device] = configuration.device[_device]; - this[_configuration] = configuration; - const device = assertDevice(this, { - prefix, - context: "configuration.device", - }); + const { err } = ops.op_webgpu_surface_configure({ + surfaceRid: this[_surfaceRid], + deviceRid: device.rid, + format: configuration.format, + viewFormats: configuration.viewFormats, + usage: configuration.usage, + width: configuration.width, + height: configuration.height, + alphaMode: configuration.alphaMode, + }); - const { err } = ops.op_webgpu_surface_configure({ - surfaceRid: this[_surfaceRid], - deviceRid: device.rid, - format: configuration.format, - viewFormats: configuration.viewFormats, - usage: configuration.usage, - width: configuration.width, - height: configuration.height, - alphaMode: configuration.alphaMode, - }); + device.pushError(err); + } - device.pushError(err); - } + unconfigure() { + webidl.assertBranded(this, GPUCanvasContextPrototype); - unconfigure() { - webidl.assertBranded(this, GPUCanvasContextPrototype); + this[_configuration] = null; + this[_device] = null; + } - this[_configuration] = null; - this[_device] = null; - } + getCurrentTexture() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = + "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; - getCurrentTexture() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - const prefix = - "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; - - if (this[_configuration] === null) { - throw new DOMException( - "context is not configured.", - "InvalidStateError", - ); - } - - const device = assertDevice(this, { prefix, context: "this" }); - - if (this[_currentTexture]) { - return this[_currentTexture]; - } - - const { rid } = ops.op_webgpu_surface_get_current_texture( - device.rid, - this[_surfaceRid], + if (this[_configuration] === null) { + throw new DOMException( + "context is not configured.", + "InvalidStateError", ); + } - const texture = createGPUTexture( - { - size: { - width: this[_configuration].width, - height: this[_configuration].height, - depthOrArrayLayers: 1, - }, - mipLevelCount: 1, - sampleCount: 1, - dimension: "2d", - format: this[_configuration].format, - usage: this[_configuration].usage, + const device = assertDevice(this, { prefix, context: "this" }); + + if (this[_currentTexture]) { + return this[_currentTexture]; + } + + const { rid } = ops.op_webgpu_surface_get_current_texture( + device.rid, + this[_surfaceRid], + ); + + const texture = createGPUTexture( + { + size: { + width: this[_configuration].width, + height: this[_configuration].height, + depthOrArrayLayers: 1, }, - device, - rid, - ); - device.trackResource(texture); - this[_currentTexture] = texture; - return texture; - } - - // Extended from spec. Required to present the texture; browser don't need this. - present() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - const prefix = "Failed to execute 'present' on 'GPUCanvasContext'"; - const device = assertDevice(this[_currentTexture], { - prefix, - context: "this", - }); - ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]); - this[_currentTexture].destroy(); - this[_currentTexture] = undefined; - } - } - const GPUCanvasContextPrototype = GPUCanvasContext.prototype; - - function createCanvasContext(options) { - const canvasContext = webidl.createBranded(GPUCanvasContext); - canvasContext[_surfaceRid] = options.surfaceRid; - canvasContext[_canvas] = options.canvas; - return canvasContext; + mipLevelCount: 1, + sampleCount: 1, + dimension: "2d", + format: this[_configuration].format, + usage: this[_configuration].usage, + }, + device, + rid, + ); + device.trackResource(texture); + this[_currentTexture] = texture; + return texture; } - window.__bootstrap.webgpu = { - ...window.__bootstrap.webgpu, - GPUCanvasContext, - createCanvasContext, - }; -})(this); + // Extended from spec. Required to present the texture; browser don't need this. + present() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = "Failed to execute 'present' on 'GPUCanvasContext'"; + const device = assertDevice(this[_currentTexture], { + prefix, + context: "this", + }); + ops.op_webgpu_surface_present(device.rid, this[_surfaceRid]); + this[_currentTexture].destroy(); + this[_currentTexture] = undefined; + } +} +const GPUCanvasContextPrototype = GPUCanvasContext.prototype; + +function createCanvasContext(options) { + const canvasContext = webidl.createBranded(GPUCanvasContext); + canvasContext[_surfaceRid] = options.surfaceRid; + canvasContext[_canvas] = options.canvas; + return canvasContext; +} + +export { createCanvasContext, GPUCanvasContext }; diff --git a/ext/webgpu/src/04_surface_idl_types.js b/ext/webgpu/src/04_surface_idl_types.js index 9dcfa767ec..f9665afa68 100644 --- a/ext/webgpu/src/04_surface_idl_types.js +++ b/ext/webgpu/src/04_surface_idl_types.js @@ -6,81 +6,77 @@ /// /// -"use strict"; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { GPUTextureUsage } from "internal:ext/webgpu/01_webgpu.js"; -((window) => { - const webidl = window.__bootstrap.webidl; - const { GPUTextureUsage } = window.__bootstrap.webgpu; +// ENUM: GPUCanvasAlphaMode +webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter( + "GPUCanvasAlphaMode", + [ + "opaque", + "premultiplied", + ], +); - // ENUM: GPUCanvasAlphaMode - webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter( - "GPUCanvasAlphaMode", - [ - "opaque", - "premultiplied", - ], +// NON-SPEC: ENUM: GPUPresentMode +webidl.converters["GPUPresentMode"] = webidl.createEnumConverter( + "GPUPresentMode", + [ + "autoVsync", + "autoNoVsync", + "fifo", + "fifoRelaxed", + "immediate", + "mailbox", + ], +); + +// DICT: GPUCanvasConfiguration +const dictMembersGPUCanvasConfiguration = [ + { key: "device", converter: webidl.converters.GPUDevice, required: true }, + { + key: "format", + converter: webidl.converters.GPUTextureFormat, + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + defaultValue: GPUTextureUsage.RENDER_ATTACHMENT, + }, + { + key: "alphaMode", + converter: webidl.converters["GPUCanvasAlphaMode"], + defaultValue: "opaque", + }, + + // Extended from spec + { + key: "presentMode", + converter: webidl.converters["GPUPresentMode"], + }, + { + key: "width", + converter: webidl.converters["long"], + required: true, + }, + { + key: "height", + converter: webidl.converters["long"], + required: true, + }, + { + key: "viewFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUCanvasConfiguration"] = webidl + .createDictionaryConverter( + "GPUCanvasConfiguration", + dictMembersGPUCanvasConfiguration, ); - - // NON-SPEC: ENUM: GPUPresentMode - webidl.converters["GPUPresentMode"] = webidl.createEnumConverter( - "GPUPresentMode", - [ - "autoVsync", - "autoNoVsync", - "fifo", - "fifoRelaxed", - "immediate", - "mailbox", - ], - ); - - // DICT: GPUCanvasConfiguration - const dictMembersGPUCanvasConfiguration = [ - { key: "device", converter: webidl.converters.GPUDevice, required: true }, - { - key: "format", - converter: webidl.converters.GPUTextureFormat, - required: true, - }, - { - key: "usage", - converter: webidl.converters["GPUTextureUsageFlags"], - defaultValue: GPUTextureUsage.RENDER_ATTACHMENT, - }, - { - key: "alphaMode", - converter: webidl.converters["GPUCanvasAlphaMode"], - defaultValue: "opaque", - }, - - // Extended from spec - { - key: "presentMode", - converter: webidl.converters["GPUPresentMode"], - }, - { - key: "width", - converter: webidl.converters["long"], - required: true, - }, - { - key: "height", - converter: webidl.converters["long"], - required: true, - }, - { - key: "viewFormats", - converter: webidl.createSequenceConverter( - webidl.converters["GPUTextureFormat"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUCanvasConfiguration"] = webidl - .createDictionaryConverter( - "GPUCanvasConfiguration", - dictMembersGPUCanvasConfiguration, - ); -})(this); diff --git a/ext/webgpu/src/lib.rs b/ext/webgpu/src/lib.rs index 8e4077e7f5..83326b71ac 100644 --- a/ext/webgpu/src/lib.rs +++ b/ext/webgpu/src/lib.rs @@ -119,7 +119,7 @@ impl Resource for WebGpuQuerySet { pub fn init(unstable: bool) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/webgpu", "01_webgpu.js", "02_idl_types.js", diff --git a/ext/webgpu/src/surface.rs b/ext/webgpu/src/surface.rs index 2ce9cf4487..84d17b38d0 100644 --- a/ext/webgpu/src/surface.rs +++ b/ext/webgpu/src/surface.rs @@ -15,8 +15,8 @@ use wgpu_types::SurfaceStatus; pub fn init_surface(unstable: bool) -> Extension { Extension::builder("deno_webgpu_surface") .dependencies(vec!["deno_webidl", "deno_web", "deno_webgpu"]) - .js(include_js_files!( - prefix "internal:deno_webgpu", + .esm(include_js_files!( + prefix "internal:ext/webgpu", "03_surface.js", "04_surface_idl_types.js", )) diff --git a/ext/webidl/00_webidl.js b/ext/webidl/00_webidl.js index bb90d973ec..1357d308ea 100644 --- a/ext/webidl/00_webidl.js +++ b/ext/webidl/00_webidl.js @@ -6,556 +6,517 @@ /// -"use strict"; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeForEach, + ArrayPrototypePush, + ArrayPrototypeSort, + ArrayIteratorPrototype, + BigInt, + BigIntAsIntN, + BigIntAsUintN, + Float32Array, + Float64Array, + FunctionPrototypeBind, + Int16Array, + Int32Array, + Int8Array, + isNaN, + MathFloor, + MathFround, + MathMax, + MathMin, + MathPow, + MathRound, + MathTrunc, + Number, + NumberIsFinite, + NumberIsNaN, + // deno-lint-ignore camelcase + NumberMAX_SAFE_INTEGER, + // deno-lint-ignore camelcase + NumberMIN_SAFE_INTEGER, + ObjectAssign, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertyDescriptors, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + ObjectIs, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + ReflectApply, + ReflectDefineProperty, + ReflectGetOwnPropertyDescriptor, + ReflectHas, + ReflectOwnKeys, + RegExpPrototypeTest, + Set, + SetPrototypeEntries, + SetPrototypeForEach, + SetPrototypeKeys, + SetPrototypeValues, + SetPrototypeHas, + SetPrototypeClear, + SetPrototypeDelete, + SetPrototypeAdd, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + String, + StringFromCodePoint, + StringPrototypeCharCodeAt, + Symbol, + SymbolIterator, + SymbolToStringTag, + TypedArrayPrototypeGetSymbolToStringTag, + TypeError, + Uint16Array, + Uint32Array, + Uint8Array, + Uint8ClampedArray, +} = primordials; -((window) => { - const core = window.Deno.core; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeForEach, - ArrayPrototypePush, - ArrayPrototypeSort, - ArrayIteratorPrototype, - BigInt, - BigIntAsIntN, - BigIntAsUintN, - Float32Array, - Float64Array, - FunctionPrototypeBind, - Int16Array, - Int32Array, - Int8Array, - isNaN, - MathFloor, - MathFround, - MathMax, - MathMin, - MathPow, - MathRound, - MathTrunc, - Number, - NumberIsFinite, - NumberIsNaN, - // deno-lint-ignore camelcase - NumberMAX_SAFE_INTEGER, - // deno-lint-ignore camelcase - NumberMIN_SAFE_INTEGER, - ObjectAssign, - ObjectCreate, - ObjectDefineProperties, - ObjectDefineProperty, - ObjectGetOwnPropertyDescriptor, - ObjectGetOwnPropertyDescriptors, - ObjectGetPrototypeOf, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - ObjectIs, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - ReflectApply, - ReflectDefineProperty, - ReflectGetOwnPropertyDescriptor, - ReflectHas, - ReflectOwnKeys, - RegExpPrototypeTest, - Set, - SetPrototypeEntries, - SetPrototypeForEach, - SetPrototypeKeys, - SetPrototypeValues, - SetPrototypeHas, - SetPrototypeClear, - SetPrototypeDelete, - SetPrototypeAdd, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - String, - StringFromCodePoint, - StringPrototypeCharCodeAt, - Symbol, - SymbolIterator, - SymbolToStringTag, - TypedArrayPrototypeGetSymbolToStringTag, - TypeError, - Uint16Array, - Uint32Array, - Uint8Array, - Uint8ClampedArray, - } = window.__bootstrap.primordials; +function makeException(ErrorType, message, opts = {}) { + return new ErrorType( + `${opts.prefix ? opts.prefix + ": " : ""}${ + opts.context ? opts.context : "Value" + } ${message}`, + ); +} - function makeException(ErrorType, message, opts = {}) { - return new ErrorType( - `${opts.prefix ? opts.prefix + ": " : ""}${ - opts.context ? opts.context : "Value" - } ${message}`, - ); +function toNumber(value) { + if (typeof value === "bigint") { + throw TypeError("Cannot convert a BigInt value to a number"); + } + return Number(value); +} + +function type(V) { + if (V === null) { + return "Null"; + } + switch (typeof V) { + case "undefined": + return "Undefined"; + case "boolean": + return "Boolean"; + case "number": + return "Number"; + case "string": + return "String"; + case "symbol": + return "Symbol"; + case "bigint": + return "BigInt"; + case "object": + // Falls through + case "function": + // Falls through + default: + // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for + // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for + // such cases. So treat the default case as an object. + return "Object"; + } +} + +// Round x to the nearest integer, choosing the even integer if it lies halfway between two. +function evenRound(x) { + // There are four cases for numbers with fractional part being .5: + // + // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example + // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 + // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 + // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 + // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 + // (where n is a non-negative integer) + // + // Branch here for cases 1 and 4 + if ( + (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || + (x < 0 && x % 1 === -0.5 && (x & 1) === 1) + ) { + return censorNegativeZero(MathFloor(x)); } - function toNumber(value) { - if (typeof value === "bigint") { - throw TypeError("Cannot convert a BigInt value to a number"); - } - return Number(value); + return censorNegativeZero(MathRound(x)); +} + +function integerPart(n) { + return censorNegativeZero(MathTrunc(n)); +} + +function sign(x) { + return x < 0 ? -1 : 1; +} + +function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; + } + return signMightNotMatch; +} + +function censorNegativeZero(x) { + return x === 0 ? 0 : x; +} + +function createIntegerConversion(bitLength, typeOpts) { + const isSigned = !typeOpts.unsigned; + + let lowerBound; + let upperBound; + if (bitLength === 64) { + upperBound = NumberMAX_SAFE_INTEGER; + lowerBound = !isSigned ? 0 : NumberMIN_SAFE_INTEGER; + } else if (!isSigned) { + lowerBound = 0; + upperBound = MathPow(2, bitLength) - 1; + } else { + lowerBound = -MathPow(2, bitLength - 1); + upperBound = MathPow(2, bitLength - 1) - 1; } - function type(V) { - if (V === null) { - return "Null"; - } - switch (typeof V) { - case "undefined": - return "Undefined"; - case "boolean": - return "Boolean"; - case "number": - return "Number"; - case "string": - return "String"; - case "symbol": - return "Symbol"; - case "bigint": - return "BigInt"; - case "object": - // Falls through - case "function": - // Falls through - default: - // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for - // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for - // such cases. So treat the default case as an object. - return "Object"; - } - } + const twoToTheBitLength = MathPow(2, bitLength); + const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); - // Round x to the nearest integer, choosing the even integer if it lies halfway between two. - function evenRound(x) { - // There are four cases for numbers with fractional part being .5: - // - // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example - // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 - // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 - // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 - // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 - // (where n is a non-negative integer) - // - // Branch here for cases 1 and 4 - if ( - (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || - (x < 0 && x % 1 === -0.5 && (x & 1) === 1) - ) { - return censorNegativeZero(MathFloor(x)); - } + return (V, opts = {}) => { + let x = toNumber(V); + x = censorNegativeZero(x); - return censorNegativeZero(MathRound(x)); - } - - function integerPart(n) { - return censorNegativeZero(MathTrunc(n)); - } - - function sign(x) { - return x < 0 ? -1 : 1; - } - - function modulo(x, y) { - // https://tc39.github.io/ecma262/#eqn-modulo - // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos - const signMightNotMatch = x % y; - if (sign(y) !== sign(signMightNotMatch)) { - return signMightNotMatch + y; - } - return signMightNotMatch; - } - - function censorNegativeZero(x) { - return x === 0 ? 0 : x; - } - - function createIntegerConversion(bitLength, typeOpts) { - const isSigned = !typeOpts.unsigned; - - let lowerBound; - let upperBound; - if (bitLength === 64) { - upperBound = NumberMAX_SAFE_INTEGER; - lowerBound = !isSigned ? 0 : NumberMIN_SAFE_INTEGER; - } else if (!isSigned) { - lowerBound = 0; - upperBound = MathPow(2, bitLength) - 1; - } else { - lowerBound = -MathPow(2, bitLength - 1); - upperBound = MathPow(2, bitLength - 1) - 1; - } - - const twoToTheBitLength = MathPow(2, bitLength); - const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); - - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); - - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } - - x = integerPart(x); - - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } - - return x; + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); } - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } - - if (!NumberIsFinite(x) || x === 0) { - return 0; - } x = integerPart(x); - // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if - // possible. Hopefully it's an optimization for the non-64-bitLength cases too. - if (x >= lowerBound && x <= upperBound) { - return x; - } - - // These will not work great for bitLength of 64, but oh well. See the README for more details. - x = modulo(x, twoToTheBitLength); - if (isSigned && x >= twoToOneLessThanTheBitLength) { - return x - twoToTheBitLength; - } - return x; - }; - } - - function createLongLongConversion(bitLength, { unsigned }) { - const upperBound = NumberMAX_SAFE_INTEGER; - const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; - const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; - - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); - - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } - - x = integerPart(x); - - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } - - return x; - } - - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } - - if (!NumberIsFinite(x) || x === 0) { - return 0; - } - - let xBigInt = BigInt(integerPart(x)); - xBigInt = asBigIntN(bitLength, xBigInt); - return Number(xBigInt); - }; - } - - const converters = []; - - converters.any = (V) => { - return V; - }; - - converters.boolean = function (val) { - return !!val; - }; - - converters.byte = createIntegerConversion(8, { unsigned: false }); - converters.octet = createIntegerConversion(8, { unsigned: true }); - - converters.short = createIntegerConversion(16, { unsigned: false }); - converters["unsigned short"] = createIntegerConversion(16, { - unsigned: true, - }); - - converters.long = createIntegerConversion(32, { unsigned: false }); - converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); - - converters["long long"] = createLongLongConversion(64, { unsigned: false }); - converters["unsigned long long"] = createLongLongConversion(64, { - unsigned: true, - }); - - converters.float = (V, opts) => { - const x = toNumber(V); - - if (!NumberIsFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } - - if (ObjectIs(x, -0)) { - return x; - } - - const y = MathFround(x); - - if (!NumberIsFinite(y)) { - throw makeException( - TypeError, - "is outside the range of a single-precision floating-point value", - opts, - ); - } - - return y; - }; - - converters["unrestricted float"] = (V, _opts) => { - const x = toNumber(V); - - if (isNaN(x)) { - return x; - } - - if (ObjectIs(x, -0)) { - return x; - } - - return MathFround(x); - }; - - converters.double = (V, opts) => { - const x = toNumber(V); - - if (!NumberIsFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } - - return x; - }; - - converters["unrestricted double"] = (V, _opts) => { - const x = toNumber(V); - - return x; - }; - - converters.DOMString = function (V, opts = {}) { - if (typeof V === "string") { - return V; - } else if (V === null && opts.treatNullAsEmptyString) { - return ""; - } else if (typeof V === "symbol") { - throw makeException( - TypeError, - "is a symbol, which cannot be converted to a string", - opts, - ); - } - - return String(V); - }; - - // deno-lint-ignore no-control-regex - const IS_BYTE_STRING = /^[\x00-\xFF]*$/; - converters.ByteString = (V, opts) => { - const x = converters.DOMString(V, opts); - if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) { - throw makeException(TypeError, "is not a valid ByteString", opts); - } - return x; - }; - - converters.USVString = (V, opts) => { - const S = converters.DOMString(V, opts); - const n = S.length; - let U = ""; - for (let i = 0; i < n; ++i) { - const c = StringPrototypeCharCodeAt(S, i); - if (c < 0xd800 || c > 0xdfff) { - U += StringFromCodePoint(c); - } else if (0xdc00 <= c && c <= 0xdfff) { - U += StringFromCodePoint(0xfffd); - } else if (i === n - 1) { - U += StringFromCodePoint(0xfffd); - } else { - const d = StringPrototypeCharCodeAt(S, i + 1); - if (0xdc00 <= d && d <= 0xdfff) { - const a = c & 0x3ff; - const b = d & 0x3ff; - U += StringFromCodePoint((2 << 15) + (2 << 9) * a + b); - ++i; - } else { - U += StringFromCodePoint(0xfffd); - } - } - } - return U; - }; - - converters.object = (V, opts) => { - if (type(V) !== "Object") { - throw makeException(TypeError, "is not an object", opts); - } - - return V; - }; - - // Not exported, but used in Function and VoidFunction. - - // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so - // handling for that is omitted. - function convertCallbackFunction(V, opts) { - if (typeof V !== "function") { - throw makeException(TypeError, "is not a function", opts); - } - return V; - } - - function isDataView(V) { - return ArrayBufferIsView(V) && - TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; - } - - function isNonSharedArrayBuffer(V) { - return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); - } - - function isSharedArrayBuffer(V) { - // deno-lint-ignore prefer-primordials - return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); - } - - converters.ArrayBuffer = (V, opts = {}) => { - if (!isNonSharedArrayBuffer(V)) { - if (opts.allowShared && !isSharedArrayBuffer(V)) { + if (x < lowerBound || x > upperBound) { throw makeException( TypeError, - "is not an ArrayBuffer or SharedArrayBuffer", + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, opts, ); } - throw makeException(TypeError, "is not an ArrayBuffer", opts); + + return x; } - return V; + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); + + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if + // possible. Hopefully it's an optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } + + // These will not work great for bitLength of 64, but oh well. See the README for more details. + x = modulo(x, twoToTheBitLength); + if (isSigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; }; +} - converters.DataView = (V, opts = {}) => { - if (!isDataView(V)) { - throw makeException(TypeError, "is not a DataView", opts); +function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = NumberMAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; + + return (V, opts = {}) => { + let x = toNumber(V); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; } - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; +} + +const converters = []; + +converters.any = (V) => { + return V; +}; + +converters.boolean = function (val) { + return !!val; +}; + +converters.byte = createIntegerConversion(8, { unsigned: false }); +converters.octet = createIntegerConversion(8, { unsigned: true }); + +converters.short = createIntegerConversion(16, { unsigned: false }); +converters["unsigned short"] = createIntegerConversion(16, { + unsigned: true, +}); + +converters.long = createIntegerConversion(32, { unsigned: false }); +converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + +converters["long long"] = createLongLongConversion(64, { unsigned: false }); +converters["unsigned long long"] = createLongLongConversion(64, { + unsigned: true, +}); + +converters.float = (V, opts) => { + const x = toNumber(V); + + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + if (ObjectIs(x, -0)) { + return x; + } + + const y = MathFround(x); + + if (!NumberIsFinite(y)) { + throw makeException( + TypeError, + "is outside the range of a single-precision floating-point value", + opts, + ); + } + + return y; +}; + +converters["unrestricted float"] = (V, _opts) => { + const x = toNumber(V); + + if (isNaN(x)) { + return x; + } + + if (ObjectIs(x, -0)) { + return x; + } + + return MathFround(x); +}; + +converters.double = (V, opts) => { + const x = toNumber(V); + + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + return x; +}; + +converters["unrestricted double"] = (V, _opts) => { + const x = toNumber(V); + + return x; +}; + +converters.DOMString = function (V, opts = {}) { + if (typeof V === "string") { + return V; + } else if (V === null && opts.treatNullAsEmptyString) { + return ""; + } else if (typeof V === "symbol") { + throw makeException( + TypeError, + "is a symbol, which cannot be converted to a string", + opts, + ); + } + + return String(V); +}; + +// deno-lint-ignore no-control-regex +const IS_BYTE_STRING = /^[\x00-\xFF]*$/; +converters.ByteString = (V, opts) => { + const x = converters.DOMString(V, opts); + if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) { + throw makeException(TypeError, "is not a valid ByteString", opts); + } + return x; +}; + +converters.USVString = (V, opts) => { + const S = converters.DOMString(V, opts); + const n = S.length; + let U = ""; + for (let i = 0; i < n; ++i) { + const c = StringPrototypeCharCodeAt(S, i); + if (c < 0xd800 || c > 0xdfff) { + U += StringFromCodePoint(c); + } else if (0xdc00 <= c && c <= 0xdfff) { + U += StringFromCodePoint(0xfffd); + } else if (i === n - 1) { + U += StringFromCodePoint(0xfffd); + } else { + const d = StringPrototypeCharCodeAt(S, i + 1); + if (0xdc00 <= d && d <= 0xdfff) { + const a = c & 0x3ff; + const b = d & 0x3ff; + U += StringFromCodePoint((2 << 15) + (2 << 9) * a + b); + ++i; + } else { + U += StringFromCodePoint(0xfffd); + } + } + } + return U; +}; + +converters.object = (V, opts) => { + if (type(V) !== "Object") { + throw makeException(TypeError, "is not an object", opts); + } + + return V; +}; + +// Not exported, but used in Function and VoidFunction. + +// Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so +// handling for that is omitted. +function convertCallbackFunction(V, opts) { + if (typeof V !== "function") { + throw makeException(TypeError, "is not a function", opts); + } + return V; +} + +function isDataView(V) { + return ArrayBufferIsView(V) && + TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; +} + +function isNonSharedArrayBuffer(V) { + return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); +} + +function isSharedArrayBuffer(V) { + // deno-lint-ignore prefer-primordials + return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); +} + +converters.ArrayBuffer = (V, opts = {}) => { + if (!isNonSharedArrayBuffer(V)) { + if (opts.allowShared && !isSharedArrayBuffer(V)) { throw makeException( TypeError, - "is backed by a SharedArrayBuffer, which is not allowed", + "is not an ArrayBuffer or SharedArrayBuffer", opts, ); } + throw makeException(TypeError, "is not an ArrayBuffer", opts); + } - return V; - }; + return V; +}; - // Returns the unforgeable `TypedArray` constructor name or `undefined`, - // if the `this` value isn't a valid `TypedArray` object. - // - // https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag - const typedArrayNameGetter = ObjectGetOwnPropertyDescriptor( - ObjectGetPrototypeOf(Uint8Array).prototype, - SymbolToStringTag, - ).get; - ArrayPrototypeForEach( - [ - Int8Array, - Int16Array, - Int32Array, - Uint8Array, - Uint16Array, - Uint32Array, - Uint8ClampedArray, - Float32Array, - Float64Array, - ], - (func) => { - const name = func.name; - const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; - converters[name] = (V, opts = {}) => { - if (!ArrayBufferIsView(V) || typedArrayNameGetter.call(V) !== name) { - throw makeException( - TypeError, - `is not ${article} ${name} object`, - opts, - ); - } - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } +converters.DataView = (V, opts = {}) => { + if (!isDataView(V)) { + throw makeException(TypeError, "is not a DataView", opts); + } - return V; - }; - }, - ); + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is backed by a SharedArrayBuffer, which is not allowed", + opts, + ); + } - // Common definitions + return V; +}; - converters.ArrayBufferView = (V, opts = {}) => { - if (!ArrayBufferIsView(V)) { - throw makeException( - TypeError, - "is not a view on an ArrayBuffer or SharedArrayBuffer", - opts, - ); - } - - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } - - return V; - }; - - converters.BufferSource = (V, opts = {}) => { - if (ArrayBufferIsView(V)) { +// Returns the unforgeable `TypedArray` constructor name or `undefined`, +// if the `this` value isn't a valid `TypedArray` object. +// +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const typedArrayNameGetter = ObjectGetOwnPropertyDescriptor( + ObjectGetPrototypeOf(Uint8Array).prototype, + SymbolToStringTag, +).get; +ArrayPrototypeForEach( + [ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array, + ], + (func) => { + const name = func.name; + const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; + converters[name] = (V, opts = {}) => { + if (!ArrayBufferIsView(V) || typedArrayNameGetter.call(V) !== name) { + throw makeException( + TypeError, + `is not ${article} ${name} object`, + opts, + ); + } if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { throw makeException( TypeError, @@ -565,627 +526,662 @@ } return V; - } + }; + }, +); - if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { +// Common definitions + +converters.ArrayBufferView = (V, opts = {}) => { + if (!ArrayBufferIsView(V)) { + throw makeException( + TypeError, + "is not a view on an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + return V; +}; + +converters.BufferSource = (V, opts = {}) => { + if (ArrayBufferIsView(V)) { + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { throw makeException( TypeError, - "is not an ArrayBuffer or a view on one", - opts, - ); - } - if ( - opts.allowShared && - !isSharedArrayBuffer(V) && - !isNonSharedArrayBuffer(V) - ) { - throw makeException( - TypeError, - "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + "is a view on a SharedArrayBuffer, which is not allowed", opts, ); } return V; - }; + } - converters.DOMTimeStamp = converters["unsigned long long"]; - converters.DOMHighResTimeStamp = converters["double"]; + if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or a view on one", + opts, + ); + } + if ( + opts.allowShared && + !isSharedArrayBuffer(V) && + !isNonSharedArrayBuffer(V) + ) { + throw makeException( + TypeError, + "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + opts, + ); + } - converters.Function = convertCallbackFunction; + return V; +}; - converters.VoidFunction = convertCallbackFunction; +converters.DOMTimeStamp = converters["unsigned long long"]; +converters.DOMHighResTimeStamp = converters["double"]; - converters["UVString?"] = createNullableConverter( - converters.USVString, - ); - converters["sequence"] = createSequenceConverter( - converters.double, - ); - converters["sequence"] = createSequenceConverter( - converters.object, - ); - converters["Promise"] = createPromiseConverter(() => undefined); +converters.Function = convertCallbackFunction; - converters["sequence"] = createSequenceConverter( - converters.ByteString, - ); - converters["sequence>"] = createSequenceConverter( - converters["sequence"], - ); - converters["record"] = createRecordConverter( - converters.ByteString, - converters.ByteString, - ); +converters.VoidFunction = convertCallbackFunction; - converters["sequence"] = createSequenceConverter( - converters.USVString, - ); - converters["sequence>"] = createSequenceConverter( - converters["sequence"], - ); - converters["record"] = createRecordConverter( - converters.USVString, - converters.USVString, - ); +converters["UVString?"] = createNullableConverter( + converters.USVString, +); +converters["sequence"] = createSequenceConverter( + converters.double, +); +converters["sequence"] = createSequenceConverter( + converters.object, +); +converters["Promise"] = createPromiseConverter(() => undefined); - converters["sequence"] = createSequenceConverter( - converters.DOMString, - ); +converters["sequence"] = createSequenceConverter( + converters.ByteString, +); +converters["sequence>"] = createSequenceConverter( + converters["sequence"], +); +converters["record"] = createRecordConverter( + converters.ByteString, + converters.ByteString, +); - function requiredArguments(length, required, opts = {}) { - if (length < required) { - const errMsg = `${ - opts.prefix ? opts.prefix + ": " : "" - }${required} argument${ - required === 1 ? "" : "s" - } required, but only ${length} present.`; - throw new TypeError(errMsg); +converters["sequence"] = createSequenceConverter( + converters.USVString, +); +converters["sequence>"] = createSequenceConverter( + converters["sequence"], +); +converters["record"] = createRecordConverter( + converters.USVString, + converters.USVString, +); + +converters["sequence"] = createSequenceConverter( + converters.DOMString, +); + +function requiredArguments(length, required, opts = {}) { + if (length < required) { + const errMsg = `${ + opts.prefix ? opts.prefix + ": " : "" + }${required} argument${ + required === 1 ? "" : "s" + } required, but only ${length} present.`; + throw new TypeError(errMsg); + } +} + +function createDictionaryConverter(name, ...dictionaries) { + let hasRequiredKey = false; + const allMembers = []; + for (let i = 0; i < dictionaries.length; ++i) { + const members = dictionaries[i]; + for (let j = 0; j < members.length; ++j) { + const member = members[j]; + if (member.required) { + hasRequiredKey = true; + } + ArrayPrototypePush(allMembers, member); + } + } + ArrayPrototypeSort(allMembers, (a, b) => { + if (a.key == b.key) { + return 0; + } + return a.key < b.key ? -1 : 1; + }); + + const defaultValues = {}; + for (let i = 0; i < allMembers.length; ++i) { + const member = allMembers[i]; + if (ReflectHas(member, "defaultValue")) { + const idlMemberValue = member.defaultValue; + const imvType = typeof idlMemberValue; + // Copy by value types can be directly assigned, copy by reference types + // need to be re-created for each allocation. + if ( + imvType === "number" || imvType === "boolean" || + imvType === "string" || imvType === "bigint" || + imvType === "undefined" + ) { + defaultValues[member.key] = member.converter(idlMemberValue, {}); + } else { + ObjectDefineProperty(defaultValues, member.key, { + get() { + return member.converter(idlMemberValue, member.defaultValue); + }, + enumerable: true, + }); + } } } - function createDictionaryConverter(name, ...dictionaries) { - let hasRequiredKey = false; - const allMembers = []; - for (let i = 0; i < dictionaries.length; ++i) { - const members = dictionaries[i]; - for (let j = 0; j < members.length; ++j) { - const member = members[j]; - if (member.required) { - hasRequiredKey = true; - } - ArrayPrototypePush(allMembers, member); - } + return function (V, opts = {}) { + const typeV = type(V); + switch (typeV) { + case "Undefined": + case "Null": + case "Object": + break; + default: + throw makeException( + TypeError, + "can not be converted to a dictionary", + opts, + ); + } + const esDict = V; + + const idlDict = ObjectAssign({}, defaultValues); + + // NOTE: fast path Null and Undefined. + if ((V === undefined || V === null) && !hasRequiredKey) { + return idlDict; } - ArrayPrototypeSort(allMembers, (a, b) => { - if (a.key == b.key) { - return 0; - } - return a.key < b.key ? -1 : 1; - }); - const defaultValues = {}; for (let i = 0; i < allMembers.length; ++i) { const member = allMembers[i]; - if (ReflectHas(member, "defaultValue")) { - const idlMemberValue = member.defaultValue; - const imvType = typeof idlMemberValue; - // Copy by value types can be directly assigned, copy by reference types - // need to be re-created for each allocation. - if ( - imvType === "number" || imvType === "boolean" || - imvType === "string" || imvType === "bigint" || - imvType === "undefined" - ) { - defaultValues[member.key] = member.converter(idlMemberValue, {}); - } else { - ObjectDefineProperty(defaultValues, member.key, { - get() { - return member.converter(idlMemberValue, member.defaultValue); - }, - enumerable: true, - }); - } + const key = member.key; + + let esMemberValue; + if (typeV === "Undefined" || typeV === "Null") { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } + + if (esMemberValue !== undefined) { + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : "" + }`; + const converter = member.converter; + const idlMemberValue = converter(esMemberValue, { ...opts, context }); + idlDict[key] = idlMemberValue; + } else if (member.required) { + throw makeException( + TypeError, + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, + opts, + ); } } - return function (V, opts = {}) { - const typeV = type(V); - switch (typeV) { - case "Undefined": - case "Null": - case "Object": - break; - default: - throw makeException( - TypeError, - "can not be converted to a dictionary", - opts, - ); - } - const esDict = V; + return idlDict; + }; +} - const idlDict = ObjectAssign({}, defaultValues); +// https://heycam.github.io/webidl/#es-enumeration +function createEnumConverter(name, values) { + const E = new Set(values); - // NOTE: fast path Null and Undefined. - if ((V === undefined || V === null) && !hasRequiredKey) { - return idlDict; - } + return function (V, opts = {}) { + const S = String(V); - for (let i = 0; i < allMembers.length; ++i) { - const member = allMembers[i]; - const key = member.key; + if (!E.has(S)) { + throw new TypeError( + `${ + opts.prefix ? opts.prefix + ": " : "" + }The provided value '${S}' is not a valid enum value of type ${name}.`, + ); + } - let esMemberValue; - if (typeV === "Undefined" || typeV === "Null") { - esMemberValue = undefined; - } else { - esMemberValue = esDict[key]; - } + return S; + }; +} - if (esMemberValue !== undefined) { - const context = `'${key}' of '${name}'${ - opts.context ? ` (${opts.context})` : "" - }`; - const converter = member.converter; - const idlMemberValue = converter(esMemberValue, { ...opts, context }); - idlDict[key] = idlMemberValue; - } else if (member.required) { - throw makeException( - TypeError, - `can not be converted to '${name}' because '${key}' is required in '${name}'.`, - opts, - ); - } - } +function createNullableConverter(converter) { + return (V, opts = {}) => { + // FIXME: If Type(V) is not Object, and the conversion to an IDL value is + // being performed due to V being assigned to an attribute whose type is a + // nullable callback function that is annotated with + // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? + // value null. - return idlDict; - }; - } + if (V === null || V === undefined) return null; + return converter(V, opts); + }; +} - // https://heycam.github.io/webidl/#es-enumeration - function createEnumConverter(name, values) { - const E = new Set(values); - - return function (V, opts = {}) { - const S = String(V); - - if (!E.has(S)) { - throw new TypeError( - `${ - opts.prefix ? opts.prefix + ": " : "" - }The provided value '${S}' is not a valid enum value of type ${name}.`, - ); - } - - return S; - }; - } - - function createNullableConverter(converter) { - return (V, opts = {}) => { - // FIXME: If Type(V) is not Object, and the conversion to an IDL value is - // being performed due to V being assigned to an attribute whose type is a - // nullable callback function that is annotated with - // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? - // value null. - - if (V === null || V === undefined) return null; - return converter(V, opts); - }; - } - - // https://heycam.github.io/webidl/#es-sequence - function createSequenceConverter(converter) { - return function (V, opts = {}) { - if (type(V) !== "Object") { +// https://heycam.github.io/webidl/#es-sequence +function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (type(V) !== "Object") { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { throw makeException( TypeError, "can not be converted to sequence.", opts, ); } - const iter = V?.[SymbolIterator]?.(); - if (iter === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - const array = []; - while (true) { - const res = iter?.next?.(); - if (res === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - if (res.done === true) break; - const val = converter(res.value, { - ...opts, - context: `${opts.context}, index ${array.length}`, - }); - ArrayPrototypePush(array, val); - } - return array; - }; - } + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); + } + return array; + }; +} - function createRecordConverter(keyConverter, valueConverter) { - return (V, opts) => { - if (type(V) !== "Object") { - throw makeException( - TypeError, - "can not be converted to dictionary.", - opts, - ); - } - const result = {}; - // Fast path for common case (not a Proxy) - if (!core.isProxy(V)) { - for (const key in V) { - if (!ObjectPrototypeHasOwnProperty(V, key)) { - continue; - } - const typedKey = keyConverter(key, opts); - const value = V[key]; - const typedValue = valueConverter(value, opts); - result[typedKey] = typedValue; - } - return result; - } - // Slow path if Proxy (e.g: in WPT tests) - const keys = ReflectOwnKeys(V); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - const desc = ObjectGetOwnPropertyDescriptor(V, key); - if (desc !== undefined && desc.enumerable === true) { - const typedKey = keyConverter(key, opts); - const value = V[key]; - const typedValue = valueConverter(value, opts); - result[typedKey] = typedValue; +function createRecordConverter(keyConverter, valueConverter) { + return (V, opts) => { + if (type(V) !== "Object") { + throw makeException( + TypeError, + "can not be converted to dictionary.", + opts, + ); + } + const result = {}; + // Fast path for common case (not a Proxy) + if (!core.isProxy(V)) { + for (const key in V) { + if (!ObjectPrototypeHasOwnProperty(V, key)) { + continue; } + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; } return result; - }; - } - - function createPromiseConverter(converter) { - return (V, opts) => - PromisePrototypeThen(PromiseResolve(V), (V) => converter(V, opts)); - } - - function invokeCallbackFunction( - callable, - args, - thisArg, - returnValueConverter, - opts, - ) { - try { - const rv = ReflectApply(callable, thisArg, args); - return returnValueConverter(rv, { - prefix: opts.prefix, - context: "return value", - }); - } catch (err) { - if (opts.returnsPromise === true) { - return PromiseReject(err); - } - throw err; } - } - - const brand = Symbol("[[webidl.brand]]"); - - function createInterfaceConverter(name, prototype) { - return (V, opts) => { - if (!ObjectPrototypeIsPrototypeOf(prototype, V) || V[brand] !== brand) { - throw makeException(TypeError, `is not of type ${name}.`, opts); - } - return V; - }; - } - - // TODO(lucacasonato): have the user pass in the prototype, and not the type. - function createBranded(Type) { - const t = ObjectCreate(Type.prototype); - t[brand] = brand; - return t; - } - - function assertBranded(self, prototype) { - if ( - !ObjectPrototypeIsPrototypeOf(prototype, self) || self[brand] !== brand - ) { - throw new TypeError("Illegal invocation"); - } - } - - function illegalConstructor() { - throw new TypeError("Illegal constructor"); - } - - function define(target, source) { - const keys = ReflectOwnKeys(source); + // Slow path if Proxy (e.g: in WPT tests) + const keys = ReflectOwnKeys(V); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; - const descriptor = ReflectGetOwnPropertyDescriptor(source, key); - if (descriptor && !ReflectDefineProperty(target, key, descriptor)) { - throw new TypeError(`Cannot redefine property: ${String(key)}`); + const desc = ObjectGetOwnPropertyDescriptor(V, key); + if (desc !== undefined && desc.enumerable === true) { + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; } } + return result; + }; +} + +function createPromiseConverter(converter) { + return (V, opts) => + PromisePrototypeThen(PromiseResolve(V), (V) => converter(V, opts)); +} + +function invokeCallbackFunction( + callable, + args, + thisArg, + returnValueConverter, + opts, +) { + try { + const rv = ReflectApply(callable, thisArg, args); + return returnValueConverter(rv, { + prefix: opts.prefix, + context: "return value", + }); + } catch (err) { + if (opts.returnsPromise === true) { + return PromiseReject(err); + } + throw err; + } +} + +const brand = Symbol("[[webidl.brand]]"); + +function createInterfaceConverter(name, prototype) { + return (V, opts) => { + if (!ObjectPrototypeIsPrototypeOf(prototype, V) || V[brand] !== brand) { + throw makeException(TypeError, `is not of type ${name}.`, opts); + } + return V; + }; +} + +// TODO(lucacasonato): have the user pass in the prototype, and not the type. +function createBranded(Type) { + const t = ObjectCreate(Type.prototype); + t[brand] = brand; + return t; +} + +function assertBranded(self, prototype) { + if ( + !ObjectPrototypeIsPrototypeOf(prototype, self) || self[brand] !== brand + ) { + throw new TypeError("Illegal invocation"); + } +} + +function illegalConstructor() { + throw new TypeError("Illegal constructor"); +} + +function define(target, source) { + const keys = ReflectOwnKeys(source); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = ReflectGetOwnPropertyDescriptor(source, key); + if (descriptor && !ReflectDefineProperty(target, key, descriptor)) { + throw new TypeError(`Cannot redefine property: ${String(key)}`); + } } +} - const _iteratorInternal = Symbol("iterator internal"); +const _iteratorInternal = Symbol("iterator internal"); - const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); +const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); - function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) { - const iteratorPrototype = ObjectCreate(globalIteratorPrototype, { - [SymbolToStringTag]: { configurable: true, value: `${name} Iterator` }, +function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) { + const iteratorPrototype = ObjectCreate(globalIteratorPrototype, { + [SymbolToStringTag]: { configurable: true, value: `${name} Iterator` }, + }); + define(iteratorPrototype, { + next() { + const internal = this && this[_iteratorInternal]; + if (!internal) { + throw new TypeError( + `next() called on a value that is not a ${name} iterator object`, + ); + } + const { target, kind, index } = internal; + const values = target[dataSymbol]; + const len = values.length; + if (index >= len) { + return { value: undefined, done: true }; + } + const pair = values[index]; + internal.index = index + 1; + let result; + switch (kind) { + case "key": + result = pair[keyKey]; + break; + case "value": + result = pair[valueKey]; + break; + case "key+value": + result = [pair[keyKey], pair[valueKey]]; + break; + } + return { value: result, done: false }; + }, + }); + function createDefaultIterator(target, kind) { + const iterator = ObjectCreate(iteratorPrototype); + ObjectDefineProperty(iterator, _iteratorInternal, { + value: { target, kind, index: 0 }, + configurable: true, }); - define(iteratorPrototype, { - next() { - const internal = this && this[_iteratorInternal]; - if (!internal) { - throw new TypeError( - `next() called on a value that is not a ${name} iterator object`, - ); - } - const { target, kind, index } = internal; - const values = target[dataSymbol]; - const len = values.length; - if (index >= len) { - return { value: undefined, done: true }; - } - const pair = values[index]; - internal.index = index + 1; - let result; - switch (kind) { - case "key": - result = pair[keyKey]; - break; - case "value": - result = pair[valueKey]; - break; - case "key+value": - result = [pair[keyKey], pair[valueKey]]; - break; - } - return { value: result, done: false }; - }, - }); - function createDefaultIterator(target, kind) { - const iterator = ObjectCreate(iteratorPrototype); - ObjectDefineProperty(iterator, _iteratorInternal, { - value: { target, kind, index: 0 }, - configurable: true, - }); - return iterator; - } - - function entries() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key+value"); - } - - const properties = { - entries: { - value: entries, - writable: true, - enumerable: true, - configurable: true, - }, - [SymbolIterator]: { - value: entries, - writable: true, - enumerable: false, - configurable: true, - }, - keys: { - value: function keys() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key"); - }, - writable: true, - enumerable: true, - configurable: true, - }, - values: { - value: function values() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "value"); - }, - writable: true, - enumerable: true, - configurable: true, - }, - forEach: { - value: function forEach(idlCallback, thisArg = undefined) { - assertBranded(this, prototype.prototype); - const prefix = `Failed to execute 'forEach' on '${name}'`; - requiredArguments(arguments.length, 1, { prefix }); - idlCallback = converters["Function"](idlCallback, { - prefix, - context: "Argument 1", - }); - idlCallback = FunctionPrototypeBind( - idlCallback, - thisArg ?? globalThis, - ); - const pairs = this[dataSymbol]; - for (let i = 0; i < pairs.length; i++) { - const entry = pairs[i]; - idlCallback(entry[valueKey], entry[keyKey], this); - } - }, - writable: true, - enumerable: true, - configurable: true, - }, - }; - return ObjectDefineProperties(prototype.prototype, properties); + return iterator; } - function configurePrototype(prototype) { - const descriptors = ObjectGetOwnPropertyDescriptors(prototype.prototype); - for (const key in descriptors) { - if (!ObjectPrototypeHasOwnProperty(descriptors, key)) { - continue; - } - if (key === "constructor") continue; - const descriptor = descriptors[key]; - if ( - ReflectHas(descriptor, "value") && - typeof descriptor.value === "function" - ) { - ObjectDefineProperty(prototype.prototype, key, { - enumerable: true, - writable: true, - configurable: true, - }); - } else if (ReflectHas(descriptor, "get")) { - ObjectDefineProperty(prototype.prototype, key, { - enumerable: true, - configurable: true, - }); - } - } - ObjectDefineProperty(prototype.prototype, SymbolToStringTag, { - value: prototype.name, + function entries() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "key+value"); + } + + const properties = { + entries: { + value: entries, + writable: true, + enumerable: true, + configurable: true, + }, + [SymbolIterator]: { + value: entries, + writable: true, enumerable: false, configurable: true, - writable: false, - }); + }, + keys: { + value: function keys() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "key"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + values: { + value: function values() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "value"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + forEach: { + value: function forEach(idlCallback, thisArg = undefined) { + assertBranded(this, prototype.prototype); + const prefix = `Failed to execute 'forEach' on '${name}'`; + requiredArguments(arguments.length, 1, { prefix }); + idlCallback = converters["Function"](idlCallback, { + prefix, + context: "Argument 1", + }); + idlCallback = FunctionPrototypeBind( + idlCallback, + thisArg ?? globalThis, + ); + const pairs = this[dataSymbol]; + for (let i = 0; i < pairs.length; i++) { + const entry = pairs[i]; + idlCallback(entry[valueKey], entry[keyKey], this); + } + }, + writable: true, + enumerable: true, + configurable: true, + }, + }; + return ObjectDefineProperties(prototype.prototype, properties); +} + +function configurePrototype(prototype) { + const descriptors = ObjectGetOwnPropertyDescriptors(prototype.prototype); + for (const key in descriptors) { + if (!ObjectPrototypeHasOwnProperty(descriptors, key)) { + continue; + } + if (key === "constructor") continue; + const descriptor = descriptors[key]; + if ( + ReflectHas(descriptor, "value") && + typeof descriptor.value === "function" + ) { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + writable: true, + configurable: true, + }); + } else if (ReflectHas(descriptor, "get")) { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + configurable: true, + }); + } } + ObjectDefineProperty(prototype.prototype, SymbolToStringTag, { + value: prototype.name, + enumerable: false, + configurable: true, + writable: false, + }); +} - const setlikeInner = Symbol("[[set]]"); +const setlikeInner = Symbol("[[set]]"); - // Ref: https://webidl.spec.whatwg.org/#es-setlike - function setlike(obj, objPrototype, readonly) { +// Ref: https://webidl.spec.whatwg.org/#es-setlike +function setlike(obj, objPrototype, readonly) { + ObjectDefineProperties(obj, { + size: { + configurable: true, + enumerable: true, + get() { + assertBranded(this, objPrototype); + return obj[setlikeInner].size; + }, + }, + [SymbolIterator]: { + configurable: true, + enumerable: false, + writable: true, + value() { + assertBranded(this, objPrototype); + return obj[setlikeInner][SymbolIterator](); + }, + }, + entries: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeEntries(obj[setlikeInner]); + }, + }, + keys: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeKeys(obj[setlikeInner]); + }, + }, + values: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeValues(obj[setlikeInner]); + }, + }, + forEach: { + configurable: true, + enumerable: true, + writable: true, + value(callbackfn, thisArg) { + assertBranded(this, objPrototype); + return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg); + }, + }, + has: { + configurable: true, + enumerable: true, + writable: true, + value(value) { + assertBranded(this, objPrototype); + return SetPrototypeHas(obj[setlikeInner], value); + }, + }, + }); + + if (!readonly) { ObjectDefineProperties(obj, { - size: { - configurable: true, - enumerable: true, - get() { - assertBranded(this, objPrototype); - return obj[setlikeInner].size; - }, - }, - [SymbolIterator]: { - configurable: true, - enumerable: false, - writable: true, - value() { - assertBranded(this, objPrototype); - return obj[setlikeInner][SymbolIterator](); - }, - }, - entries: { - configurable: true, - enumerable: true, - writable: true, - value() { - assertBranded(this, objPrototype); - return SetPrototypeEntries(obj[setlikeInner]); - }, - }, - keys: { - configurable: true, - enumerable: true, - writable: true, - value() { - assertBranded(this, objPrototype); - return SetPrototypeKeys(obj[setlikeInner]); - }, - }, - values: { - configurable: true, - enumerable: true, - writable: true, - value() { - assertBranded(this, objPrototype); - return SetPrototypeValues(obj[setlikeInner]); - }, - }, - forEach: { - configurable: true, - enumerable: true, - writable: true, - value(callbackfn, thisArg) { - assertBranded(this, objPrototype); - return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg); - }, - }, - has: { + add: { configurable: true, enumerable: true, writable: true, value(value) { assertBranded(this, objPrototype); - return SetPrototypeHas(obj[setlikeInner], value); + return SetPrototypeAdd(obj[setlikeInner], value); + }, + }, + delete: { + configurable: true, + enumerable: true, + writable: true, + value(value) { + assertBranded(this, objPrototype); + return SetPrototypeDelete(obj[setlikeInner], value); + }, + }, + clear: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeClear(obj[setlikeInner]); }, }, }); - - if (!readonly) { - ObjectDefineProperties(obj, { - add: { - configurable: true, - enumerable: true, - writable: true, - value(value) { - assertBranded(this, objPrototype); - return SetPrototypeAdd(obj[setlikeInner], value); - }, - }, - delete: { - configurable: true, - enumerable: true, - writable: true, - value(value) { - assertBranded(this, objPrototype); - return SetPrototypeDelete(obj[setlikeInner], value); - }, - }, - clear: { - configurable: true, - enumerable: true, - writable: true, - value() { - assertBranded(this, objPrototype); - return SetPrototypeClear(obj[setlikeInner]); - }, - }, - }); - } } +} - window.__bootstrap ??= {}; - window.__bootstrap.webidl = { - type, - makeException, - converters, - requiredArguments, - createDictionaryConverter, - createEnumConverter, - createNullableConverter, - createSequenceConverter, - createRecordConverter, - createPromiseConverter, - invokeCallbackFunction, - createInterfaceConverter, - brand, - createBranded, - assertBranded, - illegalConstructor, - mixinPairIterable, - configurePrototype, - setlike, - setlikeInner, - }; -})(this); +export { + assertBranded, + brand, + configurePrototype, + converters, + createBranded, + createDictionaryConverter, + createEnumConverter, + createInterfaceConverter, + createNullableConverter, + createPromiseConverter, + createRecordConverter, + createSequenceConverter, + illegalConstructor, + invokeCallbackFunction, + makeException, + mixinPairIterable, + requiredArguments, + setlike, + setlikeInner, + type, +}; diff --git a/ext/webidl/benches/dict.js b/ext/webidl/benches/dict.js index 353a630eb9..b53326de9c 100644 --- a/ext/webidl/benches/dict.js +++ b/ext/webidl/benches/dict.js @@ -2,7 +2,10 @@ // deno-lint-ignore-file -const { createDictionaryConverter, converters } = globalThis.__bootstrap.webidl; +import { + converters, + createDictionaryConverter, +} from "internal:ext/webidl/00_webidl.js"; const TextDecodeOptions = createDictionaryConverter( "TextDecodeOptions", @@ -14,6 +17,7 @@ const TextDecodeOptions = createDictionaryConverter( }, ], ); +globalThis.TextDecodeOptions = TextDecodeOptions; // Sanity check { @@ -33,3 +37,4 @@ function handwrittenConverter(V) { } return defaultValue; } +globalThis.handwrittenConverter = handwrittenConverter; diff --git a/ext/webidl/benches/dict.rs b/ext/webidl/benches/dict.rs index e7df8af62d..1400a00ed5 100644 --- a/ext/webidl/benches/dict.rs +++ b/ext/webidl/benches/dict.rs @@ -11,7 +11,7 @@ fn setup() -> Vec { vec![ deno_webidl::init(), Extension::builder("deno_webidl_bench") - .js(vec![("setup", include_str!("dict.js"))]) + .esm(vec![("internal:setup", include_str!("dict.js"))]) .build(), ] } diff --git a/ext/webidl/internal.d.ts b/ext/webidl/internal.d.ts index 4ab9e33a92..9f47c42e50 100644 --- a/ext/webidl/internal.d.ts +++ b/ext/webidl/internal.d.ts @@ -4,338 +4,334 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare namespace webidl { - declare interface ConverterOpts { - /** - * The prefix for error messages created by this converter. - * Examples: - * - `Failed to construct 'Event'` - * - `Failed to execute 'removeEventListener' on 'EventTarget'` - */ - prefix: string; - } - declare interface ValueConverterOpts extends ConverterOpts { - /** - * The context of this value error messages created by this converter. - * Examples: - * - `Argument 1` - * - `Argument 3` - */ - context: string; - } - declare function makeException( - ErrorType: any, - message: string, - opts: ValueConverterOpts, - ): any; - declare interface IntConverterOpts extends ValueConverterOpts { - /** - * Wether to throw if the number is outside of the acceptable values for - * this type. - */ - enforceRange?: boolean; - /** - * Wether to clamp this number to the acceptable values for this type. - */ - clamp?: boolean; - } - declare interface StringConverterOpts extends ValueConverterOpts { - /** - * Wether to treat `null` value as an empty string. - */ - treatNullAsEmptyString?: boolean; - } - declare interface BufferConverterOpts extends ValueConverterOpts { - /** - * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). - */ - allowShared?: boolean; - } - declare const converters: { - any(v: any): any; - /** - * Convert a value into a `boolean` (bool). - */ - boolean(v: any, opts?: IntConverterOpts): boolean; - /** - * Convert a value into a `byte` (int8). - */ - byte(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `octet` (uint8). - */ - octet(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `short` (int16). - */ - short(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned short` (uint16). - */ - ["unsigned short"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long` (int32). - */ - long(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long` (uint32). - */ - ["unsigned long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long long` (int64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long long` (uint64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["unsigned long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `float` (f32). - */ - float(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted float` (f32, infinity, or NaN). - */ - ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `double` (f64). - */ - double(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted double` (f64, infinity, or NaN). - */ - ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `DOMString` (string). - */ - DOMString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `ByteString` (string with only u8 codepoints). - */ - ByteString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `USVString` (string with only valid non - * surrogate Unicode code points). - */ - USVString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into an `object` (object). - */ - object(v: any, opts?: ValueConverterOpts): object; - /** - * Convert a value into an `ArrayBuffer` (ArrayBuffer). - */ - ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; - /** - * Convert a value into a `DataView` (ArrayBuffer). - */ - DataView(v: any, opts?: BufferConverterOpts): DataView; - /** - * Convert a value into a `Int8Array` (Int8Array). - */ - Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; - /** - * Convert a value into a `Int16Array` (Int16Array). - */ - Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; - /** - * Convert a value into a `Int32Array` (Int32Array). - */ - Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; - /** - * Convert a value into a `Uint8Array` (Uint8Array). - */ - Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; - /** - * Convert a value into a `Uint16Array` (Uint16Array). - */ - Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; - /** - * Convert a value into a `Uint32Array` (Uint32Array). - */ - Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; - /** - * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). - */ - Uint8ClampedArray( - v: any, - opts?: BufferConverterOpts, - ): Uint8ClampedArray; - /** - * Convert a value into a `Float32Array` (Float32Array). - */ - Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; - /** - * Convert a value into a `Float64Array` (Float64Array). - */ - Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; - /** - * Convert a value into an `ArrayBufferView` (ArrayBufferView). - */ - ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; - /** - * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). - */ - BufferSource( - v: any, - opts?: BufferConverterOpts, - ): ArrayBuffer | ArrayBufferView; - /** - * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long - */ - DOMTimeStamp(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `Function` ((...args: any[]) => any). - */ - Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; - /** - * Convert a value into a `VoidFunction` (() => void). - */ - VoidFunction(v: any, opts?: ValueConverterOpts): () => void; - ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; - ["sequence"](v: any, opts?: ValueConverterOpts): number[]; - - [type: string]: (v: any, opts: ValueConverterOpts) => any; - }; - - /** - * Assert that the a function has at least a required amount of arguments. - */ - declare function requiredArguments( - length: number, - required: number, - opts: ConverterOpts, - ): void; - declare type Dictionary = DictionaryMember[]; - declare interface DictionaryMember { - key: string; - converter: (v: any, opts: ValueConverterOpts) => any; - defaultValue?: any; - required?: boolean; - } - - /** - * Create a converter for dictionaries. - */ - declare function createDictionaryConverter( - name: string, - ...dictionaries: Dictionary[] - ): (v: any, opts: ValueConverterOpts) => T; - - /** - * Create a converter for enums. - */ - declare function createEnumConverter( - name: string, - values: string[], - ): (v: any, opts: ValueConverterOpts) => string; - - /** - * Create a converter that makes the contained type nullable. - */ - declare function createNullableConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T | null; - - /** - * Create a converter that converts a sequence of the inner type. - */ - declare function createSequenceConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T[]; - - /** - * Create a converter that converts a Promise of the inner type. - */ - declare function createPromiseConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => Promise; - - /** - * Invoke a callback function. - */ - declare function invokeCallbackFunction( - callable: (...args: any) => any, - args: any[], - thisArg: any, - returnValueConverter: (v: any, opts: ValueConverterOpts) => T, - opts: ConverterOpts & { returnsPromise?: boolean }, - ): T; - - /** - * Throw an illegal constructor error. - */ - declare function illegalConstructor(): never; - - /** - * The branding symbol. - */ - declare const brand: unique symbol; - - /** - * Create a branded instance of an interface. - */ - declare function createBranded(self: any): any; - - /** - * Assert that self is branded. - */ - declare function assertBranded(self: any, type: any): void; - - /** - * Create a converter for interfaces. - */ - declare function createInterfaceConverter( - name: string, - prototype: any, - ): (v: any, opts: ValueConverterOpts) => any; - - declare function createRecordConverter< - K extends string | number | symbol, - V, - >( - keyConverter: (v: any, opts: ValueConverterOpts) => K, - valueConverter: (v: any, opts: ValueConverterOpts) => V, - ): ( - v: Record, - opts: ValueConverterOpts, - ) => any; - - /** - * Mix in the iterable declarations defined in WebIDL. - * https://heycam.github.io/webidl/#es-iterable - */ - declare function mixinPairIterable( - name: string, - prototype: any, - dataSymbol: symbol, - keyKey: string | number | symbol, - valueKey: string | number | symbol, - ): void; - - /** - * Configure prototype properties enumerability / writability / configurability. - */ - declare function configurePrototype(prototype: any); - - /** - * Get the WebIDL / ES type of a value. - */ - declare function type( - v: any, - ): - | "Null" - | "Undefined" - | "Boolean" - | "Number" - | "String" - | "Symbol" - | "BigInt" - | "Object"; - } +declare module "internal:ext/webidl/00_webidl.js" { + interface ConverterOpts { + /** + * The prefix for error messages created by this converter. + * Examples: + * - `Failed to construct 'Event'` + * - `Failed to execute 'removeEventListener' on 'EventTarget'` + */ + prefix: string; } + interface ValueConverterOpts extends ConverterOpts { + /** + * The context of this value error messages created by this converter. + * Examples: + * - `Argument 1` + * - `Argument 3` + */ + context: string; + } + function makeException( + ErrorType: any, + message: string, + opts: ValueConverterOpts, + ): any; + interface IntConverterOpts extends ValueConverterOpts { + /** + * Wether to throw if the number is outside of the acceptable values for + * this type. + */ + enforceRange?: boolean; + /** + * Wether to clamp this number to the acceptable values for this type. + */ + clamp?: boolean; + } + interface StringConverterOpts extends ValueConverterOpts { + /** + * Wether to treat `null` value as an empty string. + */ + treatNullAsEmptyString?: boolean; + } + interface BufferConverterOpts extends ValueConverterOpts { + /** + * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). + */ + allowShared?: boolean; + } + const converters: { + any(v: any): any; + /** + * Convert a value into a `boolean` (bool). + */ + boolean(v: any, opts?: IntConverterOpts): boolean; + /** + * Convert a value into a `byte` (int8). + */ + byte(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `octet` (uint8). + */ + octet(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `short` (int16). + */ + short(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned short` (uint16). + */ + ["unsigned short"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long` (int32). + */ + long(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long` (uint32). + */ + ["unsigned long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long long` (int64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long long` (uint64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["unsigned long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `float` (f32). + */ + float(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted float` (f32, infinity, or NaN). + */ + ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `double` (f64). + */ + double(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted double` (f64, infinity, or NaN). + */ + ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `DOMString` (string). + */ + DOMString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `ByteString` (string with only u8 codepoints). + */ + ByteString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `USVString` (string with only valid non + * surrogate Unicode code points). + */ + USVString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into an `object` (object). + */ + object(v: any, opts?: ValueConverterOpts): object; + /** + * Convert a value into an `ArrayBuffer` (ArrayBuffer). + */ + ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; + /** + * Convert a value into a `DataView` (ArrayBuffer). + */ + DataView(v: any, opts?: BufferConverterOpts): DataView; + /** + * Convert a value into a `Int8Array` (Int8Array). + */ + Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; + /** + * Convert a value into a `Int16Array` (Int16Array). + */ + Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; + /** + * Convert a value into a `Int32Array` (Int32Array). + */ + Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; + /** + * Convert a value into a `Uint8Array` (Uint8Array). + */ + Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; + /** + * Convert a value into a `Uint16Array` (Uint16Array). + */ + Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; + /** + * Convert a value into a `Uint32Array` (Uint32Array). + */ + Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; + /** + * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). + */ + Uint8ClampedArray( + v: any, + opts?: BufferConverterOpts, + ): Uint8ClampedArray; + /** + * Convert a value into a `Float32Array` (Float32Array). + */ + Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; + /** + * Convert a value into a `Float64Array` (Float64Array). + */ + Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; + /** + * Convert a value into an `ArrayBufferView` (ArrayBufferView). + */ + ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; + /** + * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). + */ + BufferSource( + v: any, + opts?: BufferConverterOpts, + ): ArrayBuffer | ArrayBufferView; + /** + * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long + */ + DOMTimeStamp(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `Function` ((...args: any[]) => any). + */ + Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; + /** + * Convert a value into a `VoidFunction` (() => void). + */ + VoidFunction(v: any, opts?: ValueConverterOpts): () => void; + ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; + ["sequence"](v: any, opts?: ValueConverterOpts): number[]; + + [type: string]: (v: any, opts: ValueConverterOpts) => any; + }; + + /** + * Assert that the a function has at least a required amount of arguments. + */ + function requiredArguments( + length: number, + required: number, + opts: ConverterOpts, + ): void; + type Dictionary = DictionaryMember[]; + interface DictionaryMember { + key: string; + converter: (v: any, opts: ValueConverterOpts) => any; + defaultValue?: any; + required?: boolean; + } + + /** + * Create a converter for dictionaries. + */ + function createDictionaryConverter( + name: string, + ...dictionaries: Dictionary[] + ): (v: any, opts: ValueConverterOpts) => T; + + /** + * Create a converter for enums. + */ + function createEnumConverter( + name: string, + values: string[], + ): (v: any, opts: ValueConverterOpts) => string; + + /** + * Create a converter that makes the contained type nullable. + */ + function createNullableConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T | null; + + /** + * Create a converter that converts a sequence of the inner type. + */ + function createSequenceConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T[]; + + /** + * Create a converter that converts a Promise of the inner type. + */ + function createPromiseConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => Promise; + + /** + * Invoke a callback function. + */ + function invokeCallbackFunction( + callable: (...args: any) => any, + args: any[], + thisArg: any, + returnValueConverter: (v: any, opts: ValueConverterOpts) => T, + opts: ConverterOpts & { returnsPromise?: boolean }, + ): T; + + /** + * Throw an illegal constructor error. + */ + function illegalConstructor(): never; + + /** + * The branding symbol. + */ + const brand: unique symbol; + + /** + * Create a branded instance of an interface. + */ + function createBranded(self: any): any; + + /** + * Assert that self is branded. + */ + function assertBranded(self: any, type: any): void; + + /** + * Create a converter for interfaces. + */ + function createInterfaceConverter( + name: string, + prototype: any, + ): (v: any, opts: ValueConverterOpts) => any; + + function createRecordConverter< + K extends string | number | symbol, + V, + >( + keyConverter: (v: any, opts: ValueConverterOpts) => K, + valueConverter: (v: any, opts: ValueConverterOpts) => V, + ): ( + v: Record, + opts: ValueConverterOpts, + ) => any; + + /** + * Mix in the iterable declarations defined in WebIDL. + * https://heycam.github.io/webidl/#es-iterable + */ + function mixinPairIterable( + name: string, + prototype: any, + dataSymbol: symbol, + keyKey: string | number | symbol, + valueKey: string | number | symbol, + ): void; + + /** + * Configure prototype properties enumerability / writability / configurability. + */ + function configurePrototype(prototype: any); + + /** + * Get the WebIDL / ES type of a value. + */ + function type( + v: any, + ): + | "Null" + | "Undefined" + | "Boolean" + | "Number" + | "String" + | "Symbol" + | "BigInt" + | "Object"; } diff --git a/ext/webidl/lib.rs b/ext/webidl/lib.rs index ae25f04c75..4e21ef7963 100644 --- a/ext/webidl/lib.rs +++ b/ext/webidl/lib.rs @@ -6,7 +6,7 @@ use deno_core::Extension; /// Load and execute the javascript code. pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/webidl", "00_webidl.js", )) diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index 9b7c45e708..305cf75a77 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -1,579 +1,576 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { URL } = window.__bootstrap.url; - const webidl = window.__bootstrap.webidl; - const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra; - const { DOMException } = window.__bootstrap.domException; - const { - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - defineEventHandler, - _skipInternalInit, - } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { Blob, BlobPrototype } = globalThis.__bootstrap.file; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeSome, - DataView, - ErrorPrototypeToString, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - PromisePrototypeThen, - RegExpPrototypeTest, - Set, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - String, - StringPrototypeEndsWith, - StringPrototypeToLowerCase, - Symbol, - SymbolIterator, - PromisePrototypeCatch, - SymbolFor, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { URL } from "internal:ext/url/00_url.js"; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { HTTP_TOKEN_CODE_POINT_RE } from "internal:ext/web/00_infra.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import { + _skipInternalInit, + CloseEvent, + defineEventHandler, + ErrorEvent, + Event, + EventTarget, + MessageEvent, +} from "internal:ext/web/02_event.js"; +import { Blob, BlobPrototype } from "internal:ext/web/09_file.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypeSome, + DataView, + ErrorPrototypeToString, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + PromisePrototypeThen, + RegExpPrototypeTest, + Set, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + String, + StringPrototypeEndsWith, + StringPrototypeToLowerCase, + Symbol, + SymbolIterator, + PromisePrototypeCatch, + SymbolFor, +} = primordials; - webidl.converters["sequence or DOMString"] = (V, opts) => { - // Union for (sequence or DOMString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence"](V, opts); - } +webidl.converters["sequence or DOMString"] = (V, opts) => { + // Union for (sequence or DOMString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence"](V, opts); } - return webidl.converters.DOMString(V, opts); - }; + } + return webidl.converters.DOMString(V, opts); +}; - webidl.converters["WebSocketSend"] = (V, opts) => { - // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); +webidl.converters["WebSocketSend"] = (V, opts) => { + // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); + } + if (typeof V === "object") { + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - if (typeof V === "object") { - if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) - ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } - return webidl.converters["USVString"](V, opts); - }; + } + return webidl.converters["USVString"](V, opts); +}; - const CONNECTING = 0; - const OPEN = 1; - const CLOSING = 2; - const CLOSED = 3; +const CONNECTING = 0; +const OPEN = 1; +const CLOSING = 2; +const CLOSED = 3; - const _readyState = Symbol("[[readyState]]"); - const _url = Symbol("[[url]]"); - const _rid = Symbol("[[rid]]"); - const _extensions = Symbol("[[extensions]]"); - const _protocol = Symbol("[[protocol]]"); - const _binaryType = Symbol("[[binaryType]]"); - const _bufferedAmount = Symbol("[[bufferedAmount]]"); - const _eventLoop = Symbol("[[eventLoop]]"); +const _readyState = Symbol("[[readyState]]"); +const _url = Symbol("[[url]]"); +const _rid = Symbol("[[rid]]"); +const _extensions = Symbol("[[extensions]]"); +const _protocol = Symbol("[[protocol]]"); +const _binaryType = Symbol("[[binaryType]]"); +const _bufferedAmount = Symbol("[[bufferedAmount]]"); +const _eventLoop = Symbol("[[eventLoop]]"); - const _server = Symbol("[[server]]"); - const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); - const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); - const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); - class WebSocket extends EventTarget { - [_rid]; +const _server = Symbol("[[server]]"); +const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); +const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); +const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); +class WebSocket extends EventTarget { + [_rid]; - [_readyState] = CONNECTING; - get readyState() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_readyState]; - } + [_readyState] = CONNECTING; + get readyState() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_readyState]; + } - get CONNECTING() { - webidl.assertBranded(this, WebSocketPrototype); - return CONNECTING; - } - get OPEN() { - webidl.assertBranded(this, WebSocketPrototype); - return OPEN; - } - get CLOSING() { - webidl.assertBranded(this, WebSocketPrototype); - return CLOSING; - } - get CLOSED() { - webidl.assertBranded(this, WebSocketPrototype); - return CLOSED; - } + get CONNECTING() { + webidl.assertBranded(this, WebSocketPrototype); + return CONNECTING; + } + get OPEN() { + webidl.assertBranded(this, WebSocketPrototype); + return OPEN; + } + get CLOSING() { + webidl.assertBranded(this, WebSocketPrototype); + return CLOSING; + } + get CLOSED() { + webidl.assertBranded(this, WebSocketPrototype); + return CLOSED; + } - [_extensions] = ""; - get extensions() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_extensions]; - } + [_extensions] = ""; + get extensions() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_extensions]; + } - [_protocol] = ""; - get protocol() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_protocol]; - } + [_protocol] = ""; + get protocol() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_protocol]; + } - [_url] = ""; - get url() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_url]; - } + [_url] = ""; + get url() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_url]; + } - [_binaryType] = "blob"; - get binaryType() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_binaryType]; - } - set binaryType(value) { - webidl.assertBranded(this, WebSocketPrototype); - value = webidl.converters.DOMString(value, { - prefix: "Failed to set 'binaryType' on 'WebSocket'", - }); - if (value === "blob" || value === "arraybuffer") { - this[_binaryType] = value; - } + [_binaryType] = "blob"; + get binaryType() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_binaryType]; + } + set binaryType(value) { + webidl.assertBranded(this, WebSocketPrototype); + value = webidl.converters.DOMString(value, { + prefix: "Failed to set 'binaryType' on 'WebSocket'", + }); + if (value === "blob" || value === "arraybuffer") { + this[_binaryType] = value; } + } - [_bufferedAmount] = 0; - get bufferedAmount() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_bufferedAmount]; - } + [_bufferedAmount] = 0; + get bufferedAmount() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_bufferedAmount]; + } - constructor(url, protocols = []) { - super(); - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'WebSocket'"; - webidl.requiredArguments(arguments.length, 1, { + constructor(url, protocols = []) { + super(); + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocket'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + protocols = webidl.converters["sequence or DOMString"]( + protocols, + { prefix, - }); - url = webidl.converters.USVString(url, { + context: "Argument 2", + }, + ); + + let wsURL; + + try { + wsURL = new URL(url); + } catch (e) { + throw new DOMException(e.message, "SyntaxError"); + } + + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); + } + + if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", + ); + } + + this[_url] = wsURL.href; + + ops.op_ws_check_permission_and_cancel_handle( + "WebSocket.abort()", + this[_url], + false, + ); + + if (typeof protocols === "string") { + protocols = [protocols]; + } + + if ( + protocols.length !== + new Set( + ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), + ).size + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); + } + + if ( + ArrayPrototypeSome( + protocols, + (protocol) => !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), + ) + ) { + throw new DOMException( + "Invalid protocol value.", + "SyntaxError", + ); + } + + PromisePrototypeThen( + core.opAsync( + "op_ws_create", + "new WebSocket()", + wsURL.href, + ArrayPrototypeJoin(protocols, ", "), + ), + (create) => { + this[_rid] = create.rid; + this[_extensions] = create.extensions; + this[_protocol] = create.protocol; + + if (this[_readyState] === CLOSING) { + PromisePrototypeThen( + core.opAsync("op_ws_close", this[_rid]), + () => { + this[_readyState] = CLOSED; + + const errEvent = new ErrorEvent("error"); + this.dispatchEvent(errEvent); + + const event = new CloseEvent("close"); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + }, + ); + } else { + this[_readyState] = OPEN; + const event = new Event("open"); + this.dispatchEvent(event); + + this[_eventLoop](); + } + }, + (err) => { + this[_readyState] = CLOSED; + + const errorEv = new ErrorEvent( + "error", + { error: err, message: ErrorPrototypeToString(err) }, + ); + this.dispatchEvent(errorEv); + + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + }, + ); + } + + send(data) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'send' on 'WebSocket'"; + + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + data = webidl.converters.WebSocketSend(data, { + prefix, + context: "Argument 1", + }); + + if (this[_readyState] !== OPEN) { + throw new DOMException("readyState not OPEN", "InvalidStateError"); + } + + const sendTypedArray = (ta) => { + this[_bufferedAmount] += ta.byteLength; + PromisePrototypeThen( + core.opAsync("op_ws_send", this[_rid], { + kind: "binary", + value: ta, + }), + () => { + this[_bufferedAmount] -= ta.byteLength; + }, + ); + }; + + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { + PromisePrototypeThen( + data.slice().arrayBuffer(), + (ab) => sendTypedArray(new DataView(ab)), + ); + } else if (ArrayBufferIsView(data)) { + sendTypedArray(data); + } else if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, data)) { + sendTypedArray(new DataView(data)); + } else { + const string = String(data); + const d = core.encode(string); + this[_bufferedAmount] += d.byteLength; + PromisePrototypeThen( + core.opAsync("op_ws_send", this[_rid], { + kind: "text", + value: string, + }), + () => { + this[_bufferedAmount] -= d.byteLength; + }, + ); + } + } + + close(code = undefined, reason = undefined) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'close' on 'WebSocket'"; + + if (code !== undefined) { + code = webidl.converters["unsigned short"](code, { prefix, + clamp: true, context: "Argument 1", }); - protocols = webidl.converters["sequence or DOMString"]( - protocols, - { - prefix, - context: "Argument 2", - }, - ); + } - let wsURL; - - try { - wsURL = new URL(url); - } catch (e) { - throw new DOMException(e.message, "SyntaxError"); - } - - if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { - throw new DOMException( - "Only ws & wss schemes are allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { - throw new DOMException( - "Fragments are not allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - this[_url] = wsURL.href; - - ops.op_ws_check_permission_and_cancel_handle( - "WebSocket.abort()", - this[_url], - false, - ); - - if (typeof protocols === "string") { - protocols = [protocols]; - } + if (reason !== undefined) { + reason = webidl.converters.USVString(reason, { + prefix, + context: "Argument 2", + }); + } + if (!this[_server]) { if ( - protocols.length !== - new Set( - ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), - ).size + code !== undefined && + !(code === 1000 || (3000 <= code && code < 5000)) ) { throw new DOMException( - "Can't supply multiple times the same protocol.", - "SyntaxError", + "The close code must be either 1000 or in the range of 3000 to 4999.", + "InvalidAccessError", ); } + } - if ( - ArrayPrototypeSome( - protocols, - (protocol) => - !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), - ) - ) { - throw new DOMException( - "Invalid protocol value.", - "SyntaxError", - ); - } + if (reason !== undefined && core.encode(reason).byteLength > 123) { + throw new DOMException( + "The close reason may not be longer than 123 bytes.", + "SyntaxError", + ); + } - PromisePrototypeThen( - core.opAsync( - "op_ws_create", - "new WebSocket()", - wsURL.href, - ArrayPrototypeJoin(protocols, ", "), - ), - (create) => { - this[_rid] = create.rid; - this[_extensions] = create.extensions; - this[_protocol] = create.protocol; + if (this[_readyState] === CONNECTING) { + this[_readyState] = CLOSING; + } else if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; - if (this[_readyState] === CLOSING) { - PromisePrototypeThen( - core.opAsync("op_ws_close", this[_rid]), - () => { - this[_readyState] = CLOSED; - - const errEvent = new ErrorEvent("error"); - this.dispatchEvent(errEvent); - - const event = new CloseEvent("close"); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - }, - ); - } else { - this[_readyState] = OPEN; - const event = new Event("open"); - this.dispatchEvent(event); - - this[_eventLoop](); - } - }, + PromisePrototypeCatch( + core.opAsync("op_ws_close", this[_rid], code, reason), (err) => { this[_readyState] = CLOSED; - const errorEv = new ErrorEvent( - "error", - { error: err, message: ErrorPrototypeToString(err) }, - ); + const errorEv = new ErrorEvent("error", { + error: err, + message: ErrorPrototypeToString(err), + }); this.dispatchEvent(errorEv); const closeEv = new CloseEvent("close"); this.dispatchEvent(closeEv); + core.tryClose(this[_rid]); }, ); } + } - send(data) { - webidl.assertBranded(this, WebSocketPrototype); - const prefix = "Failed to execute 'send' on 'WebSocket'"; + async [_eventLoop]() { + while (this[_readyState] !== CLOSED) { + const { kind, value } = await core.opAsync( + "op_ws_next_event", + this[_rid], + ); - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - data = webidl.converters.WebSocketSend(data, { - prefix, - context: "Argument 1", - }); - - if (this[_readyState] !== OPEN) { - throw new DOMException("readyState not OPEN", "InvalidStateError"); - } - - const sendTypedArray = (ta) => { - this[_bufferedAmount] += ta.byteLength; - PromisePrototypeThen( - core.opAsync("op_ws_send", this[_rid], { - kind: "binary", - value: ta, - }), - () => { - this[_bufferedAmount] -= ta.byteLength; - }, - ); - }; - - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { - PromisePrototypeThen( - data.slice().arrayBuffer(), - (ab) => sendTypedArray(new DataView(ab)), - ); - } else if (ArrayBufferIsView(data)) { - sendTypedArray(data); - } else if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, data)) { - sendTypedArray(new DataView(data)); - } else { - const string = String(data); - const d = core.encode(string); - this[_bufferedAmount] += d.byteLength; - PromisePrototypeThen( - core.opAsync("op_ws_send", this[_rid], { - kind: "text", - value: string, - }), - () => { - this[_bufferedAmount] -= d.byteLength; - }, - ); - } - } - - close(code = undefined, reason = undefined) { - webidl.assertBranded(this, WebSocketPrototype); - const prefix = "Failed to execute 'close' on 'WebSocket'"; - - if (code !== undefined) { - code = webidl.converters["unsigned short"](code, { - prefix, - clamp: true, - context: "Argument 1", - }); - } - - if (reason !== undefined) { - reason = webidl.converters.USVString(reason, { - prefix, - context: "Argument 2", - }); - } - - if (!this[_server]) { - if ( - code !== undefined && - !(code === 1000 || (3000 <= code && code < 5000)) - ) { - throw new DOMException( - "The close code must be either 1000 or in the range of 3000 to 4999.", - "InvalidAccessError", - ); + switch (kind) { + case "string": { + this[_serverHandleIdleTimeout](); + const event = new MessageEvent("message", { + data: value, + origin: this[_url], + }); + this.dispatchEvent(event); + break; } - } + case "binary": { + this[_serverHandleIdleTimeout](); + let data; - if (reason !== undefined && core.encode(reason).byteLength > 123) { - throw new DOMException( - "The close reason may not be longer than 123 bytes.", - "SyntaxError", - ); - } - - if (this[_readyState] === CONNECTING) { - this[_readyState] = CLOSING; - } else if (this[_readyState] === OPEN) { - this[_readyState] = CLOSING; - - PromisePrototypeCatch( - core.opAsync("op_ws_close", this[_rid], code, reason), - (err) => { - this[_readyState] = CLOSED; - - const errorEv = new ErrorEvent("error", { - error: err, - message: ErrorPrototypeToString(err), - }); - this.dispatchEvent(errorEv); - - const closeEv = new CloseEvent("close"); - this.dispatchEvent(closeEv); - core.tryClose(this[_rid]); - }, - ); - } - } - - async [_eventLoop]() { - while (this[_readyState] !== CLOSED) { - const { kind, value } = await core.opAsync( - "op_ws_next_event", - this[_rid], - ); - - switch (kind) { - case "string": { - this[_serverHandleIdleTimeout](); - const event = new MessageEvent("message", { - data: value, - origin: this[_url], - }); - this.dispatchEvent(event); - break; - } - case "binary": { - this[_serverHandleIdleTimeout](); - let data; - - if (this.binaryType === "blob") { - data = new Blob([value]); - } else { - data = value.buffer; - } - - const event = new MessageEvent("message", { - data, - origin: this[_url], - [_skipInternalInit]: true, - }); - this.dispatchEvent(event); - break; - } - case "ping": { - core.opAsync("op_ws_send", this[_rid], { - kind: "pong", - }); - break; - } - case "pong": { - this[_serverHandleIdleTimeout](); - break; - } - case "closed": - case "close": { - const prevState = this[_readyState]; - this[_readyState] = CLOSED; - clearTimeout(this[_idleTimeoutTimeout]); - - if (prevState === OPEN) { - try { - await core.opAsync( - "op_ws_close", - this[_rid], - value.code, - value.reason, - ); - } catch { - // ignore failures - } - } - - const event = new CloseEvent("close", { - wasClean: true, - code: value.code, - reason: value.reason, - }); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - break; - } - case "error": { - this[_readyState] = CLOSED; - - const errorEv = new ErrorEvent("error", { - message: value, - }); - this.dispatchEvent(errorEv); - - const closeEv = new CloseEvent("close"); - this.dispatchEvent(closeEv); - core.tryClose(this[_rid]); - break; - } - } - } - } - - [_serverHandleIdleTimeout]() { - if (this[_idleTimeoutDuration]) { - clearTimeout(this[_idleTimeoutTimeout]); - this[_idleTimeoutTimeout] = setTimeout(async () => { - if (this[_readyState] === OPEN) { - await core.opAsync("op_ws_send", this[_rid], { - kind: "ping", - }); - this[_idleTimeoutTimeout] = setTimeout(async () => { - if (this[_readyState] === OPEN) { - this[_readyState] = CLOSING; - const reason = "No response from ping frame."; - await core.opAsync("op_ws_close", this[_rid], 1001, reason); - this[_readyState] = CLOSED; - - const errEvent = new ErrorEvent("error", { - message: reason, - }); - this.dispatchEvent(errEvent); - - const event = new CloseEvent("close", { - wasClean: false, - code: 1001, - reason, - }); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - } else { - clearTimeout(this[_idleTimeoutTimeout]); - } - }, (this[_idleTimeoutDuration] / 2) * 1000); + if (this.binaryType === "blob") { + data = new Blob([value]); } else { - clearTimeout(this[_idleTimeoutTimeout]); + data = value.buffer; } - }, (this[_idleTimeoutDuration] / 2) * 1000); - } - } - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - url: this.url, - readyState: this.readyState, - extensions: this.extensions, - protocol: this.protocol, - binaryType: this.binaryType, - bufferedAmount: this.bufferedAmount, - }) - }`; + const event = new MessageEvent("message", { + data, + origin: this[_url], + [_skipInternalInit]: true, + }); + this.dispatchEvent(event); + break; + } + case "ping": { + core.opAsync("op_ws_send", this[_rid], { + kind: "pong", + }); + break; + } + case "pong": { + this[_serverHandleIdleTimeout](); + break; + } + case "closed": + case "close": { + const prevState = this[_readyState]; + this[_readyState] = CLOSED; + clearTimeout(this[_idleTimeoutTimeout]); + + if (prevState === OPEN) { + try { + await core.opAsync( + "op_ws_close", + this[_rid], + value.code, + value.reason, + ); + } catch { + // ignore failures + } + } + + const event = new CloseEvent("close", { + wasClean: true, + code: value.code, + reason: value.reason, + }); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + break; + } + case "error": { + this[_readyState] = CLOSED; + + const errorEv = new ErrorEvent("error", { + message: value, + }); + this.dispatchEvent(errorEv); + + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + core.tryClose(this[_rid]); + break; + } + } } } - ObjectDefineProperties(WebSocket, { - CONNECTING: { - value: 0, - }, - OPEN: { - value: 1, - }, - CLOSING: { - value: 2, - }, - CLOSED: { - value: 3, - }, - }); + [_serverHandleIdleTimeout]() { + if (this[_idleTimeoutDuration]) { + clearTimeout(this[_idleTimeoutTimeout]); + this[_idleTimeoutTimeout] = setTimeout(async () => { + if (this[_readyState] === OPEN) { + await core.opAsync("op_ws_send", this[_rid], { + kind: "ping", + }); + this[_idleTimeoutTimeout] = setTimeout(async () => { + if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; + const reason = "No response from ping frame."; + await core.opAsync("op_ws_close", this[_rid], 1001, reason); + this[_readyState] = CLOSED; - defineEventHandler(WebSocket.prototype, "message"); - defineEventHandler(WebSocket.prototype, "error"); - defineEventHandler(WebSocket.prototype, "close"); - defineEventHandler(WebSocket.prototype, "open"); + const errEvent = new ErrorEvent("error", { + message: reason, + }); + this.dispatchEvent(errEvent); - webidl.configurePrototype(WebSocket); - const WebSocketPrototype = WebSocket.prototype; + const event = new CloseEvent("close", { + wasClean: false, + code: 1001, + reason, + }); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + } else { + clearTimeout(this[_idleTimeoutTimeout]); + } + }, (this[_idleTimeoutDuration] / 2) * 1000); + } else { + clearTimeout(this[_idleTimeoutTimeout]); + } + }, (this[_idleTimeoutDuration] / 2) * 1000); + } + } - window.__bootstrap.webSocket = { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _server, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - }; -})(this); + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + readyState: this.readyState, + extensions: this.extensions, + protocol: this.protocol, + binaryType: this.binaryType, + bufferedAmount: this.bufferedAmount, + }) + }`; + } +} + +ObjectDefineProperties(WebSocket, { + CONNECTING: { + value: 0, + }, + OPEN: { + value: 1, + }, + CLOSING: { + value: 2, + }, + CLOSED: { + value: 3, + }, +}); + +defineEventHandler(WebSocket.prototype, "message"); +defineEventHandler(WebSocket.prototype, "error"); +defineEventHandler(WebSocket.prototype, "close"); +defineEventHandler(WebSocket.prototype, "open"); + +webidl.configurePrototype(WebSocket); +const WebSocketPrototype = WebSocket.prototype; + +export { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _server, + _serverHandleIdleTimeout, + WebSocket, +}; diff --git a/ext/websocket/02_websocketstream.js b/ext/websocket/02_websocketstream.js index 5d7e47cc43..b3d21297f2 100644 --- a/ext/websocket/02_websocketstream.js +++ b/ext/websocket/02_websocketstream.js @@ -1,426 +1,424 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { writableStreamClose, Deferred } = window.__bootstrap.streams; - const { DOMException } = window.__bootstrap.domException; - const { add, remove } = window.__bootstrap.abortSignal; - const { headersFromHeaderList, headerListFromHeaders, fillHeaders } = - window.__bootstrap.headers; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { Deferred, writableStreamClose } from "internal:ext/web/06_streams.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import { add, remove } from "internal:ext/web/03_abort_signal.js"; +import { + fillHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:ext/fetch/20_headers.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeJoin, + ArrayPrototypeMap, + Error, + ObjectPrototypeIsPrototypeOf, + PromisePrototypeCatch, + PromisePrototypeThen, + Set, + StringPrototypeEndsWith, + StringPrototypeToLowerCase, + Symbol, + SymbolFor, + TypeError, + Uint8ArrayPrototype, +} = primordials; - const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - Error, - ObjectPrototypeIsPrototypeOf, - PromisePrototypeCatch, - PromisePrototypeThen, - Set, - StringPrototypeEndsWith, - StringPrototypeToLowerCase, - Symbol, - SymbolFor, - TypeError, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( + "WebSocketStreamOptions", + [ + { + key: "protocols", + converter: webidl.converters["sequence"], + get defaultValue() { + return []; + }, + }, + { + key: "signal", + converter: webidl.converters.AbortSignal, + }, + { + key: "headers", + converter: webidl.converters.HeadersInit, + }, + ], +); +webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter( + "WebSocketCloseInfo", + [ + { + key: "code", + converter: webidl.converters["unsigned short"], + }, + { + key: "reason", + converter: webidl.converters.USVString, + defaultValue: "", + }, + ], +); - webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( - "WebSocketStreamOptions", - [ - { - key: "protocols", - converter: webidl.converters["sequence"], - get defaultValue() { - return []; - }, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - { - key: "headers", - converter: webidl.converters.HeadersInit, - }, - ], - ); - webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter( - "WebSocketCloseInfo", - [ - { - key: "code", - converter: webidl.converters["unsigned short"], - }, - { - key: "reason", - converter: webidl.converters.USVString, - defaultValue: "", - }, - ], - ); +const CLOSE_RESPONSE_TIMEOUT = 5000; - const CLOSE_RESPONSE_TIMEOUT = 5000; +const _rid = Symbol("[[rid]]"); +const _url = Symbol("[[url]]"); +const _connection = Symbol("[[connection]]"); +const _closed = Symbol("[[closed]]"); +const _earlyClose = Symbol("[[earlyClose]]"); +const _closeSent = Symbol("[[closeSent]]"); +class WebSocketStream { + [_rid]; - const _rid = Symbol("[[rid]]"); - const _url = Symbol("[[url]]"); - const _connection = Symbol("[[connection]]"); - const _closed = Symbol("[[closed]]"); - const _earlyClose = Symbol("[[earlyClose]]"); - const _closeSent = Symbol("[[closeSent]]"); - class WebSocketStream { - [_rid]; + [_url]; + get url() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_url]; + } - [_url]; - get url() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_url]; + constructor(url, options) { + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocketStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.WebSocketStreamOptions(options, { + prefix, + context: "Argument 2", + }); + + const wsURL = new URL(url); + + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); } - constructor(url, options) { - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'WebSocketStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - url = webidl.converters.USVString(url, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.WebSocketStreamOptions(options, { - prefix, - context: "Argument 2", - }); - - const wsURL = new URL(url); - - if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { - throw new DOMException( - "Only ws & wss schemes are allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { - throw new DOMException( - "Fragments are not allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - this[_url] = wsURL.href; - - if ( - options.protocols.length !== - new Set( - ArrayPrototypeMap( - options.protocols, - (p) => StringPrototypeToLowerCase(p), - ), - ).size - ) { - throw new DOMException( - "Can't supply multiple times the same protocol.", - "SyntaxError", - ); - } - - const headers = headersFromHeaderList([], "request"); - if (options.headers !== undefined) { - fillHeaders(headers, options.headers); - } - - const cancelRid = ops.op_ws_check_permission_and_cancel_handle( - "WebSocketStream.abort()", - this[_url], - true, + if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", ); + } - if (options.signal?.aborted) { - core.close(cancelRid); - const err = options.signal.reason; - this[_connection].reject(err); - this[_closed].reject(err); - } else { - const abort = () => { - core.close(cancelRid); - }; - options.signal?.[add](abort); - PromisePrototypeThen( - core.opAsync( - "op_ws_create", - "new WebSocketStream()", - this[_url], - options.protocols - ? ArrayPrototypeJoin(options.protocols, ", ") - : "", - cancelRid, - headerListFromHeaders(headers), + this[_url] = wsURL.href; + + if ( + options.protocols.length !== + new Set( + ArrayPrototypeMap( + options.protocols, + (p) => StringPrototypeToLowerCase(p), ), - (create) => { - options.signal?.[remove](abort); - if (this[_earlyClose]) { - PromisePrototypeThen( - core.opAsync("op_ws_close", create.rid), - () => { - PromisePrototypeThen( - (async () => { - while (true) { - const { kind } = await core.opAsync( - "op_ws_next_event", - create.rid, - ); + ).size + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); + } - if (kind === "close") { - break; - } - } - })(), - () => { - const err = new DOMException( - "Closed while connecting", - "NetworkError", + const headers = headersFromHeaderList([], "request"); + if (options.headers !== undefined) { + fillHeaders(headers, options.headers); + } + + const cancelRid = ops.op_ws_check_permission_and_cancel_handle( + "WebSocketStream.abort()", + this[_url], + true, + ); + + if (options.signal?.aborted) { + core.close(cancelRid); + const err = options.signal.reason; + this[_connection].reject(err); + this[_closed].reject(err); + } else { + const abort = () => { + core.close(cancelRid); + }; + options.signal?.[add](abort); + PromisePrototypeThen( + core.opAsync( + "op_ws_create", + "new WebSocketStream()", + this[_url], + options.protocols ? ArrayPrototypeJoin(options.protocols, ", ") : "", + cancelRid, + headerListFromHeaders(headers), + ), + (create) => { + options.signal?.[remove](abort); + if (this[_earlyClose]) { + PromisePrototypeThen( + core.opAsync("op_ws_close", create.rid), + () => { + PromisePrototypeThen( + (async () => { + while (true) { + const { kind } = await core.opAsync( + "op_ws_next_event", + create.rid, ); - this[_connection].reject(err); - this[_closed].reject(err); - }, - ); - }, - () => { - const err = new DOMException( - "Closed while connecting", - "NetworkError", - ); - this[_connection].reject(err); - this[_closed].reject(err); - }, - ); - } else { - this[_rid] = create.rid; - const writable = new WritableStream({ - write: async (chunk) => { - if (typeof chunk === "string") { - await core.opAsync("op_ws_send", this[_rid], { - kind: "text", - value: chunk, - }); - } else if ( - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk) - ) { - await core.opAsync("op_ws_send", this[_rid], { - kind: "binary", - value: chunk, - }, chunk); - } else { - throw new TypeError( - "A chunk may only be either a string or an Uint8Array", + if (kind === "close") { + break; + } + } + })(), + () => { + const err = new DOMException( + "Closed while connecting", + "NetworkError", ); - } - }, - close: async (reason) => { - try { - this.close(reason?.code !== undefined ? reason : {}); - } catch (_) { - this.close(); - } - await this.closed; - }, - abort: async (reason) => { - try { - this.close(reason?.code !== undefined ? reason : {}); - } catch (_) { - this.close(); - } - await this.closed; - }, - }); - const pull = async (controller) => { - const { kind, value } = await core.opAsync( - "op_ws_next_event", - this[_rid], - ); - - switch (kind) { - case "string": { - controller.enqueue(value); - break; - } - case "binary": { - controller.enqueue(value); - break; - } - case "ping": { - await core.opAsync("op_ws_send", this[_rid], { - kind: "pong", - }); - await pull(controller); - break; - } - case "closed": - case "close": { - this[_closed].resolve(value); - core.tryClose(this[_rid]); - break; - } - case "error": { - const err = new Error(value); + this[_connection].reject(err); this[_closed].reject(err); - controller.error(err); - core.tryClose(this[_rid]); - break; - } - } + }, + ); + }, + () => { + const err = new DOMException( + "Closed while connecting", + "NetworkError", + ); + this[_connection].reject(err); + this[_closed].reject(err); + }, + ); + } else { + this[_rid] = create.rid; - if ( - this[_closeSent].state === "fulfilled" && - this[_closed].state === "pending" + const writable = new WritableStream({ + write: async (chunk) => { + if (typeof chunk === "string") { + await core.opAsync("op_ws_send", this[_rid], { + kind: "text", + value: chunk, + }); + } else if ( + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk) ) { - if ( - new Date().getTime() - await this[_closeSent].promise <= - CLOSE_RESPONSE_TIMEOUT - ) { - return pull(controller); - } + await core.opAsync("op_ws_send", this[_rid], { + kind: "binary", + value: chunk, + }, chunk); + } else { + throw new TypeError( + "A chunk may only be either a string or an Uint8Array", + ); + } + }, + close: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); + } + await this.closed; + }, + abort: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); + } + await this.closed; + }, + }); + const pull = async (controller) => { + const { kind, value } = await core.opAsync( + "op_ws_next_event", + this[_rid], + ); + switch (kind) { + case "string": { + controller.enqueue(value); + break; + } + case "binary": { + controller.enqueue(value); + break; + } + case "ping": { + await core.opAsync("op_ws_send", this[_rid], { + kind: "pong", + }); + await pull(controller); + break; + } + case "closed": + case "close": { this[_closed].resolve(value); core.tryClose(this[_rid]); + break; } - }; - const readable = new ReadableStream({ - start: (controller) => { - PromisePrototypeThen(this.closed, () => { - try { - controller.close(); - } catch (_) { - // needed to ignore warnings & assertions - } - try { - PromisePrototypeCatch( - writableStreamClose(writable), - () => {}, - ); - } catch (_) { - // needed to ignore warnings & assertions - } - }); + case "error": { + const err = new Error(value); + this[_closed].reject(err); + controller.error(err); + core.tryClose(this[_rid]); + break; + } + } - PromisePrototypeThen(this[_closeSent].promise, () => { - if (this[_closed].state === "pending") { - return pull(controller); - } - }); - }, - pull, - cancel: async (reason) => { + if ( + this[_closeSent].state === "fulfilled" && + this[_closed].state === "pending" + ) { + if ( + new Date().getTime() - await this[_closeSent].promise <= + CLOSE_RESPONSE_TIMEOUT + ) { + return pull(controller); + } + + this[_closed].resolve(value); + core.tryClose(this[_rid]); + } + }; + const readable = new ReadableStream({ + start: (controller) => { + PromisePrototypeThen(this.closed, () => { try { - this.close(reason?.code !== undefined ? reason : {}); + controller.close(); } catch (_) { - this.close(); + // needed to ignore warnings & assertions } - await this.closed; - }, - }); + try { + PromisePrototypeCatch( + writableStreamClose(writable), + () => {}, + ); + } catch (_) { + // needed to ignore warnings & assertions + } + }); - this[_connection].resolve({ - readable, - writable, - extensions: create.extensions ?? "", - protocol: create.protocol ?? "", - }); - } - }, - (err) => { - if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) { - // The signal was aborted. - err = options.signal.reason; - } else { - core.tryClose(cancelRid); - } - this[_connection].reject(err); - this[_closed].reject(err); - }, - ); - } - } + PromisePrototypeThen(this[_closeSent].promise, () => { + if (this[_closed].state === "pending") { + return pull(controller); + } + }); + }, + pull, + cancel: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); + } + await this.closed; + }, + }); - [_connection] = new Deferred(); - get connection() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_connection].promise; - } - - [_earlyClose] = false; - [_closed] = new Deferred(); - [_closeSent] = new Deferred(); - get closed() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_closed].promise; - } - - close(closeInfo) { - webidl.assertBranded(this, WebSocketStreamPrototype); - closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, { - prefix: "Failed to execute 'close' on 'WebSocketStream'", - context: "Argument 1", - }); - - if ( - closeInfo.code && - !(closeInfo.code === 1000 || - (3000 <= closeInfo.code && closeInfo.code < 5000)) - ) { - throw new DOMException( - "The close code must be either 1000 or in the range of 3000 to 4999.", - "InvalidAccessError", - ); - } - - const encoder = new TextEncoder(); - if ( - closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123 - ) { - throw new DOMException( - "The close reason may not be longer than 123 bytes.", - "SyntaxError", - ); - } - - let code = closeInfo.code; - if (closeInfo.reason && code === undefined) { - code = 1000; - } - - if (this[_connection].state === "pending") { - this[_earlyClose] = true; - } else if (this[_closed].state === "pending") { - PromisePrototypeThen( - core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason), - () => { - setTimeout(() => { - this[_closeSent].resolve(new Date().getTime()); - }, 0); - }, - (err) => { - this[_rid] && core.tryClose(this[_rid]); - this[_closed].reject(err); - }, - ); - } - } - - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - url: this.url, - }) - }`; + this[_connection].resolve({ + readable, + writable, + extensions: create.extensions ?? "", + protocol: create.protocol ?? "", + }); + } + }, + (err) => { + if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) { + // The signal was aborted. + err = options.signal.reason; + } else { + core.tryClose(cancelRid); + } + this[_connection].reject(err); + this[_closed].reject(err); + }, + ); } } - const WebSocketStreamPrototype = WebSocketStream.prototype; + [_connection] = new Deferred(); + get connection() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_connection].promise; + } - window.__bootstrap.webSocket.WebSocketStream = WebSocketStream; -})(this); + [_earlyClose] = false; + [_closed] = new Deferred(); + [_closeSent] = new Deferred(); + get closed() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_closed].promise; + } + + close(closeInfo) { + webidl.assertBranded(this, WebSocketStreamPrototype); + closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, { + prefix: "Failed to execute 'close' on 'WebSocketStream'", + context: "Argument 1", + }); + + if ( + closeInfo.code && + !(closeInfo.code === 1000 || + (3000 <= closeInfo.code && closeInfo.code < 5000)) + ) { + throw new DOMException( + "The close code must be either 1000 or in the range of 3000 to 4999.", + "InvalidAccessError", + ); + } + + const encoder = new TextEncoder(); + if ( + closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123 + ) { + throw new DOMException( + "The close reason may not be longer than 123 bytes.", + "SyntaxError", + ); + } + + let code = closeInfo.code; + if (closeInfo.reason && code === undefined) { + code = 1000; + } + + if (this[_connection].state === "pending") { + this[_earlyClose] = true; + } else if (this[_closed].state === "pending") { + PromisePrototypeThen( + core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason), + () => { + setTimeout(() => { + this[_closeSent].resolve(new Date().getTime()); + }, 0); + }, + (err) => { + this[_rid] && core.tryClose(this[_rid]); + this[_closed].reject(err); + }, + ); + } + } + + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + }) + }`; + } +} + +const WebSocketStreamPrototype = WebSocketStream.prototype; + +export { WebSocketStream }; diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 82a2c5918a..da6a48e45c 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -504,7 +504,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_url", "deno_webidl"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/websocket", "01_websocket.js", "02_websocketstream.js", diff --git a/ext/webstorage/01_webstorage.js b/ext/webstorage/01_webstorage.js index 27d7aac09f..d7abdfbd8f 100644 --- a/ext/webstorage/01_webstorage.js +++ b/ext/webstorage/01_webstorage.js @@ -2,191 +2,189 @@ /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - SafeArrayIterator, - Symbol, - SymbolFor, - ObjectDefineProperty, - ObjectFromEntries, - ObjectEntries, - ReflectGet, - ReflectHas, - Proxy, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeArrayIterator, + Symbol, + SymbolFor, + ObjectDefineProperty, + ObjectFromEntries, + ObjectEntries, + ReflectGet, + ReflectHas, + Proxy, +} = primordials; - const _persistent = Symbol("[[persistent]]"); +const _persistent = Symbol("[[persistent]]"); - class Storage { - [_persistent]; +class Storage { + [_persistent]; - constructor() { - webidl.illegalConstructor(); - } - - get length() { - webidl.assertBranded(this, StoragePrototype); - return ops.op_webstorage_length(this[_persistent]); - } - - key(index) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'key' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - - return ops.op_webstorage_key(index, this[_persistent]); - } - - setItem(key, value) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'setItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 2", - }); - - ops.op_webstorage_set(key, value, this[_persistent]); - } - - getItem(key) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'getItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - - return ops.op_webstorage_get(key, this[_persistent]); - } - - removeItem(key) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'removeItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - - ops.op_webstorage_remove(key, this[_persistent]); - } - - clear() { - webidl.assertBranded(this, StoragePrototype); - ops.op_webstorage_clear(this[_persistent]); - } + constructor() { + webidl.illegalConstructor(); } - const StoragePrototype = Storage.prototype; + get length() { + webidl.assertBranded(this, StoragePrototype); + return ops.op_webstorage_length(this[_persistent]); + } - function createStorage(persistent) { - const storage = webidl.createBranded(Storage); - storage[_persistent] = persistent; - - const proxy = new Proxy(storage, { - deleteProperty(target, key) { - if (typeof key == "symbol") { - delete target[key]; - } else { - target.removeItem(key); - } - return true; - }, - defineProperty(target, key, descriptor) { - if (typeof key == "symbol") { - ObjectDefineProperty(target, key, descriptor); - } else { - target.setItem(key, descriptor.value); - } - return true; - }, - get(target, key) { - if (typeof key == "symbol") return target[key]; - if (ReflectHas(target, key)) { - return ReflectGet(...new SafeArrayIterator(arguments)); - } else { - return target.getItem(key) ?? undefined; - } - }, - set(target, key, value) { - if (typeof key == "symbol") { - ObjectDefineProperty(target, key, { - value, - configurable: true, - }); - } else { - target.setItem(key, value); - } - return true; - }, - has(target, p) { - return p === SymbolFor("Deno.customInspect") || - (typeof target.getItem(p)) === "string"; - }, - ownKeys() { - return ops.op_webstorage_iterate_keys(persistent); - }, - getOwnPropertyDescriptor(target, key) { - if (arguments.length === 1) { - return undefined; - } - if (ReflectHas(target, key)) { - return undefined; - } - const value = target.getItem(key); - if (value === null) { - return undefined; - } - return { - value, - enumerable: true, - configurable: true, - writable: true, - }; - }, + key(index) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'key' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", }); - proxy[SymbolFor("Deno.customInspect")] = function (inspect) { - return `${this.constructor.name} ${ - inspect({ - length: this.length, - ...ObjectFromEntries(ObjectEntries(proxy)), - }) - }`; - }; - - return proxy; + return ops.op_webstorage_key(index, this[_persistent]); } - let localStorage; - let sessionStorage; + setItem(key, value) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'setItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 2", + }); - window.__bootstrap.webStorage = { - localStorage() { - if (!localStorage) { - localStorage = createStorage(true); + ops.op_webstorage_set(key, value, this[_persistent]); + } + + getItem(key) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'getItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + return ops.op_webstorage_get(key, this[_persistent]); + } + + removeItem(key) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'removeItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + ops.op_webstorage_remove(key, this[_persistent]); + } + + clear() { + webidl.assertBranded(this, StoragePrototype); + ops.op_webstorage_clear(this[_persistent]); + } +} + +const StoragePrototype = Storage.prototype; + +function createStorage(persistent) { + const storage = webidl.createBranded(Storage); + storage[_persistent] = persistent; + + const proxy = new Proxy(storage, { + deleteProperty(target, key) { + if (typeof key == "symbol") { + delete target[key]; + } else { + target.removeItem(key); } - return localStorage; + return true; }, - sessionStorage() { - if (!sessionStorage) { - sessionStorage = createStorage(false); + defineProperty(target, key, descriptor) { + if (typeof key == "symbol") { + ObjectDefineProperty(target, key, descriptor); + } else { + target.setItem(key, descriptor.value); } - return sessionStorage; + return true; }, - Storage, + get(target, key) { + if (typeof key == "symbol") return target[key]; + if (ReflectHas(target, key)) { + return ReflectGet(...new SafeArrayIterator(arguments)); + } else { + return target.getItem(key) ?? undefined; + } + }, + set(target, key, value) { + if (typeof key == "symbol") { + ObjectDefineProperty(target, key, { + value, + configurable: true, + }); + } else { + target.setItem(key, value); + } + return true; + }, + has(target, p) { + return p === SymbolFor("Deno.customInspect") || + (typeof target.getItem(p)) === "string"; + }, + ownKeys() { + return ops.op_webstorage_iterate_keys(persistent); + }, + getOwnPropertyDescriptor(target, key) { + if (arguments.length === 1) { + return undefined; + } + if (ReflectHas(target, key)) { + return undefined; + } + const value = target.getItem(key); + if (value === null) { + return undefined; + } + return { + value, + enumerable: true, + configurable: true, + writable: true, + }; + }, + }); + + proxy[SymbolFor("Deno.customInspect")] = function (inspect) { + return `${this.constructor.name} ${ + inspect({ + length: this.length, + ...ObjectFromEntries(ObjectEntries(proxy)), + }) + }`; }; -})(this); + + return proxy; +} + +let localStorageStorage; +function localStorage() { + if (!localStorageStorage) { + localStorageStorage = createStorage(true); + } + return localStorageStorage; +} + +let sessionStorageStorage; +function sessionStorage() { + if (!sessionStorageStorage) { + sessionStorageStorage = createStorage(false); + } + return sessionStorageStorage; +} + +export { localStorage, sessionStorage, Storage }; diff --git a/ext/webstorage/lib.rs b/ext/webstorage/lib.rs index 29deaee844..d878ad7015 100644 --- a/ext/webstorage/lib.rs +++ b/ext/webstorage/lib.rs @@ -24,7 +24,7 @@ const MAX_STORAGE_BYTES: u32 = 10 * 1024 * 1024; pub fn init(origin_storage_dir: Option) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl"]) - .js(include_js_files!( + .esm(include_js_files!( prefix "internal:ext/webstorage", "01_webstorage.js", )) diff --git a/runtime/build.rs b/runtime/build.rs index 10490a8716..bba4394f82 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -5,7 +5,6 @@ use std::env; use std::path::PathBuf; // This is a shim that allows to generate documentation on docs.rs -#[cfg(not(feature = "docsrs"))] mod not_docs { use std::path::Path; @@ -121,7 +120,7 @@ mod not_docs { } } - fn create_runtime_snapshot(snapshot_path: PathBuf, files: Vec) { + fn create_runtime_snapshot(snapshot_path: PathBuf, esm_files: Vec) { let extensions_with_js: Vec = vec![ deno_webidl::init(), deno_console::init(), @@ -158,7 +157,8 @@ mod not_docs { startup_snapshot: None, extensions: vec![], extensions_with_js, - additional_files: files, + additional_files: vec![], + additional_esm_files: esm_files, compression_cb: Some(Box::new(|vec, snapshot_slice| { lzzzz::lz4_hc::compress_to_vec( snapshot_slice, @@ -172,14 +172,19 @@ mod not_docs { pub fn build_snapshot(runtime_snapshot_path: PathBuf) { #[allow(unused_mut)] - let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); + let mut esm_files = get_js_files( + env!("CARGO_MANIFEST_DIR"), + "js", + Some(Box::new(|path| !path.ends_with("99_main.js"))), + ); + #[cfg(not(feature = "snapshot_from_snapshot"))] { let manifest = env!("CARGO_MANIFEST_DIR"); let path = PathBuf::from(manifest); - js_files.push(path.join("js").join("99_main.js")); + esm_files.push(path.join("js").join("99_main.js")); } - create_runtime_snapshot(runtime_snapshot_path, js_files); + create_runtime_snapshot(runtime_snapshot_path, esm_files); } } diff --git a/runtime/js/01_build.js b/runtime/js/01_build.js index 778331cdd6..a9515c5b84 100644 --- a/runtime/js/01_build.js +++ b/runtime/js/01_build.js @@ -1,33 +1,28 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { ObjectFreeze, StringPrototypeSplit } = window.__bootstrap.primordials; +const primordials = globalThis.__bootstrap.primordials; +const { ObjectFreeze, StringPrototypeSplit } = primordials; - const build = { - target: "unknown", - arch: "unknown", - os: "unknown", - vendor: "unknown", - env: undefined, - }; +const build = { + target: "unknown", + arch: "unknown", + os: "unknown", + vendor: "unknown", + env: undefined, +}; - function setBuildInfo(target) { - const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit( - target, - "-", - 4, - ); - build.target = target; - build.arch = arch; - build.vendor = vendor; - build.os = os; - build.env = env; - ObjectFreeze(build); - } +function setBuildInfo(target) { + const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit( + target, + "-", + 4, + ); + build.target = target; + build.arch = arch; + build.vendor = vendor; + build.os = os; + build.env = env; + ObjectFreeze(build); +} - window.__bootstrap.build = { - build, - setBuildInfo, - }; -})(this); +export { build, setBuildInfo }; diff --git a/runtime/js/01_errors.js b/runtime/js/01_errors.js index 620c64c337..7e2ad29abb 100644 --- a/runtime/js/01_errors.js +++ b/runtime/js/01_errors.js @@ -1,153 +1,149 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const { Error } = window.__bootstrap.primordials; - const { BadResource, Interrupted } = core; +const core = globalThis.Deno.core; +const { BadResource, Interrupted } = core; +const primordials = globalThis.__bootstrap.primordials; +const { Error } = primordials; - class NotFound extends Error { - constructor(msg) { - super(msg); - this.name = "NotFound"; - } +class NotFound extends Error { + constructor(msg) { + super(msg); + this.name = "NotFound"; } +} - class PermissionDenied extends Error { - constructor(msg) { - super(msg); - this.name = "PermissionDenied"; - } +class PermissionDenied extends Error { + constructor(msg) { + super(msg); + this.name = "PermissionDenied"; } +} - class ConnectionRefused extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionRefused"; - } +class ConnectionRefused extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionRefused"; } +} - class ConnectionReset extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionReset"; - } +class ConnectionReset extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionReset"; } +} - class ConnectionAborted extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionAborted"; - } +class ConnectionAborted extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionAborted"; } +} - class NotConnected extends Error { - constructor(msg) { - super(msg); - this.name = "NotConnected"; - } +class NotConnected extends Error { + constructor(msg) { + super(msg); + this.name = "NotConnected"; } +} - class AddrInUse extends Error { - constructor(msg) { - super(msg); - this.name = "AddrInUse"; - } +class AddrInUse extends Error { + constructor(msg) { + super(msg); + this.name = "AddrInUse"; } +} - class AddrNotAvailable extends Error { - constructor(msg) { - super(msg); - this.name = "AddrNotAvailable"; - } +class AddrNotAvailable extends Error { + constructor(msg) { + super(msg); + this.name = "AddrNotAvailable"; } +} - class BrokenPipe extends Error { - constructor(msg) { - super(msg); - this.name = "BrokenPipe"; - } +class BrokenPipe extends Error { + constructor(msg) { + super(msg); + this.name = "BrokenPipe"; } +} - class AlreadyExists extends Error { - constructor(msg) { - super(msg); - this.name = "AlreadyExists"; - } +class AlreadyExists extends Error { + constructor(msg) { + super(msg); + this.name = "AlreadyExists"; } +} - class InvalidData extends Error { - constructor(msg) { - super(msg); - this.name = "InvalidData"; - } +class InvalidData extends Error { + constructor(msg) { + super(msg); + this.name = "InvalidData"; } +} - class TimedOut extends Error { - constructor(msg) { - super(msg); - this.name = "TimedOut"; - } +class TimedOut extends Error { + constructor(msg) { + super(msg); + this.name = "TimedOut"; } +} - class WriteZero extends Error { - constructor(msg) { - super(msg); - this.name = "WriteZero"; - } +class WriteZero extends Error { + constructor(msg) { + super(msg); + this.name = "WriteZero"; } +} - class UnexpectedEof extends Error { - constructor(msg) { - super(msg); - this.name = "UnexpectedEof"; - } +class UnexpectedEof extends Error { + constructor(msg) { + super(msg); + this.name = "UnexpectedEof"; } +} - class Http extends Error { - constructor(msg) { - super(msg); - this.name = "Http"; - } +class Http extends Error { + constructor(msg) { + super(msg); + this.name = "Http"; } +} - class Busy extends Error { - constructor(msg) { - super(msg); - this.name = "Busy"; - } +class Busy extends Error { + constructor(msg) { + super(msg); + this.name = "Busy"; } +} - class NotSupported extends Error { - constructor(msg) { - super(msg); - this.name = "NotSupported"; - } +class NotSupported extends Error { + constructor(msg) { + super(msg); + this.name = "NotSupported"; } +} - const errors = { - NotFound, - PermissionDenied, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - NotConnected, - AddrInUse, - AddrNotAvailable, - BrokenPipe, - AlreadyExists, - InvalidData, - TimedOut, - Interrupted, - WriteZero, - UnexpectedEof, - BadResource, - Http, - Busy, - NotSupported, - }; +const errors = { + NotFound, + PermissionDenied, + ConnectionRefused, + ConnectionReset, + ConnectionAborted, + NotConnected, + AddrInUse, + AddrNotAvailable, + BrokenPipe, + AlreadyExists, + InvalidData, + TimedOut, + Interrupted, + WriteZero, + UnexpectedEof, + BadResource, + Http, + Busy, + NotSupported, +}; - window.__bootstrap.errors = { - errors, - }; -})(this); +export { errors }; diff --git a/runtime/js/01_version.js b/runtime/js/01_version.js index b1b778a428..62f3df17c8 100644 --- a/runtime/js/01_version.js +++ b/runtime/js/01_version.js @@ -1,29 +1,24 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { ObjectFreeze } = window.__bootstrap.primordials; +const primordials = globalThis.__bootstrap.primordials; +const { ObjectFreeze } = primordials; - const version = { - deno: "", - v8: "", - typescript: "", - }; +const version = { + deno: "", + v8: "", + typescript: "", +}; - function setVersions( - denoVersion, - v8Version, - tsVersion, - ) { - version.deno = denoVersion; - version.v8 = v8Version; - version.typescript = tsVersion; +function setVersions( + denoVersion, + v8Version, + tsVersion, +) { + version.deno = denoVersion; + version.v8 = v8Version; + version.typescript = tsVersion; - ObjectFreeze(version); - } + ObjectFreeze(version); +} - window.__bootstrap.version = { - version, - setVersions, - }; -})(this); +export { setVersions, version }; diff --git a/runtime/js/01_web_util.js b/runtime/js/01_web_util.js deleted file mode 100644 index 9e0bedbd80..0000000000 --- a/runtime/js/01_web_util.js +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { TypeError, Symbol } = window.__bootstrap.primordials; - const illegalConstructorKey = Symbol("illegalConstructorKey"); - - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - window.__bootstrap.webUtil = { - illegalConstructorKey, - requiredArguments, - }; -})(this); diff --git a/runtime/js/06_util.js b/runtime/js/06_util.js index 391497ba80..6ad1b3ce15 100644 --- a/runtime/js/06_util.js +++ b/runtime/js/06_util.js @@ -1,150 +1,147 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { - decodeURIComponent, - ObjectPrototypeIsPrototypeOf, - Promise, - SafeArrayIterator, - StringPrototypeReplace, - TypeError, - } = window.__bootstrap.primordials; - const { build } = window.__bootstrap.build; - const { URLPrototype } = window.__bootstrap.url; - let logDebug = false; - let logSource = "JS"; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + decodeURIComponent, + ObjectPrototypeIsPrototypeOf, + Promise, + SafeArrayIterator, + StringPrototypeReplace, + TypeError, +} = primordials; +import { build } from "internal:runtime/js/01_build.js"; +import { URLPrototype } from "internal:ext/url/00_url.js"; +let logDebug = false; +let logSource = "JS"; - function setLogDebug(debug, source) { - logDebug = debug; - if (source) { - logSource = source; - } +function setLogDebug(debug, source) { + logDebug = debug; + if (source) { + logSource = source; } +} - function log(...args) { - if (logDebug) { - // if we destructure `console` off `globalThis` too early, we don't bind to - // the right console, therefore we don't log anything out. - globalThis.console.log( - `DEBUG ${logSource} -`, - ...new SafeArrayIterator(args), - ); - } - } - - function createResolvable() { - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - promise.resolve = resolve; - promise.reject = reject; - return promise; - } - - // Keep in sync with `fromFileUrl()` in `std/path/win32.ts`. - function pathFromURLWin32(url) { - let p = StringPrototypeReplace( - url.pathname, - /^\/*([A-Za-z]:)(\/|$)/, - "$1/", - ); - p = StringPrototypeReplace( - p, - /\//g, - "\\", - ); - p = StringPrototypeReplace( - p, - /%(?![0-9A-Fa-f]{2})/g, - "%25", - ); - let path = decodeURIComponent(p); - if (url.hostname != "") { - // Note: The `URL` implementation guarantees that the drive letter and - // hostname are mutually exclusive. Otherwise it would not have been valid - // to append the hostname and path like this. - path = `\\\\${url.hostname}${path}`; - } - return path; - } - - // Keep in sync with `fromFileUrl()` in `std/path/posix.ts`. - function pathFromURLPosix(url) { - if (url.hostname !== "") { - throw new TypeError(`Host must be empty.`); - } - - return decodeURIComponent( - StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"), +function log(...args) { + if (logDebug) { + // if we destructure `console` off `globalThis` too early, we don't bind to + // the right console, therefore we don't log anything out. + globalThis.console.log( + `DEBUG ${logSource} -`, + ...new SafeArrayIterator(args), ); } +} - function pathFromURL(pathOrUrl) { - if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) { - if (pathOrUrl.protocol != "file:") { - throw new TypeError("Must be a file URL."); - } +function createResolvable() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + promise.resolve = resolve; + promise.reject = reject; + return promise; +} - return build.os == "windows" - ? pathFromURLWin32(pathOrUrl) - : pathFromURLPosix(pathOrUrl); - } - return pathOrUrl; +// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`. +function pathFromURLWin32(url) { + let p = StringPrototypeReplace( + url.pathname, + /^\/*([A-Za-z]:)(\/|$)/, + "$1/", + ); + p = StringPrototypeReplace( + p, + /\//g, + "\\", + ); + p = StringPrototypeReplace( + p, + /%(?![0-9A-Fa-f]{2})/g, + "%25", + ); + let path = decodeURIComponent(p); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; + } + return path; +} + +// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`. +function pathFromURLPosix(url) { + if (url.hostname !== "") { + throw new TypeError(`Host must be empty.`); } - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - pathFromURL, + return decodeURIComponent( + StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"), + ); +} + +function pathFromURL(pathOrUrl) { + if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) { + if (pathOrUrl.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + + return build.os == "windows" + ? pathFromURLWin32(pathOrUrl) + : pathFromURLPosix(pathOrUrl); + } + return pathOrUrl; +} + +// TODO(bartlomieju): remove +internals.pathFromURL = pathFromURL; + +function writable(value) { + return { + value, + writable: true, + enumerable: true, + configurable: true, }; +} - function writable(value) { - return { - value, - writable: true, - enumerable: true, - configurable: true, - }; - } - - function nonEnumerable(value) { - return { - value, - writable: true, - enumerable: false, - configurable: true, - }; - } - - function readOnly(value) { - return { - value, - enumerable: true, - writable: false, - configurable: true, - }; - } - - function getterOnly(getter) { - return { - get: getter, - set() {}, - enumerable: true, - configurable: true, - }; - } - - window.__bootstrap.util = { - log, - setLogDebug, - createResolvable, - pathFromURL, - writable, - nonEnumerable, - readOnly, - getterOnly, +function nonEnumerable(value) { + return { + value, + writable: true, + enumerable: false, + configurable: true, }; -})(this); +} + +function readOnly(value) { + return { + value, + enumerable: true, + writable: false, + configurable: true, + }; +} + +function getterOnly(getter) { + return { + get: getter, + set() {}, + enumerable: true, + configurable: true, + }; +} + +export { + createResolvable, + getterOnly, + log, + nonEnumerable, + pathFromURL, + readOnly, + setLogDebug, + writable, +}; diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js index 7c4af8ed0c..6019302e4d 100644 --- a/runtime/js/10_permissions.js +++ b/runtime/js/10_permissions.js @@ -1,287 +1,282 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { ops } = Deno.core; - const { Event } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { pathFromURL } = window.__bootstrap.util; - const { illegalConstructorKey } = window.__bootstrap.webUtil; - const { - ArrayIsArray, - ArrayPrototypeIncludes, - ArrayPrototypeMap, - ArrayPrototypeSlice, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - FunctionPrototypeCall, - PromiseResolve, - PromiseReject, - ReflectHas, - SafeArrayIterator, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { Event, EventTarget } from "internal:ext/web/02_event.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeMap, + ArrayPrototypeSlice, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + FunctionPrototypeCall, + PromiseResolve, + PromiseReject, + ReflectHas, + SafeArrayIterator, + Symbol, + SymbolFor, + TypeError, +} = primordials; - /** - * @typedef StatusCacheValue - * @property {PermissionState} state - * @property {PermissionStatus} status - */ +const illegalConstructorKey = Symbol("illegalConstructorKey"); - /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ - const permissionNames = [ - "read", - "write", - "net", - "env", - "sys", - "run", - "ffi", - "hrtime", - ]; +/** + * @typedef StatusCacheValue + * @property {PermissionState} state + * @property {PermissionStatus} status + */ - /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} - */ - function opQuery(desc) { - return ops.op_query_permission(desc); +/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ +const permissionNames = [ + "read", + "write", + "net", + "env", + "sys", + "run", + "ffi", + "hrtime", +]; + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opQuery(desc) { + return ops.op_query_permission(desc); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opRevoke(desc) { + return ops.op_revoke_permission(desc); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opRequest(desc) { + return ops.op_request_permission(desc); +} + +class PermissionStatus extends EventTarget { + /** @type {{ state: Deno.PermissionState }} */ + #state; + + /** @type {((this: PermissionStatus, event: Event) => any) | null} */ + onchange = null; + + /** @returns {Deno.PermissionState} */ + get state() { + return this.#state.state; } /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} + * @param {{ state: Deno.PermissionState }} state + * @param {unknown} key */ - function opRevoke(desc) { - return ops.op_revoke_permission(desc); + constructor(state = null, key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + this.#state = state; } /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} + * @param {Event} event + * @returns {boolean} */ - function opRequest(desc) { - return ops.op_request_permission(desc); + dispatchEvent(event) { + let dispatched = super.dispatchEvent(event); + if (dispatched && this.onchange) { + FunctionPrototypeCall(this.onchange, this, event); + dispatched = !event.defaultPrevented; + } + return dispatched; } - class PermissionStatus extends EventTarget { - /** @type {{ state: Deno.PermissionState }} */ - #state; - - /** @type {((this: PermissionStatus, event: Event) => any) | null} */ - onchange = null; - - /** @returns {Deno.PermissionState} */ - get state() { - return this.#state.state; - } - - /** - * @param {{ state: Deno.PermissionState }} state - * @param {unknown} key - */ - constructor(state = null, key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - this.#state = state; - } - - /** - * @param {Event} event - * @returns {boolean} - */ - dispatchEvent(event) { - let dispatched = super.dispatchEvent(event); - if (dispatched && this.onchange) { - FunctionPrototypeCall(this.onchange, this, event); - dispatched = !event.defaultPrevented; - } - return dispatched; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ state: this.state, onchange: this.onchange }) - }`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ state: this.state, onchange: this.onchange }) + }`; } +} - /** @type {Map} */ - const statusCache = new Map(); +/** @type {Map} */ +const statusCache = new Map(); - /** - * @param {Deno.PermissionDescriptor} desc - * @param {Deno.PermissionState} state - * @returns {PermissionStatus} - */ - function cache(desc, state) { - let { name: key } = desc; - if ( - (desc.name === "read" || desc.name === "write" || desc.name === "ffi") && - ReflectHas(desc, "path") - ) { - key += `-${desc.path}&`; - } else if (desc.name === "net" && desc.host) { - key += `-${desc.host}&`; - } else if (desc.name === "run" && desc.command) { - key += `-${desc.command}&`; - } else if (desc.name === "env" && desc.variable) { - key += `-${desc.variable}&`; - } else if (desc.name === "sys" && desc.kind) { - key += `-${desc.kind}&`; - } else { - key += "$"; +/** + * @param {Deno.PermissionDescriptor} desc + * @param {Deno.PermissionState} state + * @returns {PermissionStatus} + */ +function cache(desc, state) { + let { name: key } = desc; + if ( + (desc.name === "read" || desc.name === "write" || desc.name === "ffi") && + ReflectHas(desc, "path") + ) { + key += `-${desc.path}&`; + } else if (desc.name === "net" && desc.host) { + key += `-${desc.host}&`; + } else if (desc.name === "run" && desc.command) { + key += `-${desc.command}&`; + } else if (desc.name === "env" && desc.variable) { + key += `-${desc.variable}&`; + } else if (desc.name === "sys" && desc.kind) { + key += `-${desc.kind}&`; + } else { + key += "$"; + } + if (MapPrototypeHas(statusCache, key)) { + const status = MapPrototypeGet(statusCache, key); + if (status.state !== state) { + status.state = state; + status.status.dispatchEvent(new Event("change", { cancelable: false })); } - if (MapPrototypeHas(statusCache, key)) { - const status = MapPrototypeGet(statusCache, key); - if (status.state !== state) { - status.state = state; - status.status.dispatchEvent(new Event("change", { cancelable: false })); - } - return status.status; - } - /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ - const status = { state }; - status.status = new PermissionStatus(status, illegalConstructorKey); - MapPrototypeSet(statusCache, key, status); return status.status; } + /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ + const status = { state }; + status.status = new PermissionStatus(status, illegalConstructorKey); + MapPrototypeSet(statusCache, key, status); + return status.status; +} - /** - * @param {unknown} desc - * @returns {desc is Deno.PermissionDescriptor} - */ - function isValidDescriptor(desc) { - return typeof desc === "object" && desc !== null && - ArrayPrototypeIncludes(permissionNames, desc.name); +/** + * @param {unknown} desc + * @returns {desc is Deno.PermissionDescriptor} + */ +function isValidDescriptor(desc) { + return typeof desc === "object" && desc !== null && + ArrayPrototypeIncludes(permissionNames, desc.name); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {desc is Deno.PermissionDescriptor} + */ +function formDescriptor(desc) { + if ( + desc.name === "read" || desc.name === "write" || desc.name === "ffi" + ) { + desc.path = pathFromURL(desc.path); + } else if (desc.name === "run") { + desc.command = pathFromURL(desc.command); + } +} + +class Permissions { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } } - /** - * @param {Deno.PermissionDescriptor} desc - * @returns {desc is Deno.PermissionDescriptor} - */ - function formDescriptor(desc) { - if ( - desc.name === "read" || desc.name === "write" || desc.name === "ffi" + query(desc) { + try { + return PromiseResolve(this.querySync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + querySync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); + } + + formDescriptor(desc); + + const state = opQuery(desc); + return cache(desc, state); + } + + revoke(desc) { + try { + return PromiseResolve(this.revokeSync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + revokeSync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); + } + + formDescriptor(desc); + + const state = opRevoke(desc); + return cache(desc, state); + } + + request(desc) { + try { + return PromiseResolve(this.requestSync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + requestSync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); + } + + formDescriptor(desc); + + const state = opRequest(desc); + return cache(desc, state); + } +} + +const permissions = new Permissions(illegalConstructorKey); + +/** Converts all file URLs in FS allowlists to paths. */ +function serializePermissions(permissions) { + if (typeof permissions == "object" && permissions != null) { + const serializedPermissions = {}; + for ( + const key of new SafeArrayIterator(["read", "write", "run", "ffi"]) ) { - desc.path = pathFromURL(desc.path); - } else if (desc.name === "run") { - desc.command = pathFromURL(desc.command); - } - } - - class Permissions { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - } - - query(desc) { - try { - return PromiseResolve(this.querySync(desc)); - } catch (error) { - return PromiseReject(error); - } - } - - querySync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeMap( + permissions[key], + (path) => pathFromURL(path), ); - } - - formDescriptor(desc); - - const state = opQuery(desc); - return cache(desc, state); - } - - revoke(desc) { - try { - return PromiseResolve(this.revokeSync(desc)); - } catch (error) { - return PromiseReject(error); + } else { + serializedPermissions[key] = permissions[key]; } } - - revokeSync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ); - } - - formDescriptor(desc); - - const state = opRevoke(desc); - return cache(desc, state); - } - - request(desc) { - try { - return PromiseResolve(this.requestSync(desc)); - } catch (error) { - return PromiseReject(error); + for ( + const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"]) + ) { + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); + } else { + serializedPermissions[key] = permissions[key]; } } - - requestSync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ); - } - - formDescriptor(desc); - - const state = opRequest(desc); - return cache(desc, state); - } + return serializedPermissions; } + return permissions; +} - const permissions = new Permissions(illegalConstructorKey); - - /** Converts all file URLs in FS allowlists to paths. */ - function serializePermissions(permissions) { - if (typeof permissions == "object" && permissions != null) { - const serializedPermissions = {}; - for ( - const key of new SafeArrayIterator(["read", "write", "run", "ffi"]) - ) { - if (ArrayIsArray(permissions[key])) { - serializedPermissions[key] = ArrayPrototypeMap( - permissions[key], - (path) => pathFromURL(path), - ); - } else { - serializedPermissions[key] = permissions[key]; - } - } - for ( - const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"]) - ) { - if (ArrayIsArray(permissions[key])) { - serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); - } else { - serializedPermissions[key] = permissions[key]; - } - } - return serializedPermissions; - } - return permissions; - } - - window.__bootstrap.permissions = { - serializePermissions, - permissions, - Permissions, - PermissionStatus, - }; -})(this); +export { Permissions, permissions, PermissionStatus, serializePermissions }; diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js index 85c01e1a92..56ceb92f39 100644 --- a/runtime/js/11_workers.js +++ b/runtime/js/11_workers.js @@ -1,254 +1,253 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Error, - ObjectPrototypeIsPrototypeOf, - StringPrototypeStartsWith, - String, - SymbolIterator, - SymbolToStringTag, - } = window.__bootstrap.primordials; - const webidl = window.__bootstrap.webidl; - const { URL } = window.__bootstrap.url; - const { getLocationHref } = window.__bootstrap.location; - const { serializePermissions } = window.__bootstrap.permissions; - const { log } = window.__bootstrap.util; - const { ErrorEvent, MessageEvent, defineEventHandler } = - window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { - deserializeJsMessageData, - serializeJsMessageData, - MessagePortPrototype, - } = window.__bootstrap.messagePort; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectPrototypeIsPrototypeOf, + StringPrototypeStartsWith, + String, + SymbolIterator, + SymbolToStringTag, +} = primordials; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import { URL } from "internal:ext/url/00_url.js"; +import { getLocationHref } from "internal:ext/web/12_location.js"; +import { serializePermissions } from "internal:runtime/js/10_permissions.js"; +import { log } from "internal:runtime/js/06_util.js"; +import { + defineEventHandler, + ErrorEvent, + EventTarget, + MessageEvent, +} from "internal:ext/web/02_event.js"; +import { + deserializeJsMessageData, + MessagePortPrototype, + serializeJsMessageData, +} from "internal:ext/web/13_message_port.js"; - function createWorker( - specifier, +function createWorker( + specifier, + hasSourceCode, + sourceCode, + permissions, + name, + workerType, +) { + return ops.op_create_worker({ hasSourceCode, - sourceCode, - permissions, name, + permissions: serializePermissions(permissions), + sourceCode, + specifier, workerType, - ) { - return ops.op_create_worker({ - hasSourceCode, + }); +} + +function hostTerminateWorker(id) { + ops.op_host_terminate_worker(id); +} + +function hostPostMessage(id, data) { + ops.op_host_post_message(id, data); +} + +function hostRecvCtrl(id) { + return core.opAsync("op_host_recv_ctrl", id); +} + +function hostRecvMessage(id) { + return core.opAsync("op_host_recv_message", id); +} + +class Worker extends EventTarget { + #id = 0; + #name = ""; + + // "RUNNING" | "CLOSED" | "TERMINATED" + // "TERMINATED" means that any controls or messages received will be + // discarded. "CLOSED" means that we have received a control + // indicating that the worker is no longer running, but there might + // still be messages left to receive. + #status = "RUNNING"; + + constructor(specifier, options = {}) { + super(); + specifier = String(specifier); + const { + deno, name, - permissions: serializePermissions(permissions), - sourceCode, + type = "classic", + } = options; + + const workerType = webidl.converters["WorkerType"](type); + + if ( + StringPrototypeStartsWith(specifier, "./") || + StringPrototypeStartsWith(specifier, "../") || + StringPrototypeStartsWith(specifier, "/") || workerType === "classic" + ) { + const baseUrl = getLocationHref(); + if (baseUrl != null) { + specifier = new URL(specifier, baseUrl).href; + } + } + + this.#name = name; + let hasSourceCode, sourceCode; + if (workerType === "classic") { + hasSourceCode = true; + sourceCode = `importScripts("#");`; + } else { + hasSourceCode = false; + sourceCode = ""; + } + + const id = createWorker( specifier, + hasSourceCode, + sourceCode, + deno?.permissions, + name, workerType, + ); + this.#id = id; + this.#pollControl(); + this.#pollMessages(); + } + + #handleError(e) { + const event = new ErrorEvent("error", { + cancelable: true, + message: e.message, + lineno: e.lineNumber ? e.lineNumber : undefined, + colno: e.columnNumber ? e.columnNumber : undefined, + filename: e.fileName, + error: null, }); - } - function hostTerminateWorker(id) { - ops.op_host_terminate_worker(id); - } - - function hostPostMessage(id, data) { - ops.op_host_post_message(id, data); - } - - function hostRecvCtrl(id) { - return core.opAsync("op_host_recv_ctrl", id); - } - - function hostRecvMessage(id) { - return core.opAsync("op_host_recv_message", id); - } - - class Worker extends EventTarget { - #id = 0; - #name = ""; - - // "RUNNING" | "CLOSED" | "TERMINATED" - // "TERMINATED" means that any controls or messages received will be - // discarded. "CLOSED" means that we have received a control - // indicating that the worker is no longer running, but there might - // still be messages left to receive. - #status = "RUNNING"; - - constructor(specifier, options = {}) { - super(); - specifier = String(specifier); - const { - deno, - name, - type = "classic", - } = options; - - const workerType = webidl.converters["WorkerType"](type); - - if ( - StringPrototypeStartsWith(specifier, "./") || - StringPrototypeStartsWith(specifier, "../") || - StringPrototypeStartsWith(specifier, "/") || workerType === "classic" - ) { - const baseUrl = getLocationHref(); - if (baseUrl != null) { - specifier = new URL(specifier, baseUrl).href; - } - } - - this.#name = name; - let hasSourceCode, sourceCode; - if (workerType === "classic") { - hasSourceCode = true; - sourceCode = `importScripts("#");`; - } else { - hasSourceCode = false; - sourceCode = ""; - } - - const id = createWorker( - specifier, - hasSourceCode, - sourceCode, - deno?.permissions, - name, - workerType, - ); - this.#id = id; - this.#pollControl(); - this.#pollMessages(); + this.dispatchEvent(event); + // Don't bubble error event to window for loader errors (`!e.fileName`). + // TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user + // errors. It won't be there for non-awaited async ops for example. + if (e.fileName && !event.defaultPrevented) { + globalThis.dispatchEvent(event); } - #handleError(e) { - const event = new ErrorEvent("error", { - cancelable: true, - message: e.message, - lineno: e.lineNumber ? e.lineNumber : undefined, - colno: e.columnNumber ? e.columnNumber : undefined, - filename: e.fileName, - error: null, - }); + return event.defaultPrevented; + } - this.dispatchEvent(event); - // Don't bubble error event to window for loader errors (`!e.fileName`). - // TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user - // errors. It won't be there for non-awaited async ops for example. - if (e.fileName && !event.defaultPrevented) { - window.dispatchEvent(event); + #pollControl = async () => { + while (this.#status === "RUNNING") { + const { 0: type, 1: data } = await hostRecvCtrl(this.#id); + + // If terminate was called then we ignore all messages + if (this.#status === "TERMINATED") { + return; } - return event.defaultPrevented; + switch (type) { + case 1: { // TerminalError + this.#status = "CLOSED"; + } /* falls through */ + case 2: { // Error + if (!this.#handleError(data)) { + throw new Error("Unhandled error in child worker."); + } + break; + } + case 3: { // Close + log(`Host got "close" message from worker: ${this.#name}`); + this.#status = "CLOSED"; + return; + } + default: { + throw new Error(`Unknown worker event: "${type}"`); + } + } } + }; - #pollControl = async () => { - while (this.#status === "RUNNING") { - const { 0: type, 1: data } = await hostRecvCtrl(this.#id); - - // If terminate was called then we ignore all messages - if (this.#status === "TERMINATED") { - return; - } - - switch (type) { - case 1: { // TerminalError - this.#status = "CLOSED"; - } /* falls through */ - case 2: { // Error - if (!this.#handleError(data)) { - throw new Error("Unhandled error in child worker."); - } - break; - } - case 3: { // Close - log(`Host got "close" message from worker: ${this.#name}`); - this.#status = "CLOSED"; - return; - } - default: { - throw new Error(`Unknown worker event: "${type}"`); - } - } + #pollMessages = async () => { + while (this.#status !== "TERMINATED") { + const data = await hostRecvMessage(this.#id); + if (this.#status === "TERMINATED" || data === null) { + return; } - }; - - #pollMessages = async () => { - while (this.#status !== "TERMINATED") { - const data = await hostRecvMessage(this.#id); - if (this.#status === "TERMINATED" || data === null) { - return; - } - let message, transferables; - try { - const v = deserializeJsMessageData(data); - message = v[0]; - transferables = v[1]; - } catch (err) { - const event = new MessageEvent("messageerror", { - cancelable: false, - data: err, - }); - this.dispatchEvent(event); - return; - } - const event = new MessageEvent("message", { + let message, transferables; + try { + const v = deserializeJsMessageData(data); + message = v[0]; + transferables = v[1]; + } catch (err) { + const event = new MessageEvent("messageerror", { cancelable: false, - data: message, - ports: transferables.filter((t) => - ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t) - ), + data: err, }); this.dispatchEvent(event); + return; } - }; - - postMessage(message, transferOrOptions = {}) { - const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - const data = serializeJsMessageData(message, transfer); - if (this.#status === "RUNNING") { - hostPostMessage(this.#id, data); - } + const event = new MessageEvent("message", { + cancelable: false, + data: message, + ports: transferables.filter((t) => + ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t) + ), + }); + this.dispatchEvent(event); } + }; - terminate() { - if (this.#status !== "TERMINATED") { - this.#status = "TERMINATED"; - hostTerminateWorker(this.#id); - } + postMessage(message, transferOrOptions = {}) { + const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); + } + const { transfer } = options; + const data = serializeJsMessageData(message, transfer); + if (this.#status === "RUNNING") { + hostPostMessage(this.#id, data); } - - [SymbolToStringTag] = "Worker"; } - defineEventHandler(Worker.prototype, "error"); - defineEventHandler(Worker.prototype, "message"); - defineEventHandler(Worker.prototype, "messageerror"); + terminate() { + if (this.#status !== "TERMINATED") { + this.#status = "TERMINATED"; + hostTerminateWorker(this.#id); + } + } - webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [ - "classic", - "module", - ]); + [SymbolToStringTag] = "Worker"; +} - window.__bootstrap.worker = { - Worker, - }; -})(this); +defineEventHandler(Worker.prototype, "error"); +defineEventHandler(Worker.prototype, "message"); +defineEventHandler(Worker.prototype, "messageerror"); + +webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [ + "classic", + "module", +]); + +export { Worker }; diff --git a/runtime/js/12_io.js b/runtime/js/12_io.js index f6b2d6b877..b9ff1190b1 100644 --- a/runtime/js/12_io.js +++ b/runtime/js/12_io.js @@ -3,238 +3,236 @@ // Interfaces 100% copied from Go. // Documentation liberally lifted from them too. // Thank you! We love Go! <3 -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Uint8Array, - ArrayPrototypePush, - MathMin, - TypedArrayPrototypeSubarray, - TypedArrayPrototypeSet, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Uint8Array, + ArrayPrototypePush, + MathMin, + TypedArrayPrototypeSubarray, + TypedArrayPrototypeSet, +} = primordials; - const DEFAULT_BUFFER_SIZE = 32 * 1024; - // Seek whence values. - // https://golang.org/pkg/io/#pkg-constants - const SeekMode = { - 0: "Start", - 1: "Current", - 2: "End", +const DEFAULT_BUFFER_SIZE = 32 * 1024; +// Seek whence values. +// https://golang.org/pkg/io/#pkg-constants +const SeekMode = { + 0: "Start", + 1: "Current", + 2: "End", - Start: 0, - Current: 1, - End: 2, - }; + Start: 0, + Current: 1, + End: 2, +}; - async function copy( - src, - dst, - options, - ) { - let n = 0; - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - let gotEOF = false; - while (gotEOF === false) { - const result = await src.read(b); - if (result === null) { - gotEOF = true; - } else { - let nwritten = 0; - while (nwritten < result) { - nwritten += await dst.write( - TypedArrayPrototypeSubarray(b, nwritten, result), - ); - } - n += nwritten; - } - } - return n; - } - - async function* iter( - r, - options, - ) { - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - while (true) { - const result = await r.read(b); - if (result === null) { - break; - } - - yield TypedArrayPrototypeSubarray(b, 0, result); - } - } - - function* iterSync( - r, - options, - ) { - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - while (true) { - const result = r.readSync(b); - if (result === null) { - break; - } - - yield TypedArrayPrototypeSubarray(b, 0, result); - } - } - - function readSync(rid, buffer) { - if (buffer.length === 0) { - return 0; - } - - const nread = ops.op_read_sync(rid, buffer); - - return nread === 0 ? null : nread; - } - - async function read(rid, buffer) { - if (buffer.length === 0) { - return 0; - } - - const nread = await core.read(rid, buffer); - - return nread === 0 ? null : nread; - } - - function writeSync(rid, data) { - return ops.op_write_sync(rid, data); - } - - function write(rid, data) { - return core.write(rid, data); - } - - const READ_PER_ITER = 64 * 1024; // 64kb - - function readAll(r) { - return readAllInner(r); - } - async function readAllInner(r, options) { - const buffers = []; - const signal = options?.signal ?? null; - while (true) { - signal?.throwIfAborted(); - const buf = new Uint8Array(READ_PER_ITER); - const read = await r.read(buf); - if (typeof read == "number") { - ArrayPrototypePush(buffers, new Uint8Array(buf.buffer, 0, read)); - } else { - break; - } - } - signal?.throwIfAborted(); - - return concatBuffers(buffers); - } - - function readAllSync(r) { - const buffers = []; - - while (true) { - const buf = new Uint8Array(READ_PER_ITER); - const read = r.readSync(buf); - if (typeof read == "number") { - ArrayPrototypePush(buffers, TypedArrayPrototypeSubarray(buf, 0, read)); - } else { - break; - } - } - - return concatBuffers(buffers); - } - - function concatBuffers(buffers) { - let totalLen = 0; - for (let i = 0; i < buffers.length; ++i) { - totalLen += buffers[i].byteLength; - } - - const contents = new Uint8Array(totalLen); - - let n = 0; - for (let i = 0; i < buffers.length; ++i) { - const buf = buffers[i]; - TypedArrayPrototypeSet(contents, buf, n); - n += buf.byteLength; - } - - return contents; - } - - function readAllSyncSized(r, size) { - const buf = new Uint8Array(size + 1); // 1B to detect extended files - let cursor = 0; - - while (cursor < size) { - const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); - const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); - const read = r.readSync(slice); - if (typeof read == "number") { - cursor += read; - } else { - break; - } - } - - // Handle truncated or extended files during read - if (cursor > size) { - // Read remaining and concat - return concatBuffers([buf, readAllSync(r)]); - } else { // cursor == size - return TypedArrayPrototypeSubarray(buf, 0, cursor); - } - } - - async function readAllInnerSized(r, size, options) { - const buf = new Uint8Array(size + 1); // 1B to detect extended files - let cursor = 0; - const signal = options?.signal ?? null; - while (cursor < size) { - signal?.throwIfAborted(); - const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); - const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); - const read = await r.read(slice); - if (typeof read == "number") { - cursor += read; - } else { - break; - } - } - signal?.throwIfAborted(); - - // Handle truncated or extended files during read - if (cursor > size) { - // Read remaining and concat - return concatBuffers([buf, await readAllInner(r, options)]); +async function copy( + src, + dst, + options, +) { + let n = 0; + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + let gotEOF = false; + while (gotEOF === false) { + const result = await src.read(b); + if (result === null) { + gotEOF = true; } else { - return TypedArrayPrototypeSubarray(buf, 0, cursor); + let nwritten = 0; + while (nwritten < result) { + nwritten += await dst.write( + TypedArrayPrototypeSubarray(b, nwritten, result), + ); + } + n += nwritten; + } + } + return n; +} + +async function* iter( + r, + options, +) { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = await r.read(b); + if (result === null) { + break; + } + + yield TypedArrayPrototypeSubarray(b, 0, result); + } +} + +function* iterSync( + r, + options, +) { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = r.readSync(b); + if (result === null) { + break; + } + + yield TypedArrayPrototypeSubarray(b, 0, result); + } +} + +function readSync(rid, buffer) { + if (buffer.length === 0) { + return 0; + } + + const nread = ops.op_read_sync(rid, buffer); + + return nread === 0 ? null : nread; +} + +async function read(rid, buffer) { + if (buffer.length === 0) { + return 0; + } + + const nread = await core.read(rid, buffer); + + return nread === 0 ? null : nread; +} + +function writeSync(rid, data) { + return ops.op_write_sync(rid, data); +} + +function write(rid, data) { + return core.write(rid, data); +} + +const READ_PER_ITER = 64 * 1024; // 64kb + +function readAll(r) { + return readAllInner(r); +} +async function readAllInner(r, options) { + const buffers = []; + const signal = options?.signal ?? null; + while (true) { + signal?.throwIfAborted(); + const buf = new Uint8Array(READ_PER_ITER); + const read = await r.read(buf); + if (typeof read == "number") { + ArrayPrototypePush(buffers, new Uint8Array(buf.buffer, 0, read)); + } else { + break; + } + } + signal?.throwIfAborted(); + + return concatBuffers(buffers); +} + +function readAllSync(r) { + const buffers = []; + + while (true) { + const buf = new Uint8Array(READ_PER_ITER); + const read = r.readSync(buf); + if (typeof read == "number") { + ArrayPrototypePush(buffers, TypedArrayPrototypeSubarray(buf, 0, read)); + } else { + break; } } - window.__bootstrap.io = { - iterSync, - iter, - copy, - SeekMode, - read, - readSync, - write, - writeSync, - readAll, - readAllInner, - readAllSync, - readAllSyncSized, - readAllInnerSized, - }; -})(this); + return concatBuffers(buffers); +} + +function concatBuffers(buffers) { + let totalLen = 0; + for (let i = 0; i < buffers.length; ++i) { + totalLen += buffers[i].byteLength; + } + + const contents = new Uint8Array(totalLen); + + let n = 0; + for (let i = 0; i < buffers.length; ++i) { + const buf = buffers[i]; + TypedArrayPrototypeSet(contents, buf, n); + n += buf.byteLength; + } + + return contents; +} + +function readAllSyncSized(r, size) { + const buf = new Uint8Array(size + 1); // 1B to detect extended files + let cursor = 0; + + while (cursor < size) { + const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); + const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); + const read = r.readSync(slice); + if (typeof read == "number") { + cursor += read; + } else { + break; + } + } + + // Handle truncated or extended files during read + if (cursor > size) { + // Read remaining and concat + return concatBuffers([buf, readAllSync(r)]); + } else { // cursor == size + return TypedArrayPrototypeSubarray(buf, 0, cursor); + } +} + +async function readAllInnerSized(r, size, options) { + const buf = new Uint8Array(size + 1); // 1B to detect extended files + let cursor = 0; + const signal = options?.signal ?? null; + while (cursor < size) { + signal?.throwIfAborted(); + const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); + const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); + const read = await r.read(slice); + if (typeof read == "number") { + cursor += read; + } else { + break; + } + } + signal?.throwIfAborted(); + + // Handle truncated or extended files during read + if (cursor > size) { + // Read remaining and concat + return concatBuffers([buf, await readAllInner(r, options)]); + } else { + return TypedArrayPrototypeSubarray(buf, 0, cursor); + } +} + +export { + copy, + iter, + iterSync, + read, + readAll, + readAllInner, + readAllInnerSized, + readAllSync, + readAllSyncSized, + readSync, + SeekMode, + write, + writeSync, +}; diff --git a/runtime/js/13_buffer.js b/runtime/js/13_buffer.js index 180e9d26dc..8e87eefa52 100644 --- a/runtime/js/13_buffer.js +++ b/runtime/js/13_buffer.js @@ -3,257 +3,249 @@ // This code has been ported almost directly from Go's src/bytes/buffer.go // Copyright 2009 The Go Authors. All rights reserved. BSD license. // https://github.com/golang/go/blob/master/LICENSE -"use strict"; -((window) => { - const { assert } = window.__bootstrap.infra; - const { - TypedArrayPrototypeSubarray, - TypedArrayPrototypeSlice, - TypedArrayPrototypeSet, - MathFloor, - MathMin, - PromiseResolve, - Uint8Array, - Error, - } = window.__bootstrap.primordials; +import { assert } from "internal:ext/web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + TypedArrayPrototypeSubarray, + TypedArrayPrototypeSlice, + TypedArrayPrototypeSet, + MathFloor, + MathMin, + PromiseResolve, + Uint8Array, + Error, +} = primordials; - // MIN_READ is the minimum ArrayBuffer size passed to a read call by - // buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond - // what is required to hold the contents of r, readFrom() will not grow the - // underlying buffer. - const MIN_READ = 32 * 1024; - const MAX_SIZE = 2 ** 32 - 2; +// MIN_READ is the minimum ArrayBuffer size passed to a read call by +// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond +// what is required to hold the contents of r, readFrom() will not grow the +// underlying buffer. +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; - // `off` is the offset into `dst` where it will at which to begin writing values - // from `src`. - // Returns the number of bytes copied. - function copyBytes(src, dst, off = 0) { - const r = dst.byteLength - off; - if (src.byteLength > r) { - src = TypedArrayPrototypeSubarray(src, 0, r); +// `off` is the offset into `dst` where it will at which to begin writing values +// from `src`. +// Returns the number of bytes copied. +function copyBytes(src, dst, off = 0) { + const r = dst.byteLength - off; + if (src.byteLength > r) { + src = TypedArrayPrototypeSubarray(src, 0, r); + } + TypedArrayPrototypeSet(dst, src, off); + return src.byteLength; +} + +class Buffer { + #buf = null; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + + constructor(ab) { + if (ab == null) { + this.#buf = new Uint8Array(0); + return; } - TypedArrayPrototypeSet(dst, src, off); - return src.byteLength; + + this.#buf = new Uint8Array(ab); } - class Buffer { - #buf = null; // contents are the bytes buf[off : len(buf)] - #off = 0; // read at buf[off], write at buf[buf.byteLength] + bytes(options = { copy: true }) { + if (options.copy === false) { + return TypedArrayPrototypeSubarray(this.#buf, this.#off); + } + return TypedArrayPrototypeSlice(this.#buf, this.#off); + } - constructor(ab) { - if (ab == null) { - this.#buf = new Uint8Array(0); - return; + empty() { + return this.#buf.byteLength <= this.#off; + } + + get length() { + return this.#buf.byteLength - this.#off; + } + + get capacity() { + return this.#buf.buffer.byteLength; + } + + truncate(n) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + readSync(p) { + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + if (p.byteLength === 0) { + // this edge case is tested in 'bufferReadEmptyAtEOF' test + return 0; + } + return null; + } + const nread = copyBytes( + TypedArrayPrototypeSubarray(this.#buf, this.#off), + p, + ); + this.#off += nread; + return nread; + } + + read(p) { + const rr = this.readSync(p); + return PromiseResolve(rr); + } + + writeSync(p) { + const m = this.#grow(p.byteLength); + return copyBytes(p, this.#buf, m); + } + + write(p) { + const n = this.writeSync(p); + return PromiseResolve(n); + } + + #grow(n) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= MathFloor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(MathMin(2 * c + n, MAX_SIZE)); + copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(MathMin(m + n, MAX_SIZE)); + return m; + } + + grow(n) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } + + async readFrom(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); + if (nread === null) { + return n; } - this.#buf = new Uint8Array(ab); - } + // write will grow if needed + if (shouldGrow) { + this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); + } else this.#reslice(this.length + nread); - bytes(options = { copy: true }) { - if (options.copy === false) { - return TypedArrayPrototypeSubarray(this.#buf, this.#off); - } - return TypedArrayPrototypeSlice(this.#buf, this.#off); - } - - empty() { - return this.#buf.byteLength <= this.#off; - } - - get length() { - return this.#buf.byteLength - this.#off; - } - - get capacity() { - return this.#buf.buffer.byteLength; - } - - truncate(n) { - if (n === 0) { - this.reset(); - return; - } - if (n < 0 || n > this.length) { - throw Error("bytes.Buffer: truncation out of range"); - } - this.#reslice(this.#off + n); - } - - reset() { - this.#reslice(0); - this.#off = 0; - } - - #tryGrowByReslice(n) { - const l = this.#buf.byteLength; - if (n <= this.capacity - l) { - this.#reslice(l + n); - return l; - } - return -1; - } - - #reslice(len) { - assert(len <= this.#buf.buffer.byteLength); - this.#buf = new Uint8Array(this.#buf.buffer, 0, len); - } - - readSync(p) { - if (this.empty()) { - // Buffer is empty, reset to recover space. - this.reset(); - if (p.byteLength === 0) { - // this edge case is tested in 'bufferReadEmptyAtEOF' test - return 0; - } - return null; - } - const nread = copyBytes( - TypedArrayPrototypeSubarray(this.#buf, this.#off), - p, - ); - this.#off += nread; - return nread; - } - - read(p) { - const rr = this.readSync(p); - return PromiseResolve(rr); - } - - writeSync(p) { - const m = this.#grow(p.byteLength); - return copyBytes(p, this.#buf, m); - } - - write(p) { - const n = this.writeSync(p); - return PromiseResolve(n); - } - - #grow(n) { - const m = this.length; - // If buffer is empty, reset to recover space. - if (m === 0 && this.#off !== 0) { - this.reset(); - } - // Fast: Try to grow by means of a reslice. - const i = this.#tryGrowByReslice(n); - if (i >= 0) { - return i; - } - const c = this.capacity; - if (n <= MathFloor(c / 2) - m) { - // We can slide things down instead of allocating a new - // ArrayBuffer. We only need m+n <= c to slide, but - // we instead let capacity get twice as large so we - // don't spend all our time copying. - copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), this.#buf); - } else if (c + n > MAX_SIZE) { - throw new Error("The buffer cannot be grown beyond the maximum size."); - } else { - // Not enough space anywhere, we need to allocate. - const buf = new Uint8Array(MathMin(2 * c + n, MAX_SIZE)); - copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), buf); - this.#buf = buf; - } - // Restore this.#off and len(this.#buf). - this.#off = 0; - this.#reslice(MathMin(m + n, MAX_SIZE)); - return m; - } - - grow(n) { - if (n < 0) { - throw Error("Buffer.grow: negative count"); - } - const m = this.#grow(n); - this.#reslice(m); - } - - async readFrom(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while (true) { - const shouldGrow = this.capacity - this.length < MIN_READ; - // read into tmp buffer if there's not enough room - // otherwise read directly into the internal buffer - const buf = shouldGrow - ? tmp - : new Uint8Array(this.#buf.buffer, this.length); - - const nread = await r.read(buf); - if (nread === null) { - return n; - } - - // write will grow if needed - if (shouldGrow) { - this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); - } else this.#reslice(this.length + nread); - - n += nread; - } - } - - readFromSync(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while (true) { - const shouldGrow = this.capacity - this.length < MIN_READ; - // read into tmp buffer if there's not enough room - // otherwise read directly into the internal buffer - const buf = shouldGrow - ? tmp - : new Uint8Array(this.#buf.buffer, this.length); - - const nread = r.readSync(buf); - if (nread === null) { - return n; - } - - // write will grow if needed - if (shouldGrow) { - this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); - } else this.#reslice(this.length + nread); - - n += nread; - } + n += nread; } } - async function readAll(r) { - const buf = new Buffer(); - await buf.readFrom(r); - return buf.bytes(); - } + readFromSync(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); - function readAllSync(r) { - const buf = new Buffer(); - buf.readFromSync(r); - return buf.bytes(); - } + const nread = r.readSync(buf); + if (nread === null) { + return n; + } - async function writeAll(w, arr) { - let nwritten = 0; - while (nwritten < arr.length) { - nwritten += await w.write(TypedArrayPrototypeSubarray(arr, nwritten)); + // write will grow if needed + if (shouldGrow) { + this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); + } else this.#reslice(this.length + nread); + + n += nread; } } +} - function writeAllSync(w, arr) { - let nwritten = 0; - while (nwritten < arr.length) { - nwritten += w.writeSync(TypedArrayPrototypeSubarray(arr, nwritten)); - } +async function readAll(r) { + const buf = new Buffer(); + await buf.readFrom(r); + return buf.bytes(); +} + +function readAllSync(r) { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} + +async function writeAll(w, arr) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += await w.write(TypedArrayPrototypeSubarray(arr, nwritten)); } +} - window.__bootstrap.buffer = { - writeAll, - writeAllSync, - readAll, - readAllSync, - Buffer, - }; -})(this); +function writeAllSync(w, arr) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += w.writeSync(TypedArrayPrototypeSubarray(arr, nwritten)); + } +} + +export { Buffer, readAll, readAllSync, writeAll, writeAllSync }; diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js index 87b2015fb9..17b0b41ac6 100644 --- a/runtime/js/30_fs.js +++ b/runtime/js/30_fs.js @@ -1,385 +1,375 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Date, - DatePrototype, - MathTrunc, - ObjectPrototypeIsPrototypeOf, - SymbolAsyncIterator, - SymbolIterator, - Function, - ObjectEntries, - Uint32Array, - } = window.__bootstrap.primordials; - const { pathFromURL } = window.__bootstrap.util; - const build = window.__bootstrap.build.build; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Date, + DatePrototype, + MathTrunc, + ObjectPrototypeIsPrototypeOf, + SymbolAsyncIterator, + SymbolIterator, + Function, + ObjectEntries, + Uint32Array, +} = primordials; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { build } from "internal:runtime/js/01_build.js"; - function chmodSync(path, mode) { - ops.op_chmod_sync(pathFromURL(path), mode); - } +function chmodSync(path, mode) { + ops.op_chmod_sync(pathFromURL(path), mode); +} - async function chmod(path, mode) { - await core.opAsync("op_chmod_async", pathFromURL(path), mode); - } +async function chmod(path, mode) { + await core.opAsync("op_chmod_async", pathFromURL(path), mode); +} - function chownSync( - path, +function chownSync( + path, + uid, + gid, +) { + ops.op_chown_sync(pathFromURL(path), uid, gid); +} + +async function chown( + path, + uid, + gid, +) { + await core.opAsync( + "op_chown_async", + pathFromURL(path), uid, gid, - ) { - ops.op_chown_sync(pathFromURL(path), uid, gid); - } + ); +} - async function chown( - path, - uid, - gid, - ) { - await core.opAsync( - "op_chown_async", - pathFromURL(path), - uid, - gid, - ); - } +function copyFileSync( + fromPath, + toPath, +) { + ops.op_copy_file_sync( + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} - function copyFileSync( - fromPath, - toPath, - ) { - ops.op_copy_file_sync( - pathFromURL(fromPath), - pathFromURL(toPath), - ); - } +async function copyFile( + fromPath, + toPath, +) { + await core.opAsync( + "op_copy_file_async", + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} - async function copyFile( - fromPath, - toPath, - ) { - await core.opAsync( - "op_copy_file_async", - pathFromURL(fromPath), - pathFromURL(toPath), - ); - } +function cwd() { + return ops.op_cwd(); +} - function cwd() { - return ops.op_cwd(); - } +function chdir(directory) { + ops.op_chdir(pathFromURL(directory)); +} - function chdir(directory) { - ops.op_chdir(pathFromURL(directory)); - } +function makeTempDirSync(options = {}) { + return ops.op_make_temp_dir_sync(options); +} - function makeTempDirSync(options = {}) { - return ops.op_make_temp_dir_sync(options); - } +function makeTempDir(options = {}) { + return core.opAsync("op_make_temp_dir_async", options); +} - function makeTempDir(options = {}) { - return core.opAsync("op_make_temp_dir_async", options); - } +function makeTempFileSync(options = {}) { + return ops.op_make_temp_file_sync(options); +} - function makeTempFileSync(options = {}) { - return ops.op_make_temp_file_sync(options); - } +function makeTempFile(options = {}) { + return core.opAsync("op_make_temp_file_async", options); +} - function makeTempFile(options = {}) { - return core.opAsync("op_make_temp_file_async", options); - } - - function mkdirArgs(path, options) { - const args = { path: pathFromURL(path), recursive: false }; - if (options != null) { - if (typeof options.recursive == "boolean") { - args.recursive = options.recursive; - } - if (options.mode) { - args.mode = options.mode; - } +function mkdirArgs(path, options) { + const args = { path: pathFromURL(path), recursive: false }; + if (options != null) { + if (typeof options.recursive == "boolean") { + args.recursive = options.recursive; + } + if (options.mode) { + args.mode = options.mode; } - return args; } + return args; +} - function mkdirSync(path, options) { - ops.op_mkdir_sync(mkdirArgs(path, options)); - } +function mkdirSync(path, options) { + ops.op_mkdir_sync(mkdirArgs(path, options)); +} - async function mkdir( - path, - options, - ) { - await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); - } +async function mkdir( + path, + options, +) { + await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); +} - function readDirSync(path) { - return ops.op_read_dir_sync(pathFromURL(path))[ - SymbolIterator - ](); - } +function readDirSync(path) { + return ops.op_read_dir_sync(pathFromURL(path))[ + SymbolIterator + ](); +} - function readDir(path) { - const array = core.opAsync( - "op_read_dir_async", - pathFromURL(path), - ); - return { - async *[SymbolAsyncIterator]() { - yield* await array; - }, - }; - } +function readDir(path) { + const array = core.opAsync( + "op_read_dir_async", + pathFromURL(path), + ); + return { + async *[SymbolAsyncIterator]() { + yield* await array; + }, + }; +} - function readLinkSync(path) { - return ops.op_read_link_sync(pathFromURL(path)); - } +function readLinkSync(path) { + return ops.op_read_link_sync(pathFromURL(path)); +} - function readLink(path) { - return core.opAsync("op_read_link_async", pathFromURL(path)); - } +function readLink(path) { + return core.opAsync("op_read_link_async", pathFromURL(path)); +} - function realPathSync(path) { - return ops.op_realpath_sync(pathFromURL(path)); - } +function realPathSync(path) { + return ops.op_realpath_sync(pathFromURL(path)); +} - function realPath(path) { - return core.opAsync("op_realpath_async", pathFromURL(path)); - } +function realPath(path) { + return core.opAsync("op_realpath_async", pathFromURL(path)); +} - function removeSync( - path, - options = {}, - ) { - ops.op_remove_sync( - pathFromURL(path), - !!options.recursive, - ); - } +function removeSync( + path, + options = {}, +) { + ops.op_remove_sync( + pathFromURL(path), + !!options.recursive, + ); +} - async function remove( - path, - options = {}, - ) { - await core.opAsync( - "op_remove_async", - pathFromURL(path), - !!options.recursive, - ); - } +async function remove( + path, + options = {}, +) { + await core.opAsync( + "op_remove_async", + pathFromURL(path), + !!options.recursive, + ); +} - function renameSync(oldpath, newpath) { - ops.op_rename_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - ); - } +function renameSync(oldpath, newpath) { + ops.op_rename_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} - async function rename(oldpath, newpath) { - await core.opAsync( - "op_rename_async", - pathFromURL(oldpath), - pathFromURL(newpath), - ); - } +async function rename(oldpath, newpath) { + await core.opAsync( + "op_rename_async", + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} - // Extract the FsStat object from the encoded buffer. - // See `runtime/ops/fs.rs` for the encoder. - // - // This is not a general purpose decoder. There are 4 types: - // - // 1. date - // offset += 4 - // 1/0 | extra padding | high u32 | low u32 - // if date[0] == 1, new Date(u64) else null - // - // 2. bool - // offset += 2 - // 1/0 | extra padding - // - // 3. u64 - // offset += 2 - // high u32 | low u32 - // - // 4. ?u64 converts a zero u64 value to JS null on Windows. - function createByteStruct(types) { - // types can be "date", "bool" or "u64". - // `?` prefix means optional on windows. - let offset = 0; - let str = - 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; - const typeEntries = ObjectEntries(types); - for (let i = 0; i < typeEntries.length; ++i) { - let { 0: name, 1: type } = typeEntries[i]; +// Extract the FsStat object from the encoded buffer. +// See `runtime/ops/fs.rs` for the encoder. +// +// This is not a general purpose decoder. There are 4 types: +// +// 1. date +// offset += 4 +// 1/0 | extra padding | high u32 | low u32 +// if date[0] == 1, new Date(u64) else null +// +// 2. bool +// offset += 2 +// 1/0 | extra padding +// +// 3. u64 +// offset += 2 +// high u32 | low u32 +// +// 4. ?u64 converts a zero u64 value to JS null on Windows. +function createByteStruct(types) { + // types can be "date", "bool" or "u64". + // `?` prefix means optional on windows. + let offset = 0; + let str = + 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; + const typeEntries = ObjectEntries(types); + for (let i = 0; i < typeEntries.length; ++i) { + let { 0: name, 1: type } = typeEntries[i]; - const optional = type.startsWith("?"); - if (optional) type = type.slice(1); + const optional = type.startsWith("?"); + if (optional) type = type.slice(1); - if (type == "u64") { - if (!optional) { - str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; - } else { - str += `${name}: (unix ? (view[${offset}] + view[${ - offset + 1 - }] * 2**32) : (view[${offset}] + view[${ - offset + 1 - }] * 2**32) || null),`; - } - } else if (type == "date") { - str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ - offset + 2 - }] + view[${offset + 3}] * 2**32),`; - offset += 2; + if (type == "u64") { + if (!optional) { + str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; } else { - str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`; + str += `${name}: (unix ? (view[${offset}] + view[${ + offset + 1 + }] * 2**32) : (view[${offset}] + view[${ + offset + 1 + }] * 2**32) || null),`; } + } else if (type == "date") { + str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ + offset + 2 + }] + view[${offset + 3}] * 2**32),`; offset += 2; + } else { + str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`; } - str += "};"; - // ...so you don't like eval huh? don't worry, it only executes during snapshot :) - return [new Function("view", str), new Uint32Array(offset)]; + offset += 2; } + str += "};"; + // ...so you don't like eval huh? don't worry, it only executes during snapshot :) + return [new Function("view", str), new Uint32Array(offset)]; +} - const { 0: statStruct, 1: statBuf } = createByteStruct({ - isFile: "bool", - isDirectory: "bool", - isSymlink: "bool", - size: "u64", - mtime: "date", - atime: "date", - birthtime: "date", - dev: "?u64", - ino: "?u64", - mode: "?u64", - nlink: "?u64", - uid: "?u64", - gid: "?u64", - rdev: "?u64", - blksize: "?u64", - blocks: "?u64", +const { 0: statStruct, 1: statBuf } = createByteStruct({ + isFile: "bool", + isDirectory: "bool", + isSymlink: "bool", + size: "u64", + mtime: "date", + atime: "date", + birthtime: "date", + dev: "?u64", + ino: "?u64", + mode: "?u64", + nlink: "?u64", + uid: "?u64", + gid: "?u64", + rdev: "?u64", + blksize: "?u64", + blocks: "?u64", +}); + +function parseFileInfo(response) { + const unix = build.os === "darwin" || build.os === "linux"; + return { + isFile: response.isFile, + isDirectory: response.isDirectory, + isSymlink: response.isSymlink, + size: response.size, + mtime: response.mtimeSet !== null ? new Date(response.mtime) : null, + atime: response.atimeSet !== null ? new Date(response.atime) : null, + birthtime: response.birthtimeSet !== null + ? new Date(response.birthtime) + : null, + // Only non-null if on Unix + dev: unix ? response.dev : null, + ino: unix ? response.ino : null, + mode: unix ? response.mode : null, + nlink: unix ? response.nlink : null, + uid: unix ? response.uid : null, + gid: unix ? response.gid : null, + rdev: unix ? response.rdev : null, + blksize: unix ? response.blksize : null, + blocks: unix ? response.blocks : null, + }; +} + +function fstatSync(rid) { + ops.op_fstat_sync(rid, statBuf); + return statStruct(statBuf); +} + +async function fstat(rid) { + return parseFileInfo(await core.opAsync("op_fstat_async", rid)); +} + +async function lstat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: true, }); + return parseFileInfo(res); +} - function parseFileInfo(response) { - const unix = build.os === "darwin" || build.os === "linux"; - return { - isFile: response.isFile, - isDirectory: response.isDirectory, - isSymlink: response.isSymlink, - size: response.size, - mtime: response.mtimeSet !== null ? new Date(response.mtime) : null, - atime: response.atimeSet !== null ? new Date(response.atime) : null, - birthtime: response.birthtimeSet !== null - ? new Date(response.birthtime) - : null, - // Only non-null if on Unix - dev: unix ? response.dev : null, - ino: unix ? response.ino : null, - mode: unix ? response.mode : null, - nlink: unix ? response.nlink : null, - uid: unix ? response.uid : null, - gid: unix ? response.gid : null, - rdev: unix ? response.rdev : null, - blksize: unix ? response.blksize : null, - blocks: unix ? response.blocks : null, - }; +function lstatSync(path) { + ops.op_stat_sync( + pathFromURL(path), + true, + statBuf, + ); + return statStruct(statBuf); +} + +async function stat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: false, + }); + return parseFileInfo(res); +} + +function statSync(path) { + ops.op_stat_sync( + pathFromURL(path), + false, + statBuf, + ); + return statStruct(statBuf); +} + +function coerceLen(len) { + if (len == null || len < 0) { + return 0; } - function fstatSync(rid) { - ops.op_fstat_sync(rid, statBuf); - return statStruct(statBuf); - } + return len; +} - async function fstat(rid) { - return parseFileInfo(await core.opAsync("op_fstat_async", rid)); - } +function ftruncateSync(rid, len) { + ops.op_ftruncate_sync(rid, coerceLen(len)); +} - async function lstat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: true, - }); - return parseFileInfo(res); - } +async function ftruncate(rid, len) { + await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); +} - function lstatSync(path) { - ops.op_stat_sync( - pathFromURL(path), - true, - statBuf, - ); - return statStruct(statBuf); - } +function truncateSync(path, len) { + ops.op_truncate_sync(path, coerceLen(len)); +} - async function stat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: false, - }); - return parseFileInfo(res); - } +async function truncate(path, len) { + await core.opAsync("op_truncate_async", path, coerceLen(len)); +} - function statSync(path) { - ops.op_stat_sync( - pathFromURL(path), - false, - statBuf, - ); - return statStruct(statBuf); - } +function umask(mask) { + return ops.op_umask(mask); +} - function coerceLen(len) { - if (len == null || len < 0) { - return 0; - } +function linkSync(oldpath, newpath) { + ops.op_link_sync(oldpath, newpath); +} - return len; - } +async function link(oldpath, newpath) { + await core.opAsync("op_link_async", oldpath, newpath); +} - function ftruncateSync(rid, len) { - ops.op_ftruncate_sync(rid, coerceLen(len)); - } - - async function ftruncate(rid, len) { - await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); - } - - function truncateSync(path, len) { - ops.op_truncate_sync(path, coerceLen(len)); - } - - async function truncate(path, len) { - await core.opAsync("op_truncate_async", path, coerceLen(len)); - } - - function umask(mask) { - return ops.op_umask(mask); - } - - function linkSync(oldpath, newpath) { - ops.op_link_sync(oldpath, newpath); - } - - async function link(oldpath, newpath) { - await core.opAsync("op_link_async", oldpath, newpath); - } - - function toUnixTimeFromEpoch(value) { - if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { - const time = value.valueOf(); - const seconds = MathTrunc(time / 1e3); - const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; - - return [ - seconds, - nanoseconds, - ]; - } - - const seconds = value; - const nanoseconds = 0; +function toUnixTimeFromEpoch(value) { + if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + const time = value.valueOf(); + const seconds = MathTrunc(time / 1e3); + const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; return [ seconds, @@ -387,174 +377,182 @@ ]; } - function futimeSync( + const seconds = value; + const nanoseconds = 0; + + return [ + seconds, + nanoseconds, + ]; +} + +function futimeSync( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); +} + +async function futime( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_futime_async", rid, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); - } + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} - async function futime( - rid, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_futime_async", - rid, - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } +function utimeSync( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_utime_sync( + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} - function utimeSync( - path, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_utime_sync( - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } +async function utime( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_utime_async", + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} - async function utime( - path, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_utime_async", - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } +function symlinkSync( + oldpath, + newpath, + options, +) { + ops.op_symlink_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} - function symlinkSync( - oldpath, - newpath, - options, - ) { - ops.op_symlink_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); - } +async function symlink( + oldpath, + newpath, + options, +) { + await core.opAsync( + "op_symlink_async", + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} - async function symlink( - oldpath, - newpath, - options, - ) { - await core.opAsync( - "op_symlink_async", - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); - } +function fdatasyncSync(rid) { + ops.op_fdatasync_sync(rid); +} - function fdatasyncSync(rid) { - ops.op_fdatasync_sync(rid); - } +async function fdatasync(rid) { + await core.opAsync("op_fdatasync_async", rid); +} - async function fdatasync(rid) { - await core.opAsync("op_fdatasync_async", rid); - } +function fsyncSync(rid) { + ops.op_fsync_sync(rid); +} - function fsyncSync(rid) { - ops.op_fsync_sync(rid); - } +async function fsync(rid) { + await core.opAsync("op_fsync_async", rid); +} - async function fsync(rid) { - await core.opAsync("op_fsync_async", rid); - } +function flockSync(rid, exclusive) { + ops.op_flock_sync(rid, exclusive === true); +} - function flockSync(rid, exclusive) { - ops.op_flock_sync(rid, exclusive === true); - } +async function flock(rid, exclusive) { + await core.opAsync("op_flock_async", rid, exclusive === true); +} - async function flock(rid, exclusive) { - await core.opAsync("op_flock_async", rid, exclusive === true); - } +function funlockSync(rid) { + ops.op_funlock_sync(rid); +} - function funlockSync(rid) { - ops.op_funlock_sync(rid); - } +async function funlock(rid) { + await core.opAsync("op_funlock_async", rid); +} - async function funlock(rid) { - await core.opAsync("op_funlock_async", rid); - } - - window.__bootstrap.fs = { - cwd, - chdir, - chmodSync, - chmod, - chown, - chownSync, - copyFile, - copyFileSync, - makeTempFile, - makeTempDir, - makeTempFileSync, - makeTempDirSync, - mkdir, - mkdirSync, - readDir, - readDirSync, - readLinkSync, - readLink, - realPathSync, - realPath, - remove, - removeSync, - renameSync, - rename, - lstat, - lstatSync, - stat, - statSync, - ftruncate, - ftruncateSync, - truncate, - truncateSync, - umask, - link, - linkSync, - fstatSync, - fstat, - futime, - futimeSync, - utime, - utimeSync, - symlink, - symlinkSync, - fdatasync, - fdatasyncSync, - fsync, - fsyncSync, - flock, - flockSync, - funlock, - funlockSync, - }; -})(this); +export { + chdir, + chmod, + chmodSync, + chown, + chownSync, + copyFile, + copyFileSync, + cwd, + fdatasync, + fdatasyncSync, + flock, + flockSync, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + funlock, + funlockSync, + futime, + futimeSync, + link, + linkSync, + lstat, + lstatSync, + makeTempDir, + makeTempDirSync, + makeTempFile, + makeTempFileSync, + mkdir, + mkdirSync, + readDir, + readDirSync, + readLink, + readLinkSync, + realPath, + realPathSync, + remove, + removeSync, + rename, + renameSync, + stat, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + umask, + utime, + utimeSync, +}; diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js index 8069c3a34f..720545b40f 100644 --- a/runtime/js/30_os.js +++ b/runtime/js/30_os.js @@ -1,123 +1,122 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { Event } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { - Error, - SymbolFor, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { Event, EventTarget } from "internal:ext/web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + SymbolFor, +} = primordials; - const windowDispatchEvent = EventTarget.prototype.dispatchEvent.bind(window); +const windowDispatchEvent = EventTarget.prototype.dispatchEvent.bind( + globalThis, +); - function loadavg() { - return ops.op_loadavg(); - } +function loadavg() { + return ops.op_loadavg(); +} - function hostname() { - return ops.op_hostname(); - } +function hostname() { + return ops.op_hostname(); +} - function osRelease() { - return ops.op_os_release(); - } +function osRelease() { + return ops.op_os_release(); +} - function createOsUptime(opFn) { - return function osUptime() { - return opFn(); - }; - } - - function systemMemoryInfo() { - return ops.op_system_memory_info(); - } - - function networkInterfaces() { - return ops.op_network_interfaces(); - } - - function gid() { - return ops.op_gid(); - } - - function uid() { - return ops.op_uid(); - } - - // This is an internal only method used by the test harness to override the - // behavior of exit when the exit sanitizer is enabled. - let exitHandler = null; - function setExitHandler(fn) { - exitHandler = fn; - } - - function exit(code) { - // Set exit code first so unload event listeners can override it. - if (typeof code === "number") { - ops.op_set_exit_code(code); - } else { - code = 0; - } - - // Dispatches `unload` only when it's not dispatched yet. - if (!window[SymbolFor("isUnloadDispatched")]) { - // Invokes the `unload` hooks before exiting - // ref: https://github.com/denoland/deno/issues/3603 - windowDispatchEvent(new Event("unload")); - } - - if (exitHandler) { - exitHandler(code); - return; - } - - ops.op_exit(); - throw new Error("Code not reachable"); - } - - function setEnv(key, value) { - ops.op_set_env(key, value); - } - - function getEnv(key) { - return ops.op_get_env(key) ?? undefined; - } - - function deleteEnv(key) { - ops.op_delete_env(key); - } - - const env = { - get: getEnv, - toObject() { - return ops.op_env(); - }, - set: setEnv, - has(key) { - return getEnv(key) !== undefined; - }, - delete: deleteEnv, +function createOsUptime(opFn) { + return function osUptime() { + return opFn(); }; +} - function execPath() { - return ops.op_exec_path(); +function systemMemoryInfo() { + return ops.op_system_memory_info(); +} + +function networkInterfaces() { + return ops.op_network_interfaces(); +} + +function gid() { + return ops.op_gid(); +} + +function uid() { + return ops.op_uid(); +} + +// This is an internal only method used by the test harness to override the +// behavior of exit when the exit sanitizer is enabled. +let exitHandler = null; +function setExitHandler(fn) { + exitHandler = fn; +} + +function exit(code) { + // Set exit code first so unload event listeners can override it. + if (typeof code === "number") { + ops.op_set_exit_code(code); + } else { + code = 0; } - window.__bootstrap.os = { - env, - execPath, - exit, - gid, - hostname, - loadavg, - networkInterfaces, - osRelease, - createOsUptime, - setExitHandler, - systemMemoryInfo, - uid, - }; -})(this); + // Dispatches `unload` only when it's not dispatched yet. + if (!globalThis[SymbolFor("isUnloadDispatched")]) { + // Invokes the `unload` hooks before exiting + // ref: https://github.com/denoland/deno/issues/3603 + windowDispatchEvent(new Event("unload")); + } + + if (exitHandler) { + exitHandler(code); + return; + } + + ops.op_exit(); + throw new Error("Code not reachable"); +} + +function setEnv(key, value) { + ops.op_set_env(key, value); +} + +function getEnv(key) { + return ops.op_get_env(key) ?? undefined; +} + +function deleteEnv(key) { + ops.op_delete_env(key); +} + +const env = { + get: getEnv, + toObject() { + return ops.op_env(); + }, + set: setEnv, + has(key) { + return getEnv(key) !== undefined; + }, + delete: deleteEnv, +}; + +function execPath() { + return ops.op_exec_path(); +} + +export { + createOsUptime, + env, + execPath, + exit, + gid, + hostname, + loadavg, + networkInterfaces, + osRelease, + setExitHandler, + systemMemoryInfo, + uid, +}; diff --git a/runtime/js/40_diagnostics.js b/runtime/js/40_diagnostics.js index 6939b52042..669a54263c 100644 --- a/runtime/js/40_diagnostics.js +++ b/runtime/js/40_diagnostics.js @@ -3,22 +3,17 @@ // Diagnostic provides an abstraction for advice/errors received from a // compiler, which is strongly influenced by the format of TypeScript // diagnostics. -"use strict"; -((window) => { - const DiagnosticCategory = { - 0: "Warning", - 1: "Error", - 2: "Suggestion", - 3: "Message", +const DiagnosticCategory = { + 0: "Warning", + 1: "Error", + 2: "Suggestion", + 3: "Message", - Warning: 0, - Error: 1, - Suggestion: 2, - Message: 3, - }; + Warning: 0, + Error: 1, + Suggestion: 2, + Message: 3, +}; - window.__bootstrap.diagnostics = { - DiagnosticCategory, - }; -})(this); +export { DiagnosticCategory }; diff --git a/runtime/js/40_files.js b/runtime/js/40_files.js index 4471610ccd..98c569a095 100644 --- a/runtime/js/40_files.js +++ b/runtime/js/40_files.js @@ -1,293 +1,299 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { read, readSync, write, writeSync } = window.__bootstrap.io; - const { ftruncate, ftruncateSync, fstat, fstatSync } = window.__bootstrap.fs; - const { pathFromURL } = window.__bootstrap.util; - const { readableStreamForRid, writableStreamForRid } = - window.__bootstrap.streams; - const { - ArrayPrototypeFilter, - Error, - ObjectValues, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { read, readSync, write, writeSync } from "internal:runtime/js/12_io.js"; +import { + fstat, + fstatSync, + ftruncate, + ftruncateSync, +} from "internal:runtime/js/30_fs.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { + readableStreamForRid, + writableStreamForRid, +} from "internal:ext/web/06_streams.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + Error, + ObjectValues, +} = primordials; - function seekSync( - rid, - offset, - whence, - ) { - return ops.op_seek_sync({ rid, offset, whence }); - } +function seekSync( + rid, + offset, + whence, +) { + return ops.op_seek_sync({ rid, offset, whence }); +} - function seek( - rid, - offset, - whence, - ) { - return core.opAsync("op_seek_async", { rid, offset, whence }); - } +function seek( + rid, + offset, + whence, +) { + return core.opAsync("op_seek_async", { rid, offset, whence }); +} - function openSync( - path, +function openSync( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = ops.op_open_sync( + pathFromURL(path), options, - ) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = ops.op_open_sync( - pathFromURL(path), - options, - mode, - ); + mode, + ); - return new FsFile(rid); - } + return new FsFile(rid); +} - async function open( - path, +async function open( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = await core.opAsync( + "op_open_async", + pathFromURL(path), options, + mode, + ); + + return new FsFile(rid); +} + +function createSync(path) { + return openSync(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +function create(path) { + return open(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +class FsFile { + #rid = 0; + + #readable; + #writable; + + constructor(rid) { + this.#rid = rid; + } + + get rid() { + return this.#rid; + } + + write(p) { + return write(this.rid, p); + } + + writeSync(p) { + return writeSync(this.rid, p); + } + + truncate(len) { + return ftruncate(this.rid, len); + } + + truncateSync(len) { + return ftruncateSync(this.rid, len); + } + + read(p) { + return read(this.rid, p); + } + + readSync(p) { + return readSync(this.rid, p); + } + + seek(offset, whence) { + return seek(this.rid, offset, whence); + } + + seekSync(offset, whence) { + return seekSync(this.rid, offset, whence); + } + + stat() { + return fstat(this.rid); + } + + statSync() { + return fstatSync(this.rid); + } + + close() { + core.close(this.rid); + } + + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRid(this.rid); + } + return this.#readable; + } + + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); + } + return this.#writable; + } +} + +class Stdin { + #readable; + + constructor() { + } + + get rid() { + return 0; + } + + read(p) { + return read(this.rid, p); + } + + readSync(p) { + return readSync(this.rid, p); + } + + close() { + core.close(this.rid); + } + + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRid(this.rid); + } + return this.#readable; + } + + setRaw(mode, options = {}) { + const cbreak = !!(options.cbreak ?? false); + ops.op_stdin_set_raw(mode, cbreak); + } +} + +class Stdout { + #writable; + + constructor() { + } + + get rid() { + return 1; + } + + write(p) { + return write(this.rid, p); + } + + writeSync(p) { + return writeSync(this.rid, p); + } + + close() { + core.close(this.rid); + } + + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); + } + return this.#writable; + } +} + +class Stderr { + #writable; + + constructor() { + } + + get rid() { + return 2; + } + + write(p) { + return write(this.rid, p); + } + + writeSync(p) { + return writeSync(this.rid, p); + } + + close() { + core.close(this.rid); + } + + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); + } + return this.#writable; + } +} + +const stdin = new Stdin(); +const stdout = new Stdout(); +const stderr = new Stderr(); + +function checkOpenOptions(options) { + if ( + ArrayPrototypeFilter( + ObjectValues(options), + (val) => val === true, + ).length === 0 ) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = await core.opAsync( - "op_open_async", - pathFromURL(path), - options, - mode, + throw new Error("OpenOptions requires at least one option to be true"); + } + + if (options.truncate && !options.write) { + throw new Error("'truncate' option requires 'write' option"); + } + + const createOrCreateNewWithoutWriteOrAppend = + (options.create || options.createNew) && + !(options.write || options.append); + + if (createOrCreateNewWithoutWriteOrAppend) { + throw new Error( + "'create' or 'createNew' options require 'write' or 'append' option", ); - - return new FsFile(rid); } +} - function createSync(path) { - return openSync(path, { - read: true, - write: true, - truncate: true, - create: true, - }); - } - - function create(path) { - return open(path, { - read: true, - write: true, - truncate: true, - create: true, - }); - } - - class FsFile { - #rid = 0; - - #readable; - #writable; - - constructor(rid) { - this.#rid = rid; - } - - get rid() { - return this.#rid; - } - - write(p) { - return write(this.rid, p); - } - - writeSync(p) { - return writeSync(this.rid, p); - } - - truncate(len) { - return ftruncate(this.rid, len); - } - - truncateSync(len) { - return ftruncateSync(this.rid, len); - } - - read(p) { - return read(this.rid, p); - } - - readSync(p) { - return readSync(this.rid, p); - } - - seek(offset, whence) { - return seek(this.rid, offset, whence); - } - - seekSync(offset, whence) { - return seekSync(this.rid, offset, whence); - } - - stat() { - return fstat(this.rid); - } - - statSync() { - return fstatSync(this.rid); - } - - close() { - core.close(this.rid); - } - - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRid(this.rid); - } - return this.#readable; - } - - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } - } - - class Stdin { - #readable; - - constructor() { - } - - get rid() { - return 0; - } - - read(p) { - return read(this.rid, p); - } - - readSync(p) { - return readSync(this.rid, p); - } - - close() { - core.close(this.rid); - } - - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRid(this.rid); - } - return this.#readable; - } - - setRaw(mode, options = {}) { - const cbreak = !!(options.cbreak ?? false); - ops.op_stdin_set_raw(mode, cbreak); - } - } - - class Stdout { - #writable; - - constructor() { - } - - get rid() { - return 1; - } - - write(p) { - return write(this.rid, p); - } - - writeSync(p) { - return writeSync(this.rid, p); - } - - close() { - core.close(this.rid); - } - - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } - } - - class Stderr { - #writable; - - constructor() { - } - - get rid() { - return 2; - } - - write(p) { - return write(this.rid, p); - } - - writeSync(p) { - return writeSync(this.rid, p); - } - - close() { - core.close(this.rid); - } - - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } - } - - const stdin = new Stdin(); - const stdout = new Stdout(); - const stderr = new Stderr(); - - function checkOpenOptions(options) { - if ( - ArrayPrototypeFilter( - ObjectValues(options), - (val) => val === true, - ).length === 0 - ) { - throw new Error("OpenOptions requires at least one option to be true"); - } - - if (options.truncate && !options.write) { - throw new Error("'truncate' option requires 'write' option"); - } - - const createOrCreateNewWithoutWriteOrAppend = - (options.create || options.createNew) && - !(options.write || options.append); - - if (createOrCreateNewWithoutWriteOrAppend) { - throw new Error( - "'create' or 'createNew' options require 'write' or 'append' option", - ); - } - } - - window.__bootstrap.files = { - stdin, - stdout, - stderr, - File: FsFile, - FsFile, - create, - createSync, - open, - openSync, - seek, - seekSync, - }; -})(this); +const File = FsFile; +export { + create, + createSync, + File, + FsFile, + open, + openSync, + seek, + seekSync, + stderr, + stdin, + stdout, +}; diff --git a/runtime/js/40_fs_events.js b/runtime/js/40_fs_events.js index b410e22990..4c2f5fc9a5 100644 --- a/runtime/js/40_fs_events.js +++ b/runtime/js/40_fs_events.js @@ -1,70 +1,63 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { BadResourcePrototype, InterruptedPrototype } = core; - const { - ArrayIsArray, - ObjectPrototypeIsPrototypeOf, - PromiseResolve, - SymbolAsyncIterator, - } = window.__bootstrap.primordials; - class FsWatcher { - #rid = 0; +const core = globalThis.Deno.core; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ObjectPrototypeIsPrototypeOf, + PromiseResolve, + SymbolAsyncIterator, +} = primordials; +class FsWatcher { + #rid = 0; - constructor(paths, options) { - const { recursive } = options; - this.#rid = ops.op_fs_events_open({ recursive, paths }); - } + constructor(paths, options) { + const { recursive } = options; + this.#rid = ops.op_fs_events_open({ recursive, paths }); + } - get rid() { - return this.#rid; - } + get rid() { + return this.#rid; + } - async next() { - try { - const value = await core.opAsync("op_fs_events_poll", this.rid); - return value - ? { value, done: false } - : { value: undefined, done: true }; - } catch (error) { - if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) { - return { value: undefined, done: true }; - } else if ( - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) - ) { - return { value: undefined, done: true }; - } - throw error; + async next() { + try { + const value = await core.opAsync("op_fs_events_poll", this.rid); + return value ? { value, done: false } : { value: undefined, done: true }; + } catch (error) { + if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) { + return { value: undefined, done: true }; + } else if ( + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) + ) { + return { value: undefined, done: true }; } - } - - // TODO(kt3k): This is deprecated. Will be removed in v2.0. - // See https://github.com/denoland/deno/issues/10577 for details - return(value) { - core.close(this.rid); - return PromiseResolve({ value, done: true }); - } - - close() { - core.close(this.rid); - } - - [SymbolAsyncIterator]() { - return this; + throw error; } } - function watchFs( - paths, - options = { recursive: true }, - ) { - return new FsWatcher(ArrayIsArray(paths) ? paths : [paths], options); + // TODO(kt3k): This is deprecated. Will be removed in v2.0. + // See https://github.com/denoland/deno/issues/10577 for details + return(value) { + core.close(this.rid); + return PromiseResolve({ value, done: true }); } - window.__bootstrap.fsEvents = { - watchFs, - }; -})(this); + close() { + core.close(this.rid); + } + + [SymbolAsyncIterator]() { + return this; + } +} + +function watchFs( + paths, + options = { recursive: true }, +) { + return new FsWatcher(ArrayIsArray(paths) ? paths : [paths], options); +} + +export { watchFs }; diff --git a/runtime/js/40_http.js b/runtime/js/40_http.js index 22288b5d54..afacf7b278 100644 --- a/runtime/js/40_http.js +++ b/runtime/js/40_http.js @@ -1,15 +1,11 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import { HttpConn } from "internal:ext/http/01_http.js"; -((window) => { - const core = window.__bootstrap.core; - const ops = core.ops; - const { HttpConn } = window.__bootstrap.http; +function serveHttp(conn) { + const rid = ops.op_http_start(conn.rid); + return new HttpConn(rid, conn.remoteAddr, conn.localAddr); +} - function serveHttp(conn) { - const rid = ops.op_http_start(conn.rid); - return new HttpConn(rid, conn.remoteAddr, conn.localAddr); - } - - window.__bootstrap.http.serveHttp = serveHttp; -})(globalThis); +export { serveHttp }; diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js index 5ad24c0943..87e898b56a 100644 --- a/runtime/js/40_process.js +++ b/runtime/js/40_process.js @@ -1,139 +1,133 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { FsFile } = window.__bootstrap.files; - const { readAll } = window.__bootstrap.io; - const { pathFromURL } = window.__bootstrap.util; - const { assert } = window.__bootstrap.infra; - const { - ArrayPrototypeMap, - ArrayPrototypeSlice, - TypeError, - ObjectEntries, - SafeArrayIterator, - String, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { FsFile } from "internal:runtime/js/40_files.js"; +import { readAll } from "internal:runtime/js/12_io.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { assert } from "internal:ext/web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeSlice, + TypeError, + ObjectEntries, + SafeArrayIterator, + String, +} = primordials; - function opKill(pid, signo, apiName) { - ops.op_kill(pid, signo, apiName); +function opKill(pid, signo, apiName) { + ops.op_kill(pid, signo, apiName); +} + +function kill(pid, signo = "SIGTERM") { + opKill(pid, signo, "Deno.kill()"); +} + +function opRunStatus(rid) { + return core.opAsync("op_run_status", rid); +} + +function opRun(request) { + assert(request.cmd.length > 0); + return ops.op_run(request); +} + +async function runStatus(rid) { + const res = await opRunStatus(rid); + + if (res.gotSignal) { + const signal = res.exitSignal; + return { success: false, code: 128 + signal, signal }; + } else if (res.exitCode != 0) { + return { success: false, code: res.exitCode }; + } else { + return { success: true, code: 0 }; } +} - function kill(pid, signo = "SIGTERM") { - opKill(pid, signo, "Deno.kill()"); - } +class Process { + constructor(res) { + this.rid = res.rid; + this.pid = res.pid; - function opRunStatus(rid) { - return core.opAsync("op_run_status", rid); - } + if (res.stdinRid && res.stdinRid > 0) { + this.stdin = new FsFile(res.stdinRid); + } - function opRun(request) { - assert(request.cmd.length > 0); - return ops.op_run(request); - } + if (res.stdoutRid && res.stdoutRid > 0) { + this.stdout = new FsFile(res.stdoutRid); + } - async function runStatus(rid) { - const res = await opRunStatus(rid); - - if (res.gotSignal) { - const signal = res.exitSignal; - return { success: false, code: 128 + signal, signal }; - } else if (res.exitCode != 0) { - return { success: false, code: res.exitCode }; - } else { - return { success: true, code: 0 }; + if (res.stderrRid && res.stderrRid > 0) { + this.stderr = new FsFile(res.stderrRid); } } - class Process { - constructor(res) { - this.rid = res.rid; - this.pid = res.pid; + status() { + return runStatus(this.rid); + } - if (res.stdinRid && res.stdinRid > 0) { - this.stdin = new FsFile(res.stdinRid); - } - - if (res.stdoutRid && res.stdoutRid > 0) { - this.stdout = new FsFile(res.stdoutRid); - } - - if (res.stderrRid && res.stderrRid > 0) { - this.stderr = new FsFile(res.stderrRid); - } + async output() { + if (!this.stdout) { + throw new TypeError("stdout was not piped"); } - - status() { - return runStatus(this.rid); - } - - async output() { - if (!this.stdout) { - throw new TypeError("stdout was not piped"); - } - try { - return await readAll(this.stdout); - } finally { - this.stdout.close(); - } - } - - async stderrOutput() { - if (!this.stderr) { - throw new TypeError("stderr was not piped"); - } - try { - return await readAll(this.stderr); - } finally { - this.stderr.close(); - } - } - - close() { - core.close(this.rid); - } - - kill(signo = "SIGTERM") { - opKill(this.pid, signo, "Deno.Process.kill()"); + try { + return await readAll(this.stdout); + } finally { + this.stdout.close(); } } - function run({ - cmd, - cwd = undefined, - clearEnv = false, - env = {}, - gid = undefined, - uid = undefined, - stdout = "inherit", - stderr = "inherit", - stdin = "inherit", - }) { - if (cmd[0] != null) { - cmd = [ - pathFromURL(cmd[0]), - ...new SafeArrayIterator(ArrayPrototypeSlice(cmd, 1)), - ]; + async stderrOutput() { + if (!this.stderr) { + throw new TypeError("stderr was not piped"); + } + try { + return await readAll(this.stderr); + } finally { + this.stderr.close(); } - const res = opRun({ - cmd: ArrayPrototypeMap(cmd, String), - cwd, - clearEnv, - env: ObjectEntries(env), - gid, - uid, - stdin, - stdout, - stderr, - }); - return new Process(res); } - window.__bootstrap.process = { - run, - Process, - kill, - }; -})(this); + close() { + core.close(this.rid); + } + + kill(signo = "SIGTERM") { + opKill(this.pid, signo, "Deno.Process.kill()"); + } +} + +function run({ + cmd, + cwd = undefined, + clearEnv = false, + env = {}, + gid = undefined, + uid = undefined, + stdout = "inherit", + stderr = "inherit", + stdin = "inherit", +}) { + if (cmd[0] != null) { + cmd = [ + pathFromURL(cmd[0]), + ...new SafeArrayIterator(ArrayPrototypeSlice(cmd, 1)), + ]; + } + const res = opRun({ + cmd: ArrayPrototypeMap(cmd, String), + cwd, + clearEnv, + env: ObjectEntries(env), + gid, + uid, + stdin, + stdout, + stderr, + }); + return new Process(res); +} + +export { kill, Process, run }; diff --git a/runtime/js/40_read_file.js b/runtime/js/40_read_file.js index 7c28989039..b7f0a70124 100644 --- a/runtime/js/40_read_file.js +++ b/runtime/js/40_read_file.js @@ -1,78 +1,70 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { pathFromURL } = window.__bootstrap.util; - const { abortSignal } = window.__bootstrap; +const core = globalThis.Deno.core; +const ops = core.ops; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; - function readFileSync(path) { - return ops.op_readfile_sync(pathFromURL(path)); +function readFileSync(path) { + return ops.op_readfile_sync(pathFromURL(path)); +} + +async function readFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); } - async function readFile(path, options) { - let cancelRid; - let abortHandler; + try { + const read = await core.opAsync( + "op_readfile_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - - try { - const read = await core.opAsync( - "op_readfile_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } } } +} - function readTextFileSync(path) { - return ops.op_readfile_text_sync(pathFromURL(path)); +function readTextFileSync(path) { + return ops.op_readfile_text_sync(pathFromURL(path)); +} + +async function readTextFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); } - async function readTextFile(path, options) { - let cancelRid; - let abortHandler; + try { + const read = await core.opAsync( + "op_readfile_text_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - - try { - const read = await core.opAsync( - "op_readfile_text_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } } } +} - window.__bootstrap.readFile = { - readFile, - readFileSync, - readTextFileSync, - readTextFile, - }; -})(this); +export { readFile, readFileSync, readTextFile, readTextFileSync }; diff --git a/runtime/js/40_signals.js b/runtime/js/40_signals.js index ff1502a555..4ae3101515 100644 --- a/runtime/js/40_signals.js +++ b/runtime/js/40_signals.js @@ -1,88 +1,83 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - SafeSetIterator, - Set, - SetPrototypeDelete, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeSetIterator, + Set, + SetPrototypeDelete, + SymbolFor, + TypeError, +} = primordials; - function bindSignal(signo) { - return ops.op_signal_bind(signo); +function bindSignal(signo) { + return ops.op_signal_bind(signo); +} + +function pollSignal(rid) { + const promise = core.opAsync("op_signal_poll", rid); + core.unrefOp(promise[SymbolFor("Deno.core.internalPromiseId")]); + return promise; +} + +function unbindSignal(rid) { + ops.op_signal_unbind(rid); +} + +// Stores signal listeners and resource data. This has type of +// `Record void> }` +const signalData = {}; + +/** Gets the signal handlers and resource data of the given signal */ +function getSignalData(signo) { + return signalData[signo] ?? + (signalData[signo] = { rid: undefined, listeners: new Set() }); +} + +function checkSignalListenerType(listener) { + if (typeof listener !== "function") { + throw new TypeError( + `Signal listener must be a function. "${typeof listener}" is given.`, + ); } +} - function pollSignal(rid) { - const promise = core.opAsync("op_signal_poll", rid); - core.unrefOp(promise[SymbolFor("Deno.core.internalPromiseId")]); - return promise; +function addSignalListener(signo, listener) { + checkSignalListenerType(listener); + + const sigData = getSignalData(signo); + sigData.listeners.add(listener); + + if (!sigData.rid) { + // If signal resource doesn't exist, create it. + // The program starts listening to the signal + sigData.rid = bindSignal(signo); + loop(sigData); } +} - function unbindSignal(rid) { - ops.op_signal_unbind(rid); +function removeSignalListener(signo, listener) { + checkSignalListenerType(listener); + + const sigData = getSignalData(signo); + SetPrototypeDelete(sigData.listeners, listener); + + if (sigData.listeners.size === 0 && sigData.rid) { + unbindSignal(sigData.rid); + sigData.rid = undefined; } +} - // Stores signal listeners and resource data. This has type of - // `Record void> }` - const signalData = {}; - - /** Gets the signal handlers and resource data of the given signal */ - function getSignalData(signo) { - return signalData[signo] ?? - (signalData[signo] = { rid: undefined, listeners: new Set() }); - } - - function checkSignalListenerType(listener) { - if (typeof listener !== "function") { - throw new TypeError( - `Signal listener must be a function. "${typeof listener}" is given.`, - ); +async function loop(sigData) { + while (sigData.rid) { + if (await pollSignal(sigData.rid)) { + return; + } + for (const listener of new SafeSetIterator(sigData.listeners)) { + listener(); } } +} - function addSignalListener(signo, listener) { - checkSignalListenerType(listener); - - const sigData = getSignalData(signo); - sigData.listeners.add(listener); - - if (!sigData.rid) { - // If signal resource doesn't exist, create it. - // The program starts listening to the signal - sigData.rid = bindSignal(signo); - loop(sigData); - } - } - - function removeSignalListener(signo, listener) { - checkSignalListenerType(listener); - - const sigData = getSignalData(signo); - SetPrototypeDelete(sigData.listeners, listener); - - if (sigData.listeners.size === 0 && sigData.rid) { - unbindSignal(sigData.rid); - sigData.rid = undefined; - } - } - - async function loop(sigData) { - while (sigData.rid) { - if (await pollSignal(sigData.rid)) { - return; - } - for (const listener of new SafeSetIterator(sigData.listeners)) { - listener(); - } - } - } - - window.__bootstrap.signals = { - addSignalListener, - removeSignalListener, - }; -})(this); +export { addSignalListener, removeSignalListener }; diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index ecbab52ad9..dfccec5d7f 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -1,34 +1,239 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { pathFromURL } = window.__bootstrap.util; - const { illegalConstructorKey } = window.__bootstrap.webUtil; - const { add, remove } = window.__bootstrap.abortSignal; - const { - ArrayPrototypeMap, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - String, - TypeError, - PromisePrototypeThen, - SafePromiseAll, - SymbolFor, - } = window.__bootstrap.primordials; - const { - readableStreamCollectIntoUint8Array, - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - ReadableStreamPrototype, - writableStreamForRid, - } = window.__bootstrap.streams; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { add, remove } from "internal:ext/web/03_abort_signal.js"; +const { + ArrayPrototypeMap, + ObjectEntries, + ObjectPrototypeIsPrototypeOf, + String, + TypeError, + PromisePrototypeThen, + SafePromiseAll, + SymbolFor, + Symbol, +} = primordials; +import { + readableStreamCollectIntoUint8Array, + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + ReadableStreamPrototype, + writableStreamForRid, +} from "internal:ext/web/06_streams.js"; - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); +const illegalConstructorKey = Symbol("illegalConstructorKey"); - function spawnChildInner(opFn, command, apiName, { +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); + +function spawnChildInner(opFn, command, apiName, { + args = [], + cwd = undefined, + clearEnv = false, + env = {}, + uid = undefined, + gid = undefined, + stdin = "null", + stdout = "piped", + stderr = "piped", + signal = undefined, + windowsRawArguments = false, +} = {}) { + const child = opFn({ + cmd: pathFromURL(command), + args: ArrayPrototypeMap(args, String), + cwd: pathFromURL(cwd), + clearEnv, + env: ObjectEntries(env), + uid, + gid, + stdin, + stdout, + stderr, + windowsRawArguments, + }, apiName); + return new Child(illegalConstructorKey, { + ...child, + signal, + }); +} + +function createSpawnChild(opFn) { + return function spawnChild(command, options = {}) { + return spawnChildInner(opFn, command, "Deno.Command().spawn()", options); + }; +} + +function collectOutput(readableStream) { + if ( + !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream)) + ) { + return null; + } + + return readableStreamCollectIntoUint8Array(readableStream); +} + +class Child { + #rid; + #waitPromiseId; + #unrefed = false; + + #pid; + get pid() { + return this.#pid; + } + + #stdin = null; + get stdin() { + if (this.#stdin == null) { + throw new TypeError("stdin is not piped"); + } + return this.#stdin; + } + + #stdoutPromiseId; + #stdoutRid; + #stdout = null; + get stdout() { + if (this.#stdout == null) { + throw new TypeError("stdout is not piped"); + } + return this.#stdout; + } + + #stderrPromiseId; + #stderrRid; + #stderr = null; + get stderr() { + if (this.#stderr == null) { + throw new TypeError("stderr is not piped"); + } + return this.#stderr; + } + + constructor(key = null, { + signal, + rid, + pid, + stdinRid, + stdoutRid, + stderrRid, + } = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + + this.#rid = rid; + this.#pid = pid; + + if (stdinRid !== null) { + this.#stdin = writableStreamForRid(stdinRid); + } + + if (stdoutRid !== null) { + this.#stdoutRid = stdoutRid; + this.#stdout = readableStreamForRidUnrefable(stdoutRid); + } + + if (stderrRid !== null) { + this.#stderrRid = stderrRid; + this.#stderr = readableStreamForRidUnrefable(stderrRid); + } + + const onAbort = () => this.kill("SIGTERM"); + signal?.[add](onAbort); + + const waitPromise = core.opAsync("op_spawn_wait", this.#rid); + this.#waitPromiseId = waitPromise[promiseIdSymbol]; + this.#status = PromisePrototypeThen(waitPromise, (res) => { + this.#rid = null; + signal?.[remove](onAbort); + return res; + }); + } + + #status; + get status() { + return this.#status; + } + + async output() { + if (this.#stdout?.locked) { + throw new TypeError( + "Can't collect output because stdout is locked", + ); + } + if (this.#stderr?.locked) { + throw new TypeError( + "Can't collect output because stderr is locked", + ); + } + + const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([ + this.#status, + collectOutput(this.#stdout), + collectOutput(this.#stderr), + ]); + + return { + success: status.success, + code: status.code, + signal: status.signal, + get stdout() { + if (stdout == null) { + throw new TypeError("stdout is not piped"); + } + return stdout; + }, + get stderr() { + if (stderr == null) { + throw new TypeError("stderr is not piped"); + } + return stderr; + }, + }; + } + + kill(signo = "SIGTERM") { + if (this.#rid === null) { + throw new TypeError("Child process has already terminated."); + } + ops.op_kill(this.#pid, signo, "Deno.Child.kill()"); + } + + ref() { + this.#unrefed = false; + core.refOp(this.#waitPromiseId); + if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout); + if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr); + } + + unref() { + this.#unrefed = true; + core.unrefOp(this.#waitPromiseId); + if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout); + if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr); + } +} + +function createSpawn(opFn) { + return function spawn(command, options) { + if (options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", + ); + } + return spawnChildInner(opFn, command, "Deno.Command().output()", options) + .output(); + }; +} + +function createSpawnSync(opFn) { + return function spawnSync(command, { args = [], cwd = undefined, clearEnv = false, @@ -38,10 +243,14 @@ stdin = "null", stdout = "piped", stderr = "piped", - signal = undefined, windowsRawArguments = false, } = {}) { - const child = opFn({ + if (stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", + ); + } + const result = opFn({ cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), @@ -53,281 +262,74 @@ stdout, stderr, windowsRawArguments, - }, apiName); - return new Child(illegalConstructorKey, { - ...child, - signal, }); - } - - function createSpawnChild(opFn) { - return function spawnChild(command, options = {}) { - return spawnChildInner(opFn, command, "Deno.Command().spawn()", options); - }; - } - - function collectOutput(readableStream) { - if ( - !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream)) - ) { - return null; - } - - return readableStreamCollectIntoUint8Array(readableStream); - } - - class Child { - #rid; - #waitPromiseId; - #unrefed = false; - - #pid; - get pid() { - return this.#pid; - } - - #stdin = null; - get stdin() { - if (this.#stdin == null) { - throw new TypeError("stdin is not piped"); - } - return this.#stdin; - } - - #stdoutPromiseId; - #stdoutRid; - #stdout = null; - get stdout() { - if (this.#stdout == null) { - throw new TypeError("stdout is not piped"); - } - return this.#stdout; - } - - #stderrPromiseId; - #stderrRid; - #stderr = null; - get stderr() { - if (this.#stderr == null) { - throw new TypeError("stderr is not piped"); - } - return this.#stderr; - } - - constructor(key = null, { - signal, - rid, - pid, - stdinRid, - stdoutRid, - stderrRid, - } = null) { - if (key !== illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - - this.#rid = rid; - this.#pid = pid; - - if (stdinRid !== null) { - this.#stdin = writableStreamForRid(stdinRid); - } - - if (stdoutRid !== null) { - this.#stdoutRid = stdoutRid; - this.#stdout = readableStreamForRidUnrefable(stdoutRid); - } - - if (stderrRid !== null) { - this.#stderrRid = stderrRid; - this.#stderr = readableStreamForRidUnrefable(stderrRid); - } - - const onAbort = () => this.kill("SIGTERM"); - signal?.[add](onAbort); - - const waitPromise = core.opAsync("op_spawn_wait", this.#rid); - this.#waitPromiseId = waitPromise[promiseIdSymbol]; - this.#status = PromisePrototypeThen(waitPromise, (res) => { - this.#rid = null; - signal?.[remove](onAbort); - return res; - }); - } - - #status; - get status() { - return this.#status; - } - - async output() { - if (this.#stdout?.locked) { - throw new TypeError( - "Can't collect output because stdout is locked", - ); - } - if (this.#stderr?.locked) { - throw new TypeError( - "Can't collect output because stderr is locked", - ); - } - - const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([ - this.#status, - collectOutput(this.#stdout), - collectOutput(this.#stderr), - ]); - - return { - success: status.success, - code: status.code, - signal: status.signal, - get stdout() { - if (stdout == null) { - throw new TypeError("stdout is not piped"); - } - return stdout; - }, - get stderr() { - if (stderr == null) { - throw new TypeError("stderr is not piped"); - } - return stderr; - }, - }; - } - - kill(signo = "SIGTERM") { - if (this.#rid === null) { - throw new TypeError("Child process has already terminated."); - } - ops.op_kill(this.#pid, signo, "Deno.Child.kill()"); - } - - ref() { - this.#unrefed = false; - core.refOp(this.#waitPromiseId); - if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout); - if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr); - } - - unref() { - this.#unrefed = true; - core.unrefOp(this.#waitPromiseId); - if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout); - if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr); - } - } - - function createSpawn(opFn) { - return function spawn(command, options) { - if (options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", - ); - } - return spawnChildInner(opFn, command, "Deno.Command().output()", options) - .output(); - }; - } - - function createSpawnSync(opFn) { - return function spawnSync(command, { - args = [], - cwd = undefined, - clearEnv = false, - env = {}, - uid = undefined, - gid = undefined, - stdin = "null", - stdout = "piped", - stderr = "piped", - windowsRawArguments = false, - } = {}) { - if (stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", - ); - } - const result = opFn({ - cmd: pathFromURL(command), - args: ArrayPrototypeMap(args, String), - cwd: pathFromURL(cwd), - clearEnv, - env: ObjectEntries(env), - uid, - gid, - stdin, - stdout, - stderr, - windowsRawArguments, - }); - return { - success: result.status.success, - code: result.status.code, - signal: result.status.signal, - get stdout() { - if (result.stdout == null) { - throw new TypeError("stdout is not piped"); - } - return result.stdout; - }, - get stderr() { - if (result.stderr == null) { - throw new TypeError("stderr is not piped"); - } - return result.stderr; - }, - }; - }; - } - - function createCommand(spawn, spawnSync, spawnChild) { - return class Command { - #command; - #options; - - constructor(command, options) { - this.#command = command; - this.#options = options; - } - - output() { - if (this.#options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); + return { + success: result.status.success, + code: result.status.code, + signal: result.status.signal, + get stdout() { + if (result.stdout == null) { + throw new TypeError("stdout is not piped"); } - return spawn(this.#command, this.#options); - } - - outputSync() { - if (this.#options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); + return result.stdout; + }, + get stderr() { + if (result.stderr == null) { + throw new TypeError("stderr is not piped"); } - return spawnSync(this.#command, this.#options); - } - - spawn() { - const options = { - ...(this.#options ?? {}), - stdout: this.#options?.stdout ?? "inherit", - stderr: this.#options?.stderr ?? "inherit", - stdin: this.#options?.stdin ?? "inherit", - }; - return spawnChild(this.#command, options); - } + return result.stderr; + }, }; - } - - window.__bootstrap.spawn = { - Child, - ChildProcess: Child, - createCommand, - createSpawn, - createSpawnChild, - createSpawnSync, }; -})(this); +} + +function createCommand(spawn, spawnSync, spawnChild) { + return class Command { + #command; + #options; + + constructor(command, options) { + this.#command = command; + this.#options = options; + } + + output() { + if (this.#options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); + } + return spawn(this.#command, this.#options); + } + + outputSync() { + if (this.#options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); + } + return spawnSync(this.#command, this.#options); + } + + spawn() { + const options = { + ...(this.#options ?? {}), + stdout: this.#options?.stdout ?? "inherit", + stderr: this.#options?.stderr ?? "inherit", + stdin: this.#options?.stdin ?? "inherit", + }; + return spawnChild(this.#command, options); + } + }; +} + +const ChildProcess = Child; + +export { + Child, + ChildProcess, + createCommand, + createSpawn, + createSpawnChild, + createSpawnSync, +}; diff --git a/runtime/js/40_tty.js b/runtime/js/40_tty.js index 3263dc814f..859f492757 100644 --- a/runtime/js/40_tty.js +++ b/runtime/js/40_tty.js @@ -1,28 +1,23 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Uint32Array, + Uint8Array, +} = primordials; -((window) => { - const { - Uint32Array, - Uint8Array, - } = window.__bootstrap.primordials; - const core = window.Deno.core; - const ops = core.ops; +const size = new Uint32Array(2); - const size = new Uint32Array(2); - function consoleSize() { - ops.op_console_size(size); - return { columns: size[0], rows: size[1] }; - } +function consoleSize() { + ops.op_console_size(size); + return { columns: size[0], rows: size[1] }; +} - const isattyBuffer = new Uint8Array(1); - function isatty(rid) { - ops.op_isatty(rid, isattyBuffer); - return !!isattyBuffer[0]; - } +const isattyBuffer = new Uint8Array(1); +function isatty(rid) { + ops.op_isatty(rid, isattyBuffer); + return !!isattyBuffer[0]; +} - window.__bootstrap.tty = { - consoleSize, - isatty, - }; -})(this); +export { consoleSize, isatty }; diff --git a/runtime/js/40_write_file.js b/runtime/js/40_write_file.js index a32ef441bc..a9c870ca37 100644 --- a/runtime/js/40_write_file.js +++ b/runtime/js/40_write_file.js @@ -1,107 +1,100 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.__bootstrap.core; - const ops = core.ops; - const { abortSignal } = window.__bootstrap; - const { pathFromURL } = window.__bootstrap.util; - const { open } = window.__bootstrap.files; - const { ReadableStreamPrototype } = window.__bootstrap.streams; - const { ObjectPrototypeIsPrototypeOf } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { open } from "internal:runtime/js/40_files.js"; +import { ReadableStreamPrototype } from "internal:ext/web/06_streams.js"; +const { ObjectPrototypeIsPrototypeOf } = primordials; - function writeFileSync( - path, +function writeFileSync( + path, + data, + options = {}, +) { + options.signal?.throwIfAborted(); + ops.op_write_file_sync( + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, data, - options = {}, - ) { - options.signal?.throwIfAborted(); - ops.op_write_file_sync( - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - ); + ); +} + +async function writeFile( + path, + data, + options = {}, +) { + let cancelRid; + let abortHandler; + if (options.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); } - - async function writeFile( - path, - data, - options = {}, - ) { - let cancelRid; - let abortHandler; - if (options.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - try { - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - const file = await open(path, { - mode: options.mode, - append: options.append ?? false, - create: options.create ?? true, - createNew: options.createNew ?? false, - write: true, - }); - await data.pipeTo(file.writable, { - signal: options.signal, - }); - } else { - await core.opAsync( - "op_write_file_async", - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - cancelRid, - ); - } - } finally { - if (options.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } - } - } - - function writeTextFileSync( - path, - data, - options = {}, - ) { - const encoder = new TextEncoder(); - return writeFileSync(path, encoder.encode(data), options); - } - - function writeTextFile( - path, - data, - options = {}, - ) { + try { if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - return writeFile( - path, - data.pipeThrough(new TextEncoderStream()), - options, - ); + const file = await open(path, { + mode: options.mode, + append: options.append ?? false, + create: options.create ?? true, + createNew: options.createNew ?? false, + write: true, + }); + await data.pipeTo(file.writable, { + signal: options.signal, + }); } else { - const encoder = new TextEncoder(); - return writeFile(path, encoder.encode(data), options); + await core.opAsync( + "op_write_file_async", + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, + data, + cancelRid, + ); + } + } finally { + if (options.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); } } +} - window.__bootstrap.writeFile = { - writeTextFile, - writeTextFileSync, - writeFile, - writeFileSync, - }; -})(this); +function writeTextFileSync( + path, + data, + options = {}, +) { + const encoder = new TextEncoder(); + return writeFileSync(path, encoder.encode(data), options); +} + +function writeTextFile( + path, + data, + options = {}, +) { + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { + return writeFile( + path, + data.pipeThrough(new TextEncoderStream()), + options, + ); + } else { + const encoder = new TextEncoder(); + return writeFile(path, encoder.encode(data), options); + } +} + +export { writeFile, writeFileSync, writeTextFile, writeTextFileSync }; diff --git a/runtime/js/41_prompt.js b/runtime/js/41_prompt.js index 1d5acc028f..441db9a2f3 100644 --- a/runtime/js/41_prompt.js +++ b/runtime/js/41_prompt.js @@ -1,82 +1,76 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { stdin } = window.__bootstrap.files; - const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } = - window.__bootstrap.primordials; - const { isatty } = window.__bootstrap.tty; - const LF = StringPrototypeCharCodeAt("\n", 0); - const CR = StringPrototypeCharCodeAt("\r", 0); - const core = window.Deno.core; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +import { isatty } from "internal:runtime/js/40_tty.js"; +import { stdin } from "internal:runtime/js/40_files.js"; +const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } = + primordials; +const LF = StringPrototypeCharCodeAt("\n", 0); +const CR = StringPrototypeCharCodeAt("\r", 0); - function alert(message = "Alert") { - if (!isatty(stdin.rid)) { - return; - } - - core.print(`${message} [Enter] `, false); - - readLineFromStdinSync(); +function alert(message = "Alert") { + if (!isatty(stdin.rid)) { + return; } - function confirm(message = "Confirm") { - if (!isatty(stdin.rid)) { - return false; - } + core.print(`${message} [Enter] `, false); - core.print(`${message} [y/N] `, false); + readLineFromStdinSync(); +} - const answer = readLineFromStdinSync(); - - return answer === "Y" || answer === "y"; +function confirm(message = "Confirm") { + if (!isatty(stdin.rid)) { + return false; } - function prompt(message = "Prompt", defaultValue) { - defaultValue ??= null; + core.print(`${message} [y/N] `, false); - if (!isatty(stdin.rid)) { - return null; - } + const answer = readLineFromStdinSync(); - core.print(`${message} `, false); + return answer === "Y" || answer === "y"; +} - if (defaultValue) { - core.print(`[${defaultValue}] `, false); - } +function prompt(message = "Prompt", defaultValue) { + defaultValue ??= null; - return readLineFromStdinSync() || defaultValue; + if (!isatty(stdin.rid)) { + return null; } - function readLineFromStdinSync() { - const c = new Uint8Array(1); - const buf = []; + core.print(`${message} `, false); - while (true) { + if (defaultValue) { + core.print(`[${defaultValue}] `, false); + } + + return readLineFromStdinSync() || defaultValue; +} + +function readLineFromStdinSync() { + const c = new Uint8Array(1); + const buf = []; + + while (true) { + const n = stdin.readSync(c); + if (n === null || n === 0) { + break; + } + if (c[0] === CR) { const n = stdin.readSync(c); - if (n === null || n === 0) { - break; - } - if (c[0] === CR) { - const n = stdin.readSync(c); - if (c[0] === LF) { - break; - } - ArrayPrototypePush(buf, CR); - if (n === null || n === 0) { - break; - } - } if (c[0] === LF) { break; } - ArrayPrototypePush(buf, c[0]); + ArrayPrototypePush(buf, CR); + if (n === null || n === 0) { + break; + } } - return core.decode(new Uint8Array(buf)); + if (c[0] === LF) { + break; + } + ArrayPrototypePush(buf, c[0]); } + return core.decode(new Uint8Array(buf)); +} - window.__bootstrap.prompt = { - alert, - confirm, - prompt, - }; -})(this); +export { alert, confirm, prompt }; diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index d2c76e0039..7b791017b1 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -1,154 +1,181 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const __bootstrap = window.__bootstrap; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as timers from "internal:ext/web/02_timers.js"; +import * as httpClient from "internal:ext/fetch/22_http_client.js"; +import * as console from "internal:ext/console/02_console.js"; +import * as ffi from "internal:ext/ffi/00_ffi.js"; +import * as net from "internal:ext/net/01_net.js"; +import * as tls from "internal:ext/net/02_tls.js"; +import * as http from "internal:ext/http/01_http.js"; +import * as flash from "internal:ext/flash/01_http.js"; +import * as build from "internal:runtime/js/01_build.js"; +import * as errors from "internal:runtime/js/01_errors.js"; +import * as version from "internal:runtime/js/01_version.js"; +import * as permissions from "internal:runtime/js/10_permissions.js"; +import * as io from "internal:runtime/js/12_io.js"; +import * as buffer from "internal:runtime/js/13_buffer.js"; +import * as fs from "internal:runtime/js/30_fs.js"; +import * as os from "internal:runtime/js/30_os.js"; +import * as diagnostics from "internal:runtime/js/40_diagnostics.js"; +import * as files from "internal:runtime/js/40_files.js"; +import * as fsEvents from "internal:runtime/js/40_fs_events.js"; +import * as process from "internal:runtime/js/40_process.js"; +import * as readFile from "internal:runtime/js/40_read_file.js"; +import * as signals from "internal:runtime/js/40_signals.js"; +import * as tty from "internal:runtime/js/40_tty.js"; +import * as writeFile from "internal:runtime/js/40_write_file.js"; +import * as spawn from "internal:runtime/js/40_spawn.js"; +// TODO(bartlomieju): this is funky we have two `http` imports +import * as httpRuntime from "internal:runtime/js/40_http.js"; - __bootstrap.denoNs = { - metrics: core.metrics, - Process: __bootstrap.process.Process, - run: __bootstrap.process.run, - isatty: __bootstrap.tty.isatty, - writeFileSync: __bootstrap.writeFile.writeFileSync, - writeFile: __bootstrap.writeFile.writeFile, - writeTextFileSync: __bootstrap.writeFile.writeTextFileSync, - writeTextFile: __bootstrap.writeFile.writeTextFile, - readTextFile: __bootstrap.readFile.readTextFile, - readTextFileSync: __bootstrap.readFile.readTextFileSync, - readFile: __bootstrap.readFile.readFile, - readFileSync: __bootstrap.readFile.readFileSync, - watchFs: __bootstrap.fsEvents.watchFs, - chmodSync: __bootstrap.fs.chmodSync, - chmod: __bootstrap.fs.chmod, - chown: __bootstrap.fs.chown, - chownSync: __bootstrap.fs.chownSync, - copyFileSync: __bootstrap.fs.copyFileSync, - cwd: __bootstrap.fs.cwd, - makeTempDirSync: __bootstrap.fs.makeTempDirSync, - makeTempDir: __bootstrap.fs.makeTempDir, - makeTempFileSync: __bootstrap.fs.makeTempFileSync, - makeTempFile: __bootstrap.fs.makeTempFile, - memoryUsage: () => core.ops.op_runtime_memory_usage(), - mkdirSync: __bootstrap.fs.mkdirSync, - mkdir: __bootstrap.fs.mkdir, - chdir: __bootstrap.fs.chdir, - copyFile: __bootstrap.fs.copyFile, - readDirSync: __bootstrap.fs.readDirSync, - readDir: __bootstrap.fs.readDir, - readLinkSync: __bootstrap.fs.readLinkSync, - readLink: __bootstrap.fs.readLink, - realPathSync: __bootstrap.fs.realPathSync, - realPath: __bootstrap.fs.realPath, - removeSync: __bootstrap.fs.removeSync, - remove: __bootstrap.fs.remove, - renameSync: __bootstrap.fs.renameSync, - rename: __bootstrap.fs.rename, - version: __bootstrap.version.version, - build: __bootstrap.build.build, - statSync: __bootstrap.fs.statSync, - lstatSync: __bootstrap.fs.lstatSync, - stat: __bootstrap.fs.stat, - lstat: __bootstrap.fs.lstat, - truncateSync: __bootstrap.fs.truncateSync, - truncate: __bootstrap.fs.truncate, - ftruncateSync: __bootstrap.fs.ftruncateSync, - ftruncate: __bootstrap.fs.ftruncate, - futime: __bootstrap.fs.futime, - futimeSync: __bootstrap.fs.futimeSync, - errors: __bootstrap.errors.errors, - // TODO(kt3k): Remove this export at v2 - // See https://github.com/denoland/deno/issues/9294 - customInspect: __bootstrap.console.customInspect, - inspect: __bootstrap.console.inspect, - env: __bootstrap.os.env, - exit: __bootstrap.os.exit, - execPath: __bootstrap.os.execPath, - Buffer: __bootstrap.buffer.Buffer, - readAll: __bootstrap.buffer.readAll, - readAllSync: __bootstrap.buffer.readAllSync, - writeAll: __bootstrap.buffer.writeAll, - writeAllSync: __bootstrap.buffer.writeAllSync, - copy: __bootstrap.io.copy, - iter: __bootstrap.io.iter, - iterSync: __bootstrap.io.iterSync, - SeekMode: __bootstrap.io.SeekMode, - read: __bootstrap.io.read, - readSync: __bootstrap.io.readSync, - write: __bootstrap.io.write, - writeSync: __bootstrap.io.writeSync, - File: __bootstrap.files.File, - FsFile: __bootstrap.files.FsFile, - open: __bootstrap.files.open, - openSync: __bootstrap.files.openSync, - create: __bootstrap.files.create, - createSync: __bootstrap.files.createSync, - stdin: __bootstrap.files.stdin, - stdout: __bootstrap.files.stdout, - stderr: __bootstrap.files.stderr, - seek: __bootstrap.files.seek, - seekSync: __bootstrap.files.seekSync, - connect: __bootstrap.net.connect, - listen: __bootstrap.net.listen, - loadavg: __bootstrap.os.loadavg, - connectTls: __bootstrap.tls.connectTls, - listenTls: __bootstrap.tls.listenTls, - startTls: __bootstrap.tls.startTls, - shutdown: __bootstrap.net.shutdown, - fstatSync: __bootstrap.fs.fstatSync, - fstat: __bootstrap.fs.fstat, - fsyncSync: __bootstrap.fs.fsyncSync, - fsync: __bootstrap.fs.fsync, - fdatasyncSync: __bootstrap.fs.fdatasyncSync, - fdatasync: __bootstrap.fs.fdatasync, - symlink: __bootstrap.fs.symlink, - symlinkSync: __bootstrap.fs.symlinkSync, - link: __bootstrap.fs.link, - linkSync: __bootstrap.fs.linkSync, - permissions: __bootstrap.permissions.permissions, - Permissions: __bootstrap.permissions.Permissions, - PermissionStatus: __bootstrap.permissions.PermissionStatus, - serveHttp: __bootstrap.http.serveHttp, - resolveDns: __bootstrap.net.resolveDns, - upgradeWebSocket: __bootstrap.http.upgradeWebSocket, - utime: __bootstrap.fs.utime, - utimeSync: __bootstrap.fs.utimeSync, - kill: __bootstrap.process.kill, - addSignalListener: __bootstrap.signals.addSignalListener, - removeSignalListener: __bootstrap.signals.removeSignalListener, - refTimer: __bootstrap.timers.refTimer, - unrefTimer: __bootstrap.timers.unrefTimer, - osRelease: __bootstrap.os.osRelease, - osUptime: __bootstrap.os.osUptime, - hostname: __bootstrap.os.hostname, - systemMemoryInfo: __bootstrap.os.systemMemoryInfo, - networkInterfaces: __bootstrap.os.networkInterfaces, - consoleSize: __bootstrap.tty.consoleSize, - gid: __bootstrap.os.gid, - uid: __bootstrap.os.uid, - }; +const denoNs = { + metrics: core.metrics, + Process: process.Process, + run: process.run, + isatty: tty.isatty, + writeFileSync: writeFile.writeFileSync, + writeFile: writeFile.writeFile, + writeTextFileSync: writeFile.writeTextFileSync, + writeTextFile: writeFile.writeTextFile, + readTextFile: readFile.readTextFile, + readTextFileSync: readFile.readTextFileSync, + readFile: readFile.readFile, + readFileSync: readFile.readFileSync, + watchFs: fsEvents.watchFs, + chmodSync: fs.chmodSync, + chmod: fs.chmod, + chown: fs.chown, + chownSync: fs.chownSync, + copyFileSync: fs.copyFileSync, + cwd: fs.cwd, + makeTempDirSync: fs.makeTempDirSync, + makeTempDir: fs.makeTempDir, + makeTempFileSync: fs.makeTempFileSync, + makeTempFile: fs.makeTempFile, + memoryUsage: () => ops.op_runtime_memory_usage(), + mkdirSync: fs.mkdirSync, + mkdir: fs.mkdir, + chdir: fs.chdir, + copyFile: fs.copyFile, + readDirSync: fs.readDirSync, + readDir: fs.readDir, + readLinkSync: fs.readLinkSync, + readLink: fs.readLink, + realPathSync: fs.realPathSync, + realPath: fs.realPath, + removeSync: fs.removeSync, + remove: fs.remove, + renameSync: fs.renameSync, + rename: fs.rename, + version: version.version, + build: build.build, + statSync: fs.statSync, + lstatSync: fs.lstatSync, + stat: fs.stat, + lstat: fs.lstat, + truncateSync: fs.truncateSync, + truncate: fs.truncate, + ftruncateSync: fs.ftruncateSync, + ftruncate: fs.ftruncate, + futime: fs.futime, + futimeSync: fs.futimeSync, + errors: errors.errors, + // TODO(kt3k): Remove this export at v2 + // See https://github.com/denoland/deno/issues/9294 + customInspect: console.customInspect, + inspect: console.inspect, + env: os.env, + exit: os.exit, + execPath: os.execPath, + Buffer: buffer.Buffer, + readAll: buffer.readAll, + readAllSync: buffer.readAllSync, + writeAll: buffer.writeAll, + writeAllSync: buffer.writeAllSync, + copy: io.copy, + iter: io.iter, + iterSync: io.iterSync, + SeekMode: io.SeekMode, + read: io.read, + readSync: io.readSync, + write: io.write, + writeSync: io.writeSync, + File: files.File, + FsFile: files.FsFile, + open: files.open, + openSync: files.openSync, + create: files.create, + createSync: files.createSync, + stdin: files.stdin, + stdout: files.stdout, + stderr: files.stderr, + seek: files.seek, + seekSync: files.seekSync, + connect: net.connect, + listen: net.listen, + loadavg: os.loadavg, + connectTls: tls.connectTls, + listenTls: tls.listenTls, + startTls: tls.startTls, + shutdown: net.shutdown, + fstatSync: fs.fstatSync, + fstat: fs.fstat, + fsyncSync: fs.fsyncSync, + fsync: fs.fsync, + fdatasyncSync: fs.fdatasyncSync, + fdatasync: fs.fdatasync, + symlink: fs.symlink, + symlinkSync: fs.symlinkSync, + link: fs.link, + linkSync: fs.linkSync, + permissions: permissions.permissions, + Permissions: permissions.Permissions, + PermissionStatus: permissions.PermissionStatus, + // TODO(bartlomieju): why is this not in one of extensions? + serveHttp: httpRuntime.serveHttp, + resolveDns: net.resolveDns, + upgradeWebSocket: http.upgradeWebSocket, + utime: fs.utime, + utimeSync: fs.utimeSync, + kill: process.kill, + addSignalListener: signals.addSignalListener, + removeSignalListener: signals.removeSignalListener, + refTimer: timers.refTimer, + unrefTimer: timers.unrefTimer, + osRelease: os.osRelease, + osUptime: os.osUptime, + hostname: os.hostname, + systemMemoryInfo: os.systemMemoryInfo, + networkInterfaces: os.networkInterfaces, + consoleSize: tty.consoleSize, + gid: os.gid, + uid: os.uid, +}; - __bootstrap.denoNsUnstable = { - DiagnosticCategory: __bootstrap.diagnostics.DiagnosticCategory, - listenDatagram: __bootstrap.net.listenDatagram, - umask: __bootstrap.fs.umask, - HttpClient: __bootstrap.fetch.HttpClient, - createHttpClient: __bootstrap.fetch.createHttpClient, - http: __bootstrap.http, - dlopen: __bootstrap.ffi.dlopen, - UnsafeCallback: __bootstrap.ffi.UnsafeCallback, - UnsafePointer: __bootstrap.ffi.UnsafePointer, - UnsafePointerView: __bootstrap.ffi.UnsafePointerView, - UnsafeFnPointer: __bootstrap.ffi.UnsafeFnPointer, - flock: __bootstrap.fs.flock, - flockSync: __bootstrap.fs.flockSync, - funlock: __bootstrap.fs.funlock, - funlockSync: __bootstrap.fs.funlockSync, - Child: __bootstrap.spawn.Child, - ChildProcess: __bootstrap.spawn.ChildProcess, - Command: __bootstrap.spawn.Command, - serve: __bootstrap.flash.serve, - upgradeHttp: __bootstrap.http.upgradeHttp, - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - }; -})(this); +const denoNsUnstable = { + DiagnosticCategory: diagnostics.DiagnosticCategory, + listenDatagram: net.listenDatagram, + umask: fs.umask, + HttpClient: httpClient.HttpClient, + createHttpClient: httpClient.createHttpClient, + // TODO(bartlomieju): why is it needed? + http, + dlopen: ffi.dlopen, + UnsafeCallback: ffi.UnsafeCallback, + UnsafePointer: ffi.UnsafePointer, + UnsafePointerView: ffi.UnsafePointerView, + UnsafeFnPointer: ffi.UnsafeFnPointer, + flock: fs.flock, + flockSync: fs.flockSync, + funlock: fs.funlock, + funlockSync: fs.funlockSync, + Child: spawn.Child, + ChildProcess: spawn.ChildProcess, + Command: spawn.Command, + upgradeHttp: http.upgradeHttp, + upgradeHttpRaw: flash.upgradeHttpRaw, +}; + +export { denoNs, denoNsUnstable }; diff --git a/runtime/js/98_global_scope.js b/runtime/js/98_global_scope.js index 499538a324..dcb3bd4869 100644 --- a/runtime/js/98_global_scope.js +++ b/runtime/js/98_global_scope.js @@ -1,238 +1,286 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = Deno.core; - const { - ObjectDefineProperties, - SymbolFor, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +const { + ObjectDefineProperties, + SymbolFor, +} = primordials; - const util = window.__bootstrap.util; - const location = window.__bootstrap.location; - const event = window.__bootstrap.event; - const eventTarget = window.__bootstrap.eventTarget; - const timers = window.__bootstrap.timers; - const base64 = window.__bootstrap.base64; - const encoding = window.__bootstrap.encoding; - const Console = window.__bootstrap.console.Console; - const caches = window.__bootstrap.caches; - const compression = window.__bootstrap.compression; - const worker = window.__bootstrap.worker; - const performance = window.__bootstrap.performance; - const crypto = window.__bootstrap.crypto; - const url = window.__bootstrap.url; - const urlPattern = window.__bootstrap.urlPattern; - const headers = window.__bootstrap.headers; - const streams = window.__bootstrap.streams; - const fileReader = window.__bootstrap.fileReader; - const webgpu = window.__bootstrap.webgpu; - const webSocket = window.__bootstrap.webSocket; - const broadcastChannel = window.__bootstrap.broadcastChannel; - const file = window.__bootstrap.file; - const formData = window.__bootstrap.formData; - const fetch = window.__bootstrap.fetch; - const messagePort = window.__bootstrap.messagePort; - const webidl = window.__bootstrap.webidl; - const domException = window.__bootstrap.domException; - const abortSignal = window.__bootstrap.abortSignal; - const globalInterfaces = window.__bootstrap.globalInterfaces; - const webStorage = window.__bootstrap.webStorage; - const prompt = window.__bootstrap.prompt; +import * as util from "internal:runtime/js/06_util.js"; +import * as location from "internal:ext/web/12_location.js"; +import * as event from "internal:ext/web/02_event.js"; +import * as timers from "internal:ext/web/02_timers.js"; +import * as base64 from "internal:ext/web/05_base64.js"; +import * as encoding from "internal:ext/web/08_text_encoding.js"; +import * as console from "internal:ext/console/02_console.js"; +import * as caches from "internal:ext/cache/01_cache.js"; +import * as compression from "internal:ext/web/14_compression.js"; +import * as worker from "internal:runtime/js/11_workers.js"; +import * as performance from "internal:ext/web/15_performance.js"; +import * as crypto from "internal:ext/crypto/00_crypto.js"; +import * as url from "internal:ext/url/00_url.js"; +import * as urlPattern from "internal:ext/url/01_urlpattern.js"; +import * as headers from "internal:ext/fetch/20_headers.js"; +import * as streams from "internal:ext/web/06_streams.js"; +import * as fileReader from "internal:ext/web/10_filereader.js"; +import * as webgpu from "internal:ext/webgpu/01_webgpu.js"; +import * as webSocket from "internal:ext/websocket/01_websocket.js"; +import * as webSocketStream from "internal:ext/websocket/02_websocketstream.js"; +import * as broadcastChannel from "internal:ext/broadcast_channel/01_broadcast_channel.js"; +import * as file from "internal:ext/web/09_file.js"; +import * as formData from "internal:ext/fetch/21_formdata.js"; +import * as request from "internal:ext/fetch/23_request.js"; +import * as response from "internal:ext/fetch/23_response.js"; +import * as fetch from "internal:ext/fetch/26_fetch.js"; +import * as messagePort from "internal:ext/web/13_message_port.js"; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import * as abortSignal from "internal:ext/web/03_abort_signal.js"; +import * as globalInterfaces from "internal:ext/web/04_global_interfaces.js"; +import * as webStorage from "internal:ext/webstorage/01_webstorage.js"; +import * as prompt from "internal:runtime/js/41_prompt.js"; - // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope - const windowOrWorkerGlobalScope = { - AbortController: util.nonEnumerable(abortSignal.AbortController), - AbortSignal: util.nonEnumerable(abortSignal.AbortSignal), - Blob: util.nonEnumerable(file.Blob), - ByteLengthQueuingStrategy: util.nonEnumerable( - streams.ByteLengthQueuingStrategy, - ), - CloseEvent: util.nonEnumerable(event.CloseEvent), - CompressionStream: util.nonEnumerable(compression.CompressionStream), - CountQueuingStrategy: util.nonEnumerable( - streams.CountQueuingStrategy, - ), - CryptoKey: util.nonEnumerable(crypto.CryptoKey), - CustomEvent: util.nonEnumerable(event.CustomEvent), - DecompressionStream: util.nonEnumerable(compression.DecompressionStream), - DOMException: util.nonEnumerable(domException.DOMException), - ErrorEvent: util.nonEnumerable(event.ErrorEvent), - Event: util.nonEnumerable(event.Event), - EventTarget: util.nonEnumerable(eventTarget.EventTarget), - File: util.nonEnumerable(file.File), - FileReader: util.nonEnumerable(fileReader.FileReader), - FormData: util.nonEnumerable(formData.FormData), - Headers: util.nonEnumerable(headers.Headers), - MessageEvent: util.nonEnumerable(event.MessageEvent), - Performance: util.nonEnumerable(performance.Performance), - PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), - PerformanceMark: util.nonEnumerable(performance.PerformanceMark), - PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure), - PromiseRejectionEvent: util.nonEnumerable(event.PromiseRejectionEvent), - ProgressEvent: util.nonEnumerable(event.ProgressEvent), - ReadableStream: util.nonEnumerable(streams.ReadableStream), - ReadableStreamDefaultReader: util.nonEnumerable( - streams.ReadableStreamDefaultReader, - ), - Request: util.nonEnumerable(fetch.Request), - Response: util.nonEnumerable(fetch.Response), - TextDecoder: util.nonEnumerable(encoding.TextDecoder), - TextEncoder: util.nonEnumerable(encoding.TextEncoder), - TextDecoderStream: util.nonEnumerable(encoding.TextDecoderStream), - TextEncoderStream: util.nonEnumerable(encoding.TextEncoderStream), - TransformStream: util.nonEnumerable(streams.TransformStream), - URL: util.nonEnumerable(url.URL), - URLPattern: util.nonEnumerable(urlPattern.URLPattern), - URLSearchParams: util.nonEnumerable(url.URLSearchParams), - WebSocket: util.nonEnumerable(webSocket.WebSocket), - MessageChannel: util.nonEnumerable(messagePort.MessageChannel), - MessagePort: util.nonEnumerable(messagePort.MessagePort), - Worker: util.nonEnumerable(worker.Worker), - WritableStream: util.nonEnumerable(streams.WritableStream), - WritableStreamDefaultWriter: util.nonEnumerable( - streams.WritableStreamDefaultWriter, - ), - WritableStreamDefaultController: util.nonEnumerable( - streams.WritableStreamDefaultController, - ), - ReadableByteStreamController: util.nonEnumerable( - streams.ReadableByteStreamController, - ), - ReadableStreamBYOBReader: util.nonEnumerable( - streams.ReadableStreamBYOBReader, - ), - ReadableStreamBYOBRequest: util.nonEnumerable( - streams.ReadableStreamBYOBRequest, - ), - ReadableStreamDefaultController: util.nonEnumerable( - streams.ReadableStreamDefaultController, - ), - TransformStreamDefaultController: util.nonEnumerable( - streams.TransformStreamDefaultController, - ), - atob: util.writable(base64.atob), - btoa: util.writable(base64.btoa), - clearInterval: util.writable(timers.clearInterval), - clearTimeout: util.writable(timers.clearTimeout), - caches: { - enumerable: true, - configurable: true, - get: caches.cacheStorage, +// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope +const windowOrWorkerGlobalScope = { + AbortController: util.nonEnumerable(abortSignal.AbortController), + AbortSignal: util.nonEnumerable(abortSignal.AbortSignal), + Blob: util.nonEnumerable(file.Blob), + ByteLengthQueuingStrategy: util.nonEnumerable( + streams.ByteLengthQueuingStrategy, + ), + CloseEvent: util.nonEnumerable(event.CloseEvent), + CompressionStream: util.nonEnumerable(compression.CompressionStream), + CountQueuingStrategy: util.nonEnumerable( + streams.CountQueuingStrategy, + ), + CryptoKey: util.nonEnumerable(crypto.CryptoKey), + CustomEvent: util.nonEnumerable(event.CustomEvent), + DecompressionStream: util.nonEnumerable(compression.DecompressionStream), + DOMException: util.nonEnumerable(DOMException), + ErrorEvent: util.nonEnumerable(event.ErrorEvent), + Event: util.nonEnumerable(event.Event), + EventTarget: util.nonEnumerable(event.EventTarget), + File: util.nonEnumerable(file.File), + FileReader: util.nonEnumerable(fileReader.FileReader), + FormData: util.nonEnumerable(formData.FormData), + Headers: util.nonEnumerable(headers.Headers), + MessageEvent: util.nonEnumerable(event.MessageEvent), + Performance: util.nonEnumerable(performance.Performance), + PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), + PerformanceMark: util.nonEnumerable(performance.PerformanceMark), + PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure), + PromiseRejectionEvent: util.nonEnumerable(event.PromiseRejectionEvent), + ProgressEvent: util.nonEnumerable(event.ProgressEvent), + ReadableStream: util.nonEnumerable(streams.ReadableStream), + ReadableStreamDefaultReader: util.nonEnumerable( + streams.ReadableStreamDefaultReader, + ), + Request: util.nonEnumerable(request.Request), + Response: util.nonEnumerable(response.Response), + TextDecoder: util.nonEnumerable(encoding.TextDecoder), + TextEncoder: util.nonEnumerable(encoding.TextEncoder), + TextDecoderStream: util.nonEnumerable(encoding.TextDecoderStream), + TextEncoderStream: util.nonEnumerable(encoding.TextEncoderStream), + TransformStream: util.nonEnumerable(streams.TransformStream), + URL: util.nonEnumerable(url.URL), + URLPattern: util.nonEnumerable(urlPattern.URLPattern), + URLSearchParams: util.nonEnumerable(url.URLSearchParams), + WebSocket: util.nonEnumerable(webSocket.WebSocket), + MessageChannel: util.nonEnumerable(messagePort.MessageChannel), + MessagePort: util.nonEnumerable(messagePort.MessagePort), + Worker: util.nonEnumerable(worker.Worker), + WritableStream: util.nonEnumerable(streams.WritableStream), + WritableStreamDefaultWriter: util.nonEnumerable( + streams.WritableStreamDefaultWriter, + ), + WritableStreamDefaultController: util.nonEnumerable( + streams.WritableStreamDefaultController, + ), + ReadableByteStreamController: util.nonEnumerable( + streams.ReadableByteStreamController, + ), + ReadableStreamBYOBReader: util.nonEnumerable( + streams.ReadableStreamBYOBReader, + ), + ReadableStreamBYOBRequest: util.nonEnumerable( + streams.ReadableStreamBYOBRequest, + ), + ReadableStreamDefaultController: util.nonEnumerable( + streams.ReadableStreamDefaultController, + ), + TransformStreamDefaultController: util.nonEnumerable( + streams.TransformStreamDefaultController, + ), + atob: util.writable(base64.atob), + btoa: util.writable(base64.btoa), + clearInterval: util.writable(timers.clearInterval), + clearTimeout: util.writable(timers.clearTimeout), + caches: { + enumerable: true, + configurable: true, + get: caches.cacheStorage, + }, + CacheStorage: util.nonEnumerable(caches.CacheStorage), + Cache: util.nonEnumerable(caches.Cache), + console: util.nonEnumerable( + new console.Console((msg, level) => core.print(msg, level > 1)), + ), + crypto: util.readOnly(crypto.crypto), + Crypto: util.nonEnumerable(crypto.Crypto), + SubtleCrypto: util.nonEnumerable(crypto.SubtleCrypto), + fetch: util.writable(fetch.fetch), + performance: util.writable(performance.performance), + reportError: util.writable(event.reportError), + setInterval: util.writable(timers.setInterval), + setTimeout: util.writable(timers.setTimeout), + structuredClone: util.writable(messagePort.structuredClone), + // Branding as a WebIDL object + [webidl.brand]: util.nonEnumerable(webidl.brand), +}; + +const unstableWindowOrWorkerGlobalScope = { + BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel), + WebSocketStream: util.nonEnumerable(webSocketStream.WebSocketStream), + + GPU: util.nonEnumerable(webgpu.GPU), + GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), + GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), + GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), + GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), + GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), + GPUDevice: util.nonEnumerable(webgpu.GPUDevice), + GPUQueue: util.nonEnumerable(webgpu.GPUQueue), + GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), + GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), + GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), + GPUTexture: util.nonEnumerable(webgpu.GPUTexture), + GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), + GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), + GPUSampler: util.nonEnumerable(webgpu.GPUSampler), + GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), + GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), + GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), + GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), + GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), + GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), + GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), + GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), + GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), + GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), + GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), + GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), + GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), + GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), + GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), + GPUError: util.nonEnumerable(webgpu.GPUError), + GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), + GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), +}; + +class Navigator { + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} + +const navigator = webidl.createBranded(Navigator); + +let numCpus, userAgent, language; + +function setNumCpus(val) { + numCpus = val; +} + +function setUserAgent(val) { + userAgent = val; +} + +function setLanguage(val) { + language = val; +} + +ObjectDefineProperties(Navigator.prototype, { + gpu: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return webgpu.gpu; }, - CacheStorage: util.nonEnumerable(caches.CacheStorage), - Cache: util.nonEnumerable(caches.Cache), - console: util.nonEnumerable( - new Console((msg, level) => core.print(msg, level > 1)), - ), - crypto: util.readOnly(crypto.crypto), - Crypto: util.nonEnumerable(crypto.Crypto), - SubtleCrypto: util.nonEnumerable(crypto.SubtleCrypto), - fetch: util.writable(fetch.fetch), - performance: util.writable(performance.performance), - reportError: util.writable(event.reportError), - setInterval: util.writable(timers.setInterval), - setTimeout: util.writable(timers.setTimeout), - structuredClone: util.writable(messagePort.structuredClone), - // Branding as a WebIDL object - [webidl.brand]: util.nonEnumerable(webidl.brand), - }; - - const unstableWindowOrWorkerGlobalScope = { - BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel), - WebSocketStream: util.nonEnumerable(webSocket.WebSocketStream), - - GPU: util.nonEnumerable(webgpu.GPU), - GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), - GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), - GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), - GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), - GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), - GPUDevice: util.nonEnumerable(webgpu.GPUDevice), - GPUQueue: util.nonEnumerable(webgpu.GPUQueue), - GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), - GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), - GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), - GPUTexture: util.nonEnumerable(webgpu.GPUTexture), - GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), - GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), - GPUSampler: util.nonEnumerable(webgpu.GPUSampler), - GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), - GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), - GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), - GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), - GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), - GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), - GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), - GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), - GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), - GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), - GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), - GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), - GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), - GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), - GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), - GPUError: util.nonEnumerable(webgpu.GPUError), - GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), - GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), - }; - - class Navigator { - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } - } - - const navigator = webidl.createBranded(Navigator); - - let numCpus, userAgent, language; - - function setNumCpus(val) { - numCpus = val; - } - - function setUserAgent(val) { - userAgent = val; - } - - function setLanguage(val) { - language = val; - } - - ObjectDefineProperties(Navigator.prototype, { - gpu: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return webgpu.gpu; - }, + }, + hardwareConcurrency: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return numCpus; }, - hardwareConcurrency: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return numCpus; - }, + }, + userAgent: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return userAgent; }, - userAgent: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return userAgent; - }, + }, + language: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return language; + }, + }, + languages: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return [language]; + }, + }, +}); +const NavigatorPrototype = Navigator.prototype; + +class WorkerNavigator { + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} + +const workerNavigator = webidl.createBranded(WorkerNavigator); + +ObjectDefineProperties(WorkerNavigator.prototype, { + gpu: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, WorkerNavigatorPrototype); + return webgpu.gpu; + }, + }, + hardwareConcurrency: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, WorkerNavigatorPrototype); + return numCpus; }, language: { configurable: true, enumerable: true, get() { - webidl.assertBranded(this, NavigatorPrototype); + webidl.assertBranded(this, WorkerNavigatorPrototype); return language; }, }, @@ -240,96 +288,47 @@ configurable: true, enumerable: true, get() { - webidl.assertBranded(this, NavigatorPrototype); + webidl.assertBranded(this, WorkerNavigatorPrototype); return [language]; }, }, - }); - const NavigatorPrototype = Navigator.prototype; + }, +}); +const WorkerNavigatorPrototype = WorkerNavigator.prototype; - class WorkerNavigator { - constructor() { - webidl.illegalConstructor(); - } +const mainRuntimeGlobalProperties = { + Location: location.locationConstructorDescriptor, + location: location.locationDescriptor, + Window: globalInterfaces.windowConstructorDescriptor, + window: util.getterOnly(() => globalThis), + self: util.getterOnly(() => globalThis), + Navigator: util.nonEnumerable(Navigator), + navigator: util.getterOnly(() => navigator), + alert: util.writable(prompt.alert), + confirm: util.writable(prompt.confirm), + prompt: util.writable(prompt.prompt), + localStorage: util.getterOnly(webStorage.localStorage), + sessionStorage: util.getterOnly(webStorage.sessionStorage), + Storage: util.nonEnumerable(webStorage.Storage), +}; - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } - } +const workerRuntimeGlobalProperties = { + WorkerLocation: location.workerLocationConstructorDescriptor, + location: location.workerLocationDescriptor, + WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, + DedicatedWorkerGlobalScope: + globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, + WorkerNavigator: util.nonEnumerable(WorkerNavigator), + navigator: util.getterOnly(() => workerNavigator), + self: util.getterOnly(() => globalThis), +}; - const workerNavigator = webidl.createBranded(WorkerNavigator); - - ObjectDefineProperties(WorkerNavigator.prototype, { - gpu: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return webgpu.gpu; - }, - }, - hardwareConcurrency: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return numCpus; - }, - language: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return language; - }, - }, - languages: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return [language]; - }, - }, - }, - }); - const WorkerNavigatorPrototype = WorkerNavigator.prototype; - - const mainRuntimeGlobalProperties = { - Location: location.locationConstructorDescriptor, - location: location.locationDescriptor, - Window: globalInterfaces.windowConstructorDescriptor, - window: util.getterOnly(() => globalThis), - self: util.getterOnly(() => globalThis), - Navigator: util.nonEnumerable(Navigator), - navigator: util.getterOnly(() => navigator), - alert: util.writable(prompt.alert), - confirm: util.writable(prompt.confirm), - prompt: util.writable(prompt.prompt), - localStorage: util.getterOnly(webStorage.localStorage), - sessionStorage: util.getterOnly(webStorage.sessionStorage), - Storage: util.nonEnumerable(webStorage.Storage), - }; - - const workerRuntimeGlobalProperties = { - WorkerLocation: location.workerLocationConstructorDescriptor, - location: location.workerLocationDescriptor, - WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, - DedicatedWorkerGlobalScope: - globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, - WorkerNavigator: util.nonEnumerable(WorkerNavigator), - navigator: util.getterOnly(() => workerNavigator), - self: util.getterOnly(() => globalThis), - }; - - window.__bootstrap.globalScope = { - windowOrWorkerGlobalScope, - unstableWindowOrWorkerGlobalScope, - mainRuntimeGlobalProperties, - workerRuntimeGlobalProperties, - - setNumCpus, - setUserAgent, - setLanguage, - }; -})(this); +export { + mainRuntimeGlobalProperties, + setLanguage, + setNumCpus, + setUserAgent, + unstableWindowOrWorkerGlobalScope, + windowOrWorkerGlobalScope, + workerRuntimeGlobalProperties, +}; diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index e1d089bc5d..7970071681 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -1,5 +1,4 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // Removes the `__proto__` for security reasons. // https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ @@ -8,678 +7,677 @@ delete Object.prototype.__proto__; // Remove Intl.v8BreakIterator because it is a non-standard API. delete Intl.v8BreakIterator; -((window) => { - const core = Deno.core; - const ops = core.ops; - const { - ArrayPrototypeIndexOf, - ArrayPrototypePush, - ArrayPrototypeShift, - ArrayPrototypeSplice, - ArrayPrototypeMap, - DateNow, - Error, - ErrorPrototype, - FunctionPrototypeCall, - FunctionPrototypeBind, - ObjectAssign, - ObjectDefineProperty, - ObjectDefineProperties, - ObjectFreeze, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - PromiseResolve, - Symbol, - SymbolFor, - SymbolIterator, - PromisePrototypeThen, - SafeWeakMap, - TypeError, - WeakMapPrototypeDelete, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; - const util = window.__bootstrap.util; - const event = window.__bootstrap.event; - const eventTarget = window.__bootstrap.eventTarget; - const location = window.__bootstrap.location; - const build = window.__bootstrap.build; - const version = window.__bootstrap.version; - const os = window.__bootstrap.os; - const timers = window.__bootstrap.timers; - const colors = window.__bootstrap.colors; - const inspectArgs = window.__bootstrap.console.inspectArgs; - const quoteString = window.__bootstrap.console.quoteString; - const internals = window.__bootstrap.internals; - const performance = window.__bootstrap.performance; - const url = window.__bootstrap.url; - const fetch = window.__bootstrap.fetch; - const messagePort = window.__bootstrap.messagePort; - const denoNs = window.__bootstrap.denoNs; - const denoNsUnstable = window.__bootstrap.denoNsUnstable; - const errors = window.__bootstrap.errors.errors; - const webidl = window.__bootstrap.webidl; - const domException = window.__bootstrap.domException; - const { defineEventHandler, reportException } = window.__bootstrap.event; - const { deserializeJsMessageData, serializeJsMessageData } = - window.__bootstrap.messagePort; - const { - windowOrWorkerGlobalScope, - unstableWindowOrWorkerGlobalScope, - workerRuntimeGlobalProperties, - mainRuntimeGlobalProperties, - setNumCpus, - setUserAgent, - setLanguage, - } = window.__bootstrap.globalScope; +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIndexOf, + ArrayPrototypePush, + ArrayPrototypeShift, + ArrayPrototypeSplice, + ArrayPrototypeMap, + DateNow, + Error, + ErrorPrototype, + FunctionPrototypeCall, + FunctionPrototypeBind, + ObjectAssign, + ObjectDefineProperty, + ObjectDefineProperties, + ObjectFreeze, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + PromiseResolve, + Symbol, + SymbolFor, + SymbolIterator, + PromisePrototypeThen, + SafeWeakMap, + TypeError, + WeakMapPrototypeDelete, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; +import * as util from "internal:runtime/js/06_util.js"; +import * as event from "internal:ext/web/02_event.js"; +import * as location from "internal:ext/web/12_location.js"; +import * as build from "internal:runtime/js/01_build.js"; +import * as version from "internal:runtime/js/01_version.js"; +import * as os from "internal:runtime/js/30_os.js"; +import * as timers from "internal:ext/web/02_timers.js"; +import * as colors from "internal:ext/console/01_colors.js"; +import * as net from "internal:ext/net/01_net.js"; +import { + inspectArgs, + quoteString, + wrapConsole, +} from "internal:ext/console/02_console.js"; +import * as performance from "internal:ext/web/15_performance.js"; +import * as url from "internal:ext/url/00_url.js"; +import * as fetch from "internal:ext/fetch/26_fetch.js"; +import * as messagePort from "internal:ext/web/13_message_port.js"; +import { denoNs, denoNsUnstable } from "internal:runtime/js/90_deno_ns.js"; +import { errors } from "internal:runtime/js/01_errors.js"; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import * as flash from "internal:ext/flash/01_http.js"; +import * as spawn from "internal:runtime/js/40_spawn.js"; +import { + mainRuntimeGlobalProperties, + setLanguage, + setNumCpus, + setUserAgent, + unstableWindowOrWorkerGlobalScope, + windowOrWorkerGlobalScope, + workerRuntimeGlobalProperties, +} from "internal:runtime/js/98_global_scope.js"; - let windowIsClosing = false; +let windowIsClosing = false; +let globalThis_; - function windowClose() { - if (!windowIsClosing) { - windowIsClosing = true; - // Push a macrotask to exit after a promise resolve. - // This is not perfect, but should be fine for first pass. - PromisePrototypeThen( - PromiseResolve(), - () => - FunctionPrototypeCall(timers.setTimeout, null, () => { - // This should be fine, since only Window/MainWorker has .close() - os.exit(0); - }, 0), - ); - } - } - - function workerClose() { - if (isClosing) { - return; - } - - isClosing = true; - ops.op_worker_close(); - } - - function postMessage(message, transferOrOptions = {}) { - const prefix = - "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - const data = serializeJsMessageData(message, transfer); - ops.op_worker_post_message(data); - } - - let isClosing = false; - let globalDispatchEvent; - - async function pollForMessages() { - if (!globalDispatchEvent) { - globalDispatchEvent = FunctionPrototypeBind( - globalThis.dispatchEvent, - globalThis, - ); - } - while (!isClosing) { - const data = await core.opAsync("op_worker_recv_message"); - if (data === null) break; - const v = deserializeJsMessageData(data); - const message = v[0]; - const transferables = v[1]; - - const msgEvent = new event.MessageEvent("message", { - cancelable: false, - data: message, - ports: transferables.filter((t) => - ObjectPrototypeIsPrototypeOf(messagePort.MessagePortPrototype, t) - ), - }); - - try { - globalDispatchEvent(msgEvent); - } catch (e) { - const errorEvent = new event.ErrorEvent("error", { - cancelable: true, - message: e.message, - lineno: e.lineNumber ? e.lineNumber + 1 : undefined, - colno: e.columnNumber ? e.columnNumber + 1 : undefined, - filename: e.fileName, - error: e, - }); - - globalDispatchEvent(errorEvent); - if (!errorEvent.defaultPrevented) { - throw e; - } - } - } - } - - let loadedMainWorkerScript = false; - - function importScripts(...urls) { - if (ops.op_worker_get_type() === "module") { - throw new TypeError("Can't import scripts in a module worker."); - } - - const baseUrl = location.getLocationHref(); - const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => { - try { - return new url.URL(scriptUrl, baseUrl ?? undefined).href; - } catch { - throw new domException.DOMException( - "Failed to parse URL.", - "SyntaxError", - ); - } - }); - - // A classic worker's main script has looser MIME type checks than any - // imported scripts, so we use `loadedMainWorkerScript` to distinguish them. - // TODO(andreubotella) Refactor worker creation so the main script isn't - // loaded with `importScripts()`. - const scripts = ops.op_worker_sync_fetch( - parsedUrls, - !loadedMainWorkerScript, - ); - loadedMainWorkerScript = true; - - for (let i = 0; i < scripts.length; ++i) { - const { url, script } = scripts[i]; - const err = core.evalContext(script, url)[1]; - if (err !== null) { - throw err.thrown; - } - } - } - - function opMainModule() { - return ops.op_main_module(); - } - - function formatException(error) { - if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, error)) { - return null; - } else if (typeof error == "string") { - return `Uncaught ${ - inspectArgs([quoteString(error)], { - colors: !colors.getNoColor(), - }) - }`; - } else { - return `Uncaught ${ - inspectArgs([error], { colors: !colors.getNoColor() }) - }`; - } - } - - function runtimeStart(runtimeOptions, source) { - core.setMacrotaskCallback(timers.handleTimerMacrotask); - core.setMacrotaskCallback(promiseRejectMacrotaskCallback); - core.setWasmStreamingCallback(fetch.handleWasmStreaming); - core.setReportExceptionCallback(reportException); - ops.op_set_format_exception_callback(formatException); - version.setVersions( - runtimeOptions.denoVersion, - runtimeOptions.v8Version, - runtimeOptions.tsVersion, - ); - build.setBuildInfo(runtimeOptions.target); - util.setLogDebug(runtimeOptions.debugFlag, source); - colors.setNoColor(runtimeOptions.noColor || !runtimeOptions.isTty); - // deno-lint-ignore prefer-primordials - Error.prepareStackTrace = core.prepareStackTrace; - registerErrors(); - } - - function registerErrors() { - core.registerErrorClass("NotFound", errors.NotFound); - core.registerErrorClass("PermissionDenied", errors.PermissionDenied); - core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused); - core.registerErrorClass("ConnectionReset", errors.ConnectionReset); - core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted); - core.registerErrorClass("NotConnected", errors.NotConnected); - core.registerErrorClass("AddrInUse", errors.AddrInUse); - core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable); - core.registerErrorClass("BrokenPipe", errors.BrokenPipe); - core.registerErrorClass("AlreadyExists", errors.AlreadyExists); - core.registerErrorClass("InvalidData", errors.InvalidData); - core.registerErrorClass("TimedOut", errors.TimedOut); - core.registerErrorClass("Interrupted", errors.Interrupted); - core.registerErrorClass("WriteZero", errors.WriteZero); - core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof); - core.registerErrorClass("BadResource", errors.BadResource); - core.registerErrorClass("Http", errors.Http); - core.registerErrorClass("Busy", errors.Busy); - core.registerErrorClass("NotSupported", errors.NotSupported); - core.registerErrorBuilder( - "DOMExceptionOperationError", - function DOMExceptionOperationError(msg) { - return new domException.DOMException(msg, "OperationError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionQuotaExceededError", - function DOMExceptionQuotaExceededError(msg) { - return new domException.DOMException(msg, "QuotaExceededError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionNotSupportedError", - function DOMExceptionNotSupportedError(msg) { - return new domException.DOMException(msg, "NotSupported"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionNetworkError", - function DOMExceptionNetworkError(msg) { - return new domException.DOMException(msg, "NetworkError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionAbortError", - function DOMExceptionAbortError(msg) { - return new domException.DOMException(msg, "AbortError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionInvalidCharacterError", - function DOMExceptionInvalidCharacterError(msg) { - return new domException.DOMException(msg, "InvalidCharacterError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionDataError", - function DOMExceptionDataError(msg) { - return new domException.DOMException(msg, "DataError"); - }, +function windowClose() { + if (!windowIsClosing) { + windowIsClosing = true; + // Push a macrotask to exit after a promise resolve. + // This is not perfect, but should be fine for first pass. + PromisePrototypeThen( + PromiseResolve(), + () => + FunctionPrototypeCall(timers.setTimeout, null, () => { + // This should be fine, since only Window/MainWorker has .close() + os.exit(0); + }, 0), ); } +} - const pendingRejections = []; - const pendingRejectionsReasons = new SafeWeakMap(); - - function promiseRejectCallback(type, promise, reason) { - switch (type) { - case 0: { - ops.op_store_pending_promise_rejection(promise, reason); - ArrayPrototypePush(pendingRejections, promise); - WeakMapPrototypeSet(pendingRejectionsReasons, promise, reason); - break; - } - case 1: { - ops.op_remove_pending_promise_rejection(promise); - const index = ArrayPrototypeIndexOf(pendingRejections, promise); - if (index > -1) { - ArrayPrototypeSplice(pendingRejections, index, 1); - WeakMapPrototypeDelete(pendingRejectionsReasons, promise); - } - break; - } - default: - return false; - } - - return !!globalThis.onunhandledrejection || - eventTarget.listenerCount(globalThis, "unhandledrejection") > 0; +function workerClose() { + if (isClosing) { + return; } - function promiseRejectMacrotaskCallback() { - while (pendingRejections.length > 0) { - const promise = ArrayPrototypeShift(pendingRejections); - const hasPendingException = ops.op_has_pending_promise_rejection( - promise, - ); - const reason = WeakMapPrototypeGet(pendingRejectionsReasons, promise); - WeakMapPrototypeDelete(pendingRejectionsReasons, promise); + isClosing = true; + ops.op_worker_close(); +} - if (!hasPendingException) { - continue; - } - - const rejectionEvent = new event.PromiseRejectionEvent( - "unhandledrejection", - { - cancelable: true, - promise, - reason, - }, - ); - - const errorEventCb = (event) => { - if (event.error === reason) { - ops.op_remove_pending_promise_rejection(promise); - } - }; - // Add a callback for "error" event - it will be dispatched - // if error is thrown during dispatch of "unhandledrejection" - // event. - globalThis.addEventListener("error", errorEventCb); - globalThis.dispatchEvent(rejectionEvent); - globalThis.removeEventListener("error", errorEventCb); - - // If event was not prevented (or "unhandledrejection" listeners didn't - // throw) we will let Rust side handle it. - if (rejectionEvent.defaultPrevented) { - ops.op_remove_pending_promise_rejection(promise); - } - } - return true; - } - - let hasBootstrapped = false; - - function bootstrapMainRuntime(runtimeOptions) { - if (hasBootstrapped) { - throw new Error("Worker runtime already bootstrapped"); - } - - core.initializeAsyncOps(); - performance.setTimeOrigin(DateNow()); - - const consoleFromV8 = window.Deno.core.console; - const wrapConsole = window.__bootstrap.console.wrapConsole; - - // Remove bootstrapping data from the global scope - const __bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - delete globalThis.bootstrap; - util.log("bootstrapMainRuntime"); - hasBootstrapped = true; - - // If the `--location` flag isn't set, make `globalThis.location` `undefined` and - // writable, so that they can mock it themselves if they like. If the flag was - // set, define `globalThis.location`, using the provided value. - if (runtimeOptions.location == null) { - mainRuntimeGlobalProperties.location = { - writable: true, - }; - } else { - location.setLocationHref(runtimeOptions.location); - } - - ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); - if (runtimeOptions.unstableFlag) { - ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); - } - ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties); - ObjectDefineProperties(globalThis, { - close: util.writable(windowClose), - closed: util.getterOnly(() => windowIsClosing), - }); - ObjectSetPrototypeOf(globalThis, Window.prototype); - - if (runtimeOptions.inspectFlag) { - const consoleFromDeno = globalThis.console; - wrapConsole(consoleFromDeno, consoleFromV8); - } - - eventTarget.setEventTargetData(globalThis); - - defineEventHandler(window, "error"); - defineEventHandler(window, "load"); - defineEventHandler(window, "beforeunload"); - defineEventHandler(window, "unload"); - defineEventHandler(window, "unhandledrejection"); - - core.setPromiseRejectCallback(promiseRejectCallback); - - const isUnloadDispatched = SymbolFor("isUnloadDispatched"); - // Stores the flag for checking whether unload is dispatched or not. - // This prevents the recursive dispatches of unload events. - // See https://github.com/denoland/deno/issues/9201. - window[isUnloadDispatched] = false; - window.addEventListener("unload", () => { - window[isUnloadDispatched] = true; - }); - - runtimeStart(runtimeOptions); - - setNumCpus(runtimeOptions.cpuCount); - setUserAgent(runtimeOptions.userAgent); - setLanguage(runtimeOptions.locale); - - const internalSymbol = Symbol("Deno.internal"); - - // These have to initialized here and not in `90_deno_ns.js` because - // the op function that needs to be passed will be invalidated by creating - // a snapshot - ObjectAssign(internals, { - nodeUnstable: { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), - __bootstrap.spawn.createSpawnSync( - ops.op_node_unstable_spawn_sync, - ), - __bootstrap.spawn.createSpawnChild( - ops.op_node_unstable_spawn_child, - ), - ), - serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_node_unstable_net_listen_udp, - ops.op_node_unstable_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_node_unstable_os_uptime), - }, - }); - - // FIXME(bartlomieju): temporarily add whole `Deno.core` to - // `Deno[Deno.internal]` namespace. It should be removed and only necessary - // methods should be left there. - ObjectAssign(internals, { - core, - }); - - const finalDenoNs = { - internal: internalSymbol, - [internalSymbol]: internals, - resources: core.resources, - close: core.close, - ...denoNs, - }; - ObjectDefineProperties(finalDenoNs, { - pid: util.readOnly(runtimeOptions.pid), - ppid: util.readOnly(runtimeOptions.ppid), - noColor: util.readOnly(runtimeOptions.noColor), - args: util.readOnly(ObjectFreeze(runtimeOptions.args)), - mainModule: util.getterOnly(opMainModule), - }); - - if (runtimeOptions.unstableFlag) { - ObjectAssign(finalDenoNs, denoNsUnstable); - // These have to initialized here and not in `90_deno_ns.js` because - // the op function that needs to be passed will be invalidated by creating - // a snapshot - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_spawn_child), - __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), - __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), - ), - serve: __bootstrap.flash.createServe(ops.op_flash_serve), - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_net_listen_udp, - ops.op_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_os_uptime), - }); - } - - // Setup `Deno` global - we're actually overriding already existing global - // `Deno` with `Deno` namespace from "./deno.ts". - ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); - - util.log("args", runtimeOptions.args); - } - - function bootstrapWorkerRuntime( - runtimeOptions, - name, - internalName, +function postMessage(message, transferOrOptions = {}) { + const prefix = + "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined ) { - if (hasBootstrapped) { - throw new Error("Worker runtime already bootstrapped"); - } + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); + } + const { transfer } = options; + const data = messagePort.serializeJsMessageData(message, transfer); + ops.op_worker_post_message(data); +} - core.initializeAsyncOps(); - performance.setTimeOrigin(DateNow()); +let isClosing = false; +let globalDispatchEvent; - const consoleFromV8 = window.Deno.core.console; - const wrapConsole = window.__bootstrap.console.wrapConsole; +async function pollForMessages() { + if (!globalDispatchEvent) { + globalDispatchEvent = FunctionPrototypeBind( + globalThis.dispatchEvent, + globalThis, + ); + } + while (!isClosing) { + const data = await core.opAsync("op_worker_recv_message"); + if (data === null) break; + const v = messagePort.deserializeJsMessageData(data); + const message = v[0]; + const transferables = v[1]; - // Remove bootstrapping data from the global scope - const __bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - delete globalThis.bootstrap; - util.log("bootstrapWorkerRuntime"); - hasBootstrapped = true; - ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); - if (runtimeOptions.unstableFlag) { - ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); - } - ObjectDefineProperties(globalThis, workerRuntimeGlobalProperties); - ObjectDefineProperties(globalThis, { - name: util.writable(name), - // TODO(bartlomieju): should be readonly? - close: util.nonEnumerable(workerClose), - postMessage: util.writable(postMessage), + const msgEvent = new event.MessageEvent("message", { + cancelable: false, + data: message, + ports: transferables.filter((t) => + ObjectPrototypeIsPrototypeOf(messagePort.MessagePortPrototype, t) + ), }); - if (runtimeOptions.enableTestingFeaturesFlag) { - ObjectDefineProperty( - globalThis, - "importScripts", - util.writable(importScripts), + + try { + globalDispatchEvent(msgEvent); + } catch (e) { + const errorEvent = new event.ErrorEvent("error", { + cancelable: true, + message: e.message, + lineno: e.lineNumber ? e.lineNumber + 1 : undefined, + colno: e.columnNumber ? e.columnNumber + 1 : undefined, + filename: e.fileName, + error: e, + }); + + globalDispatchEvent(errorEvent); + if (!errorEvent.defaultPrevented) { + throw e; + } + } + } +} + +let loadedMainWorkerScript = false; + +function importScripts(...urls) { + if (ops.op_worker_get_type() === "module") { + throw new TypeError("Can't import scripts in a module worker."); + } + + const baseUrl = location.getLocationHref(); + const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => { + try { + return new url.URL(scriptUrl, baseUrl ?? undefined).href; + } catch { + throw new DOMException( + "Failed to parse URL.", + "SyntaxError", ); } - ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); + }); + // A classic worker's main script has looser MIME type checks than any + // imported scripts, so we use `loadedMainWorkerScript` to distinguish them. + // TODO(andreubotella) Refactor worker creation so the main script isn't + // loaded with `importScripts()`. + const scripts = ops.op_worker_sync_fetch( + parsedUrls, + !loadedMainWorkerScript, + ); + loadedMainWorkerScript = true; + + for (let i = 0; i < scripts.length; ++i) { + const { url, script } = scripts[i]; + const err = core.evalContext(script, url)[1]; + if (err !== null) { + throw err.thrown; + } + } +} + +function opMainModule() { + return ops.op_main_module(); +} + +function formatException(error) { + if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, error)) { + return null; + } else if (typeof error == "string") { + return `Uncaught ${ + inspectArgs([quoteString(error)], { + colors: !colors.getNoColor(), + }) + }`; + } else { + return `Uncaught ${inspectArgs([error], { colors: !colors.getNoColor() })}`; + } +} + +function runtimeStart(runtimeOptions, source) { + core.setMacrotaskCallback(timers.handleTimerMacrotask); + core.setMacrotaskCallback(promiseRejectMacrotaskCallback); + core.setWasmStreamingCallback(fetch.handleWasmStreaming); + core.setReportExceptionCallback(event.reportException); + ops.op_set_format_exception_callback(formatException); + version.setVersions( + runtimeOptions.denoVersion, + runtimeOptions.v8Version, + runtimeOptions.tsVersion, + ); + build.setBuildInfo(runtimeOptions.target); + util.setLogDebug(runtimeOptions.debugFlag, source); + colors.setNoColor(runtimeOptions.noColor || !runtimeOptions.isTty); + // deno-lint-ignore prefer-primordials + Error.prepareStackTrace = core.prepareStackTrace; + registerErrors(); +} + +function registerErrors() { + core.registerErrorClass("NotFound", errors.NotFound); + core.registerErrorClass("PermissionDenied", errors.PermissionDenied); + core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused); + core.registerErrorClass("ConnectionReset", errors.ConnectionReset); + core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted); + core.registerErrorClass("NotConnected", errors.NotConnected); + core.registerErrorClass("AddrInUse", errors.AddrInUse); + core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable); + core.registerErrorClass("BrokenPipe", errors.BrokenPipe); + core.registerErrorClass("AlreadyExists", errors.AlreadyExists); + core.registerErrorClass("InvalidData", errors.InvalidData); + core.registerErrorClass("TimedOut", errors.TimedOut); + core.registerErrorClass("Interrupted", errors.Interrupted); + core.registerErrorClass("WriteZero", errors.WriteZero); + core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof); + core.registerErrorClass("BadResource", errors.BadResource); + core.registerErrorClass("Http", errors.Http); + core.registerErrorClass("Busy", errors.Busy); + core.registerErrorClass("NotSupported", errors.NotSupported); + core.registerErrorBuilder( + "DOMExceptionOperationError", + function DOMExceptionOperationError(msg) { + return new DOMException(msg, "OperationError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionQuotaExceededError", + function DOMExceptionQuotaExceededError(msg) { + return new DOMException(msg, "QuotaExceededError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionNotSupportedError", + function DOMExceptionNotSupportedError(msg) { + return new DOMException(msg, "NotSupported"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionNetworkError", + function DOMExceptionNetworkError(msg) { + return new DOMException(msg, "NetworkError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionAbortError", + function DOMExceptionAbortError(msg) { + return new DOMException(msg, "AbortError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionInvalidCharacterError", + function DOMExceptionInvalidCharacterError(msg) { + return new DOMException(msg, "InvalidCharacterError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionDataError", + function DOMExceptionDataError(msg) { + return new DOMException(msg, "DataError"); + }, + ); +} + +const pendingRejections = []; +const pendingRejectionsReasons = new SafeWeakMap(); + +function promiseRejectCallback(type, promise, reason) { + switch (type) { + case 0: { + ops.op_store_pending_promise_rejection(promise, reason); + ArrayPrototypePush(pendingRejections, promise); + WeakMapPrototypeSet(pendingRejectionsReasons, promise, reason); + break; + } + case 1: { + ops.op_remove_pending_promise_rejection(promise); + const index = ArrayPrototypeIndexOf(pendingRejections, promise); + if (index > -1) { + ArrayPrototypeSplice(pendingRejections, index, 1); + WeakMapPrototypeDelete(pendingRejectionsReasons, promise); + } + break; + } + default: + return false; + } + + return !!globalThis_.onunhandledrejection || + event.listenerCount(globalThis_, "unhandledrejection") > 0; +} + +function promiseRejectMacrotaskCallback() { + while (pendingRejections.length > 0) { + const promise = ArrayPrototypeShift(pendingRejections); + const hasPendingException = ops.op_has_pending_promise_rejection( + promise, + ); + const reason = WeakMapPrototypeGet(pendingRejectionsReasons, promise); + WeakMapPrototypeDelete(pendingRejectionsReasons, promise); + + if (!hasPendingException) { + continue; + } + + const rejectionEvent = new event.PromiseRejectionEvent( + "unhandledrejection", + { + cancelable: true, + promise, + reason, + }, + ); + + const errorEventCb = (event) => { + if (event.error === reason) { + ops.op_remove_pending_promise_rejection(promise); + } + }; + // Add a callback for "error" event - it will be dispatched + // if error is thrown during dispatch of "unhandledrejection" + // event. + globalThis_.addEventListener("error", errorEventCb); + globalThis_.dispatchEvent(rejectionEvent); + globalThis_.removeEventListener("error", errorEventCb); + + // If event was not prevented (or "unhandledrejection" listeners didn't + // throw) we will let Rust side handle it. + if (rejectionEvent.defaultPrevented) { + ops.op_remove_pending_promise_rejection(promise); + } + } + return true; +} + +let hasBootstrapped = false; + +function bootstrapMainRuntime(runtimeOptions) { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); + } + + core.initializeAsyncOps(); + performance.setTimeOrigin(DateNow()); + globalThis_ = globalThis; + + const consoleFromV8 = globalThis.Deno.core.console; + + // Remove bootstrapping data from the global scope + delete globalThis.__bootstrap; + delete globalThis.bootstrap; + util.log("bootstrapMainRuntime"); + hasBootstrapped = true; + + // If the `--location` flag isn't set, make `globalThis.location` `undefined` and + // writable, so that they can mock it themselves if they like. If the flag was + // set, define `globalThis.location`, using the provided value. + if (runtimeOptions.location == null) { + mainRuntimeGlobalProperties.location = { + writable: true, + }; + } else { + location.setLocationHref(runtimeOptions.location); + } + + ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); + if (runtimeOptions.unstableFlag) { + ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); + } + ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties); + ObjectDefineProperties(globalThis, { + close: util.writable(windowClose), + closed: util.getterOnly(() => windowIsClosing), + }); + ObjectSetPrototypeOf(globalThis, Window.prototype); + + if (runtimeOptions.inspectFlag) { const consoleFromDeno = globalThis.console; wrapConsole(consoleFromDeno, consoleFromV8); + } - eventTarget.setEventTargetData(globalThis); + event.setEventTargetData(globalThis); + event.saveGlobalThisReference(globalThis); - defineEventHandler(self, "message"); - defineEventHandler(self, "error", undefined, true); - defineEventHandler(self, "unhandledrejection"); + event.defineEventHandler(globalThis, "error"); + event.defineEventHandler(globalThis, "load"); + event.defineEventHandler(globalThis, "beforeunload"); + event.defineEventHandler(globalThis, "unload"); + event.defineEventHandler(globalThis, "unhandledrejection"); - core.setPromiseRejectCallback(promiseRejectCallback); + core.setPromiseRejectCallback(promiseRejectCallback); - // `Deno.exit()` is an alias to `self.close()`. Setting and exit - // code using an op in worker context is a no-op. - os.setExitHandler((_exitCode) => { - workerClose(); - }); + const isUnloadDispatched = SymbolFor("isUnloadDispatched"); + // Stores the flag for checking whether unload is dispatched or not. + // This prevents the recursive dispatches of unload events. + // See https://github.com/denoland/deno/issues/9201. + globalThis[isUnloadDispatched] = false; + globalThis.addEventListener("unload", () => { + globalThis_[isUnloadDispatched] = true; + }); - runtimeStart( - runtimeOptions, - internalName ?? name, - ); + runtimeStart(runtimeOptions); - location.setLocationHref(runtimeOptions.location); + setNumCpus(runtimeOptions.cpuCount); + setUserAgent(runtimeOptions.userAgent); + setLanguage(runtimeOptions.locale); - setNumCpus(runtimeOptions.cpuCount); - setLanguage(runtimeOptions.locale); + const internalSymbol = Symbol("Deno.internal"); - globalThis.pollForMessages = pollForMessages; + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + Command: spawn.createCommand( + spawn.createSpawn(ops.op_node_unstable_spawn_child), + spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), + ), + serve: flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: flash.upgradeHttpRaw, + listenDatagram: net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + osUptime: os.createOsUptime(ops.op_node_unstable_os_uptime), + }, + }); - const internalSymbol = Symbol("Deno.internal"); + // FIXME(bartlomieju): temporarily add whole `Deno.core` to + // `Deno[Deno.internal]` namespace. It should be removed and only necessary + // methods should be left there. + ObjectAssign(internals, { + core, + }); + const finalDenoNs = { + internal: internalSymbol, + [internalSymbol]: internals, + resources: core.resources, + close: core.close, + ...denoNs, + }; + ObjectDefineProperties(finalDenoNs, { + pid: util.readOnly(runtimeOptions.pid), + ppid: util.readOnly(runtimeOptions.ppid), + noColor: util.readOnly(runtimeOptions.noColor), + args: util.readOnly(ObjectFreeze(runtimeOptions.args)), + mainModule: util.getterOnly(opMainModule), + }); + + if (runtimeOptions.unstableFlag) { + ObjectAssign(finalDenoNs, denoNsUnstable); // These have to initialized here and not in `90_deno_ns.js` because // the op function that needs to be passed will be invalidated by creating // a snapshot - ObjectAssign(internals, { - nodeUnstable: { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), - __bootstrap.spawn.createSpawnSync( - ops.op_node_unstable_spawn_sync, - ), - __bootstrap.spawn.createSpawnChild( - ops.op_node_unstable_spawn_child, - ), - ), - serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_node_unstable_net_listen_udp, - ops.op_node_unstable_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_node_unstable_os_uptime), - }, + ObjectAssign(finalDenoNs, { + Command: spawn.createCommand( + spawn.createSpawn(ops.op_spawn_child), + spawn.createSpawnSync(ops.op_spawn_sync), + spawn.createSpawnChild(ops.op_spawn_child), + ), + serve: flash.createServe(ops.op_flash_serve), + listenDatagram: net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), + osUptime: os.createOsUptime(ops.op_os_uptime), }); - - // FIXME(bartlomieju): temporarily add whole `Deno.core` to - // `Deno[Deno.internal]` namespace. It should be removed and only necessary - // methods should be left there. - ObjectAssign(internals, { - core, - }); - - const finalDenoNs = { - internal: internalSymbol, - [internalSymbol]: internals, - resources: core.resources, - close: core.close, - ...denoNs, - }; - if (runtimeOptions.unstableFlag) { - ObjectAssign(finalDenoNs, denoNsUnstable); - // These have to initialized here and not in `90_deno_ns.js` because - // the op function that needs to be passed will be invalidated by creating - // a snapshot - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_spawn_child), - __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), - __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), - ), - serve: __bootstrap.flash.createServe(ops.op_flash_serve), - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_net_listen_udp, - ops.op_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_os_uptime), - }); - } - ObjectDefineProperties(finalDenoNs, { - pid: util.readOnly(runtimeOptions.pid), - noColor: util.readOnly(runtimeOptions.noColor), - args: util.readOnly(ObjectFreeze(runtimeOptions.args)), - }); - // Setup `Deno` global - we're actually overriding already - // existing global `Deno` with `Deno` namespace from "./deno.ts". - ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); } + // Setup `Deno` global - we're actually overriding already existing global + // `Deno` with `Deno` namespace from "./deno.ts". + ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); + + util.log("args", runtimeOptions.args); +} + +function bootstrapWorkerRuntime( + runtimeOptions, + name, + internalName, +) { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); + } + + core.initializeAsyncOps(); + performance.setTimeOrigin(DateNow()); + globalThis_ = globalThis; + + const consoleFromV8 = globalThis.Deno.core.console; + + // Remove bootstrapping data from the global scope + delete globalThis.__bootstrap; + delete globalThis.bootstrap; + util.log("bootstrapWorkerRuntime"); + hasBootstrapped = true; + ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); + if (runtimeOptions.unstableFlag) { + ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); + } + ObjectDefineProperties(globalThis, workerRuntimeGlobalProperties); ObjectDefineProperties(globalThis, { - bootstrap: { - value: { - mainRuntime: bootstrapMainRuntime, - workerRuntime: bootstrapWorkerRuntime, - }, - configurable: true, + name: util.writable(name), + // TODO(bartlomieju): should be readonly? + close: util.nonEnumerable(workerClose), + postMessage: util.writable(postMessage), + }); + if (runtimeOptions.enableTestingFeaturesFlag) { + ObjectDefineProperty( + globalThis, + "importScripts", + util.writable(importScripts), + ); + } + ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); + + const consoleFromDeno = globalThis.console; + wrapConsole(consoleFromDeno, consoleFromV8); + + event.setEventTargetData(globalThis); + event.saveGlobalThisReference(globalThis); + + event.defineEventHandler(self, "message"); + event.defineEventHandler(self, "error", undefined, true); + event.defineEventHandler(self, "unhandledrejection"); + + core.setPromiseRejectCallback(promiseRejectCallback); + + // `Deno.exit()` is an alias to `self.close()`. Setting and exit + // code using an op in worker context is a no-op. + os.setExitHandler((_exitCode) => { + workerClose(); + }); + + runtimeStart( + runtimeOptions, + internalName ?? name, + ); + + location.setLocationHref(runtimeOptions.location); + + setNumCpus(runtimeOptions.cpuCount); + setLanguage(runtimeOptions.locale); + + globalThis.pollForMessages = pollForMessages; + + const internalSymbol = Symbol("Deno.internal"); + + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + Command: spawn.createCommand( + spawn.createSpawn(ops.op_node_unstable_spawn_child), + spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), + ), + serve: flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: flash.upgradeHttpRaw, + listenDatagram: net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + osUptime: os.createOsUptime(ops.op_node_unstable_os_uptime), }, }); -})(this); + + // FIXME(bartlomieju): temporarily add whole `Deno.core` to + // `Deno[Deno.internal]` namespace. It should be removed and only necessary + // methods should be left there. + ObjectAssign(internals, { + core, + }); + + const finalDenoNs = { + internal: internalSymbol, + [internalSymbol]: internals, + resources: core.resources, + close: core.close, + ...denoNs, + }; + if (runtimeOptions.unstableFlag) { + ObjectAssign(finalDenoNs, denoNsUnstable); + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(finalDenoNs, { + Command: spawn.createCommand( + spawn.createSpawn(ops.op_spawn_child), + spawn.createSpawnSync(ops.op_spawn_sync), + spawn.createSpawnChild(ops.op_spawn_child), + ), + serve: flash.createServe(ops.op_flash_serve), + listenDatagram: net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), + osUptime: os.createOsUptime(ops.op_os_uptime), + }); + } + ObjectDefineProperties(finalDenoNs, { + pid: util.readOnly(runtimeOptions.pid), + noColor: util.readOnly(runtimeOptions.noColor), + args: util.readOnly(ObjectFreeze(runtimeOptions.args)), + }); + // Setup `Deno` global - we're actually overriding already + // existing global `Deno` with `Deno` namespace from "./deno.ts". + ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); +} + +ObjectDefineProperties(globalThis, { + bootstrap: { + value: { + mainRuntime: bootstrapMainRuntime, + workerRuntime: bootstrapWorkerRuntime, + }, + configurable: true, + }, +}); diff --git a/tools/bench/README.md b/tools/bench/README.md deleted file mode 100644 index 78529d1063..0000000000 --- a/tools/bench/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## Re-bootstrapping - -Re-bootstrapping allows deno devs to bench/profile/test JS-side changes without -doing a full `cargo build --release --bin deno` which takes roughly ~4mn on M1s -more on other machines which significantly slows down iteration & -experimentation. - -## Example - -```js -import { benchSync, rebootstrap } from "./tools/bench/mod.js"; - -const bootstrap = rebootstrap([ - "webidl", - "console", - "url", - "web", - "fetch", -]); - -benchSync("resp_w_h", 1e6, () => - new bootstrap.fetch.Response("yolo", { - status: 200, - headers: { - server: "deno", - "content-type": "text/plain", - }, - })); -``` - -This code can then benched and profiled (using Chrome's DevTools) similar to -regular userland code and the original source files appear in the DevTools as -you would expect. diff --git a/tools/bench/mod.js b/tools/bench/mod.js deleted file mode 100644 index 9d90818a5b..0000000000 --- a/tools/bench/mod.js +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -export * from "./rebench.js"; -export * from "./rebootstrap.js"; diff --git a/tools/bench/rebench.js b/tools/bench/rebench.js deleted file mode 100644 index 7d77a79cd1..0000000000 --- a/tools/bench/rebench.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -export function benchSync(name, n, innerLoop) { - const t1 = Date.now(); - for (let i = 0; i < n; i++) { - innerLoop(i); - } - const t2 = Date.now(); - console.log(benchStats(name, n, t1, t2)); -} - -export async function benchAsync(name, n, innerLoop) { - const t1 = Date.now(); - for (let i = 0; i < n; i++) { - await innerLoop(i); - } - const t2 = Date.now(); - console.log(benchStats(name, n, t1, t2)); -} - -function benchStats(name, n, t1, t2) { - const dt = (t2 - t1) / 1e3; - const r = n / dt; - const ns = Math.floor(dt / n * 1e9); - return `${name}:${" ".repeat(20 - name.length)}\t` + - `n = ${n}, dt = ${dt.toFixed(3)}s, r = ${r.toFixed(0)}/s, t = ${ns}ns/op`; -} diff --git a/tools/bench/rebootstrap.js b/tools/bench/rebootstrap.js deleted file mode 100644 index 0e94285d59..0000000000 --- a/tools/bench/rebootstrap.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { dirname, fromFileUrl, join } from "../../test_util/std/path/mod.ts"; -import { expandGlobSync } from "../../test_util/std/fs/mod.ts"; - -const ROOT_DIR = join(dirname(fromFileUrl(import.meta.url)), "..", ".."); - -export function rebootstrap(exts) { - [ - "core/00_primordials.js", - ...exts.map((e) => `ext/${e}/*.js`), - ] - .map((pattern) => join(ROOT_DIR, pattern)) - .map((pattern) => [...expandGlobSync(pattern)]) - .flat() - .map((entry) => entry.path) - .forEach((file) => { - Deno.core.evalContext(Deno.readTextFileSync(file), file); - }); - const bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - // Patch dispatchEvent so we don't crash when MainWorker exits via: - // `window.dispatchEvent(new Event('unload'))` - // which fails since symbols are mangled during rebootstrap - globalThis.dispatchEvent = () => {}; - return bootstrap; -} diff --git a/tools/wgpu_sync.js b/tools/wgpu_sync.js index 6edc4c92fc..4d14f43cf0 100755 --- a/tools/wgpu_sync.js +++ b/tools/wgpu_sync.js @@ -92,11 +92,23 @@ async function patchSrcLib() { ); } +async function patchSurface() { + await patchFile( + join(TARGET_DIR, "src", "surface.rs"), + (data) => + data.replace( + `prefix "internal:deno_webgpu",`, + `prefix "internal:ext/webgpu",`, + ), + ); +} + async function main() { await clearTargetDir(); await checkoutUpstream(); await patchCargo(); await patchSrcLib(); + await patchSurface(); await bash(join(ROOT_PATH, "tools", "format.js")); }