diff options
Diffstat (limited to 'ext')
76 files changed, 34307 insertions, 34493 deletions
diff --git a/ext/broadcast_channel/01_broadcast_channel.js b/ext/broadcast_channel/01_broadcast_channel.js index 82bede8b0..fb23554bf 100644 --- a/ext/broadcast_channel/01_broadcast_channel.js +++ b/ext/broadcast_channel/01_broadcast_channel.js @@ -2,150 +2,149 @@ /// <reference path="../../core/internal.d.ts" /> -"use strict"; - -((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 channels = []; - let rid = null; - - 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)); +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; + +const _name = Symbol("[[name]]"); +const _closed = Symbol("[[closed]]"); + +const channels = []; +let rid = null; + +async function recv() { + while (channels.length > 0) { + const message = await core.opAsync("op_broadcast_recv", rid); + + 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]; - - if (channel === source) continue; // Don't self-send. - if (channel[_name] !== name) continue; - if (channel[_closed]) continue; - - 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); - }; - - defer(go); - } - } + core.close(rid); + rid = null; +} - // 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); - } +function dispatch(source, name, data) { + for (let i = 0; i < channels.length; ++i) { + const channel = channels[i]; - class BroadcastChannel extends EventTarget { - [_name]; - [_closed] = false; + if (channel === source) continue; // Don't self-send. + if (channel[_name] !== name) continue; + if (channel[_closed]) continue; - get name() { - return this[_name]; - } + 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); + }; - constructor(name) { - super(); + defer(go); + } +} - const prefix = "Failed to construct 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); +// 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); +} - this[_name] = webidl.converters["DOMString"](name, { - prefix, - context: "Argument 1", - }); +class BroadcastChannel extends EventTarget { + [_name]; + [_closed] = false; - this[webidl.brand] = webidl.brand; + get name() { + return this[_name]; + } - ArrayPrototypePush(channels, this); + constructor(name) { + super(); - 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(); - } - } + const prefix = "Failed to construct 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - postMessage(message) { - webidl.assertBranded(this, BroadcastChannelPrototype); + this[_name] = webidl.converters["DOMString"](name, { + prefix, + context: "Argument 1", + }); - const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + this[webidl.brand] = webidl.brand; - if (this[_closed]) { - throw new DOMException("Already closed", "InvalidStateError"); - } + ArrayPrototypePush(channels, this); - if (typeof message === "function" || typeof message === "symbol") { - throw new DOMException("Uncloneable value", "DataCloneError"); - } + 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(); + } + } - const data = core.serialize(message); + postMessage(message) { + webidl.assertBranded(this, BroadcastChannelPrototype); - // Send to other listeners in this VM. - dispatch(this, this[_name], new Uint8Array(data)); + const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - // Send to listeners in other VMs. - defer(() => { - if (!this[_closed]) { - core.opAsync("op_broadcast_send", rid, this[_name], data); - } - }); + if (this[_closed]) { + throw new DOMException("Already closed", "InvalidStateError"); } - close() { - webidl.assertBranded(this, BroadcastChannelPrototype); - this[_closed] = true; + if (typeof message === "function" || typeof message === "symbol") { + throw new DOMException("Uncloneable value", "DataCloneError"); + } - const index = ArrayPrototypeIndexOf(channels, this); - if (index === -1) return; + const data = core.serialize(message); - ArrayPrototypeSplice(channels, index, 1); - if (channels.length === 0) { - ops.op_broadcast_unsubscribe(rid); + // 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; +defineEventHandler(BroadcastChannel.prototype, "message"); +defineEventHandler(BroadcastChannel.prototype, "messageerror"); +const BroadcastChannelPrototype = BroadcastChannel.prototype; - window.__bootstrap.broadcastChannel = { BroadcastChannel }; -})(this); +export { BroadcastChannel }; diff --git a/ext/broadcast_channel/lib.rs b/ext/broadcast_channel/lib.rs index 674d2414d..0cd17a026 100644 --- a/ext/broadcast_channel/lib.rs +++ b/ext/broadcast_channel/lib.rs @@ -112,7 +112,7 @@ pub fn init<BC: BroadcastChannel + 'static>( ) -> 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 bf0243e3c..f49db3b84 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 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 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); - } + 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"); +const _matchAll = Symbol("[[matchAll]]"); +const _id = Symbol("id"); - class Cache { - /** @type {number} */ - [_id]; +class Cache { + /** @type {number} */ + [_id]; - constructor() { - webidl.illegalConstructor(); - } + 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 '*'"); - } + /** 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(); + // 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 = ""; + // 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); + // 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; } - } finally { - core.close(rid); + await core.writeAll(rid, value); } + } finally { + core.close(rid); } - // Step 12-19: TODO(@satyarohith): do the insertion in background. } + // 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-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); + /** 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; } - return await core.opAsync("op_cache_delete", { - cacheId: this[_id], - requestUrl: r.url, - }); + } 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); + /** 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", + // 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: url.toString(), + requestHeaders: innerRequest.headerList, + }, + ); + if (matchResult) { + const { 0: meta, 1: responseBodyRid } = matchResult; + let body = null; + if (responseBodyRid !== null) { + body = readableStreamForRid(responseBodyRid); + } + 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 888407153..a35ac6246 100644 --- a/ext/cache/lib.rs +++ b/ext/cache/lib.rs @@ -27,7 +27,7 @@ pub fn init<CA: Cache + 'static>( ) -> 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 00425f08b..d01edd247 100644 --- a/ext/console/01_colors.js +++ b/ext/console/01_colors.js @@ -2,110 +2,107 @@ /// <reference path="../../core/internal.d.ts" /> -"use strict"; - -((window) => { - const { - RegExp, - StringPrototypeReplace, - ArrayPrototypeJoin, - } = window.__bootstrap.primordials; - - let noColor = false; - - function setNoColor(value) { - noColor = value; - } - - 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, +const primordials = globalThis.__bootstrap.primordials; +const { + RegExp, + StringPrototypeReplace, + ArrayPrototypeJoin, +} = primordials; + +let noColor = false; + +function setNoColor(value) { + noColor = value; +} + +function getNoColor() { + return noColor; +} + +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 a9ec52488..22524e6b9 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -2,2385 +2,2379 @@ /// <reference path="../../core/internal.d.ts" /> -"use strict"; - -((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 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; - } - - return ObjectPrototypePropertyIsEnumerable(obj, prop); +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"; + +function isInvalidDate(x) { + return isNaN(DatePrototypeGetTime(x)); +} + +function hasOwnProperty(obj, v) { + if (obj == null) { + return false; } + return ObjectPrototypeHasOwnProperty(obj, v); +} - // 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; +function propertyIsEnumerable(obj, prop) { + if ( + obj == null || + typeof obj.propertyIsEnumerable !== "function" + ) { + return false; } - 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)) - ); + 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; } - 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; } - - 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; + out += tableChars.right; + return out; +} + +function canRightAlign(value) { + const isNumber = !isNaN(value); + return isNumber; +} + +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); + + 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); - - 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); - } - } + result += + `${tableChars.bottomLeft}${ + ArrayPrototypeJoin(divider, tableChars.bottomMiddle) + }` + + tableChars.bottomRight; + + return result; +} +/* End of forked part */ + +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 ?? ""; + } + return ""; +} + +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"; + } - const divider = ArrayPrototypeMap( - columnWidths, - (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2), + // 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, ); - - 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`; + refStr = refIndex; + // Filter out the empty string for the case we only have + // non-enumerable symbols. + if ( + propString.length > 0 && + propString !== "{}" + ) { + suffix = ` ${propString}`; } - - result += - `${tableChars.bottomLeft}${ - ArrayPrototypeJoin(divider, tableChars.bottomMiddle) - }` + - tableChars.bottomRight; - - return result; - } - /* End of forked part */ - - 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 ?? ""; - } - return ""; + if (value.name && value.name !== "anonymous") { + // from MDN spec + return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; } - - function maybeColor(fn, inspectOptions) { - return inspectOptions.colors ? fn : (s) => s; + 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}]`); } - 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"; - } + 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"); + } + } - // 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 + 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}`; - } - } - - if (value.name && value.name !== "anonymous") { - // from MDN spec - return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; - } - return cyan(`${refStr}[${cstrName}]`) + suffix; - } + ArrayPrototypePush(entries, entry); + inspectOptions.indentLevel--; - function inspectIterable( - value, - options, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return cyan(`[${options.typeName}]`); + if (skipTo) { + // subtract skipped (empty) items + entriesLengthWithoutEmptyItems -= skipTo - i; + i = skipTo; + } } - - 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"); + } else { + let i = 0; + while (true) { + let el; + try { + const res = iter.next(); + if (res.done) { + break; } - } - - let entriesLengthWithoutEmptyItems = entriesLength; - if (options.typeName === "Array") { - for ( - let i = 0, j = 0; - i < entriesLength && j < inspectOptions.iterableLimit; - i++, j++ - ) { + 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++; - const { entry, skipTo } = options.entryHandler( - [i, value[i]], - inspectOptions, + ArrayPrototypePush( + entries, + options.entryHandler( + el, + inspectOptions, + ), ); - ArrayPrototypePush(entries, entry); inspectOptions.indentLevel--; - - if (skipTo) { - // subtract skipped (empty) items - entriesLengthWithoutEmptyItems -= skipTo - i; - i = skipTo; - } - } - } 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++; + } else { + break; } + i++; } + } - if (options.sort) { - ArrayPrototypeSort(entries); - } - - if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { - const nmore = entriesLengthWithoutEmptyItems - - inspectOptions.iterableLimit; - ArrayPrototypePush(entries, `... ${nmore} more items`); - } + if (options.sort) { + ArrayPrototypeSort(entries); + } - const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; + if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { + const nmore = entriesLengthWithoutEmptyItems - + inspectOptions.iterableLimit; + ArrayPrototypePush(entries, `... ${nmore} more items`); + } - 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); + 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(groups, entryIndentation) + ArrayPrototypeJoin(entries, 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, + 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) ) { - 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; + 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]; } - 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; - } + 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); + } + // 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 (iterableLimit < entries.length) { - ArrayPrototypePush(tmp, entries[entriesLength]); + 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]; } - entries = tmp; + ArrayPrototypePush(tmp, str); + } + if (iterableLimit < entries.length) { + ArrayPrototypePush(tmp, entries[entriesLength]); } - return entries; + entries = tmp; } - - let circular; - function handleCircular(value, cyan) { - let index = 1; - if (circular === undefined) { - circular = new Map(); + 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); - } else { - index = MapPrototypeGet(circular, value); - if (index === undefined) { - index = circular.size + 1; - MapPrototypeSet(circular, value, index); - } } - // Circular string is cyan - return cyan(`[Circular *${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); } - 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); - } + 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"); - } + return inspectFunction(value, inspectOptions); + case "object": // null is bold + if (value === null) { + return bold("null"); + } - if (ctxHas(value)) { - return handleCircular(value, cyan); - } + if (ctxHas(value)) { + return handleCircular(value, cyan); + } - return inspectObject( - value, - inspectOptions, - proxyDetails, - ); - default: - // Not implemented is red - return red("[Not Implemented]"); - } + 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", - ), - ); +} + +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", + }; - // 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 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", + ), + ); +} - return quoteString(string); +// 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); } - // 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 quoteString(string); +} - return `Symbol(${quoteString(symbol.description)})`; +// 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); } - const CTX_STACK = []; - function ctxHas(x) { - // Only check parent contexts - return ArrayPrototypeIncludes( - ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1), - x, - ); + if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { + return SymbolPrototypeToString(symbol); } - // 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); - } + 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; - } +} + +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( + 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 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, - ); - } - - 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), - ); + ); +} + +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), + ); +} + +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; + } } - function inspectRegExp(value, inspectOptions) { - const red = maybeColor(colors.red, inspectOptions); - return red(RegExpPrototypeToString(value)); // RegExps are red + 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(`<ref *${index}> `)); + } + } } + ArrayPrototypeShift(causes); - function inspectError(value, cyan) { - const causes = [value]; + let finalMessage = MapPrototypeGet(refMap, value) ?? ""; - let err = value; - while (err.cause) { - if (ArrayPrototypeIncludes(causes, err.cause)) { - ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); + 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; - } 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(`<ref *${index}> `)); - } } - } - 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 += line; finalMessage += "\n"; - finalMessage += ArrayPrototypeJoin(stackLines, "\n"); - } else { - finalMessage += value.stack; } - - finalMessage += ArrayPrototypeJoin( + const aggregateMessage = ArrayPrototypeJoin( ArrayPrototypeMap( - causes, - (cause) => - "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + - (cause?.stack ?? cause), + value.errors, + (error) => + StringPrototypeReplace( + inspectArgs([error]), + /^(?!\s*$)/gm, + StringPrototypeRepeat(" ", 4), + ), ), - "", + "\n", ); - - return finalMessage; + finalMessage += aggregateMessage; + finalMessage += "\n"; + finalMessage += ArrayPrototypeJoin(stackLines, "\n"); + } else { + finalMessage += value.stack; } - function inspectStringObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan + 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("<pending>")} }`; } - function inspectBooleanObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan - } + const prefix = state === PromiseState.Fulfilled + ? "" + : `${red("<rejected>")} `; - 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 - } + inspectOptions.indentLevel++; + const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; + inspectOptions.indentLevel--; - function inspectBigIntObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan + if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { + return `Promise {\n${ + StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) + }${str}\n}`; } - function inspectSymbolObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Symbol: ${maybeQuoteSymbol(SymbolPrototypeValueOf(value))}]`); // wrappers are in cyan - } + return `Promise { ${str} }`; +} - const PromiseState = { - Pending: 0, - Fulfilled: 1, - Rejected: 2, - }; - - function inspectPromise( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - const red = maybeColor(colors.red, inspectOptions); +function inspectProxy( + targetAndHandler, + inspectOptions, +) { + return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; +} - const { 0: state, 1: result } = core.getPromiseDetails(value); - - if (state === PromiseState.Pending) { - return `Promise { ${cyan("<pending>")} }`; - } - - const prefix = state === PromiseState.Fulfilled - ? "" - : `${red("<rejected>")} `; +function inspectRawObject( + value, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); - inspectOptions.indentLevel++; - const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; - inspectOptions.indentLevel--; + if (inspectOptions.indentLevel >= inspectOptions.depth) { + return [cyan("[Object]"), ""]; // wrappers are in cyan + } - if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { - return `Promise {\n${ - StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) - }${str}\n}`; - } + let baseString; - return `Promise { ${str} }`; + let shouldShowDisplayName = false; + let displayName = value[ + SymbolToStringTag + ]; + if (!displayName) { + displayName = getClassInstanceName(value); } - - function inspectProxy( - targetAndHandler, - inspectOptions, + if ( + displayName && displayName !== "Object" && displayName !== "anonymous" ) { - return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; + shouldShowDisplayName = true; } - function inspectRawObject( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return [cyan("[Object]"), ""]; // wrappers are in cyan - } + const entries = []; + const stringKeys = ObjectKeys(value); + const symbolKeys = ObjectGetOwnPropertySymbols(value); + if (inspectOptions.sorted) { + ArrayPrototypeSort(stringKeys); + ArrayPrototypeSort( + symbolKeys, + (s1, s2) => + StringPrototypeLocaleCompare( + s1.description ?? "", + s2.description ?? "", + ), + ); + } - let baseString; + const red = maybeColor(colors.red, inspectOptions); - let shouldShowDisplayName = false; - let displayName = value[ - SymbolToStringTag - ]; - if (!displayName) { - displayName = getClassInstanceName(value); - } - if ( - displayName && displayName !== "Object" && displayName !== "anonymous" - ) { - shouldShowDisplayName = true; - } + inspectOptions.indentLevel++; - const entries = []; - const stringKeys = ObjectKeys(value); - const symbolKeys = ObjectGetOwnPropertySymbols(value); - if (inspectOptions.sorted) { - ArrayPrototypeSort(stringKeys); - ArrayPrototypeSort( - symbolKeys, - (s1, s2) => - StringPrototypeLocaleCompare( - s1.description ?? "", - s2.description ?? "", - ), + 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}`, ); - } - - 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}]`); + } else { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor.get !== undefined && descriptor.set !== undefined) { ArrayPrototypePush( entries, - `${maybeQuoteString(key)}: ${inspectedValue}`, + `${maybeQuoteString(key)}: [Getter/Setter]`, ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `${maybeQuoteString(key)}: [Getter]`); } 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) - }`, - ); - } + 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; - } + 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}`); + 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)}]: ${inspectedValue}`, + `[${maybeQuoteSymbol(key)}]: [Getter/Setter]`, ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `[${maybeQuoteSymbol(key)}]: [Getter]`); } 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) - }`, - ); - } + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: ${ + inspectValueWithQuotes(value[key], inspectOptions) + }`, + ); } } + } - inspectOptions.indentLevel--; + inspectOptions.indentLevel--; - // Making sure color codes are ignored when calculating the total length - const totalLength = entries.length + inspectOptions.indentLevel + - colors.stripColor(ArrayPrototypeJoin(entries, "")).length; + // 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 (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}`; - } + if (shouldShowDisplayName) { + baseString = `${displayName} ${baseString}`; + } - let refIndex = ""; - if (circular !== undefined) { - const index = MapPrototypeGet(circular, value); - if (index !== undefined) { - refIndex = cyan(`<ref *${index}> `); - } + let refIndex = ""; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, value); + if (index !== undefined) { + refIndex = cyan(`<ref *${index}> `); } - - 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, - ); + 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 { - // Otherwise, default object formatting - let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions); - insp = refIndex + insp; - return insp; + ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); } + return [ + MathRound((r_ + m) * 255), + MathRound((g_ + m) * 255), + MathRound((b_ + m) * 255), + ]; } - - 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 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--; } - 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]); - } - currentKey = null; - currentPart = ""; - inValue = false; - continue; + } else if (inValue) { + if (c == ";") { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); } - } 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]; - } - - 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`; + return css; +} + +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.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 (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 (!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, "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, "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"; - } + } + 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; - - 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 = ""; - } + } + return ansi; +} + +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 = ""; + + 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"; + if (char == "%") { + string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + appendedChars = i + 1; + } } } - - 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); - } + string += StringPrototypeSlice(first, appendedChars); + if (usedStyle) { + string += "\x1b[0m"; } + } - if (rInspectOptions.indentLevel > 0) { - const groupIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - rInspectOptions.indentLevel, - ); - string = groupIndent + - StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); + 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); } + } - return string; + if (rInspectOptions.indentLevel > 0) { + const groupIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + rInspectOptions.indentLevel, + ); + string = groupIndent + + StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); } - const countMap = new Map(); - const timerMap = new Map(); - const isConsoleInstance = Symbol("isConsoleInstance"); + return string; +} - function getConsoleInspectOptions() { - return { - ...DEFAULT_INSPECT_OPTIONS, - colors: !colors.getNoColor(), - }; - } +const countMap = new Map(); +const timerMap = new Map(); +const isConsoleInstance = Symbol("isConsoleInstance"); - 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; - } +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, - ); - }; + log = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; - debug = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 0, - ); - }; + debug = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 0, + ); + }; - info = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 1, - ); - }; + info = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; - dir = (obj = undefined, options = {}) => { - this.#printFunc( - inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + - "\n", - 1, - ); - }; + dir = (obj = undefined, options = {}) => { + this.#printFunc( + inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + + "\n", + 1, + ); + }; - dirxml = this.dir; + dirxml = this.dir; - warn = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 2, - ); - }; + warn = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 2, + ); + }; - error = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 3, - ); - }; + error = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 3, + ); + }; - assert = (condition = false, ...args) => { - if (condition) { - return; - } + assert = (condition = false, ...args) => { + if (condition) { + return; + } - if (args.length === 0) { - this.error("Assertion failed"); - return; - } + if (args.length === 0) { + this.error("Assertion failed"); + return; + } - const [first, ...rest] = new SafeArrayIterator(args); + const [first, ...rest] = new SafeArrayIterator(args); - if (typeof first === "string") { - this.error( - `Assertion failed: ${first}`, - ...new SafeArrayIterator(rest), - ); - return; - } + if (typeof first === "string") { + this.error( + `Assertion failed: ${first}`, + ...new SafeArrayIterator(rest), + ); + return; + } - this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); - }; + this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); + }; - count = (label = "default") => { - label = String(label); + 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); - } + 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)}`); - }; + this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); + }; - countReset = (label = "default") => { - label = String(label); + countReset = (label = "default") => { + label = String(label); - if (MapPrototypeHas(countMap, label)) { - MapPrototypeSet(countMap, label, 0); - } else { - this.warn(`Count for '${label}' does not exist`); - } - }; + 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", - ); - } + 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); - } + 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 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 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]); + 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, ""); } - - 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; + ArrayPrototypePush(values, ""); } - MapPrototypeSet(timerMap, label, DateNow()); - }; + ArrayPrototypePush(indexKeys, k); + }); - timeLog = (label = "default", ...args) => { - label = String(label); + 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]; - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exists`); - return; - } + toTable(header, body); + }; - const startTime = MapPrototypeGet(timerMap, label); - const duration = DateNow() - startTime; + time = (label = "default") => { + label = String(label); - this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); - }; + if (MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' already exists`); + return; + } - timeEnd = (label = "default") => { - label = String(label); + MapPrototypeSet(timerMap, label, DateNow()); + }; - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exist`); - return; - } + timeLog = (label = "default", ...args) => { + label = String(label); - const startTime = MapPrototypeGet(timerMap, label); - MapPrototypeDelete(timerMap, label); - const duration = DateNow() - startTime; + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exists`); + return; + } - this.info(`${label}: ${duration}ms`); - }; + const startTime = MapPrototypeGet(timerMap, label); + const duration = DateNow() - startTime; - group = (...label) => { - if (label.length > 0) { - this.log(...new SafeArrayIterator(label)); - } - this.indentLevel += 2; - }; + this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); + }; - groupCollapsed = this.group; + timeEnd = (label = "default") => { + label = String(label); - groupEnd = () => { - if (this.indentLevel > 0) { - this.indentLevel -= 2; - } - }; - - clear = () => { - this.indentLevel = 0; - this.#printFunc(CSI.kClear, 1); - this.#printFunc(CSI.kClearScreenDown, 1); - }; + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exist`); + return; + } - trace = (...args) => { - const message = inspectArgs( - args, - { ...getConsoleInspectOptions(), indentLevel: 0 }, - ); - const err = { - name: "Trace", - message, - }; - ErrorCaptureStackTrace(err, this.trace); - this.error(err.stack); - }; + const startTime = MapPrototypeGet(timerMap, label); + MapPrototypeDelete(timerMap, label); + const duration = DateNow() - startTime; - // These methods are noops, but when the inspector is connected, they - // call into V8. - profile = (_label) => {}; - profileEnd = (_label) => {}; - timeStamp = (_label) => {}; + this.info(`${label}: ${duration}ms`); + }; - static [SymbolHasInstance](instance) { - return instance[isConsoleInstance]; + group = (...label) => { + if (label.length > 0) { + this.log(...new SafeArrayIterator(label)); } - } + this.indentLevel += 2; + }; - const customInspect = SymbolFor("Deno.customInspect"); + groupCollapsed = this.group; - function inspect( - value, - inspectOptions = {}, - ) { - circular = undefined; - return inspectValue(value, { - ...DEFAULT_INSPECT_OPTIONS, - ...inspectOptions, - }); - } + groupEnd = () => { + if (this.indentLevel > 0) { + this.indentLevel -= 2; + } + }; - /** 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; - }, - }); + clear = () => { + this.indentLevel = 0; + this.#printFunc(CSI.kClear, 1); + this.#printFunc(CSI.kClearScreenDown, 1); + }; - function getDescendantPropertyDescriptor(object, key) { - let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); - if (!propertyDescriptor) { - const prototype = ReflectGetPrototypeOf(object); - if (prototype) { - propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); - } - } - return propertyDescriptor; - } + trace = (...args) => { + const message = inspectArgs( + args, + { ...getConsoleInspectOptions(), indentLevel: 0 }, + ); + const err = { + name: "Trace", + message, + }; + ErrorCaptureStackTrace(err, this.trace); + this.error(err.stack); + }; - function getEvaluatedDescriptor(object, key) { - return { - configurable: true, - enumerable: true, - value: object[key], - }; - } - } + // These methods are noops, but when the inspector is connected, they + // call into V8. + profile = (_label) => {}; + profileEnd = (_label) => {}; + timeStamp = (_label) => {}; - // 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], - ); + 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 { - // Add additional console APIs from the inspector - consoleFromDeno[key] = consoleFromV8[key]; + 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, - }; - - window.__bootstrap.console = { - CSI, - inspectArgs, - Console, - customInspect, - inspect, - wrapConsole, - createFilteredInspectProxy, - quoteString, - }; -})(this); + function getEvaluatedDescriptor(object, key) { + return { + configurable: true, + enumerable: true, + value: object[key], + }; + } +} + +// 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 57c5d120b..965a879e7 100644 --- a/ext/console/internal.d.ts +++ b/ext/console/internal.d.ts @@ -3,14 +3,10 @@ /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> -declare namespace globalThis { - declare namespace __bootstrap { - declare namespace console { - declare function createFilteredInspectProxy<TObject>(params: { - object: TObject; - keys: (keyof TObject)[]; - evaluate: boolean; - }): Record<string, unknown>; - } - } +declare module "internal:ext/console/02_console.js" { + function createFilteredInspectProxy<TObject>(params: { + object: TObject; + keys: (keyof TObject)[]; + evaluate: boolean; + }): Record<string, unknown>; } diff --git a/ext/console/lib.rs b/ext/console/lib.rs index 4b3b45029..31ec884c1 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 7d30dcccf..c79a1e4e8 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -6,2394 +6,2534 @@ /// <reference path="../webidl/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - - 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; - - // P-521 is not yet supported. - const supportedNamedCurves = ["P-256", "P-384"]; - const recognisedUsages = [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]; - - 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 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", - }, - }; +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; + +// P-521 is not yet supported. +const supportedNamedCurves = ["P-256", "P-384"]; +const recognisedUsages = [ + "encrypt", + "decrypt", + "sign", + "verify", + "deriveKey", + "deriveBits", + "wrapKey", + "unwrapKey", +]; + +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 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); + } - // 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", + ); + } - // 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; + // Fast path everything below if the registered dictionary is "None". + if (desiredType === null) { + return { name: algName }; + } - // 5. - let desiredType = undefined; - for (const key in registeredAlgorithms) { - if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { - continue; - } - if ( - StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) - ) { - algName = key; - desiredType = registeredAlgorithms[key]; - } + // 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; } - if (desiredType === undefined) { - throw new DOMException( - "Unrecognized algorithm name", - "NotSupportedError", + 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"); } + } - // 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; + return normalizedAlgorithm; +} + +/** + * @param {ArrayBufferView | ArrayBuffer} input + * @returns {Uint8Array} + */ +function copyBuffer(input) { + return TypedArrayPrototypeSlice( + ArrayBufferIsView(input) + ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + : new Uint8Array(input), + ); +} + +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(); + } - // 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"); - } - } + /** @returns {string} */ + get type() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_type]; + } - return normalizedAlgorithm; + /** @returns {boolean} */ + get extractable() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_extractable]; } - /** - * @param {ArrayBufferView | ArrayBuffer} input - * @returns {Uint8Array} - */ - function copyBuffer(input) { - return TypedArrayPrototypeSlice( - ArrayBufferIsView(input) - ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) - : new Uint8Array(input), - ); + /** @returns {string[]} */ + get usages() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_usages]; } - 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 {object} */ + get algorithm() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_algorithm]; + } - /** @returns {string} */ - get type() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_type]; - } + [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<object, object>} */ +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", + ); + } - /** @returns {boolean} */ - get extractable() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_extractable]; + // 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."); + } - /** @returns {string[]} */ - get usages() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_usages]; + // 2. + return length; } - - /** @returns {object} */ - get algorithm() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_algorithm]; + case "HKDF": { + // 1. + return null; } - - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - type: this.type, - extractable: this.extractable, - algorithm: this.algorithm, - usages: this.usages, - }) - }`; + case "PBKDF2": { + // 1. + return null; } + default: + throw new TypeError("unreachable"); } +} - 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; +class SubtleCrypto { + constructor() { + webidl.illegalConstructor(); } - // https://w3c.github.io/webcrypto/#concept-usage-intersection /** - * @param {string[]} a - * @param {string[]} b - * @returns + * @param {string} algorithm + * @param {BufferSource} data + * @returns {Promise<Uint8Array>} */ - function usageIntersection(a, b) { - return a.filter((i) => b.includes(i)); - } + 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", + }); - // TODO(lucacasonato): this should be moved to rust - /** @type {WeakMap<object, object>} */ - const KEY_STORE = new WeakMap(); + data = copyBuffer(data); - 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", - ); - } + algorithm = normalizeAlgorithm(algorithm, "digest"); - // 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."); - } + const result = await core.opAsync( + "op_crypto_subtle_digest", + algorithm.name, + data, + ); - // 2. - return length; - } - case "HKDF": { - // 1. - return null; - } - case "PBKDF2": { - // 1. - return null; - } - default: - throw new TypeError("unreachable"); - } + return result.buffer; } - class SubtleCrypto { - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {string} algorithm - * @param {BufferSource} data - * @returns {Promise<Uint8Array>} - */ - 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", - }); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise<any>} + */ + 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", + }); - data = copyBuffer(data); + // 2. + data = copyBuffer(data); - algorithm = normalizeAlgorithm(algorithm, "digest"); + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); - const result = await core.opAsync( - "op_crypto_subtle_digest", - algorithm.name, - data, + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Encryption algorithm doesn't match key algorithm.", + "InvalidAccessError", ); + } - return result.buffer; + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { + throw new DOMException( + "Key does not support the 'encrypt' operation.", + "InvalidAccessError", + ); } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise<any>} - */ - 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 await encrypt(normalizedAlgorithm, key, data); + } - // 2. - data = copyBuffer(data); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise<any>} + */ + 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", + }); - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); + // 2. + data = copyBuffer(data); - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Encryption algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { - throw new DOMException( - "Key does not support the 'encrypt' operation.", - "InvalidAccessError", - ); - } + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Decryption algorithm doesn't match key algorithm.", + "OperationError", + ); + } - return await encrypt(normalizedAlgorithm, key, data); + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { + throw new DOMException( + "Key does not support the 'decrypt' operation.", + "InvalidAccessError", + ); } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise<any>} - */ - 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", - }); + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 2. - data = copyBuffer(data); + switch (normalizedAlgorithm.name) { + case "RSA-OAEP": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); + // 2. + if (normalizedAlgorithm.label) { + normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); + } else { + normalizedAlgorithm.label = new Uint8Array(); + } - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Decryption algorithm doesn't match key algorithm.", - "OperationError", - ); - } + // 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); - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { - throw new DOMException( - "Key does not support the 'decrypt' operation.", - "InvalidAccessError", - ); + // 6. + return plainText.buffer; } + case "AES-CBC": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - 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", - ); - } + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Counter must be 16 bytes", + "OperationError", + ); + } - // 2. - if (normalizedAlgorithm.label) { - normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); - } else { - normalizedAlgorithm.label = new Uint8Array(); - } + const plainText = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "AES-CBC", + iv: normalizedAlgorithm.iv, + length: key[_algorithm].length, + }, data); - // 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-CTR": { + normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - // 6. - return plainText.buffer; + // 1. + if (normalizedAlgorithm.counter.byteLength !== 16) { + throw new DOMException( + "Counter vector must be 16 bytes", + "OperationError", + ); } - 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; + // 2. + if ( + normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 + ) { + throw new DOMException( + "Counter length must not be 0 or greater than 128", + "OperationError", + ); } - case "AES-CTR": { - normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - // 1. - if (normalizedAlgorithm.counter.byteLength !== 16) { - throw new DOMException( - "Counter vector must be 16 bytes", - "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); - // 2. - if ( - normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 - ) { - throw new DOMException( - "Counter length must not be 0 or greater than 128", - "OperationError", - ); - } + // 4. + return cipherText.buffer; + } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 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); + // 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", + ); + } - // 4. - return cipherText.buffer; + // 2. + if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + throw new DOMException( + "Tag length overflows ciphertext", + "OperationError", + ); } - 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", - ); - } + // 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", + ); + } - // 2. - if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + // 4. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { throw new DOMException( - "Tag length overflows ciphertext", + "Additional data too large", "OperationError", ); } + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } - // 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); - // 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"); + // 9. + return plaintext.buffer; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } + } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise<any>} - */ - 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", - }); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise<any>} + */ + 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", + }); - // 1. - data = copyBuffer(data); + // 1. + data = copyBuffer(data); - // 2. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); + // 2. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + 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", - ); - } + // 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", - ); - } + // 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", - ); - } + 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); + // 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; + return signature.buffer; + } + case "RSA-PSS": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - 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); + // 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; + return signature.buffer; + } + case "ECDSA": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - 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"); - } + // 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); + 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; + 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); + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "HMAC", + hash: hashAlgorithm, + }, data); - return signature.buffer; + return signature.buffer; + } + case "Ed25519": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - 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; + // 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<any>} - */ - // 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<KeyUsage>"](keyUsages, { - prefix, - context: "Argument 5", - }); + throw new TypeError("unreachable"); + } - // 2. - if (format !== "jwk") { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - keyData = copyBuffer(keyData); - } else { - throw new TypeError("keyData is a JsonWebKey"); - } + /** + * @param {string} format + * @param {BufferSource} keyData + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsages[]} keyUsages + * @returns {Promise<any>} + */ + // 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<KeyUsage>"](keyUsages, { + prefix, + context: "Argument 5", + }); + + // 2. + if (format !== "jwk") { + if ( + ArrayBufferIsView(keyData) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) + ) { + keyData = copyBuffer(keyData); } else { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - throw new TypeError("keyData is not a JsonWebKey"); - } + 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 normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); - const algorithmName = normalizedAlgorithm.name; + 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"); + 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<any>} - */ - // 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", - }); + /** + * @param {string} format + * @param {CryptoKey} key + * @returns {Promise<any>} + */ + // 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 handle = key[_handle]; + // 2. + const innerKey = WeakMapPrototypeGet(KEY_STORE, handle); - const algorithmName = key[_algorithm].name; + const algorithmName = key[_algorithm].name; - let result; + 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"); + switch (algorithmName) { + case "HMAC": { + result = exportKeyHMAC(format, key, innerKey); + break; } - - if (key.extractable === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); + 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"); + } - return result; + if (key.extractable === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); } - /** - * @param {AlgorithmIdentifier} algorithm - * @param {CryptoKey} baseKey - * @param {number | null} length - * @returns {Promise<ArrayBuffer>} - */ - 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, { + return result; + } + + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number | null} length + * @returns {Promise<ArrayBuffer>} + */ + 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 2", + context: "Argument 3", }); - 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; + // 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<ArrayBuffer>} - */ - async deriveKey( - algorithm, - baseKey, + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number} length + * @returns {Promise<ArrayBuffer>} + */ + 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<KeyUsage>"](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 ) { - 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<KeyUsage>"](keyUsages, { - prefix, - context: "Argument 5", - }); + throw new SyntaxError("Invalid key usages"); + } + // 17. + return result; + } - // 2-3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} signature + * @param {BufferSource} data + * @returns {Promise<boolean>} + */ + 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", + }); - // 4-5. - const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm( - derivedKeyType, - "importKey", + // 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", ); + } - // 6-7. - const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm( - derivedKeyType, - "get key length", + if (!ArrayPrototypeIncludes(key[_usages], "verify")) { + throw new DOMException( + "Key does not support the 'verify' operation.", + "InvalidAccessError", ); + } - // 8-10. + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 11. - if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { - throw new DOMException( - "Invalid algorithm name", - "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", + ); + } - // 12. - if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) { - throw new DOMException( - "baseKey usages does not contain `deriveKey`", - "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; - // 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, - ); + // 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", + ); + } - // 16. - if ( - ArrayPrototypeIncludes(["private", "secret"], result[_type]) && - keyUsages.length == 0 - ) { - throw new SyntaxError("Invalid key usages"); + return ops.op_verify_ed25519(keyData, data, signature); } - // 17. - return result; } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} signature - * @param {BufferSource} data - * @returns {Promise<boolean>} - */ - 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", - }); + throw new TypeError("unreachable"); + } - // 2. - signature = copyBuffer(signature); + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise<any>} + */ + 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. - data = copyBuffer(data); + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); + } - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); + // 8. + if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { + throw new DOMException( + "Wrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + // 9. + if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { + throw new DOMException( + "Key does not support the 'wrapKey' operation.", + "InvalidAccessError", + ); + } - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Verifying algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 10. NotSupportedError will be thrown in step 12. + // 11. + if (key[_extractable] === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); + } - if (!ArrayPrototypeIncludes(key[_usages], "verify")) { - throw new DOMException( - "Key does not support the 'verify' operation.", - "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; + } - switch (normalizedAlgorithm.name) { - case "RSASSA-PKCS1-v1_5": { - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } + // 14-15. + if ( + supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = wrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { + switch (normalizedAlgorithm.name) { + case "AES-KW": { + const cipherText = await ops.op_crypto_wrap_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", - ); - } + algorithm: normalizedAlgorithm.name, + }, bytes); - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "RSA-PSS", - hash: hashAlgorithm, - signature, - }, data); + // 4. + return cipherText.buffer; } - case "HMAC": { - const hash = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "HMAC", - hash, - signature, - }, data); + default: { + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); } - case "ECDSA": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // 2. - const hash = normalizedAlgorithm.hash.name; + } + } 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<CryptoKey>} + */ + 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<KeyUsage>"](keyUsages, { + prefix, + context: "Argument 7", + }); - // 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", - ); - } + // 2. + wrappedKey = copyBuffer(wrappedKey); - return ops.op_verify_ed25519(keyData, data, signature); - } - } + let normalizedAlgorithm; - throw new TypeError("unreachable"); + try { + // 3. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); + } catch (_) { + // 4. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); } - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise<any>} - */ - 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", - }); + // 6. + const normalizedKeyAlgorithm = normalizeAlgorithm( + unwrappedKeyAlgorithm, + "importKey", + ); - let normalizedAlgorithm; + // 11. + if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { + throw new DOMException( + "Unwrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } - try { - // 2. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); - } catch (_) { - // 3. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); - } + // 12. + if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { + throw new DOMException( + "Key does not support the 'unwrapKey' operation.", + "InvalidAccessError", + ); + } - // 8. - if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { - throw new DOMException( - "Wrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 13. + let key; + if ( + supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = unwrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 9. - if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { - throw new DOMException( - "Key does not support the 'wrapKey' operation.", - "InvalidAccessError", - ); + 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", + ); + } - // 10. NotSupportedError will be thrown in step 12. - // 11. - if (key[_extractable] === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); + 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); + } - // 12. - const exportedKey = await this.exportKey(format, key); + // 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; + } - 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; + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise<any>} + */ + 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<KeyUsage>"](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"); + } + } - // 14-15. - if ( - supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined - ) { - const handle = wrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + return result; + } +} +const SubtleCryptoPrototype = SubtleCrypto.prototype; - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const cipherText = await ops.op_crypto_wrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, bytes); +async function generateKey(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; - // 4. - return cipherText.buffer; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined + switch (algorithmName) { + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== 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", - ); + throw new DOMException("Invalid key usages", "SyntaxError"); } - } - /** - * @param {string} format - * @param {BufferSource} wrappedKey - * @param {CryptoKey} unwrappingKey - * @param {AlgorithmIdentifier} unwrapAlgorithm - * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise<CryptoKey>} - */ - 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, + + // 2. + const keyData = await core.opAsync( + "op_crypto_generate_key", { - prefix, - context: "Argument 5", + algorithm: "RSA", + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, }, ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: "Argument 6", - }); - keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, { - prefix, - context: "Argument 7", + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, }); - // 2. - wrappedKey = copyBuffer(wrappedKey); + // 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, + ); - let normalizedAlgorithm; + // 14-18. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); - try { - // 3. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); - } catch (_) { - // 4. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); + // 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"); } - // 6. - const normalizedKeyAlgorithm = normalizeAlgorithm( - unwrappedKeyAlgorithm, - "importKey", + // 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, + }); - // 11. - if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { - throw new DOMException( - "Unwrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 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, + ); - // 12. - if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { - throw new DOMException( - "Key does not support the 'unwrapKey' operation.", - "InvalidAccessError", - ); + // 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"); } - // 13. - let key; + // 2-3. + const handle = {}; if ( - supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined + ArrayPrototypeIncludes( + supportedNamedCurves, + namedCurve, + ) ) { - const handle = unwrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + 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"); + } - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const plainText = await ops.op_crypto_unwrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, wrappedKey); + // 4-6. + const algorithm = { + name: algorithmName, + namedCurve, + }; + + // 7-11. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + handle, + ); - // 4. - key = plainText.buffer; - break; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined + // 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 ) { - // 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", - ); + throw new DOMException("Invalid key usages", "SyntaxError"); } - let bytes; - // 14. - if (format !== "jwk") { - bytes = key; + // 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 { - const k = new Uint8Array(key); - let str = ""; - for (let i = 0; i < k.length; i++) { - str += StringFromCharCode(k[i]); - } - bytes = JSONParse(str); + throw new DOMException("Curve not supported", "NotSupportedError"); } - // 15. - const result = await this.importKey( - format, - bytes, - normalizedKeyAlgorithm, + // 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, - keyUsages, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, ); - // 16. + + // 17-20. + return { publicKey, privateKey }; + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": { + // 1. if ( - (result[_type] == "secret" || result[_type] == "private") && - keyUsages.length == 0 + ArrayPrototypeFind( + usages, + (u) => + !ArrayPrototypeIncludes([ + "encrypt", + "decrypt", + "wrapKey", + "unwrapKey", + ], u), + ) !== undefined ) { - throw new SyntaxError("Invalid key type."); - } - // 17. - result[_extractable] = extractable; - // 18. - result[_usages] = usageIntersection(keyUsages, recognisedUsages); - // 19. - return result; - } + throw new DOMException("Invalid key usages", "SyntaxError"); + } - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise<any>} - */ - 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<KeyUsage>"](keyUsages, { - prefix, - context: "Argument 3", - }); + 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"); + } - const usages = keyUsages; + 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 normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey"); - const result = await generateKey( - normalizedAlgorithm, + const privateKey = constructKey( + "private", extractable, - usages, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, ); - 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) + return { publicKey, privateKey }; + } + case "Ed25519": { + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined ) { - if (result.privateKey[_usages].length === 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + throw new DOMException("Invalid key usages", "SyntaxError"); } - return result; - } - } - const SubtleCryptoPrototype = SubtleCrypto.prototype; + 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"); + } - async function generateKey(normalizedAlgorithm, extractable, usages) { - const algorithmName = normalizedAlgorithm.name; + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - 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"); - } + const publicHandle = {}; + WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); - // 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, - }; + const algorithm = { + name: algorithmName, + }; - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + publicHandle, + ); - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); - // 19-22. - return { publicKey, privateKey }; + return { publicKey, privateKey }; + } + case "HMAC": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - 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, - }); + // 2. + let length; + if (normalizedAlgorithm.length === undefined) { + length = null; + } else if (normalizedAlgorithm.length !== 0) { + length = normalizedAlgorithm.length; + } else { + throw new DOMException("Invalid length", "OperationError"); + } - // 4-8. - const algorithm = { - name: algorithmName, - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - hash: normalizedAlgorithm.hash, - }; + // 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, + }); - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["encrypt", "wrapKey"]), - algorithm, - handle, - ); + // 6-10. + const algorithm = { + name: algorithmName, + hash: { + name: normalizedAlgorithm.hash.name, + }, + length: keyData.byteLength * 8, + }; - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["decrypt", "unwrapKey"]), - algorithm, - handle, - ); + // 5, 11-13. + const key = constructKey( + "secret", + extractable, + usages, + algorithm, + handle, + ); - // 19-22. - return { publicKey, privateKey }; + // 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"); } - case "ECDSA": { - const namedCurve = normalizedAlgorithm.namedCurve; - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, keyData); - // 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"); - } + // 2-3. + const algorithm = { + name: "Ed25519", + }; - // 4-6. - const algorithm = { - name: algorithmName, - namedCurve, - }; + // 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"); + } - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - // 17-20. - return { publicKey, privateKey }; + 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"); } - case "ECDH": { - const namedCurve = normalizedAlgorithm.namedCurve; - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], 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"); + } - // 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"); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - // 4-6. - const algorithm = { - name: algorithmName, - namedCurve, - }; + const algorithm = { + name: "Ed25519", + }; - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - handle, - ); - - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "jwk": { + // 1. + const jwk = keyData; - // 17-20. - return { publicKey, privateKey }; - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": { - // 1. + // 2. + if (jwk.d !== undefined) { if ( ArrayPrototypeFind( - usages, + keyUsages, (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), + !ArrayPrototypeIncludes( + ["sign"], + u, + ), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); } - - return generateKeyAES(normalizedAlgorithm, extractable, usages); - } - case "AES-KW": { - // 1. + } else { if ( ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u), + keyUsages, + (u) => + !ArrayPrototypeIncludes( + ["verify"], + 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, - }; + // 3. + if (jwk.kty !== "OKP") { + throw new DOMException("Invalid key type", "DataError"); + } - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - publicHandle, - ); + // 4. + if (jwk.crv !== "Ed25519") { + throw new DOMException("Invalid curve", "DataError"); + } - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); + // 5. + if (jwk.alg !== undefined && jwk.alg !== "EdDSA") { + throw new DOMException("Invalid algorithm", "DataError"); + } - return { publicKey, privateKey }; + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ) { + throw new DOMException("Invalid key usage", "DataError"); } - case "Ed25519": { + + // 7. + if (jwk.key_ops !== undefined) { if ( ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), ) !== undefined ) { - throw new DOMException("Invalid key usages", "SyntaxError"); + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); } - 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) + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) ) { - throw new DOMException("Failed to generate key", "OperationError"); + 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 publicHandle = {}; - WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); - const algorithm = { - name: algorithmName, + name: "Ed25519", }; - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - publicHandle, - ); - - const privateKey = constructKey( + return constructKey( "private", extractable, - usageIntersection(usages, ["sign"]), + usageIntersection(keyUsages, recognisedUsages), algorithm, handle, ); + } else { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - 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, - }); + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - // 6-10. const algorithm = { - name: algorithmName, - hash: { - name: normalizedAlgorithm.hash.name, - }, - length: keyData.byteLength * 8, + name: "Ed25519", }; - // 5, 11-13. - const key = constructKey( - "secret", + return constructKey( + "public", extractable, - usages, + usageIntersection(keyUsages, recognisedUsages), algorithm, handle, ); - - // 14. - return key; } } + 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"); + } - 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); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, keyData); + // 2-3. + const algorithm = { + name: "X25519", + }; - // 2-3. - const algorithm = { - name: "Ed25519", - }; + // 4-6. + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + case "spki": { + // 1. + if (keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 4-6. - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_x25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); } - 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 handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + const algorithm = { + name: "X25519", + }; - const algorithm = { - name: "Ed25519", - }; + 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"); + } - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const privateKeyData = new Uint8Array(32); + if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { + throw new DOMException("Invalid key data", "DataError"); } - case "pkcs8": { - // 1. + + 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(["sign"], u), + (u) => + !ArrayPrototypeIncludes( + ["deriveKey", "deriveBits"], + 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.d === undefined && keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 3. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } + // 4. + 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.crv !== "X25519") { + 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) { + if (jwk.use !== "enc") { + throw new DOMException("Invalid key use", "DataError"); } + } - // 6. + // 7. + if (jwk.key_ops !== undefined) { if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined ) { - 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"); + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "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, + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", ); } } - 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"); - } + // 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, keyData); + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - // 2-3. const algorithm = { name: "X25519", }; - // 4-6. return constructKey( - "public", + "private", extractable, - [], + usageIntersection(keyUsages, ["deriveKey", "deriveBits"]), 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"); - } + } 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); @@ -2410,600 +2550,538 @@ handle, ); } - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + } + 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", + }; - const privateKeyData = new Uint8Array(32); - if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } + // 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", + ); + } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + // 5. + jwk.key_ops = key.usages; - const algorithm = { - name: "X25519", - }; + // 6. + jwk.ext = key[_extractable]; - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 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"); } - 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"); - } - } + break; + } + case "jwk": { + // 1. + const jwk = keyData; - // 3. - if (jwk.d === undefined && keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", + ); + } - // 4. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } + // Section 6.4.1 of RFC7518 + if (jwk.k === undefined) { + throw new DOMException( + "'k' property of JsonWebKey must be present", + "DataError", + ); + } - // 5. - if (jwk.crv !== "X25519") { - throw new DOMException("Invalid curve", "DataError"); - } + // 4. + const { rawData } = ops.op_crypto_import_key( + { algorithm: "AES" }, + { jwkSecret: jwk }, + ); + data = rawData.data; - // 6. - if (keyUsages.length > 0 && jwk.use !== undefined) { - if (jwk.use !== "enc") { - throw new DOMException("Invalid key use", "DataError"); + // 5. + switch (data.byteLength * 8) { + case 128: + if ( + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][128] + ) { + throw new DOMException("Invalid algorithm", "DataError"); } - } - - // 7. - if (jwk.key_ops !== undefined) { + break; + case 192: if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][192] ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); + throw new DOMException("Invalid algorithm", "DataError"); } - + break; + case 256: if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][256] ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); + throw new DOMException("Invalid algorithm", "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, + break; + default: + throw new DOMException( + "Invalid key length", + "DataError", ); - } } - 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; + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc" + ) { + throw new DOMException("Invalid key usages", "DataError"); } - 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", - ); + // 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", + ); } - // 5. - jwk.key_ops = key.usages; - - // 6. - jwk.ext = key[_extractable]; + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } - // 7. - return jwk; + // 8. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + + break; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } - function importKeyAES( - format, - normalizedAlgorithm, - keyData, + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data, + }); + + // 4-7. + const algorithm = { + name: algorithmName, + length: data.byteLength * 8, + }; + + const key = constructKey( + "secret", extractable, - keyUsages, - supportedKeyUsages, + 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 ) { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - const algorithmName = normalizedAlgorithm.name; + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 2. - let data = keyData; + // 3. + let hash; + let data; - switch (format) { - case "raw": { - // 2. - if ( - !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8) - ) { - throw new DOMException("Invalid key length", "Datarror"); - } + // 4. https://w3c.github.io/webcrypto/#hmac-operations + switch (format) { + case "raw": { + data = keyData; + hash = normalizedAlgorithm.hash; + break; + } + case "jwk": { + const jwk = keyData; - break; + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", + ); } - 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", + ); + } - // 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; - // 4. - const { rawData } = ops.op_crypto_import_key( - { algorithm: "AES" }, - { jwkSecret: jwk }, - ); - data = rawData.data; + // 5. + hash = normalizedAlgorithm.hash; - // 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: + // 6. + switch (hash.name) { + case "SHA-1": { + if (jwk.alg !== undefined && jwk.alg !== "HS1") { throw new DOMException( - "Invalid key length", + "'alg' property of JsonWebKey must be 'HS1'", "DataError", ); + } + break; } - - // 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 - ) { + case "SHA-256": { + if (jwk.alg !== undefined && jwk.alg !== "HS256") { throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + "'alg' property of JsonWebKey must be 'HS256'", "DataError", ); } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { + break; + } + case "SHA-384": { + if (jwk.alg !== undefined && jwk.alg !== "HS384") { throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + "'alg' property of JsonWebKey must be 'HS384'", "DataError", ); } + break; } - - // 8. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); + case "SHA-512": { + if (jwk.alg !== undefined && jwk.alg !== "HS512") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS512'", + "DataError", + ); + } + break; } - - 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; + default: + throw new TypeError("unreachable"); } - 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 }, + // 7. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ) { + throw new DOMException( + "'use' property of JsonWebKey must be 'sig'", + "DataError", ); - 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. + // 8. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined ) { throw new DOMException( - "'use' property of JsonWebKey must be 'sig'", + "'key_ops' property of JsonWebKey is invalid", "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) { + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } + } - break; + // 9. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + + 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"); + // 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", + ); } - // 7. - if (normalizedAlgorithm.length !== undefined) { + 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 ( - normalizedAlgorithm.length > length || - normalizedAlgorithm.length <= (length - 8) + !ArrayPrototypeIncludes( + supportedNamedCurves, + normalizedAlgorithm.namedCurve, + ) ) { throw new DOMException( - "Key length is invalid", + "Invalid namedCurve", "DataError", ); } - length = normalizedAlgorithm.length; - } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data, - }); + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - const algorithm = { - name: "HMAC", - length, - hash, - }; + // 3. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { raw: keyData }); - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); - return key; - } + // 4-5. + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); - function importKeyEC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name]; + 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"); + } - switch (format) { - case "raw": { - // 1. - if ( - !ArrayPrototypeIncludes( - supportedNamedCurves, - normalizedAlgorithm.namedCurve, - ) - ) { - throw new DOMException( - "Invalid namedCurve", - "DataError", - ); - } + // 2-9. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { pkcs8: keyData }); - // 2. + 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, @@ -3016,53 +3094,159 @@ ) { throw new DOMException("Invalid key usages", "SyntaxError"); } + } else if (keyUsages.length != 0) { + throw new DOMException("Key usage must be empty", "SyntaxError"); + } - // 3. - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { raw: keyData }); + // 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 handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); - // 4-5. - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, + // 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", ); + } - return key; + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + jwk.use !== supportedUsages.jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, + "DataError", + ); } - case "pkcs8": { - // 1. + + // 5. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { if ( ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), ) !== undefined ) { - throw new DOMException("Invalid key usages", "SyntaxError"); + 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", + ); } - // 2-9. + 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, - }, { pkcs8: keyData }); + }, { jwkPrivateEc: jwk }); const handle = {}; WeakMapPrototypeSet(KEY_STORE, handle, rawData); @@ -3081,31 +3265,11 @@ ); 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 + } else { const { rawData } = ops.op_crypto_import_key({ algorithm: normalizedAlgorithm.name, namedCurve: normalizedAlgorithm.namedCurve, - }, { spki: keyData }); + }, { jwkPublicEc: jwk }); const handle = {}; WeakMapPrototypeSet(KEY_STORE, handle, rawData); @@ -3115,7 +3279,6 @@ namedCurve: normalizedAlgorithm.namedCurve, }; - // 6-8. const key = constructKey( "public", extractable, @@ -3126,238 +3289,367 @@ return key; } - case "jwk": { - const jwk = keyData; + } + 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"); + } - const keyType = (jwk.d !== undefined) ? "private" : "public"; + // 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 }, + ); - // 2. + 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(supportedUsages[keyType], u), + (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 (jwk.kty !== "EC") { + // 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( - "'kty' property of JsonWebKey must be 'EC'", + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } - // 4. if ( - keyUsages.length > 0 && jwk.use !== undefined && - jwk.use !== supportedUsages.jwkUse + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) ) { throw new DOMException( - `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } + } - // 5. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { + 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( - "'key_ops' member of JsonWebKey is invalid", + `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, "DataError", ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { + } + } 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( - "'key_ops' member of JsonWebKey is invalid", + `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`, "DataError", ); - } } - - // 6. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "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 (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", - ); - } - } - } + // 9. + if (hash !== undefined) { + // 9.1. + const normalizedHash = normalizeAlgorithm(hash, "digest"); - // 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) { + // 9.2. + if (normalizedHash.name !== normalizedAlgorithm.hash.name) { throw new DOMException( - "'y' property of JsonWebKey is required for EC keys", + `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, "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; + // 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 { - 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, + throw new DOMException( + "only optimized private keys are supported", + "NotSupportedError", ); - - 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 }, + { jwkPrivateRsa: jwk }, ); const handle = {}; @@ -3379,31 +3671,28 @@ ); 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"); + } 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", + ); } - // 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 }, + { jwkPublicRsa: jwk }, ); const handle = {}; @@ -3426,453 +3715,241 @@ 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"); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyHKDF( + format, + keyData, + extractable, + keyUsages, +) { + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); } - function importKeyHKDF( - format, - keyData, - extractable, - keyUsages, + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined ) { - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 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", + ); + } - // 2. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } + // 3. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); - // 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"); + } - // 4-8. - const algorithm = { - name: "HKDF", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 9. - return key; + // 3. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); } - function importKeyPBKDF2( - format, - keyData, - extractable, - keyUsages, - ) { - // 1. - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } + // 4. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + // 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. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); + 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", + }; - // 4. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); + // 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"); + } +} - // 5-9. - const algorithm = { - name: "PBKDF2", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); +function exportKeyRSA(format, key, innerKey) { + switch (format) { + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); + } - // 10. - return key; - } + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + format: "pkcs8", + }, innerKey); - function exportKeyHMAC(format, key, innerKey) { - // 1. - if (innerKey == null) { - throw new DOMException("Key is not available", "OperationError"); + // 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); - 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", - }; + return data.buffer; + } + case "jwk": { + // 1-2. + const jwk = { + kty: "RSA", + }; - // 3. - const data = ops.op_crypto_export_key({ - format: "jwksecret", - algorithm: key[_algorithm].name, - }, innerKey); - jwk.k = data.k; + // 3. + const hash = key[_algorithm].hash.name; - // 4. - const algorithm = key[_algorithm]; - // 5. - const hash = algorithm.hash; - // 6. - switch (hash.name) { + // 4. + if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") { + switch (hash) { case "SHA-1": - jwk.alg = "HS1"; + jwk.alg = "RS1"; break; case "SHA-256": - jwk.alg = "HS256"; + jwk.alg = "RS256"; break; case "SHA-384": - jwk.alg = "HS384"; + jwk.alg = "RS384"; break; case "SHA-512": - jwk.alg = "HS512"; + jwk.alg = "RS512"; break; default: throw new DOMException( @@ -3880,848 +3957,763 @@ "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", - ); + } 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", + ); } - - // 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", - ); + } 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", + ); } - - // 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); + // 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; + // 7. + jwk.key_ops = key.usages; - // 8. - jwk.ext = key[_extractable]; + // 8. + jwk.ext = key[_extractable]; - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + 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; +function exportKeyEd25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - 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; + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - 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)]), + 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", ); - 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; + + 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); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + 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; +function exportKeyX25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - 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; + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - 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)]), + 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", ); - 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: + + 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", - ); - } +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); + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "raw", + }, innerKey); - return data.buffer; + return data.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); } - 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); + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "pkcs8", + }, innerKey); - return data.buffer; + return data.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - 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", + ); } - // 2. + 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, - format: "spki", }, innerKey); + ObjectAssign(jwk, data); - 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; + // 4. + jwk.key_ops = key.usages; - // 5. - jwk.ext = key[_extractable]; + // 5. + jwk.ext = key[_extractable]; - return jwk; - } else { // ECDH - // 1-2. - const jwk = { - kty: "EC", - }; + return jwk; + } else { // ECDH + // 1-2. + const jwk = { + kty: "EC", + }; - // missing step from spec - jwk.alg = "ECDH"; + // missing step from spec + jwk.alg = "ECDH"; - // 3.1 - jwk.crv = key[_algorithm].namedCurve; + // 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); + // 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; + // 4. + jwk.key_ops = key.usages; - // 5. - jwk.ext = key[_extractable]; + // 5. + jwk.ext = key[_extractable]; - return jwk; - } + return jwk; } - default: - throw new DOMException("Not implemented", "NotSupportedError"); } + 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"); - } +async function generateKeyAES(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; - // 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, - }); + // 2. + if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) { + throw new DOMException("Invalid key length", "OperationError"); + } - // 6-8. - const algorithm = { - name: algorithmName, - length: normalizedAlgorithm.length, - }; + // 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, - ); + // 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"); + } - // 12. - return key; - } + if (normalizedAlgorithm.iterations == 0) { + throw new DOMException( + "iterations must not be zero", + "OperationError", + ); + } - 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"); - } + const handle = baseKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - if (normalizedAlgorithm.iterations == 0) { - throw new DOMException( - "iterations must not be zero", - "OperationError", - ); - } + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - const handle = baseKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + const buf = await core.opAsync("op_crypto_derive_bits", { + key: keyData, + algorithm: "PBKDF2", + hash: normalizedAlgorithm.hash.name, + iterations: normalizedAlgorithm.iterations, + length, + }, normalizedAlgorithm.salt); - normalizedAlgorithm.salt = copyBuffer(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: keyData, - algorithm: "PBKDF2", - hash: normalizedAlgorithm.hash.name, - iterations: normalizedAlgorithm.iterations, + key: baseKeyData, + publicKey: publicKeyData, + algorithm: "ECDH", + namedCurve: publicKey[_algorithm].namedCurve, 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)); - } + // 8. + if (length === null) { + return buf.buffer; + } else if (buf.buffer.byteLength * 8 < length) { + throw new DOMException("Invalid length", "OperationError"); } else { - throw new DOMException("Not implemented", "NotSupportedError"); + 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"); } - 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); + const handle = baseKey[_handle]; + const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle); - normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info); + 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); + 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; + 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", + ); } - 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); + // 5. + const kHandle = baseKey[_handle]; + const k = WeakMapPrototypeGet(KEY_STORE, kHandle); - const uHandle = publicKey[_handle]; - const u = WeakMapPrototypeGet(KEY_STORE, uHandle); + 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); + const secret = new Uint8Array(32); + const isIdentity = ops.op_derive_bits_x25519(k, u, secret); - // 6. - if (isIdentity) { - throw new DOMException("Invalid key", "OperationError"); - } + // 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)); - } + // 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"); } + 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(); - } +async function encrypt(normalizedAlgorithm, key, data) { + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 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); + switch (normalizedAlgorithm.name) { + case "RSA-OAEP": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 6. - return cipherText.buffer; + // 2. + if (normalizedAlgorithm.label) { + normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); + } else { + normalizedAlgorithm.label = new Uint8Array(); } - case "AES-CBC": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (normalizedAlgorithm.iv.byteLength !== 16) { - throw new DOMException( - "Initialization vector must be 16 bytes", - "OperationError", - ); - } + // 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); - // 2. - const cipherText = await core.opAsync("op_crypto_encrypt", { - key: keyData, - algorithm: "AES-CBC", - length: key[_algorithm].length, - iv: normalizedAlgorithm.iv, - }, data); + // 6. + return cipherText.buffer; + } + case "AES-CBC": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 4. - return cipherText.buffer; + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Initialization vector must be 16 bytes", + "OperationError", + ); } - 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", - ); - } + // 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); - // 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); + // 1. + if (normalizedAlgorithm.counter.byteLength !== 16) { + throw new DOMException( + "Counter vector must be 16 bytes", + "OperationError", + ); + } - // 4. - return cipherText.buffer; + // 2. + if ( + normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128 + ) { + throw new DOMException( + "Counter length must not be 0 or greater than 128", + "OperationError", + ); } - case "AES-GCM": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (data.byteLength > (2 ** 39) - 256) { - throw new DOMException( - "Plaintext too large", - "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); - // 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", - ); - } + // 1. + if (data.byteLength > (2 ** 39) - 256) { + throw new DOMException( + "Plaintext too large", + "OperationError", + ); + } - // 3. - if (normalizedAlgorithm.additionalData !== undefined) { - if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { - throw new DOMException( - "Additional data 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", + ); + } - // 4. - if (normalizedAlgorithm.tagLength == undefined) { - normalizedAlgorithm.tagLength = 128; - } else if ( - !ArrayPrototypeIncludes( - [32, 64, 96, 104, 112, 120, 128], - normalizedAlgorithm.tagLength, - ) - ) { + // 3. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { throw new DOMException( - "Invalid tag length", + "Additional data too large", "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) + // 4. + if (normalizedAlgorithm.tagLength == undefined) { + normalizedAlgorithm.tagLength = 128; + } else if ( + !ArrayPrototypeIncludes( + [32, 64, 96, 104, 112, 120, 128], + normalizedAlgorithm.tagLength, ) ) { throw new DOMException( - "The provided ArrayBufferView is not an integer array type", - "TypeMismatchError", + "Invalid tag length", + "OperationError", ); } - const ui8 = new Uint8Array( - arrayBufferView.buffer, - arrayBufferView.byteOffset, - arrayBufferView.byteLength, - ); - ops.op_crypto_get_random_values(ui8); - return arrayBufferView; - } + // 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); - randomUUID() { - webidl.assertBranded(this, CryptoPrototype); - return ops.op_crypto_random_uuid(); + // 8. + return cipherText.buffer; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} - get subtle() { - webidl.assertBranded(this, CryptoPrototype); - return subtle; - } +webidl.configurePrototype(SubtleCrypto); +const subtle = webidl.createBranded(SubtleCrypto); - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; +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; } - webidl.configurePrototype(Crypto); - const CryptoPrototype = Crypto.prototype; + randomUUID() { + webidl.assertBranded(this, CryptoPrototype); + return ops.op_crypto_random_uuid(); + } - window.__bootstrap.crypto = { - SubtleCrypto, - crypto: webidl.createBranded(Crypto), - Crypto, - CryptoKey, - }; -})(this); + 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 d381672fd..86d50f8a5 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -4,484 +4,481 @@ /// <reference path="../../core/lib.deno_core.d.ts" /> /// <reference path="../webidl/internal.d.ts" /> -"use strict"; - -((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["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.KeyFormat = webidl.createEnumConverter("KeyFormat", [ - "raw", - "pkcs8", - "spki", - "jwk", - ]); - - webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]); - - webidl.converters["sequence<KeyUsage>"] = webidl.createSequenceConverter( - webidl.converters.KeyUsage, - ); +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; + +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.KeyType = webidl.createEnumConverter("KeyType", [ + "public", + "private", + "secret", +]); + +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["sequence<KeyUsage>"] = webidl.createSequenceConverter( + webidl.converters.KeyUsage, +); + +webidl.converters.HashAlgorithmIdentifier = + webidl.converters.AlgorithmIdentifier; + +/** @type {webidl.Dictionary} */ +const dictAlgorithm = [{ + key: "name", + converter: webidl.converters.DOMString, + required: true, +}]; + +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.HashAlgorithmIdentifier = - webidl.converters.AlgorithmIdentifier; +webidl.converters.RsaKeyGenParams = webidl + .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); - /** @type {__bootstrap.webidl.Dictionary} */ - const dictAlgorithm = [{ - key: "name", - converter: webidl.converters.DOMString, +const dictRsaHashedKeyGenParams = [ + ...new SafeArrayIterator(dictRsaKeyGenParams), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, required: true, - }]; - - webidl.converters.Algorithm = webidl - .createDictionaryConverter("Algorithm", dictAlgorithm); - - webidl.converters.BigInteger = webidl.converters.Uint8Array; - - /** @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.RsaKeyGenParams = webidl - .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); - - const dictRsaHashedKeyGenParams = [ - ...new SafeArrayIterator(dictRsaKeyGenParams), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; - - webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( - "RsaHashedKeyGenParams", - dictRsaHashedKeyGenParams, - ); + }, +]; + +webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( + "RsaHashedKeyGenParams", + dictRsaHashedKeyGenParams, +); + +const dictRsaHashedImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; - const dictRsaHashedImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; - - webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( - "RsaHashedImportParams", - dictRsaHashedImportParams, - ); +webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( + "RsaHashedImportParams", + dictRsaHashedImportParams, +); - webidl.converters.NamedCurve = webidl.converters.DOMString; +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, + }, +]; - const dictEcKeyImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "namedCurve", - converter: webidl.converters.NamedCurve, - required: true, - }, - ]; +webidl.converters.AesKeyGenParams = webidl + .createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); - webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( - "EcKeyImportParams", - dictEcKeyImportParams, - ); +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, + }, +]; - 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<RsaOtherPrimesInfo>"] = 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<DOMString>"], - }, - { - 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<RsaOtherPrimesInfo>"], - }, - { - key: "k", - converter: webidl.converters["DOMString"], - }, - ]; - - webidl.converters.JsonWebKey = webidl.createDictionaryConverter( - "JsonWebKey", - dictJsonWebKey, - ); +webidl.converters["EcdsaParams"] = webidl + .createDictionaryConverter("EcdsaParams", dictEcdsaParams); - 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 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<RsaOtherPrimesInfo>"] = webidl + .createSequenceConverter( + webidl.converters.RsaOtherPrimesInfo, ); - 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); +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<DOMString>"], + }, + { + 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<RsaOtherPrimesInfo>"], + }, + { + 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); diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 906e7d06f..de47e467b 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<u64>) -> 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 3ed554ecb..000000000 --- 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 54e635522..9790bb69f 100644 --- a/ext/fetch/20_headers.js +++ b/ext/fetch/20_headers.js @@ -8,472 +8,470 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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; - - const _headerList = Symbol("header list"); - const _iterableHeaders = Symbol("iterable headers"); - const _guard = Symbol("guard"); - - /** - * @typedef Header - * @type {[string, string]} - */ - /** - * @typedef HeaderList - * @type {Header[]} - */ +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"); + +/** + * @typedef Header + * @type {[string, string]} + */ + +/** + * @typedef HeaderList + * @type {Header[]} + */ + +/** + * @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."); + } - /** - * @param {string} potentialValue - * @returns {string} - */ - function normalizeHeaderValue(potentialValue) { - return httpTrim(potentialValue); + // 3. + if (headers[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); } - /** - * @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}`, - ); + // 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; } - appendHeader(headers, header[0], header[1]); + } else { + if (input[position] !== "\u002C") throw new TypeError("Unreachable"); + position += 1; } - } else { - for (const key in object) { - if (!ObjectPrototypeHasOwnProperty(object, key)) { - continue; + } + + 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; } - appendHeader(headers, key, object[key]); + 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; + }, + ); } - // Regex matching illegal chars in a header value - // deno-lint-ignore no-control-regex - const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; + /** @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); + } + } /** - * 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); + 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", + }); - // 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") { + if (this[_guard] == "immutable") { throw new TypeError("Headers are immutable."); } - // 7. - const list = headers[_headerList]; + const list = this[_headerList]; const lowercaseName = byteLowerCase(name); for (let i = 0; i < list.length; i++) { if (byteLowerCase(list[i][0]) === lowercaseName) { - name = list[i][0]; - break; + ArrayPrototypeSplice(list, i, 1); + i--; } } - 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"); + 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); } /** - * 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, ""); + 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", + }); - ArrayPrototypePush(values, value); - value = ""; + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); } - 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; - } + 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 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; - }, - ); } + return false; + } - /** @param {HeadersInit} [init] */ - constructor(init = undefined) { - const prefix = "Failed to construct 'Headers'"; - if (init !== undefined) { - init = webidl.converters["HeadersInit"](init, { - prefix, - context: "Argument 1", - }); - } + /** + * @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", + }); - this[webidl.brand] = webidl.brand; - this[_guard] = "none"; - if (init !== undefined) { - fillHeaders(this, init); - } - } + value = normalizeHeaderValue(value); - /** - * @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); + // 2. + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); } - - /** - * @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--; - } - } + if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { + throw new TypeError("Header value is not valid."); } - /** - * @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); + if (this[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); } - /** - * @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; + 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--; } } - 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]); - } + 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)}`; + [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)}`; } +} - webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); +webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); - webidl.configurePrototype(Headers); - const HeadersPrototype = Headers.prototype; +webidl.configurePrototype(Headers); +const HeadersPrototype = Headers.prototype; - webidl.converters["HeadersInit"] = (V, opts) => { - // Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence<sequence<ByteString>>"](V, opts); - } - return webidl.converters["record<ByteString, ByteString>"](V, opts); +webidl.converters["HeadersInit"] = (V, opts) => { + // Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence<sequence<ByteString>>"](V, opts); } - throw webidl.makeException( - TypeError, - "The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'", - 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; + return webidl.converters["record<ByteString, ByteString>"](V, opts); } - - /** - * @param {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]; - } - - window.__bootstrap.headers = { - headersFromHeaderList, - headerListFromHeaders, - getDecodeSplitHeader, - guardFromHeaders, - fillHeaders, - getHeader, - Headers, - }; -})(this); + throw webidl.makeException( + TypeError, + "The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'", + 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 {Headers} headers + * @returns {HeaderList} + */ +function headerListFromHeaders(headers) { + return headers[_headerList]; +} + +/** + * @param {Headers} headers + * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} + */ +function guardFromHeaders(headers) { + return headers[_guard]; +} + +export { + fillHeaders, + getDecodeSplitHeader, + getHeader, + guardFromHeaders, + headerListFromHeaders, + Headers, + headersFromHeaderList, +}; diff --git a/ext/fetch/21_formdata.js b/ext/fetch/21_formdata.js index d253976ef..1639646e0 100644 --- a/ext/fetch/21_formdata.js +++ b/ext/fetch/21_formdata.js @@ -8,516 +8,518 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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 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 }); - } - if ( - ObjectPrototypeIsPrototypeOf(FilePrototype, value) && - filename !== undefined - ) { - value = new File([value], filename, { - type: value.type, - lastModified: value.lastModified, - }); +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"); + +/** + * @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(); } - 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} */ - - 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, { + 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, { + 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, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { prefix, - context: "Argument 2", + context: "Argument 3", }); } - - const entry = createEntry(name, valueOrBlobValue, filename); - - 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, { + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); - - 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 }); + const entry = createEntry(name, valueOrBlobValue, filename); - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); + ArrayPrototypePush(this[entryList], entry); + } - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) return entry.value; + /** + * @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--; } - 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", - }); + /** + * @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 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; + 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 {boolean} - */ - has(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'has' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + /** + * @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", + }); - 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; + } - 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 + * @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, { + /** + * @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 (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, { + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { prefix, - context: "Argument 2", + context: "Argument 3", }); } + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + prefix, + context: "Argument 2", + }); + } - const entry = createEntry(name, valueOrBlobValue, filename); + 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--; - } + 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="`; - - // 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)|(?<!\r)\n/g, CRLF) + CRLF, - ); - } else { - ArrayPrototypePush( - chunks, - prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + - CRLF + - `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, - value, - CRLF, - ); - } + return StringPrototypeReplace( + isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"), + /([\n\r"])/g, + (c) => escapeMap[c], + ); +}; + +/** + * 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="`; + + // 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)|(?<!\r)\n/g, CRLF) + CRLF, + ); + } else { + ArrayPrototypePush( + chunks, + prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + + CRLF + + `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, + value, + CRLF, + ); } + } - ArrayPrototypePush(chunks, `--${boundary}--`); - - return new Blob(chunks, { - type: "multipart/form-data; boundary=" + boundary, - }); + ArrayPrototypePush(chunks, `--${boundary}--`); + + return new Blob(chunks, { + type: "multipart/form-data; boundary=" + boundary, + }); +} + +/** + * @param {string} value + * @returns {Map<string, string>} + */ +function parseContentDisposition(value) { + /** @type {Map<string, string>} */ + 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 {string} value - * @returns {Map<string, string>} + * @param {Uint8Array} body + * @param {string | undefined} boundary */ - function parseContentDisposition(value) { - /** @type {Map<string, string>} */ - 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"), - ); - } + constructor(body, boundary) { + if (!boundary) { + throw new TypeError("multipart/form-data must provide a boundary"); } - return params; + + this.boundary = `--${boundary}`; + this.body = body; + this.boundaryChars = core.encode(this.boundary); } - 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"); + /** + * @param {string} headersText + * @returns {{ headers: Headers, disposition: Map<string, string> }} + */ + #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 } - - this.boundary = `--${boundary}`; - this.body = body; - this.boundaryChars = core.encode(this.boundary); + const key = StringPrototypeSlice(rawHeader, 0, sepIndex); + const value = StringPrototypeSlice(rawHeader, sepIndex + 1); + headers.set(key, value); } - /** - * @param {string} headersText - * @returns {{ headers: Headers, disposition: Map<string, string> }} - */ - #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") ?? "", - ); + const disposition = parseContentDisposition( + headers.get("Content-Disposition") ?? "", + ); - return { headers, disposition }; - } + return { headers, disposition }; + } - /** - * @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."); + /** + * @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."); + } - const formData = new FormData(); - let headerText = ""; - let boundaryIndex = 0; - let state = 0; - let fileStart = 0; + 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; + 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) { + 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 === 3 && isNewLine) { - state = 4; - fileStart = i + 1; - } else if (state === 4) { - if (this.boundaryChars[boundaryIndex] !== byte) { - boundaryIndex = 0; - } else { - boundaryIndex++; + } + } 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 (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)); - } + 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; } + } 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; + return formData; } - - webidl.converters["FormData"] = webidl - .createInterfaceConverter("FormData", FormDataPrototype); - - globalThis.__bootstrap.formData = { - FormData, - FormDataPrototype, - formDataToBlob, - parseFormData, - formDataFromEntries, - }; -})(globalThis); +} + +/** + * @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 bea1abce2..48819650a 100644 --- a/ext/fetch/22_body.js +++ b/ext/fetch/22_body.js @@ -10,462 +10,462 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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 {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 {string} + * @param {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} stream */ - function chunkToString(chunk) { - return typeof chunk === "string" ? chunk : core.decode(chunk); + constructor(stream) { + /** @type {ReadableStream<Uint8Array> | { 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; } - class InnerBody { - /** - * @param {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} stream - */ - constructor(stream) { - /** @type {ReadableStream<Uint8Array> | { 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); + 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.consumed; } + return this.streamOrStatic; + } - /** - * @returns {boolean} - */ - consumed() { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - return isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; + /** + * 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; + } - /** - * https://fetch.spec.whatwg.org/#concept-body-consume-body - * @returns {Promise<Uint8Array>} - */ - 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; - } + /** + * @returns {boolean} + */ + consumed() { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + return isReadableStreamDisturbed(this.streamOrStatic); } + return this.streamOrStatic.consumed; + } - cancel(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - this.streamOrStatic.cancel(error); - } else { - this.streamOrStatic.consumed = true; - } + /** + * https://fetch.spec.whatwg.org/#concept-body-consume-body + * @returns {Promise<Uint8Array>} + */ + 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; } + } - error(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - errorReadableStream(this.streamOrStatic, error); - } else { - this.streamOrStatic.consumed = true; - } + cancel(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + this.streamOrStatic.cancel(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; + error(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + errorReadableStream(this.streamOrStatic, error); + } else { + this.streamOrStatic.consumed = true; } + } - /** - * @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; - } + /** + * @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; } /** - * @param {any} prototype - * @param {symbol} bodySymbol - * @param {symbol} mimeTypeSymbol - * @returns {void} + * @returns {InnerBody} */ - function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { - async function consumeBody(object, type) { - webidl.assertBranded(object, prototype); + 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; + } +} - const body = object[bodySymbol] !== null - ? await object[bodySymbol].consume() - : new Uint8Array(); +/** + * @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 mimeType = type === "Blob" || type === "FormData" - ? object[mimeTypeSymbol] - : null; - return packageData(body, type, mimeType); - } + const body = object[bodySymbol] !== null + ? await object[bodySymbol].consume() + : new Uint8Array(); - /** @type {PropertyDescriptorMap} */ - const mixin = { - body: { - /** - * @returns {ReadableStream<Uint8Array> | null} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] === null) { - return null; - } else { - return this[bodySymbol].stream; - } - }, - configurable: true, - enumerable: true, + const mimeType = type === "Blob" || type === "FormData" + ? object[mimeTypeSymbol] + : null; + return packageData(body, type, mimeType); + } + + /** @type {PropertyDescriptorMap} */ + const mixin = { + body: { + /** + * @returns {ReadableStream<Uint8Array> | null} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] === null) { + return null; + } else { + return this[bodySymbol].stream; + } }, - bodyUsed: { - /** - * @returns {boolean} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] !== null) { - return this[bodySymbol].consumed(); - } - return false; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + bodyUsed: { + /** + * @returns {boolean} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] !== null) { + return this[bodySymbol].consumed(); + } + return false; }, - arrayBuffer: { - /** @returns {Promise<ArrayBuffer>} */ - value: function arrayBuffer() { - return consumeBody(this, "ArrayBuffer"); - }, - writable: true, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + arrayBuffer: { + /** @returns {Promise<ArrayBuffer>} */ + value: function arrayBuffer() { + return consumeBody(this, "ArrayBuffer"); }, - blob: { - /** @returns {Promise<Blob>} */ - value: function blob() { - return consumeBody(this, "Blob"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + blob: { + /** @returns {Promise<Blob>} */ + value: function blob() { + return consumeBody(this, "Blob"); }, - formData: { - /** @returns {Promise<FormData>} */ - value: function formData() { - return consumeBody(this, "FormData"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + formData: { + /** @returns {Promise<FormData>} */ + value: function formData() { + return consumeBody(this, "FormData"); }, - json: { - /** @returns {Promise<any>} */ - value: function json() { - return consumeBody(this, "JSON"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + json: { + /** @returns {Promise<any>} */ + value: function json() { + return consumeBody(this, "JSON"); }, - text: { - /** @returns {Promise<string>} */ - value: function text() { - return consumeBody(this, "text"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + text: { + /** @returns {Promise<string>} */ + value: function text() { + return consumeBody(this, "text"); }, - }; - return ObjectDefineProperties(prototype, mixin); - } + 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.", - ); - } - 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] }), - ), +/** + * 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<Uint8Array> | { 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<Uint8Array> | { 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; } - 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; + } 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"); } - 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 7b9f5c446..9d37f1b7f 100644 --- a/ext/fetch/22_http_client.js +++ b/ext/fetch/22_http_client.js @@ -9,40 +9,34 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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 e266a7e44..1e8d5c1ec 100644 --- a/ext/fetch/23_request.js +++ b/ext/fetch/23_request.js @@ -8,657 +8,664 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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; - - 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](); - } +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"); + +/** + * @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]], + ); - /** - * @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 - */ + let body = null; + if (request.body !== null && !skipBody) { + body = request.body.clone(); + } - /** - * @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."); } - /** - * @param {string} m - * @returns {string} - */ - function validateAndNormalizeMethod(m) { - // Fast path for well-known methods - if (isKnownMethod(m)) { - return m; + 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]; + } - // 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."); + 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; } - return upperCase; } - class Request { + /** + * 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", + }); + + this[webidl.brand] = webidl.brand; + /** @type {InnerRequest} */ - [_request]; - /** @type {Headers} */ - [_headersCache]; - [_getHeaders]; - - /** @type {Headers} */ - get [_headers]() { - if (this[_headersCache] === undefined) { - this[_headersCache] = this[_getHeaders](); + let request; + const baseURL = getLocationHref(); + + // 4. + let signal = null; + + // 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"); } - return this[_headersCache]; + 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]; } - set [_headers](value) { - this[_headersCache] = value; + // 12. is folded into the else statement of step 6 above. + + // 22. + if (init.redirect !== undefined) { + request.redirectMode = init.redirect; } - /** @type {AbortSignal} */ - [_signal]; - get [_mimeType]() { - const values = getDecodeSplitHeader( - headerListFromHeaders(this[_headers]), - "Content-Type", - ); - return extractMimeType(values); + // 25. + if (init.method !== undefined) { + let method = init.method; + method = validateAndNormalizeMethod(method); + request.method = method; } - get [_body]() { - if (this[_flash]) { - return this[_flash].body; - } else { - return this[_request].body; - } + + // 26. + if (init.signal !== undefined) { + signal = init.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", - }); - - this[webidl.brand] = webidl.brand; - - /** @type {InnerRequest} */ - let request; - const baseURL = getLocationHref(); - - // 4. - let signal = null; - - // 5. - if (typeof input === "string") { - const parsedURL = new URL(input, baseURL); - request = newInnerRequest( - () => "GET", - parsedURL.href, - () => [], - null, - true, + // 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" }, ); - } 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]; } + request.clientRid = init.client?.rid ?? null; + } - // 12. is folded into the else statement of step 6 above. + // 27. + this[_request] = request; - // 22. - if (init.redirect !== undefined) { - request.redirectMode = init.redirect; - } + // 28. + this[_signal] = abortSignal.newSignal(); - // 25. - if (init.method !== undefined) { - let method = init.method; - method = validateAndNormalizeMethod(method); - request.method = method; - } + // 29. + if (signal !== null) { + abortSignal.follow(this[_signal], signal); + } - // 26. - if (init.signal !== undefined) { - signal = init.signal; - } + // 30. + this[_headers] = headersFromHeaderList(request.headerList, "request"); - // 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; + // 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); + } - // 27. - this[_request] = request; - - // 28. - this[_signal] = abortSignal.newSignal(); - - // 29. - if (signal !== null) { - abortSignal.follow(this[_signal], signal); - } + // 33. + let inputBody = null; + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { + inputBody = input[_body]; + } - // 30. - this[_headers] = headersFromHeaderList(request.headerList, "request"); + // 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."); + } - // 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); - } + // 35. + let initBody = null; - // 33. - let inputBody = null; - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { - inputBody = input[_body]; + // 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); } + } - // 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."); - } + // 37. + const inputOrInitBody = initBody ?? inputBody; - // 35. - let initBody = null; + // 39. + let finalBody = inputOrInitBody; - // 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); - } + // 40. + if (initBody === null && inputBody !== null) { + if (input[_body] && input[_body].unusable()) { + throw new TypeError("Input request's body is unusable."); } + finalBody = inputBody.createProxy(); + } - // 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; + } - // 41. - request.body = finalBody; + get method() { + webidl.assertBranded(this, RequestPrototype); + if (this[_method]) { + return this[_method]; } - - 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]; - } + 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 url() { + webidl.assertBranded(this, RequestPrototype); + if (this[_url]) { + return this[_url]; } - get headers() { - webidl.assertBranded(this, RequestPrototype); - return this[_headers]; + if (this[_flash]) { + this[_url] = this[_flash].urlCb(); + return this[_url]; + } else { + this[_url] = this[_request].url(); + return this[_url]; } + } - get redirect() { - webidl.assertBranded(this, RequestPrototype); - if (this[_flash]) { - return this[_flash].redirectMode; - } - return this[_request].redirectMode; - } + get headers() { + webidl.assertBranded(this, RequestPrototype); + return this[_headers]; + } - get signal() { - webidl.assertBranded(this, RequestPrototype); - return this[_signal]; + get redirect() { + webidl.assertBranded(this, RequestPrototype); + if (this[_flash]) { + return this[_flash].redirectMode; } + return this[_request].redirectMode; + } - 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(); + get signal() { + webidl.assertBranded(this, RequestPrototype); + return this[_signal]; + } - if (this[_signal]) { - abortSignal.follow(newSignal, 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[_flash]) { - return fromInnerRequest( - newReq, - newSignal, - guardFromHeaders(this[_headers]), - true, - ); - } + 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; } - - /** - * @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, + // 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]; +} + +/** + * @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; - } - - 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); + headerList: headersCb, + streamRid, + serverId, + redirectMode: "follow", + redirectCount: 0, + }; + request[_getHeaders] = () => headersFromHeaderList(headersCb(), "request"); + return request; +} + +export { + _flash, + fromFlashRequest, + fromInnerRequest, + newInnerRequest, + processUrlList, + Request, + RequestPrototype, + toInnerRequest, +}; diff --git a/ext/fetch/23_response.js b/ext/fetch/23_response.js index 070068d28..46912135a 100644 --- a/ext/fetch/23_response.js +++ b/ext/fetch/23_response.js @@ -9,510 +9,510 @@ /// <reference path="../web/06_streams_types.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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 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 _response = Symbol("response"); - const _headers = Symbol("headers"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); + +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 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"); + +/** + * @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 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: 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; + } /** - * @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] + * @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 {boolean} + * @returns {Response} */ - function nullBodyStatus(status) { - return status === 101 || status === 204 || status === 205 || status === 304; + 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 {number} status - * @returns {boolean} + * @param {any} data + * @param {ResponseInit} init + * @returns {Response} */ - function redirectStatus(status) { - return status === 301 || status === 302 || status === 303 || - status === 307 || status === 308; + 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; } /** - * https://fetch.spec.whatwg.org/#concept-response-clone - * @param {InnerResponse} response - * @returns {InnerResponse} + * @param {BodyInit | null} body + * @param {ResponseInit} init */ - function cloneInnerResponse(response) { - const urlList = [...new SafeArrayIterator(response.urlList)]; - const headerList = ArrayPrototypeMap( - response.headerList, - (x) => [x[0], x[1]], + 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 body = null; - if (response.body !== null) { - body = response.body.clone(); + let bodyWithType = null; + if (body !== null) { + bodyWithType = extractBody(body); } - - 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]; - }, - }; + initializeAResponse(this, init, bodyWithType); + this[webidl.brand] = webidl.brand; } /** - * @returns {InnerResponse} + * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} */ - 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]; - }, - }; + get type() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].type; } /** - * @param {string} error - * @returns {InnerResponse} + * @returns {string} */ - function networkError(error) { - const resp = newInnerResponse(0); - resp.type = "error"; - resp.error = error; - return resp; + 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 {InnerResponse} + * @returns {boolean} */ - function abortedNetworkError() { - const resp = networkError("aborted"); - resp.aborted = true; - return resp; + get redirected() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].urlList.length > 1; } /** - * https://fetch.spec.whatwg.org#initialize-a-response - * @param {Response} response - * @param {ResponseInit} init - * @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType + * @returns {number} */ - 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]); - } - } - } + get status() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].status; } - 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", - ], - })); - } + /** + * @returns {boolean} + */ + get ok() { + webidl.assertBranded(this, ResponsePrototype); + const status = this[_response].status; + return status >= 200 && status <= 299; } - 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" && !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); - }; + /** + * @returns {string} + */ + get statusText() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].statusMessage; + } /** - * @param {Response} response - * @returns {InnerResponse} + * @returns {Headers} */ - function toInnerResponse(response) { - return response[_response]; + get headers() { + webidl.assertBranded(this, ResponsePrototype); + return this[_headers]; } /** - * @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; + 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; } - 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); + [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 ddb023a37..9c136f242 100644 --- a/ext/fetch/26_fetch.js +++ b/ext/fetch/26_fetch.js @@ -9,578 +9,577 @@ /// <reference path="./internal.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" /> /// <reference lib="esnext" /> -"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 REQUEST_BODY_HEADER_NAMES = [ - "content-encoding", - "content-language", - "content-location", - "content-type", - ]; - - 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 {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); +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 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 {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<Uint8Array>} + */ +function createResponseBodyStream(responseBodyRid, terminator) { + const readable = readableStreamForRid(responseBodyRid); + + function onAbort() { + errorReadableStream(readable, terminator.reason); + core.tryClose(responseBodyRid); } - /** - * @param {number} responseBodyRid - * @param {AbortSignal} [terminator] - * @returns {ReadableStream<Uint8Array>} - */ - function createResponseBodyStream(responseBodyRid, terminator) { - const readable = readableStreamForRid(responseBodyRid); - - function onAbort() { - errorReadableStream(readable, terminator.reason); - core.tryClose(responseBodyRid); + // TODO(lucacasonato): clean up registration + terminator[abortSignal.add](onAbort); + + return readable; +} + +/** + * @param {InnerRequest} req + * @param {boolean} recursive + * @param {AbortSignal} terminator + * @returns {Promise<InnerResponse>} + */ +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); + const body = new InnerBody(req.blobUrlEntry.stream()); + terminator[abortSignal.add](() => body.error(terminator.reason)); + processUrlList(req.urlList, req.urlListProcessed); - return readable; + 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)], + }; } - /** - * @param {InnerRequest} req - * @param {boolean} recursive - * @param {AbortSignal} terminator - * @returns {Promise<InnerResponse>} - */ - 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> | Uint8Array | null} */ + let reqBody = null; - /** @type {ReadableStream<Uint8Array> | Uint8Array | null} */ - let reqBody = null; - - if (req.body !== null) { + if (req.body !== null) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + req.body.streamOrStatic, + ) + ) { if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - req.body.streamOrStatic, - ) + req.body.length === null || + ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) ) { - if ( - req.body.length === null || - ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) - ) { - reqBody = req.body.stream; + 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 { - 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); + reqBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } - } 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; + 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); - } + 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); } - terminator[abortSignal.add](onAbort); - - let requestSendError; - let requestSendErrorSet = false; if (requestBodyRid !== null) { - if ( - reqBody === null || - !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody) - ) { - throw new TypeError("Unreachable"); + 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; + } } - 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 + if (done && !terminator.aborted) { + try { + await core.shutdown(requestBodyRid); + } catch (err) { + if (!terminator.aborted) { 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); } + 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, + }); } - 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; - } + 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)) { + 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<InnerResponse>} + */ +function httpRedirectFetch(request, response, terminator) { + const locationHeaders = ArrayPrototypeFilter( + response.headerList, + (entry) => byteLowerCase(entry[0]) === "location", + ); + if (locationHeaders.length === 0) { return response; } - - /** - * @param {InnerRequest} request - * @param {InnerResponse} response - * @param {AbortSignal} terminator - * @returns {Promise<InnerResponse>} - */ - 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, + 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 (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 ( + ((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); } + 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 {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; + } - /** - * @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); + // 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")) { + ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); + } - if (!requestObject.headers.has("Accept-Language")) { - ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]); - } + 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); }, - ); - }); - if (opPromise) { - PromisePrototypeCatch(result, () => {}); - return (async function fetch() { - await opPromise; - return result; - })(); - } - return result; + ), + (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); +function abortFetch(request, responseObject, error) { + if (request.body !== null) { + if (WeakMapPrototypeHas(requestBodyReaders, request)) { + WeakMapPrototypeGet(requestBodyReaders, request).cancel(error); + } else { + request.body.cancel(error); } - return 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", + }); - /** - * 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", - }); - - // 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.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}`); - } + // 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 { + // 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); - } - } 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 13a91d2d0..596e3ffcb 100644 --- a/ext/fetch/internal.d.ts +++ b/ext/fetch/internal.d.ts @@ -5,106 +5,98 @@ /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> -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 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 namespace fetchBody { - function mixinBody( - prototype: any, - bodySymbol: symbol, - mimeTypeSymbol: symbol, - ): void; - class InnerBody { - constructor(stream?: ReadableStream<Uint8Array>); - stream: ReadableStream<Uint8Array>; - source: null | Uint8Array | Blob | FormData; - length: null | number; - unusable(): boolean; - consume(): Promise<Uint8Array>; - clone(): InnerBody; - } - function extractBody(object: BodyInit): { - body: InnerBody; - contentType: string | null; - }; - } +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 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/22_body.js" { + function mixinBody( + prototype: any, + bodySymbol: symbol, + mimeTypeSymbol: symbol, + ): void; + class InnerBody { + constructor(stream?: ReadableStream<Uint8Array>); + stream: ReadableStream<Uint8Array>; + source: null | Uint8Array | Blob | FormData; + length: null | number; + unusable(): boolean; + consume(): Promise<Uint8Array>; + 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 78a42cd84..93c624dd6 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 ce2e72370..6864fd638 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 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, - ); - } +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; + + constructor(pointer) { + this.pointer = pointer; + } - getInt8(offset = 0) { - return ops.op_ffi_read_i8( - this.pointer, - offset, - ); - } + getBool(offset = 0) { + return ops.op_ffi_read_bool( + this.pointer, + offset, + ); + } - getUint16(offset = 0) { - return ops.op_ffi_read_u16( - this.pointer, - offset, - ); - } + getUint8(offset = 0) { + return ops.op_ffi_read_u8( + this.pointer, + offset, + ); + } - getInt16(offset = 0) { - return ops.op_ffi_read_i16( - this.pointer, - offset, - ); - } + getInt8(offset = 0) { + return ops.op_ffi_read_i8( + this.pointer, + offset, + ); + } - getUint32(offset = 0) { - return ops.op_ffi_read_u32( - this.pointer, - offset, - ); - } + getUint16(offset = 0) { + return ops.op_ffi_read_u16( + this.pointer, + offset, + ); + } - getInt32(offset = 0) { - return ops.op_ffi_read_i32( - this.pointer, - offset, - ); - } + getInt16(offset = 0) { + return ops.op_ffi_read_i16( + this.pointer, + offset, + ); + } - getBigUint64(offset = 0) { - ops.op_ffi_read_u64( - this.pointer, - offset, - U32_BUFFER, - ); - return U64_BUFFER[0]; - } + getUint32(offset = 0) { + return ops.op_ffi_read_u32( + this.pointer, + offset, + ); + } - getBigInt64(offset = 0) { - ops.op_ffi_read_i64( - this.pointer, - offset, - U32_BUFFER, - ); - return I64_BUFFER[0]; - } + getInt32(offset = 0) { + return ops.op_ffi_read_i32( + this.pointer, + offset, + ); + } - getFloat32(offset = 0) { - return ops.op_ffi_read_f32( - this.pointer, - offset, - ); - } + getBigUint64(offset = 0) { + ops.op_ffi_read_u64( + this.pointer, + offset, + U32_BUFFER, + ); + return U64_BUFFER[0]; + } - getFloat64(offset = 0) { - return ops.op_ffi_read_f64( - this.pointer, - offset, - ); - } + getBigInt64(offset = 0) { + ops.op_ffi_read_i64( + this.pointer, + offset, + U32_BUFFER, + ); + return I64_BUFFER[0]; + } - getCString(offset = 0) { - return ops.op_ffi_cstr_read( - this.pointer, - offset, - ); - } + getFloat32(offset = 0) { + return ops.op_ffi_read_f32( + this.pointer, + offset, + ); + } - static getCString(pointer, offset = 0) { - return ops.op_ffi_cstr_read( - pointer, - offset, - ); - } + getFloat64(offset = 0) { + return ops.op_ffi_read_f64( + this.pointer, + offset, + ); + } - getArrayBuffer(byteLength, offset = 0) { - return ops.op_ffi_get_buf( - this.pointer, - offset, - byteLength, - ); - } + getCString(offset = 0) { + return ops.op_ffi_cstr_read( + this.pointer, + offset, + ); + } - static getArrayBuffer(pointer, byteLength, offset = 0) { - return ops.op_ffi_get_buf( - pointer, - offset, - byteLength, - ); - } + static getCString(pointer, offset = 0) { + return ops.op_ffi_cstr_read( + pointer, + offset, + ); + } - copyInto(destination, offset = 0) { - ops.op_ffi_buf_copy_into( - this.pointer, - offset, - destination, - destination.byteLength, - ); - } + getArrayBuffer(byteLength, offset = 0) { + return ops.op_ffi_get_buf( + this.pointer, + offset, + byteLength, + ); + } - static copyInto(pointer, destination, offset = 0) { - ops.op_ffi_buf_copy_into( - pointer, - offset, - destination, - destination.byteLength, - ); - } + static getArrayBuffer(pointer, byteLength, offset = 0) { + return ops.op_ffi_get_buf( + pointer, + offset, + 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]; - } + copyInto(destination, offset = 0) { + ops.op_ffi_buf_copy_into( + this.pointer, + offset, + destination, + destination.byteLength, + ); } - class UnsafeFnPointer { - pointer; - definition; - #structSize; - - constructor(pointer, definition) { - this.pointer = pointer; - this.definition = definition; - this.#structSize = isStruct(definition.result) - ? getTypeSizeAndAlignment(definition.result)[0] - : null; + 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]; + } +} + +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, - buffer, - ), - () => buffer, - ); - } + call(...parameters) { + if (this.definition.nonblocking) { + if (this.#structSize === null) { + return core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + ); } 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( + const buffer = new Uint8Array(this.#structSize); + return PromisePrototypeThen( + core.opAsync( + "op_ffi_call_ptr_nonblocking", this.pointer, this.definition, parameters, buffer, - ); - return 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; +} + +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"); } - 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]; + return cached; } - - 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}`); + 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]; } - 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", - ); - } - const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( - definition, - callback, + 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", ); - this.#refcount = 0; - this.#rid = rid; - this.pointer = pointer; - this.definition = definition; - this.callback = callback; } + 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; + 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; + 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); - } + close() { + this.#refcount = 0; + core.close(this.#rid); } +} - const UnsafeCallbackPrototype = UnsafeCallback.prototype; +const UnsafeCallbackPrototype = UnsafeCallback.prototype; - class DynamicLibrary { - #rid; - symbols = {}; +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; - } + 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.", - ); - } - - const name = symbols[symbol].name || symbol; - const value = ops.op_ffi_get_static( - this.#rid, - name, - type, + if (ReflectHas(symbols[symbol], "type")) { + const type = symbols[symbol].type; + if (type === "void") { + throw new TypeError( + "Foreign symbol of type 'void' is not supported.", ); - 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 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, + + 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 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 88e788457..0034a0d33 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -84,7 +84,7 @@ pub(crate) struct FfiState { pub fn init<P: FfiPermissions + 'static>(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 357bdfbe2..d2f967ada 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"; - -((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 methods = { - 0: "GET", - 1: "HEAD", - 2: "CONNECT", - 3: "PUT", - 4: "DELETE", - 5: "OPTIONS", - 6: "TRACE", - 7: "POST", - 8: "PATCH", - }; - - 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. - // - // HTTP-message = start-line - // *( header-field CRLF ) - // CRLF - // [ message-body ] +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; + +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", +}; + +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. +// +// HTTP-message = start-line +// *( header-field CRLF ) +// CRLF +// [ message-body ] +// +function http1Response( + method, + status, + headerList, + body, + bodyLen, + earlyEnd = false, +) { + // HTTP uses a "<major>.<minor>" numbering scheme + // HTTP-version = HTTP-name "/" DIGIT "." DIGIT + // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive // - function http1Response( - method, - status, - headerList, - body, - bodyLen, - earlyEnd = false, - ) { - // HTTP uses a "<major>.<minor>" 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; + // 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`; + } - 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; - } + // 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; } - function prepareFastCalls() { - return core.ops.op_flash_make_request(); + if (earlyEnd === true) { + 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; + // 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; } - 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, - ); - } + // 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; + } - if (nwritten < responseLen) { - core.opAsync( - "op_flash_respond_async", - server, - requestId, - response.slice(nwritten), - end, - ); - } + 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, + ); } - // 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> | 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 ( + 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> | 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."); + 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"); } - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; } + isStreamingResponseBody = !( + typeof respBody === "string" || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + ); } else { - respBody = new Uint8Array(0); + 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, - ); - } + 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 */ } - } + 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, + 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, ), - () => { - // Release JS lock. - readableStreamClose(respBody); - }, - ); - } catch (error) { - await reader.cancel(error); - throw error; - } - } 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, + serverId, + i, + resourceBacking.rid, + resourceBacking.autoClose, ), - respBody.byteLength, - false, - respondFast, + () => { + // Release JS lock. + readableStreamClose(respBody); + }, ); - - 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; - } - } + } catch (error) { + await reader.cancel(error); + throw error; } - } + } else { + const reader = respBody.getReader(); - if (ws) { - const wsRid = await core.opAsync( - "op_flash_upgrade_websocket", + // 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, ); - 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.", - ); + if (!done) { + while (true) { + const chunk = await reader.read(); + await respondChunked( + i, + chunk.value, + chunk.done, + ); + if (chunk.done) break; + } } - handler = options.handler; } - if (typeof handler !== "function") { - throw new TypeError("A handler function must be provided."); + } + + 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) { - options = {}; + 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 signal = options.signal; - const onError = options.onError ?? function (error) { - console.error(error); - return new Response("Internal Server Error", { status: 500 }); - }; + 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 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.", ); - }; - - 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; } + 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() { + 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) { - return; + break; } - server.closed = true; - await core.opAsync("op_flash_close_server", serverId); - await server.finished; - }, - async serve() { - let offset = 0; - while (true) { + + 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); - } + function respondChunked(token, chunk, shutdown) { + return core.opAsync( + "op_flash_respond_chunked", + serverId, + token, + chunk, + shutdown, + ); + } - if (!dateInterval) { - date = new Date().toUTCString(); - dateInterval = setInterval(() => { - date = new Date().toUTCString(); - }, 1000); - } + 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); + } - await SafePromiseAll([ - PromisePrototypeCatch(server.serve(), console.error), - serverPromise, - ]); - }; - } + if (!dateInterval) { + date = new Date().toUTCString(); + dateInterval = setInterval(() => { + date = new Date().toUTCString(); + }, 1000); + } - 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; + await SafePromiseAll([ + PromisePrototypeCatch(server.serve(), console.error), + serverPromise, + ]); + }; +} - 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); +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.", + ); } - function upgradeHttpRaw(req) { - if (!req[_flash]) { - throw new TypeError( - "Non-flash requests can not be upgraded with `upgradeHttpRaw`. Use `upgradeHttp` instead.", - ); - } + // 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; - // 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 = core.ops.op_flash_upgrade_http(streamRid, serverId); - // TODO(@littledivy): return already read first packet too. - return [new TcpConn(connRid), new Uint8Array()]; - } + 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()]; +} - window.__bootstrap.flash = { - createServe, - upgradeHttpRaw, - }; -})(this); +export { createServe, upgradeHttpRaw }; diff --git a/ext/flash/lib.rs b/ext/flash/lib.rs index d31e78caa..ab048c9cd 100644 --- a/ext/flash/lib.rs +++ b/ext/flash/lib.rs @@ -1514,7 +1514,7 @@ pub fn init<P: FlashPermissions + 'static>(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 cb98d246d..7ce5f9172 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"; - -((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"); - - 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(); - - constructor(rid, remoteAddr, localAddr) { - this.#rid = rid; - this.#remoteAddr = remoteAddr; - this.#localAddr = localAddr; - } - - /** @returns {number} */ - get rid() { - return this.#rid; - } +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; + +const connErrorSymbol = Symbol("connError"); +const _deferred = Symbol("upgradeHttpDeferred"); + +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(); + + constructor(rid, remoteAddr, localAddr) { + this.#rid = rid; + this.#remoteAddr = remoteAddr; + this.#localAddr = localAddr; + } - /** @returns {Promise<RequestEvent | null>} */ - 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; + /** @returns {number} */ + get rid() { + return this.#rid; + } - this.close(); + /** @returns {Promise<RequestEvent | null>} */ + 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(); + return null; + } - const { 0: streamRid, 1: method, 2: url } = nextRequest; - SetPrototypeAdd(this.managedResources, streamRid); - - /** @type {ReadableStream<Uint8Array> | 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, - ); + const { 0: streamRid, 1: method, 2: url } = nextRequest; + SetPrototypeAdd(this.managedResources, streamRid); - return { request, respondWith }; + /** @type {ReadableStream<Uint8Array> | 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); } - /** @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 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 }; + } - [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 }; - }, - }; + /** @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); + 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> | Uint8Array | null} */ - let respBody = null; - if (innerResp.body !== null) { - if (innerResp.body.unusable()) { - throw new TypeError("Body is unusable."); - } + // 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> | 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; + respBody = innerResp.body.stream; + } else { + const reader = innerResp.body.stream.getReader(); + const r1 = await reader.read(); + if (r1.done) { + respBody = new Uint8Array(0); } 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 = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } - } else { - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; } } 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."); + } + 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; } - const resourceBacking = getReadableStreamResourceBacking(respBody); - let reader; - if (resourceBacking) { - if (respBody.locked) { - throw new TypeError("ReadableStream is locked."); + } 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 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"); } - 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(); + 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"); - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); + httpConn.close(); - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); - } - } finally { - if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { - core.close(streamRid); + 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]]"); + +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 _ws = Symbol("[[associated_ws]]"); - - 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 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 websocketKey = request.headers.get("sec-websocket-key"); - if (websocketKey === null) { + 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( - "Invalid Header: 'sec-websocket-key' header must be set", + `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, ); } + } - 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)`, - ); - } - } + const response = fromInnerResponse(r, "immutable"); - 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; - const socket = webidl.createBranded(WebSocket); - setEventTargetData(socket); - socket[_server] = true; - response[_ws] = socket; - socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; - socket[_idleTimeoutTimeout] = null; + return { response, socket }; +} - return { response, socket }; +function upgradeHttp(req) { + if (req[_flash]) { + throw new TypeError( + "Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", + ); } - 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; +} - req[_deferred] = new Deferred(); - return req[_deferred].promise; - } - - window.__bootstrap.http = { - HttpConn, - upgradeWebSocket, - upgradeHttp, - _ws, - }; -})(this); +export { _ws, HttpConn, upgradeHttp, upgradeWebSocket }; diff --git a/ext/http/lib.rs b/ext/http/lib.rs index 0e3ebf766..a0593de8c 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 a6043786f..46561cae5 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 promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - - 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; - } - get rid() { - return this.#rid; - } +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"); + +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; + } - get remoteAddr() { - return this.#remoteAddr; - } + get rid() { + return this.#rid; + } - get localAddr() { - return this.#localAddr; - } + get remoteAddr() { + return this.#remoteAddr; + } - write(p) { - return write(this.rid, p); - } + get localAddr() { + return this.#localAddr; + } - 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; - } + write(p) { + return write(this.rid, p); + } - close() { - core.close(this.rid); - } + 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; + } - closeWrite() { - return shutdown(this.rid); - } + close() { + core.close(this.rid); + } - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRidUnrefable(this.rid); - if (this.#unref) { - readableStreamForRidUnrefableUnref(this.#readable); - } - } - return this.#readable; - } + closeWrite() { + return shutdown(this.rid); + } - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRidUnrefable(this.rid); + if (this.#unref) { + readableStreamForRidUnrefableUnref(this.#readable); } - return this.#writable; } + return this.#readable; + } - ref() { - this.#unref = false; - if (this.#readable) { - readableStreamForRidUnrefableRef(this.#readable); - } - this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); } + return this.#writable; + } - unref() { - this.#unref = true; - if (this.#readable) { - readableStreamForRidUnrefableUnref(this.#readable); - } - this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); + ref() { + this.#unref = false; + if (this.#readable) { + readableStreamForRidUnrefableRef(this.#readable); } + this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); } - class TcpConn extends Conn { - setNoDelay(noDelay = true) { - return ops.op_set_nodelay(this.rid, noDelay); + unref() { + this.#unref = true; + if (this.#readable) { + readableStreamForRidUnrefableUnref(this.#readable); } + this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); + } +} - setKeepAlive(keepAlive = true) { - return ops.op_set_keepalive(this.rid, keepAlive); - } +class TcpConn extends Conn { + setNoDelay(noDelay = true) { + return ops.op_set_nodelay(this.rid, noDelay); } - class UnixConn extends Conn {} + setKeepAlive(keepAlive = true) { + return ops.op_set_keepalive(this.rid, keepAlive); + } +} - class Listener { - #rid = 0; - #addr = null; - #unref = false; - #promiseId = null; +class UnixConn extends Conn {} - constructor(rid, addr) { - this.#rid = rid; - this.#addr = addr; - } +class Listener { + #rid = 0; + #addr = null; + #unref = false; + #promiseId = null; - get rid() { - return this.#rid; - } + constructor(rid, addr) { + this.#rid = rid; + this.#addr = addr; + } - get addr() { - return this.#addr; - } + get rid() { + return this.#rid; + } - 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"); - } + 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; + async next() { + let conn; + try { + conn = await this.accept(); + } catch (error) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) + ) { + return { value: undefined, done: true }; } - return { value: conn, done: false }; + throw error; } + return { value: conn, done: false }; + } - return(value) { - this.close(); - return PromiseResolve({ value, done: true }); - } + return(value) { + this.close(); + return PromiseResolve({ value, done: true }); + } - close() { - core.close(this.rid); - } + close() { + core.close(this.rid); + } - [SymbolAsyncIterator]() { - return this; - } + [SymbolAsyncIterator]() { + return this; + } - ref() { - this.#unref = false; - if (typeof this.#promiseId === "number") { - core.refOp(this.#promiseId); - } + 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; - } + constructor(rid, addr, bufSize = 1024) { + this.#rid = rid; + this.#addr = addr; + this.bufSize = bufSize; + } - get addr() { - return this.#addr; - } + get rid() { + return this.#rid; + } - 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]; - } + get addr() { + return this.#addr; + } - 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}`); + 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; } - } - - 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; - } + 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]; } - 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); - } + 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 TypeError(`Unsupported transport: '${transport}'`); + 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); + close() { + core.close(this.rid); + } + + async *[SymbolAsyncIterator]() { + while (true) { + try { + yield await this.receive(); + } catch (err) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err) + ) { + break; } - default: - throw new TypeError(`Unsupported transport: '${transport}'`); + 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}'`); } +} - async function connect(args) { - switch (args.transport ?? "tcp") { - case "tcp": { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_tcp", +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 632e1fbd4..4701d3da7 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); +} - function opTlsHandshake(rid) { - return core.opAsync("op_tls_handshake", rid); +class TlsConn extends Conn { + handshake() { + return opTlsHandshake(this.rid); } +} - 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}'`); } + 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); +} - 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); - } - - 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); - } + } = {}, +) { + 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 932f8c8c5..ed620fcdd 100644 --- a/ext/net/lib.rs +++ b/ext/net/lib.rs @@ -86,7 +86,7 @@ pub fn init<P: NetPermissions + 'static>( ops.extend(ops_tls::init::<P>()); 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 4ed5b3eda..de27c5180 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; - }, - }); - - 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); +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; } - 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; + 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; + }, +}); - // 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 bda74c01f..2b4a9c16c 100644 --- a/ext/node/02_require.js +++ b/ext/node/02_require.js @@ -2,943 +2,938 @@ // deno-lint-ignore-file -"use strict"; - -((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(); - - function pathDirname(filepath) { - if (filepath == null || filepath === "") { - throw new Error("Empty filepath."); - } - return ops.op_require_path_dirname(filepath); +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; + +// 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 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 result = ops.op_require_stat(filename); - if (statCache !== null && result >= 0) { - statCache.set(filename, 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; } - - 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 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, + ); } - function tryPackage(requestPath, exts, isMain, originalPath) { - const packageJsonPath = pathResolve( - requestPath, - "package.json", + 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, ); - const pkg = core.ops.op_require_read_package_scope(packageJsonPath)?.main; - if (!pkg) { - return 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', ); - } - - const filename = pathResolve(requestPath, pkg); - let actual = tryFile(filename, isMain) || - tryExtensions(filename, exts, isMain) || - tryExtensions( - pathResolve(filename, "index"), - exts, - isMain, + err.code = "MODULE_NOT_FOUND"; + err.path = pathResolve( + requestPath, + "package.json", ); - if (actual === false) { - actual = tryExtensions( - pathResolve(requestPath, "index"), - exts, - isMain, + 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; + 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); +function tryExtensions(p, exts, isMain) { + for (let i = 0; i < exts.length; i++) { + const filename = tryFile(p + exts[i], isMain); - if (filename) { - return filename; - } + 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 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"; } + 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; + }, - function getExportsForCircularRequire(module) { + getOwnPropertyDescriptor(target, prop) { 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 + ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" ) { - // This is later unset once the module is done loading. - ObjectSetPrototypeOf( - module.exports, - CircularRequirePrototypeWarningProxy, - ); + return ObjectGetOwnPropertyDescriptor(target, prop); } - - return module.exports; - } - - function emitCircularRequireWarning(prop) { - node.globalThis.process.emitWarning( - `Accessing non-existent property '${String(prop)}' of module exports ` + - "inside circular dependency", + 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, ); } - - // 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 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; } - // This only applies to requests of a specific form: - // 1. name/.* - // 2. @scope/name/.* - const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; - function resolveExports( + return ops.op_require_resolve_exports( + usesLocalNodeModulesDir, modulesPath, request, + name, + expansion, 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; + ) ?? 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; } - 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; - } + 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); - } + 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; - } - } + // 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; - 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; - - const rc = stat(basePath); - if (!trailingSlash) { - if (rc === 0) { // File. - filename = toRealPath(basePath); - } + if (exportsResolved) { + return exportsResolved; + } + } - if (!filename) { - // Try it with each of the extensions - if (exts === undefined) { - exts = ObjectKeys(Module._extensions); - } - filename = tryExtensions(basePath, exts, isMain); - } + 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; + + 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); + filename = tryExtensions(basePath, exts, isMain); } + } - if (filename) { - Module._pathCache[cacheKey] = filename; - return filename; + 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); } - return false; - }; + if (filename) { + Module._pathCache[cacheKey] = filename; + return filename; + } + } - Module._nodeModulePaths = function (fromPath) { - return ops.op_require_node_module_paths(fromPath); - }; + return false; +}; - Module._resolveLookupPaths = function (request, parent) { - const paths = []; +Module._nodeModulePaths = function (fromPath) { + return ops.op_require_node_module_paths(fromPath); +}; - if (core.ops.op_require_is_request_relative(request) && parent?.filename) { - ArrayPrototypePush( - paths, - core.ops.op_require_path_dirname(parent.filename), - ); - return paths; - } +Module._resolveLookupPaths = function (request, parent) { + const 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); - } - } - const lookupPathsResult = ops.op_require_resolve_lookup_paths( - request, - parent?.paths, - parent?.filename ?? "", + 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 module = loadNativeModule(id, id); - if (!module) { - // TODO: - // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); - throw new Error("Unknown built-in module"); - } + const filename = Module._resolveFilename(request, parent, isMain); + if (StringPrototypeStartsWith(filename, "node:")) { + // Slice 'node:' prefix + const id = StringPrototypeSlice(filename, 5); - return module.exports; + const module = loadNativeModule(id, id); + if (!module) { + // TODO: + // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); + throw new Error("Unknown built-in module"); } - const cachedModule = Module._cache[filename]; - if (cachedModule !== undefined) { - updateChildren(parent, cachedModule, true); - if (!cachedModule.loaded) { - return getExportsForCircularRequire(cachedModule); - } - return cachedModule.exports; - } + return module.exports; + } - const mod = loadNativeModule(filename, request); - if ( - mod - ) { - return mod.exports; + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); } - // Don't call updateChildren(), Module constructor already does. - const module = cachedModule || new Module(filename, parent); + return cachedModule.exports; + } - if (isMain) { - node.globalThis.process.mainModule = module; - mainModule = module; - module.id = "."; - } + 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); - Module._cache[filename] = module; - if (parent !== undefined) { - relativeResolveCache[relResolveCacheIdentifier] = filename; - } + if (isMain) { + node.globalThis.process.mainModule = module; + mainModule = module; + module.id = "."; + } - 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); - } + 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); } + } else if ( + module.exports && + ObjectGetPrototypeOf(module.exports) === + CircularRequirePrototypeWarningProxy + ) { + ObjectSetPrototypeOf(module.exports, ObjectPrototype); } + } - return module.exports; - }; - - Module._resolveFilename = function ( - request, - parent, - isMain, - options, + return module.exports; +}; + +Module._resolveFilename = function ( + request, + parent, + isMain, + options, +) { + if ( + StringPrototypeStartsWith(request, "node:") || + nativeModuleCanBeRequiredByUsers(request) ) { - if ( - StringPrototypeStartsWith(request, "node:") || - nativeModuleCanBeRequiredByUsers(request) - ) { - return request; - } + return request; + } - let paths; + let paths; - if (typeof options === "object" && options !== null) { - if (ArrayIsArray(options.paths)) { - const isRelative = ops.op_require_is_request_relative( - request, - ); + 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); + if (isRelative) { + paths = options.paths; + } else { + const fakeParent = new Module("", null); - paths = []; + 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 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]); - } + 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); + } 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 = core.ops.op_require_package_imports_resolve( - parent.filename, - request, - ); - if (maybeResolved) { - return maybeResolved; - } + 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.", - ); - } - } + // 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; } - function wrapSafe( - filename, - content, - cjsModuleInstance, + // 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"] ) { - 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; + // TODO: use proper error class + throw new Error("require ESM", filename); } - Module.prototype._compile = function (content, filename) { - const compiledWrapper = wrapSafe(filename, content, this); + Module._extensions[extension](this, filename); + this.loaded = true; - const dirname = pathDirname(filename); - const require = makeRequireFunction(this); - const exports = this.exports; - const thisValue = exports; - const module = this; - if (requireDepth === 0) { - statCache = new SafeMap(); - } + // TODO: do caching +}; - if (hasInspectBrk && !hasBrokenOnInspectBrk) { - hasBrokenOnInspectBrk = true; - core.ops.op_require_break_on_next_statement(); - } +// 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"); + } - const result = compiledWrapper.call( - thisValue, - exports, - require, - this, - filename, - dirname, - node.globalThis, - ); - if (requireDepth === 0) { - statCache = null; + 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.", + ); } - return result; - }; + } +} + +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(); + } - Module._extensions[".js"] = function (module, filename) { - const content = ops.op_require_read_file(filename); + if (hasInspectBrk && !hasBrokenOnInspectBrk) { + hasBrokenOnInspectBrk = true; + ops.op_require_break_on_next_statement(); + } - 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}`; + const result = compiledWrapper.call( + thisValue, + exports, + require, + this, + filename, + dirname, + node.globalThis, + ); + if (requireDepth === 0) { + statCache = null; + } + return result; +}; - if (module.parent) { - message += ` from ${module.parent.filename}`; - } +Module._extensions[".js"] = function (module, filename) { + const content = ops.op_require_read_file(filename); - message += ` using require()`; + 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}`; - throw new Error(message); + if (module.parent) { + message += ` from ${module.parent.filename}`; } - } - module._compile(content, filename); - }; + message += ` using require()`; - function stripBOM(content) { - if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { - content = StringPrototypeSlice(content, 1); + throw new Error(message); } - 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; - } - }; + module._compile(content, filename); +}; - // 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 stripBOM(content) { + if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { + content = StringPrototypeSlice(content, 1); } + return content; +} - 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; +// Native extension for .json +Module._extensions[".json"] = function (module, filename) { + const content = ops.op_require_read_file(filename); - function paths(request) { - return Module._resolveLookupPaths(request, mod); - } + try { + module.exports = JSONParse(stripBOM(content)); + } catch (err) { + err.message = filename + ": " + err.message; + throw err; + } +}; - resolve.paths = paths; - require.main = mainModule; - // Enable support to add extra extension types. - require.extensions = Module._extensions; - require.cache = Module._cache; +// 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); + }; - return require; + function resolve(request, options) { + return Module._resolveFilename(request, mod, false, options); } - // Matches to: - // - /foo/... - // - \foo\... - // - C:/foo/... - // - C:\foo\... - const RE_START_OF_ABS_PATH = /^([/\\]|[a-zA-Z]:[/\\])/; + require.resolve = resolve; - function isAbsolute(filenameOrUrl) { - return RE_START_OF_ABS_PATH.test(filenameOrUrl); + function paths(request) { + return Module._resolveLookupPaths(request, mod); } - 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 { + 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}`, ); } - const filename = core.ops.op_require_as_file_path(fileUrlStr); - return createRequireFromPath(filename); + 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); +} - Module.createRequire = createRequire; +Module.createRequire = createRequire; - Module._initPaths = function () { - const paths = ops.op_require_init_paths(); - modulePaths = paths; - Module.globalPaths = ArrayPrototypeSlice(modulePaths); - }; +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.syncBuiltinESMExports = function syncBuiltinESMExports() { + throw new Error("not implemented"); +}; - Module.Module = Module; +Module.Module = Module; - node.nativeModuleExports.module = Module; +node.nativeModuleExports.module = Module; - function loadNativeModule(_id, request) { - if (nativeModulePolyfill.has(request)) { - return nativeModulePolyfill.get(request); - } - 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; +function loadNativeModule(_id, request) { + if (nativeModulePolyfill.has(request)) { + return nativeModulePolyfill.get(request); } - - function nativeModuleCanBeRequiredByUsers(request) { - return !!node.nativeModuleExports[request]; + const modExports = node.nativeModuleExports[request]; + if (modExports) { + const nodeMod = new Module(request); + nodeMod.exports = modExports; + nodeMod.loaded = true; + nativeModulePolyfill.set(request, nodeMod); + return nodeMod; } - - function readPackageScope() { - throw new Error("not implemented"); + return undefined; +} + +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); } - - /** @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); + return ArrayPrototypeJoin(parts, "/"); +} + +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 ad8619889..8db2bb3a7 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -85,7 +85,7 @@ pub fn init<P: NodePermissions + 'static>( maybe_npm_resolver: Option<Rc<dyn RequireNpmResolver>>, ) -> 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 1191565ee..faaba2911 100644 --- a/ext/url/00_url.js +++ b/ext/url/00_url.js @@ -5,806 +5,803 @@ /// <reference path="../../core/lib.deno_core.d.ts" /> /// <reference path="../webidl/internal.d.ts" /> -"use strict"; - -((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"); - - // 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( +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; + +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; + +// Helper functions +function opUrlReparse(href, setter, value) { + const status = ops.op_url_reparse( + href, + setter, + value, + componentsBuf.buffer, + ); + return getSerialization(status, href); +} + +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 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}'` : ""), + ); + } +} - function opUrlParse(href, maybeBase) { - let status; - if (maybeBase === undefined) { - status = ops.op_url_parse(href, componentsBuf.buffer); +class URLSearchParams { + [_list]; + [_urlObject] = null; + + /** + * @param {string | [string][] | Record<string, string>} init + */ + constructor(init = "") { + const prefix = "Failed to construct 'URL'"; + init = webidl.converters + ["sequence<sequence<USVString>> or record<USVString, USVString> 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); + } + this[_list] = ops.op_url_parse_search_params(init); + } else if (ArrayIsArray(init)) { + // Overload: sequence<sequence<USVString>> + 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]]; + }); } else { - status = core.ops.op_url_parse_with_base( - href, - maybeBase, - componentsBuf.buffer, + // Overload: record<USVString, USVString> + this[_list] = ArrayPrototypeMap( + ObjectKeys(init), + (key) => [key, init[key]], ); } - 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}'` : ""), - ); + #updateUrlSearch() { + const url = this[_urlObject]; + if (url === null) { + return; } + url[_updateUrlSearch](this.toString()); } - class URLSearchParams { - [_list]; - [_urlObject] = null; - - /** - * @param {string | [string][] | Record<string, string>} init - */ - constructor(init = "") { - const prefix = "Failed to construct 'URL'"; - init = webidl.converters - ["sequence<sequence<USVString>> or record<USVString, USVString> 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; - } + /** + * @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(); + } - 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<sequence<USVString>> - 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]]; - }); + /** + * @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<USVString, USVString> - 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) { - ArrayPrototypeSplice(list, i, 1); - } else { + /** + * @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++; } - 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", }); + } + this[webidl.brand] = webidl.brand; + this.#serialization = opUrlParse(url, base); + this.#updateComponents(); + } - 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]); - } + #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); + } - this.#updateUrlSearch(); - } + [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)}`; + } - sort() { - webidl.assertBranded(this, URLSearchParamsPrototype); - ArrayPrototypeSort( - this[_list], - (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), + #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; - - const componentsBuf = new Uint32Array(8); - class URL { - #queryObject = null; - #serialization; - #schemeEnd; - #usernameEnd; - #hostStart; - #hostEnd; - #port; - #pathStart; - #queryStart; - #fragmentStart; + /** @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)) + : ""; + } - [_updateUrlSearch](value) { + /** @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_SEARCH, + SET_HASH, value, ); this.#updateComponents(); + } catch { + /* pass */ } + } - /** - * @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); + /** @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 */ } + } - #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); - } + /** @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); + } - [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)}`; + /** @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 */ } + } - #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), - ); - } - } + /** @return {string} */ + get href() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } - #hasAuthority() { - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 - return this.#serialization.slice(this.#schemeEnd).startsWith("://"); - } + /** @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 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)) - : ""; + /** @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}`; } - /** @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", - }); + if (scheme === "blob") { + // TODO(@littledivy): Fast path. try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HASH, - value, - ); - this.#updateComponents(); + return new URL(this.pathname).origin; } catch { - /* pass */ + return "null"; } } - /** @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 "null"; + } - /** @return {string} */ - get href() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @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 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); + /** @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(); - 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); + } catch { + /* pass */ } + } - /** @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 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); } - /** @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, - ); - } - } + const nextComponentStart = this.#queryStart || this.#fragmentStart; + return this.#serialization.slice(this.#pathStart, nextComponentStart); + } - /** @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 */ - } + /** @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 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 /* : */); + /** @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 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 */ - } + /** @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 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)); - } + /** @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 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 */ - } + /** @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 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 ""; - } - } + /** @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 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 */ - } + /** @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 searchParams() { - if (this.#queryObject == null) { - this.#queryObject = new URLSearchParams(this.search); - this.#queryObject[_urlObject] = this; - } - return this.#queryObject; + /** @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 ""; } + } - /** @return {string} */ - toString() { - 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 */ } + } - /** @return {string} */ - toJSON() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @return {string} */ + get searchParams() { + if (this.#queryObject == null) { + this.#queryObject = new URLSearchParams(this.search); + this.#queryObject[_urlObject] = this; } + return this.#queryObject; } - webidl.configurePrototype(URL); - const URLPrototype = URL.prototype; + /** @return {string} */ + toString() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } - /** - * 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<sequence<USVString>> or record<USVString, USVString> or USVString" - ] = (V, opts) => { - // Union for (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence<sequence<USVString>>"](V, opts); - } - return webidl.converters["record<USVString, USVString>"](V, opts); + /** @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<sequence<USVString>> or record<USVString, USVString> or USVString" + ] = (V, opts) => { + // Union for (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence<sequence<USVString>>"](V, opts); } - return webidl.converters.USVString(V, opts); - }; - - window.__bootstrap.url = { - URL, - URLPrototype, - URLSearchParams, - URLSearchParamsPrototype, - parseUrlEncoded, + return webidl.converters["record<USVString, USVString>"](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 14f052551..1c5882553 100644 --- a/ext/url/01_urlpattern.js +++ b/ext/url/01_urlpattern.js @@ -7,268 +7,263 @@ /// <reference path="./internal.d.ts" /> /// <reference path="./lib.deno_url.d.ts" /> -"use strict"; - -((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 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; + +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}`); - } - } + const components = ops.op_urlpattern_parse(input, baseURL); - this[_components] = components; + 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}`); + } } - get protocol() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].protocol.patternString; - } + this[_components] = components; + } - get username() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].username.patternString; - } + get protocol() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].protocol.patternString; + } - get password() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].password.patternString; - } + get username() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].username.patternString; + } - get hostname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hostname.patternString; - } + get password() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].password.patternString; + } - get port() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].port.patternString; - } + get hostname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].hostname.patternString; + } - get pathname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].pathname.patternString; - } + get port() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].port.patternString; + } - get search() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].search.patternString; - } + get pathname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].pathname.patternString; + } - get hash() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hash.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, { + /** + * @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 1", + context: "Argument 2", }); - 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 res = ops.op_urlpattern_process_match_input( + input, + baseURL, + ); + if (res === null) { + return false; + } - const values = res[0]; + 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; - } + 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, { + 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 1", + context: "Argument 2", }); - 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 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(); - } + 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, - }; + /** @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; } - - return result; + const groupEntries = ArrayPrototypeMap( + component.groupNameList, + (name, i) => [name, match[i + 1] ?? ""], + ); + const groups = ObjectFromEntries(groupEntries); + result[key] = { + input, + groups, + }; } - [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, - }) - }`; - } + return result; } - 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); - }; + [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); +}; - window.__bootstrap.urlPattern = { - URLPattern, - }; -})(globalThis); +export { URLPattern }; diff --git a/ext/url/benches/url_ops.rs b/ext/url/benches/url_ops.rs index 5a5997fc8..4b41eb3a0 100644 --- a/ext/url/benches/url_ops.rs +++ b/ext/url/benches/url_ops.rs @@ -12,9 +12,11 @@ fn setup() -> Vec<Extension> { 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 7065c432f..b8a2a000a 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 - /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> -declare namespace globalThis { - declare namespace __bootstrap { - declare var url: { - URL: typeof URL; - URLSearchParams: typeof URLSearchParams; - parseUrlEncoded(bytes: Uint8Array): [string, string][]; - }; +declare module "internal:ext/url/00_url.js" { + const URL: typeof URL; + const URLSearchParams: typeof URLSearchParams; + function parseUrlEncoded(bytes: Uint8Array): [string, string][]; +} - declare var urlPattern: { - URLPattern: typeof URLPattern; - }; - } +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 064590f29..95ca326c4 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 3f3f98165..c44b124c9 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -6,334 +6,331 @@ /// <reference path="../web/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> -"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 }; +} - /** - * 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, ""); - } +/** + * @param {string} s + * @returns {string} + */ +function byteUpperCase(s) { + return StringPrototypeReplace( + String(s), + /[a-z]/g, + function byteUpperCaseReplace(c) { + return StringPrototypeToUpperCase(c); + }, + ); +} - /** - * 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 byteLowerCase(s) { + // NOTE: correct since all callers convert to ByteString first + // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter + return StringPrototypeToLowerCase(s); +} - /** - * @param {string} s - * @returns {string} - */ - function byteUpperCase(s) { - return StringPrototypeReplace( - String(s), - /[a-z]/g, - function byteUpperCaseReplace(c) { - return StringPrototypeToUpperCase(c); - }, +/** + * 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", ); - } - - /** - * @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. + value += res.result; + position = res.position; + // 5.2. + if (position >= input.length) break; + // 5.3. + const quoteOrBackslash = input[position]; + // 5.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; - } - // 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 + // 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 + break; } - // 6. - if (extractValue) return { result: value, position }; - // 7. - return { - result: StringPrototypeSubstring(input, positionStart, position + 1), - position, - }; } + // 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 {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} 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} 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] ?? ""; +/** + * @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"; - } +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} 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; +/** + * @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, - }; -})(globalThis); +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 a4556c03c..cbec9ca22 100644 --- a/ext/web/01_dom_exception.js +++ b/ext/web/01_dom_exception.js @@ -7,197 +7,194 @@ /// <reference path="../web/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> -"use strict"; - -((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"); - - // 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<string, number>} */ - // 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]; - - // 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; - - 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]; - } +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"; + +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 2.8.1. +// https://webidl.spec.whatwg.org/#dfn-error-names-table +/** @type {Record<string, number>} */ +// 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]; + + // 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; + + 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 code() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_code]; - } + get message() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_message]; + } - [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", - ], - })); - } - } + get name() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_name]; } - 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 code() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_code]; } - window.__bootstrap.domException = { DOMException }; -})(this); + [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 2d67d5f95..17d954eb4 100644 --- a/ext/web/01_mimesniff.js +++ b/ext/web/01_mimesniff.js @@ -6,255 +6,247 @@ /// <reference path="../web/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> -"use strict"; - -((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; +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"; + +/** + * @typedef MimeType + * @property {string} type + * @property {string} subtype + * @property {Map<string,string>} 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, ""); + + // 2. + let position = 0; + const endOfInput = input.length; + + // 3. + const res1 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u002F", + ); + const type = res1.result; + position = res1.position; + + // 4. + if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { + return null; + } - /** - * @typedef MimeType - * @property {string} type - * @property {string} subtype - * @property {Map<string,string>} parameters - */ + // 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; + } - /** - * @param {string} input - * @returns {MimeType | null} - */ - function parseMimeType(input) { - // 1. - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); + // 10. + const mimeType = { + type: StringPrototypeToLowerCase(type), + subtype: StringPrototypeToLowerCase(subtype), + /** @type {Map<string, string>} */ + parameters: new Map(), + }; - // 2. - let position = 0; - const endOfInput = input.length; + // 11. + while (position < endOfInput) { + // 11.1. + position++; - // 3. + // 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; + // 11.5. + if (position < endOfInput) { + if (input[position] == "\u003B") continue; + position++; } - // 10. - const mimeType = { - type: StringPrototypeToLowerCase(type), - subtype: StringPrototypeToLowerCase(subtype), - /** @type {Map<string, string>} */ - parameters: new Map(), - }; + // 11.6. + if (position >= endOfInput) break; - // 11. - while (position < endOfInput) { - // 11.1. - position++; + // 11.7. + let parameterValue = null; - // 11.2. - const res1 = collectSequenceOfCodepoints( - input, - position, - (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), - ); - position = res1.position; + // 11.8. + if (input[position] === "\u0022") { + // 11.8.1. + const res = collectHttpQuotedString(input, position, true); + parameterValue = res.result; + position = res.position; - // 11.3. - const res2 = collectSequenceOfCodepoints( + // 11.8.2. + position++; + } else { // 11.9. + // 11.9.1. + const res = collectSequenceOfCodepoints( input, position, - (c) => c !== "\u003B" && c !== "\u003D", + (c) => c !== "\u003B", + ); + parameterValue = res.result; + position = res.position; + + // 11.9.2. + parameterValue = StringPrototypeReplaceAll( + parameterValue, + HTTP_WHITESPACE_SUFFIX_RE, + "", ); - let parameterName = res2.result; - position = res2.position; - - // 11.4. - parameterName = StringPrototypeToLowerCase(parameterName); - - // 11.5. - if (position < endOfInput) { - if (input[position] == "\u003B") continue; - position++; - } - - // 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. - 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); - } + // 11.9.3. + if (parameterValue === "") continue; } - // 12. - return mimeType; - } - - /** - * @param {MimeType} mimeType - * @returns {string} - */ - function essence(mimeType) { - return `${mimeType.type}/${mimeType.subtype}`; + // 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); + } } - /** - * @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; + // 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}"`; } - return serialization; + serialization += value; } - - /** - * 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); + 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 ( - temporaryMimeType === null || - essence(temporaryMimeType) == "*/*" + !MapPrototypeHas(mimeType.parameters, "charset") && + charset !== null ) { - 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); - } + MapPrototypeSet(mimeType.parameters, "charset", charset); } } - return mimeType; } + 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 c99eb8f6e..de5210d33 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; - - // 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; +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; + +// This should be set via setGlobalThis this is required so that if even +// user deletes globalThis it is still usable +let globalThis_; + +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 setInPassiveListener(event, value) { - event[_inPassiveListener] = value; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), + keys: EVENT_PROPS, + })); } - function setPath(event, value) { - event[_path] = value; + get type() { + return this[_attributes].type; } - function setRelatedTarget( - event, - value, - ) { - event[_attributes].relatedTarget = value; + get target() { + return this[_attributes].target; } - function setTarget(event, value) { - event[_attributes].target = value; + get srcElement() { + return null; } - function setStopImmediatePropagation( - event, - value, - ) { - event[_stopImmediatePropagationFlag] = value; + set srcElement(_) { + // this member is deprecated } - // Type guards that widen the event type - - function hasRelatedTarget( - event, - ) { - return ReflectHas(event, "relatedTarget"); + get currentTarget() { + return this[_attributes].currentTarget; } - 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; - } + composedPath() { + const path = this[_path]; + if (path.length === 0) { + return []; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), - keys: EVENT_PROPS, - })); + if (!this.currentTarget) { + throw new Error("assertion error"); } + const composedPath = [ + { + item: this.currentTarget, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }, + ]; - get type() { - return this[_attributes].type; - } + let currentTargetIndex = 0; + let currentTargetHiddenSubtreeLevel = 0; - get target() { - return this[_attributes].target; - } + for (let index = path.length - 1; index >= 0; index--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - get srcElement() { - return null; - } + if (rootOfClosedTree) { + currentTargetHiddenSubtreeLevel++; + } - set srcElement(_) { - // this member is deprecated - } + if (item === this.currentTarget) { + currentTargetIndex = index; + break; + } - get currentTarget() { - return this[_attributes].currentTarget; + if (slotInClosedTree) { + currentTargetHiddenSubtreeLevel--; + } } - composedPath() { - const path = this[_path]; - if (path.length === 0) { - return []; - } + let currentHiddenLevel = currentTargetHiddenSubtreeLevel; + let maxHiddenLevel = currentTargetHiddenSubtreeLevel; + + for (let i = currentTargetIndex - 1; i >= 0; i--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - if (!this.currentTarget) { - throw new Error("assertion error"); + if (rootOfClosedTree) { + currentHiddenLevel++; } - 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; - - for (let i = currentTargetIndex - 1; i >= 0; i--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - - if (rootOfClosedTree) { - currentHiddenLevel++; - } - - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypeUnshift(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (slotInClosedTree) { - currentHiddenLevel--; + if (slotInClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } } + } - currentHiddenLevel = currentTargetHiddenSubtreeLevel; - maxHiddenLevel = currentTargetHiddenSubtreeLevel; + currentHiddenLevel = currentTargetHiddenSubtreeLevel; + maxHiddenLevel = currentTargetHiddenSubtreeLevel; - for (let index = currentTargetIndex + 1; index < path.length; index++) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - if (slotInClosedTree) { - currentHiddenLevel++; - } + if (slotInClosedTree) { + currentHiddenLevel++; + } - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypePush(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypePush(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } - if (rootOfClosedTree) { - currentHiddenLevel--; + if (rootOfClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = 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; } + return ArrayPrototypeMap(composedPath, (p) => p.item); + } - static get CAPTURING_PHASE() { - return 1; - } + get NONE() { + return Event.NONE; + } - static get AT_TARGET() { - return 2; - } + get CAPTURING_PHASE() { + return Event.CAPTURING_PHASE; + } - static get BUBBLING_PHASE() { - return 3; - } + get AT_TARGET() { + return Event.AT_TARGET; + } - get eventPhase() { - return this[_attributes].eventPhase; - } + get BUBBLING_PHASE() { + return Event.BUBBLING_PHASE; + } - stopPropagation() { - this[_stopPropagationFlag] = true; - } + static get NONE() { + return 0; + } - get cancelBubble() { - return this[_stopPropagationFlag]; - } + static get CAPTURING_PHASE() { + return 1; + } - set cancelBubble(value) { - this[_stopPropagationFlag] = webidl.converters.boolean(value); - } + static get AT_TARGET() { + return 2; + } - stopImmediatePropagation() { - this[_stopPropagationFlag] = true; - this[_stopImmediatePropagationFlag] = true; - } + static get BUBBLING_PHASE() { + return 3; + } - get bubbles() { - return this[_attributes].bubbles; - } + get eventPhase() { + return this[_attributes].eventPhase; + } - get cancelable() { - return this[_attributes].cancelable; - } + stopPropagation() { + this[_stopPropagationFlag] = true; + } - get returnValue() { - return !this[_canceledFlag]; - } + get cancelBubble() { + return this[_stopPropagationFlag]; + } - set returnValue(value) { - if (!webidl.converters.boolean(value)) { - this[_canceledFlag] = true; - } - } + set cancelBubble(value) { + this[_stopPropagationFlag] = webidl.converters.boolean(value); + } - preventDefault() { - if (this[_attributes].cancelable && !this[_inPassiveListener]) { - this[_canceledFlag] = true; - } - } + stopImmediatePropagation() { + this[_stopPropagationFlag] = true; + this[_stopImmediatePropagationFlag] = true; + } - get defaultPrevented() { - return this[_canceledFlag]; - } + get bubbles() { + return this[_attributes].bubbles; + } - get composed() { - return this[_attributes].composed; - } + get cancelable() { + return this[_attributes].cancelable; + } - get initialized() { - return true; - } + get returnValue() { + return !this[_canceledFlag]; + } - get timeStamp() { - return this[_attributes].timeStamp; + set returnValue(value) { + if (!webidl.converters.boolean(value)) { + this[_canceledFlag] = true; } } - function defineEnumerableProps( - Ctor, - props, - ) { - for (let i = 0; i < props.length; ++i) { - const prop = props[i]; - ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); + preventDefault() { + if (this[_attributes].cancelable && !this[_inPassiveListener]) { + this[_canceledFlag] = 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; + get defaultPrevented() { + return this[_canceledFlag]; } - function getRoot(eventTarget) { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; + get composed() { + return this[_attributes].composed; } - function isNode( - eventTarget, - ) { - return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); + get initialized() { + return true; } - // 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; + get timeStamp() { + return this[_attributes].timeStamp; } - - function isShadowRoot(nodeImpl) { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); +} + +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; + } - function isSlotable( - nodeImpl, - ) { - return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); + } } - // DOM Logic functions + return false; +} - /** Append a path item to an event's path. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-path-append - */ - function appendToEventPath( - eventImpl, - target, - targetOverride, +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, + relatedTarget, + touchTargets, + slotInClosedTree, +) { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && + getMode(target) === "closed"; + + 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); - - if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { - const touchTargets = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; + let parent = getParent(targetImpl); - 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]; + relatedTarget = retarget(eventRelatedTarget, parent); - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } + if ( + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) + ) { + appendToEventPath( + eventImpl, + parent, + null, + relatedTarget, + touchTargets, + slotInClosedTree, + ); + } else if (parent === relatedTarget) { + parent = null; + } else { + targetImpl = parent; if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET + isActivationEvent && + activationTarget === null && + getHasActivationBehavior(targetImpl) ) { - invokeEventListeners(tuple, eventImpl); + activationTarget = targetImpl; } + + appendToEventPath( + eventImpl, + parent, + targetImpl, + relatedTarget, + touchTargets, + slotInClosedTree, + ); } - } - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); + if (parent !== null) { + parent = getParent(parent); + } - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); + slotInClosedTree = false; } - // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented - // if (activationTarget !== null) { - // if (!eventImpl.defaultPrevented) { - // activationTarget._activationBehavior(); - // } - // } + 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]; - return !eventImpl.defaultPrevented; - } + clearTargets = (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - /** 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; + setEventPhase(eventImpl, Event.CAPTURING_PHASE); - const { type } = eventImpl; + for (let i = path.length - 1; i >= 0; --i) { + const tuple = path[i]; - if (!targetListeners || !targetListeners[type]) { - return found; + if (tuple.target === null) { + invokeEventListeners(tuple, eventImpl); + } } - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = ArrayPrototypeSlice(targetListeners[type]); + for (let i = 0; i < path.length; i++) { + const tuple = path[i]; - 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; + if (tuple.target !== null) { + setEventPhase(eventImpl, Event.AT_TARGET); } 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; + setEventPhase(eventImpl, Event.BUBBLING_PHASE); } - found = true; - if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + (eventImpl.eventPhase === Event.BUBBLING_PHASE && + eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET ) { - 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, - ); + invokeEventListeners(tuple, eventImpl); } + } + } - setInPassiveListener(eventImpl, false); + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); - if (getStopImmediatePropagation(eventImpl)) { - return found; - } - } + 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; + 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; } - setCurrentTarget(eventImpl, tuple.item); - - try { - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); - } catch (error) { - reportException(error); + // Check if the event listener has been removed since the listeners has been cloned. + if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { + continue; } - } - function normalizeEventHandlerOptions( - options, - ) { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; - } else { - return options; - } - } + found = true; - /** 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; - } + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } - const aRoot = a.getRootNode(); + if (once) { + ArrayPrototypeSplice( + targetListeners[type], + ArrayPrototypeIndexOf(targetListeners[type], listener), + 1, + ); + } - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } + if (passive) { + setInPassiveListener(eventImpl, true); + } - a = getHost(aRoot); + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); } + } else { + FunctionPrototypeCall( + listener.callback, + eventImpl.currentTarget, + eventImpl, + ); } - } - // Accessors for non-public data + setInPassiveListener(eventImpl, false); - const eventTargetData = Symbol(); - - function setEventTargetData(target) { - target[eventTargetData] = getDefaultTargetData(); - } - - function getAssignedSlot(target) { - return Boolean(target?.[eventTargetData]?.assignedSlot); + if (getStopImmediatePropagation(eventImpl)) { + return found; + } } - function getHasActivationBehavior(target) { - return Boolean(target?.[eventTargetData]?.hasActivationBehavior); + 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; + } } - function getHost(target) { - return target?.[eventTargetData]?.host ?? null; - } + setRelatedTarget(eventImpl, tuple.relatedTarget); - function getListeners(target) { - return target?.[eventTargetData]?.listeners ?? {}; + if (eventImpl.cancelBubble) { + return; } - function getMode(target) { - return target?.[eventTargetData]?.mode ?? null; - } + setCurrentTarget(eventImpl, tuple.item); - function listenerCount(target, type) { - return getListeners(target)?.[type]?.length ?? 0; + try { + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); + } catch (error) { + reportException(error); } +} - function getDefaultTargetData() { +function normalizeEventHandlerOptions( + options, +) { + if (typeof options === "boolean" || typeof options === "undefined") { return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: ObjectCreate(null), - mode: "", + capture: Boolean(options), }; + } else { + return options; } +} - // 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); - }; - - class EventTarget { - constructor() { - this[eventTargetData] = getDefaultTargetData(); - this[webidl.brand] = webidl.brand; +/** 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; } - addEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; + const aRoot = a.getRootNode(); - webidl.requiredArguments(arguments.length, 2, { - prefix, - }); + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } - options = webidl.converters.AddEventListenerOptions(options, { - prefix, - context: "Argument 3", - }); + a = getHost(aRoot); + } + } +} - if (callback === null) { - return; - } +// Accessors for non-public data - const { listeners } = self[eventTargetData]; +const eventTargetData = Symbol(); - if (!(ReflectHas(listeners, type))) { - listeners[type] = []; - } +function setEventTargetData(target) { + target[eventTargetData] = getDefaultTargetData(); +} - 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); - }); - } - } +function getAssignedSlot(target) { + return Boolean(target?.[eventTargetData]?.assignedSlot); +} - ArrayPrototypePush(listeners[type], { callback, options }); - } +function getHasActivationBehavior(target) { + return Boolean(target?.[eventTargetData]?.hasActivationBehavior); +} - removeEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 2, { - prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", - }); +function getHost(target) { + return target?.[eventTargetData]?.host ?? null; +} - 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; - } +function getListeners(target) { + return target?.[eventTargetData]?.listeners ?? {}; +} - options = normalizeEventHandlerOptions(options); +function getMode(target) { + return target?.[eventTargetData]?.mode ?? null; +} - 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; - } - } - } +function listenerCount(target, type) { + return getListeners(target)?.[type]?.length ?? 0; +} - 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'", - }); +function getDefaultTargetData() { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: ObjectCreate(null), + mode: "", + }; +} - const { listeners } = self[eventTargetData]; - if (!ReflectHas(listeners, event.type)) { - setTarget(event, this); - return true; - } +// This is lazy loaded because there is a circular dependency with AbortSignal. +let addEventListenerOptionsConverter; - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +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, + }, + ], + ); +} - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +webidl.converters.AddEventListenerOptions = (V, opts) => { + if (webidl.type(V) !== "Object" || V === null) { + V = { capture: Boolean(V) }; + } - return dispatch(self, event); - } + lazyAddEventListenerOptionsConverter(); + return addEventListenerOptionsConverter(V, opts); +}; - getParent(_event) { - return null; - } +class EventTarget { + constructor() { + this[eventTargetData] = getDefaultTargetData(); + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(EventTarget); - const EventTargetPrototype = EventTarget.prototype; + addEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; - defineEnumerableProps(EventTarget, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", - ]); + webidl.requiredArguments(arguments.length, 2, { + prefix, + }); - class ErrorEvent extends Event { - #message = ""; - #filename = ""; - #lineno = ""; - #colno = ""; - #error = ""; + options = webidl.converters.AddEventListenerOptions(options, { + prefix, + context: "Argument 3", + }); - get message() { - return this.#message; - } - get filename() { - return this.#filename; + if (callback === null) { + return; } - get lineno() { - return this.#lineno; + + const { listeners } = self[eventTargetData]; + + if (!(ReflectHas(listeners, type))) { + listeners[type] = []; } - get colno() { - return this.#colno; + + 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; + } } - get error() { - return this.#error; + 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); + }); + } } - constructor( - type, - { - bubbles, - cancelable, - composed, - message = "", - filename = "", - lineno = 0, - colno = 0, - error, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + ArrayPrototypePush(listeners[type], { callback, options }); + } - this.#message = message; - this.#filename = filename; - this.#lineno = lineno; - this.#colno = colno; - this.#error = error; - } + removeEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", + }); - [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", - ], - })); + 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; } - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ErrorEvent"; - } + options = normalizeEventHandlerOptions(options); - defineEnumerableProps(ErrorEvent, [ - "message", - "filename", - "lineno", - "colno", - "error", - ]); + 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; + } + } + } - class CloseEvent extends Event { - #wasClean = ""; - #code = ""; - #reason = ""; + 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'", + }); - get wasClean() { - return this.#wasClean; + const { listeners } = self[eventTargetData]; + if (!ReflectHas(listeners, event.type)) { + setTarget(event, this); + return true; } - get code() { - return this.#code; + + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - get reason() { - return this.#reason; + + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - constructor(type, { + 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; - } + 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(CloseEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "wasClean", - "code", - "reason", - ], - })); - } + [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 MessageEvent extends Event { - get source() { - return null; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ErrorEvent"; +} + +defineEnumerableProps(ErrorEvent, [ + "message", + "filename", + "lineno", + "colno", + "error", +]); + +class CloseEvent extends Event { + #wasClean = ""; + #code = ""; + #reason = ""; + + get wasClean() { + return this.#wasClean; + } + get code() { + return this.#code; + } + get reason() { + return this.#reason; + } - constructor(type, eventInitDict) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - [_skipInternalInit]: eventInitDict?.[_skipInternalInit], - }); + constructor(type, { + bubbles, + cancelable, + composed, + wasClean = false, + code = 0, + reason = "", + } = {}) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.data = eventInitDict?.data ?? null; - this.ports = eventInitDict?.ports ?? []; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } + this.#wasClean = wasClean; + this.#code = code; + this.#reason = reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "data", - "origin", - "lastEventId", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "wasClean", + "code", + "reason", + ], + })); + } +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CloseEvent"; +class MessageEvent extends Event { + get source() { + return null; } - class CustomEvent extends Event { - #detail = null; + constructor(type, eventInitDict) { + super(type, { + bubbles: eventInitDict?.bubbles ?? false, + cancelable: eventInitDict?.cancelable ?? false, + composed: eventInitDict?.composed ?? false, + [_skipInternalInit]: eventInitDict?.[_skipInternalInit], + }); - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'CustomEvent'", - }); - const { detail } = eventInitDict; - this.#detail = detail; - } + this.data = eventInitDict?.data ?? null; + this.ports = eventInitDict?.ports ?? []; + this.origin = eventInitDict?.origin ?? ""; + this.lastEventId = eventInitDict?.lastEventId ?? ""; + } - get detail() { - return this.#detail; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "data", + "origin", + "lastEventId", + ], + })); + } - [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] = "CloseEvent"; +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CustomEvent"; +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; } - ReflectDefineProperty(CustomEvent.prototype, "detail", { - enumerable: true, - }); + get detail() { + return this.#detail; + } - // 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); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "detail", + ], + })); + } - this.lengthComputable = eventInitDict?.lengthComputable ?? false; - this.loaded = eventInitDict?.loaded ?? 0; - this.total = eventInitDict?.total ?? 0; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CustomEvent"; +} - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "lengthComputable", - "loaded", - "total", - ], - })); - } +ReflectDefineProperty(CustomEvent.prototype, "detail", { + enumerable: true, +}); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ProgressEvent"; +// 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; } - class PromiseRejectionEvent extends Event { - #promise = null; - #reason = null; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "lengthComputable", + "loaded", + "total", + ], + })); + } - get promise() { - return this.#promise; - } - get reason() { - return this.#reason; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ProgressEvent"; +} - constructor( - type, - { - bubbles, - cancelable, - composed, - promise, - reason, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); +class PromiseRejectionEvent extends Event { + #promise = null; + #reason = null; - this.#promise = promise; - this.#reason = reason; - } + get promise() { + return this.#promise; + } + get reason() { + return this.#reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PromiseRejectionEvent.prototype, - this, - ), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "promise", - "reason", - ], - })); - } + constructor( + type, + { + bubbles, + cancelable, + composed, + promise, + reason, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "PromiseRejectionEvent"; + this.#promise = promise; + this.#reason = reason; } - defineEnumerableProps(PromiseRejectionEvent, [ - "promise", - "reason", - ]); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PromiseRejectionEvent.prototype, + this, + ), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "promise", + "reason", + ], + })); + } - const _eventHandlers = Symbol("eventHandlers"); + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "PromiseRejectionEvent"; +} - function makeWrappedHandler(handler, isSpecialErrorEventHandler) { - function wrappedHandler(evt) { - if (typeof wrappedHandler.handler !== "function") { - return; - } +defineEnumerableProps(PromiseRejectionEvent, [ + "promise", + "reason", +]); - 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; - } +const _eventHandlers = Symbol("eventHandlers"); - return FunctionPrototypeCall(wrappedHandler.handler, this, evt); +function makeWrappedHandler(handler, isSpecialErrorEventHandler) { + function wrappedHandler(evt) { + if (typeof wrappedHandler.handler !== "function") { + return; } - 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 ( + 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; + } - 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, - }); + 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; + } - 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; - } + 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; } - } - 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"); + 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; + } } } - - // 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); + 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--; +} - window.__bootstrap.eventTarget = { - EventTarget, - setEventTargetData, - listenerCount, - }; - window.__bootstrap.event = { - reportException, - setIsTrusted, - setTarget, - defineEventHandler, - _skipInternalInit, - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - CustomEvent, - ProgressEvent, - PromiseRejectionEvent, - reportError, - }; -})(this); +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis_) { + throw new TypeError("Illegal invocation"); + } +} + +// 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 793cb1c75..373ae0ab2 100644 --- a/ext/web/02_structured_clone.js +++ b/ext/web/02_structured_clone.js @@ -6,138 +6,135 @@ /// <reference path="../web/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> -"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; } - // 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, + if (ArrayBufferIsView(value)) { + const tag = TypedArrayPrototypeGetSymbolToStringTag(value); + // DataView + if (tag === undefined) { + return new DataView( + structuredClone(DataViewPrototypeGetBuffer(value)), + DataViewPrototypeGetByteOffset(value), + DataViewPrototypeGetByteLength(value), ); - 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), - ); + // 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 a582cf428..302b6f62c 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 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); - - timerNestingLevel = task.nestingLevel; - - try { - task.action(); - } finally { - timerNestingLevel = 0; - } - return timerTasks.length === 0; +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); +} + +// --------------------------------------------------------------------------- + +/** + * 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; } - // --------------------------------------------------------------------------- - - /** - * 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<number, { cancelRid: number, isRef: boolean, promiseId: number }>} - */ - const activeTimers = new Map(); - - let nextId = 1; - - /** - * @param {Function | string} callback - * @param {number} timeout - * @param {Array<any>} 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; - - // 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; - } + const task = ArrayPrototypeShift(timerTasks); - // 2. - // 3. - if (typeof callback === "function") { - try { - FunctionPrototypeCall( - callback, - globalThis, - ...new SafeArrayIterator(args), - ); - } catch (error) { - reportException(error); - } - } else { - indirectEval(callback); - } + timerNestingLevel = task.nestingLevel; - 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; + try { + task.action(); + } finally { + timerNestingLevel = 0; + } + return timerTasks.length === 0; +} + +// --------------------------------------------------------------------------- + +/** + * 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<number, { cancelRid: number, isRef: boolean, promiseId: number }>} + */ +const activeTimers = new Map(); + +let nextId = 1; + +/** + * @param {Function | string} callback + * @param {number} timeout + * @param {Array<any>} 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); } - // --------------------------------------------------------------------------- - - /** - * @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; + // 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; + } + + // 2. + // 3. + if (typeof callback === "function") { + try { + FunctionPrototypeCall( + callback, + globalThis, + ...new SafeArrayIterator(args), + ); + } catch (error) { + reportException(error); } - // 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; + } else { + indirectEval(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, + }; - /** @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; - } + // 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, + }; - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } + // 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; } - function setTimeout(callback, timeout = 0, ...args) { - checkThis(this); - if (typeof callback !== "function") { - callback = webidl.converters.DOMString(callback); - } - timeout = webidl.converters.long(timeout); + // 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; + } + } - return initializeTimer(callback, timeout, args, false); + 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 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 checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { + throw new TypeError("Illegal invocation"); } +} - 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 setTimeout(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); } + timeout = webidl.converters.long(timeout); - function clearInterval(id = 0) { - checkThis(this); - clearTimeout(id); - } + return initializeTimer(callback, timeout, args, false); +} - function refTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || timerInfo.isRef) { - return; - } - timerInfo.isRef = true; - core.refOp(timerInfo.promiseId); +function setInterval(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); } - - function unrefTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || !timerInfo.isRef) { - return; - } - timerInfo.isRef = false; - core.unrefOp(timerInfo.promiseId); + 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); } +} - window.__bootstrap.timers = { - setTimeout, - setInterval, - clearTimeout, - clearInterval, - handleTimerMacrotask, - opNow, - refTimer, - unrefTimer, - }; -})(this); +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 cce1bac7e..96757f41f 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 /// <reference path="../../core/internal.d.ts" /> -((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; - - 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"); - - 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; - } +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 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; - } + 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); + [add](algorithm) { + if (this.aborted) { + return; } - - [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); + if (this[abortAlgos] === null) { + this[abortAlgos] = new Set(); } + SetPrototypeAdd(this[abortAlgos], algorithm); + } - [remove](algorithm) { - this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); + [signalAbort]( + reason = new DOMException("The signal has been aborted", "AbortError"), + ) { + if (this.aborted) { + return; } - - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); + this[abortReason] = reason; + if (this[abortAlgos] !== null) { + for (const algorithm of new SafeSetIterator(this[abortAlgos])) { + algorithm(); } - super(); - this[abortReason] = undefined; this[abortAlgos] = null; - this[timerId] = null; - this[webidl.brand] = webidl.brand; } + const event = new Event("abort"); + setIsTrusted(event, true); + this.dispatchEvent(event); + } - get aborted() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason] !== undefined; - } + [remove](algorithm) { + this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); + } - get reason() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason]; + 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; + } - throwIfAborted() { - webidl.assertBranded(this, AbortSignalPrototype); - if (this[abortReason] !== undefined) { - throw this[abortReason]; - } + 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]); - } + // `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]); - } + removeEventListener(...args) { + super.removeEventListener(...new SafeArrayIterator(args)); + if (this[timerId] !== null && listenerCount(this, "abort") === 0) { + unrefTimer(this[timerId]); } } - defineEventHandler(AbortSignal.prototype, "abort"); +} +defineEventHandler(AbortSignal.prototype, "abort"); - webidl.configurePrototype(AbortSignal); - const AbortSignalPrototype = AbortSignal.prototype; +webidl.configurePrototype(AbortSignal); +const AbortSignalPrototype = AbortSignal.prototype; - class AbortController { - [signal] = new AbortSignal(illegalConstructorKey); +class AbortController { + [signal] = new AbortSignal(illegalConstructorKey); - constructor() { - this[webidl.brand] = webidl.brand; - } + constructor() { + this[webidl.brand] = webidl.brand; + } - get signal() { - webidl.assertBranded(this, AbortControllerPrototype); - return this[signal]; - } + get signal() { + webidl.assertBranded(this, AbortControllerPrototype); + return this[signal]; + } - abort(reason) { - webidl.assertBranded(this, AbortControllerPrototype); - this[signal][signalAbort](reason); - } + abort(reason) { + webidl.assertBranded(this, AbortControllerPrototype); + this[signal][signalAbort](reason); } +} - webidl.configurePrototype(AbortController); - const AbortControllerPrototype = AbortController.prototype; +webidl.configurePrototype(AbortController); +const AbortControllerPrototype = AbortController.prototype; - webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( - "AbortSignal", - AbortSignal.prototype, - ); +webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( + "AbortSignal", + AbortSignal.prototype, +); - function newSignal() { - return new AbortSignal(illegalConstructorKey); - } +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) - ); - } +function follow(followingSignal, parentSignal) { + if (followingSignal.aborted) { + return; } - - window.__bootstrap.abortSignal = { - AbortSignal, - AbortController, - AbortSignalPrototype, - add, - signalAbort, - remove, - follow, - newSignal, - }; -})(this); + 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 840f93ba9..6a42968db 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 /// <reference path="../../core/internal.d.ts" /> -((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(); +class Window extends EventTarget { + constructor(key = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); + } - get [SymbolToStringTag]() { - return "Window"; - } + get [SymbolToStringTag]() { + return "Window"; } +} - class WorkerGlobalScope 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 "WorkerGlobalScope"; - } + get [SymbolToStringTag]() { + return "WorkerGlobalScope"; } +} - class DedicatedWorkerGlobalScope extends WorkerGlobalScope { - 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 "DedicatedWorkerGlobalScope"; - } + 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, +}; - 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); +export { + DedicatedWorkerGlobalScope, + dedicatedWorkerGlobalScopeConstructorDescriptor, + Window, + windowConstructorDescriptor, + WorkerGlobalScope, + workerGlobalScopeConstructorDescriptor, +}; diff --git a/ext/web/05_base64.js b/ext/web/05_base64.js index dac366ca0..9f11ec97c 100644 --- a/ext/web/05_base64.js +++ b/ext/web/05_base64.js @@ -6,68 +6,62 @@ /// <reference path="../web/internal.d.ts" /> /// <reference lib="esnext" /> -"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 1bad4f314..a88b60893 100644 --- a/ext/web/06_streams.js +++ b/ext/web/06_streams.js @@ -5,3092 +5,3199 @@ /// <reference path="./06_streams_types.d.ts" /> /// <reference path="./lib.deno_web.d.ts" /> /// <reference lib="esnext" /> -"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; - - /** @template T */ - class Deferred { - /** @type {Promise<T>} */ - #promise; - /** @type {(reject?: any) => void} */ - #reject; - /** @type {(value: T | PromiseLike<T>) => void} */ - #resolve; - /** @type {"pending" | "fulfilled"} */ - #state = "pending"; - - constructor() { - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - }); - } - - /** @returns {Promise<T>} */ - 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<T>} 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<T>} value - * @returns {Promise<T>} - */ - 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 {Promise<any>} promise */ - function setPromiseIsHandledToTrue(promise) { - PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); - } - - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise<T>} promise - * @param {(value: T) => TResult1 | PromiseLike<TResult1>} fulfillmentHandler - * @param {(reason: any) => TResult2 | PromiseLike<TResult2>=} rejectionHandler - * @returns {Promise<TResult1 | TResult2>} - */ - function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { - return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); - } - - /** - * @template T - * @template TResult - * @param {Promise<T>} promise - * @param {(value: T) => TResult | PromiseLike<TResult>} onFulfilled - * @returns {void} - */ - function uponFulfillment(promise, onFulfilled) { - uponPromise(promise, onFulfilled); - } - /** - * @template T - * @template TResult - * @param {Promise<T>} promise - * @param {(value: T) => TResult | PromiseLike<TResult>} onRejected - * @returns {void} - */ - function uponRejection(promise, onRejected) { - uponPromise(promise, undefined, onRejected); +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<T>} */ + #promise; + /** @type {(reject?: any) => void} */ + #reject; + /** @type {(value: T | PromiseLike<T>) => void} */ + #resolve; + /** @type {"pending" | "fulfilled"} */ + #state = "pending"; + + constructor() { + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); } - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise<T>} promise - * @param {(value: T) => TResult1 | PromiseLike<TResult1>} onFulfilled - * @param {(reason: any) => TResult2 | PromiseLike<TResult2>=} onRejected - * @returns {void} - */ - function uponPromise(promise, onFulfilled, onRejected) { - PromisePrototypeThen( - PromisePrototypeThen(promise, onFulfilled, onRejected), - undefined, - rethrowAssertionErrorRejection, - ); + /** @returns {Promise<T>} */ + get promise() { + return this.#promise; } - /** - * @param {ArrayBufferLike} O - * @returns {boolean} - */ - function isDetachedBuffer(O) { - return O.byteLength === 0 && ops.op_arraybuffer_was_detached(O); + /** @returns {"pending" | "fulfilled"} */ + get state() { + return this.#state; } - /** - * @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; + /** @param {any=} reason */ + reject(reason) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; } - // 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<R>} stream - * @returns {ReadableStreamDefaultReader<R>} - */ - function acquireReadableStreamDefaultReader(stream) { - return new ReadableStreamDefaultReader(stream); + this.#state = "fulfilled"; + this.#reject(reason); } - /** - * @template R - * @param {ReadableStream<R>} stream - * @returns {ReadableStreamBYOBReader<R>} - */ - function acquireReadableStreamBYOBReader(stream) { - const reader = webidl.createBranded(ReadableStreamBYOBReader); - setUpReadableStreamBYOBReader(reader, stream); - return reader; + /** @param {T | PromiseLike<T>} 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<T>} value + * @returns {Promise<T>} + */ +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}`); + }); } - - /** - * @template W - * @param {WritableStream<W>} stream - * @returns {WritableStreamDefaultWriter<W>} - */ - function acquireWritableStreamDefaultWriter(stream) { - return new WritableStreamDefaultWriter(stream); +} + +/** @param {Promise<any>} promise */ +function setPromiseIsHandledToTrue(promise) { + PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise<T>} promise + * @param {(value: T) => TResult1 | PromiseLike<TResult1>} fulfillmentHandler + * @param {(reason: any) => TResult2 | PromiseLike<TResult2>=} rejectionHandler + * @returns {Promise<TResult1 | TResult2>} + */ +function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { + return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); +} + +/** + * @template T + * @template TResult + * @param {Promise<T>} promise + * @param {(value: T) => TResult | PromiseLike<TResult>} onFulfilled + * @returns {void} + */ +function uponFulfillment(promise, onFulfilled) { + uponPromise(promise, onFulfilled); +} + +/** + * @template T + * @template TResult + * @param {Promise<T>} promise + * @param {(value: T) => TResult | PromiseLike<TResult>} onRejected + * @returns {void} + */ +function uponRejection(promise, onRejected) { + uponPromise(promise, undefined, onRejected); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise<T>} promise + * @param {(value: T) => TResult1 | PromiseLike<TResult1>} onFulfilled + * @param {(reason: any) => TResult2 | PromiseLike<TResult2>=} 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; } - - /** - * @template R - * @param {() => void} startAlgorithm - * @param {() => Promise<void>} pullAlgorithm - * @param {(reason: any) => Promise<void>} cancelAlgorithm - * @param {number=} highWaterMark - * @param {((chunk: R) => number)=} sizeAlgorithm - * @returns {ReadableStream<R>} - */ - function createReadableStream( + // 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<R>} stream + * @returns {ReadableStreamDefaultReader<R>} + */ +function acquireReadableStreamDefaultReader(stream) { + return new ReadableStreamDefaultReader(stream); +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @returns {ReadableStreamBYOBReader<R>} + */ +function acquireReadableStreamBYOBReader(stream) { + const reader = webidl.createBranded(ReadableStreamBYOBReader); + setUpReadableStreamBYOBReader(reader, stream); + return reader; +} + +/** + * @template W + * @param {WritableStream<W>} stream + * @returns {WritableStreamDefaultWriter<W>} + */ +function acquireWritableStreamDefaultWriter(stream) { + return new WritableStreamDefaultWriter(stream); +} + +/** + * @template R + * @param {() => void} startAlgorithm + * @param {() => Promise<void>} pullAlgorithm + * @param {(reason: any) => Promise<void>} cancelAlgorithm + * @param {number=} highWaterMark + * @param {((chunk: R) => number)=} sizeAlgorithm + * @returns {ReadableStream<R>} + */ +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; - } - - /** - * @template W - * @param {(controller: WritableStreamDefaultController<W>) => Promise<void>} startAlgorithm - * @param {(chunk: W) => Promise<void>} writeAlgorithm - * @param {() => Promise<void>} closeAlgorithm - * @param {(reason: any) => Promise<void>} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - * @returns {WritableStream<W>} - */ - function createWritableStream( + highWaterMark, + sizeAlgorithm, + ); + return stream; +} + +/** + * @template W + * @param {(controller: WritableStreamDefaultController<W>) => Promise<void>} startAlgorithm + * @param {(chunk: W) => Promise<void>} writeAlgorithm + * @param {() => Promise<void>} closeAlgorithm + * @param {(reason: any) => Promise<void>} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + * @returns {WritableStream<W>} + */ +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; + ); + return stream; +} + +/** + * @template T + * @param {{ [_queue]: Array<ValueWithSize<T>>, [_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; } - - /** - * @template T - * @param {{ [_queue]: Array<ValueWithSize<T>>, [_queueTotalSize]: number }} container - * @returns {T} - */ - function dequeueValue(container) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), + return valueWithSize.value; +} + +/** + * @template T + * @param {{ [_queue]: Array<ValueWithSize<T | _close>>, [_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}".`, ); - 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<ValueWithSize<T | _close>>, [_queueTotalSize]: number }} container - * @param {T} value - * @param {number} size - * @returns {void} - */ - function enqueueValueWithSize(container, value, size) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), + return highWaterMark; +} + +/** + * @template T + * @param {QueuingStrategy<T>} 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`" }, ); - 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<T>} 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<void>} pullAlgorithm - * @param {(reason: any) => Promise<void>} cancelAlgorithm - * @returns {ReadableStream} - */ - function createReadableByteStream( +} + +/** + * @param {() => void} startAlgorithm + * @param {() => Promise<void>} pullAlgorithm + * @param {(reason: any) => Promise<void>} 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; - } - - /** - * @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<I, O>} stream - * @param {Deferred<void>} startPromise - * @param {number} writableHighWaterMark - * @param {(chunk: I) => number} writableSizeAlgorithm - * @param {number} readableHighWaterMark - * @param {(chunk: O) => number} readableSizeAlgorithm - */ - function initializeTransformStream( - stream, - startPromise, + 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<I, O>} stream + * @param {Deferred<void>} 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; + } + + function writeAlgorithm(chunk) { + return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); + } + + function abortAlgorithm(reason) { + return transformStreamDefaultSinkAbortAlgorithm(stream, reason); + } + + function closeAlgorithm() { + return transformStreamDefaultSinkCloseAlgorithm(stream); + } + + stream[_writable] = createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ) { - function startAlgorithm() { - return startPromise.promise; - } - - function writeAlgorithm(chunk) { - return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); - } - - 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, - ); - - stream[_backpressure] = stream[_backpressureChangePromise] = undefined; - transformStreamSetBackpressure(stream, true); - stream[_controller] = undefined; - } + ); - /** @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 pullAlgorithm() { + return transformStreamDefaultSourcePullAlgorithm(stream); } - /** - * @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; + function cancelAlgorithm(reason) { + transformStreamErrorWritableAndUnblockWrite(stream, reason); + return resolvePromiseWith(undefined); } - /** - * @param {unknown} value - * @returns {value is ReadableStream} - */ - function isReadableStream(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _controller)); - } + stream[_readable] = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ); - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamLocked(stream) { - if (stream[_reader] === undefined) { - return false; - } - return true; + stream[_backpressure] = stream[_backpressureChangePromise] = undefined; + transformStreamSetBackpressure(stream, true); + stream[_controller] = undefined; +} + +/** @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; +} + +/** + * @param {unknown} v + * @returns {v is number} + */ +function isNonNegativeNumber(v) { + if (typeof v !== "number") { + return false; } - - /** - * @param {unknown} value - * @returns {value is ReadableStreamDefaultReader} - */ - function isReadableStreamDefaultReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readRequests)); + if (NumberIsNaN(v)) { + return false; } - - /** - * @param {unknown} value - * @returns {value is ReadableStreamBYOBReader} - */ - function isReadableStreamBYOBReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readIntoRequests)); + if (v < 0) { + return false; } - - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamDisturbed(stream) { - assert(isReadableStream(stream)); - return stream[_disturbed]; + return true; +} + +/** + * @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; } - - 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) => { + 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 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]; +} + +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); +}); + +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<Uint8Array>} + */ +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<Uint8Array>} - */ - 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); - } + 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 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); + const bytesRead = await core.read(rid, v); + if (bytesRead === 0) { tryClose(); + controller.close(); + controller.byobRequest.respond(0); + } else { + controller.byobRequest.respond(bytesRead); } - }, - cancel() { + } catch (e) { + controller.error(e); 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<Uint8Array>} - */ - 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); + } + }, + 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<Uint8Array>} + */ +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); } - }, - cancel() { + } catch (e) { + controller.error(e); core.tryClose(rid); - }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, - }; - initializeReadableStream(stream); - setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSource, - 0, - ); - return stream; - } + } + }, + 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 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 readableStreamForRidUnrefableRef(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); } - - function readableStreamForRidUnrefableUnref(stream) { - if (!readableStreamIsUnrefable(stream)) { - throw new TypeError("Not an unrefable stream"); - } - stream[_isUnref] = true; - if (stream[promiseIdSymbol] !== undefined) { - core.unrefOp(stream[promiseIdSymbol]); - } + stream[_isUnref] = false; + if (stream[promiseIdSymbol] !== undefined) { + core.refOp(stream[promiseIdSymbol]); } +} - function getReadableStreamResourceBacking(stream) { - return stream[_resourceBacking]; +function readableStreamForRidUnrefableUnref(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); } - - function getReadableStreamResourceBackingUnrefable(stream) { - return stream[_resourceBackingUnrefable]; + stream[_isUnref] = true; + if (stream[promiseIdSymbol] !== undefined) { + core.unrefOp(stream[promiseIdSymbol]); } +} - 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; +function getReadableStreamResourceBacking(stream) { + return stream[_resourceBacking]; +} - // 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(); +function getReadableStreamResourceBackingUnrefable(stream) { + return stream[_resourceBackingUnrefable]; +} - if (done) break; +async function readableStreamCollectIntoUint8Array(stream) { + const resourceBacking = getReadableStreamResourceBacking(stream) || + getReadableStreamResourceBackingUnrefable(stream); + const reader = acquireReadableStreamDefaultReader(stream); - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { - throw new TypeError( - "Can't convert value to Uint8Array while consuming the 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); } - - 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<Uint8Array>} - */ - function writableStreamForRid(rid, autoClose = true) { - const stream = webidl.createBranded(WritableStream); - stream[_resourceBacking] = { rid, autoClose }; + // slow path + /** @type {Uint8Array[]} */ + const chunks = []; + let totalLength = 0; - 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; + // 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; } - function getWritableStreamResourceBacking(stream) { - return stream[_resourceBacking]; - } + while (true) { + const { value: chunk, done } = await reader.read(); - /* - * @param {ReadableStream} stream - */ - function readableStreamThrowIfErrored(stream) { - if (stream[_state] === "errored") { - throw stream[_storedError]; - } - } + if (done) break; - /** - * @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<ValueWithSize<T | _close>>, [_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; + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { + throw new TypeError( + "Can't convert value to Uint8Array while consuming the stream", + ); } - assert(controller[_pullAgain] === false); - controller[_pulling] = true; - /** @type {Promise<void>} */ - 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; - } + 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<Uint8Array>} + */ +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); + }; - /** - * @param {ReadableByteStreamController} controller - * @param {any} e - */ - function readableByteStreamControllerError(controller, e) { - /** @type {ReadableStream<ArrayBuffer>} */ - const stream = controller[_stream]; - if (stream[_state] !== "readable") { - return; - } - readableByteStreamControllerClearPendingPullIntos(controller); - resetQueue(controller); - readableByteStreamControllerClearAlgorithms(controller); - readableStreamError(stream, e); + if (autoClose) { + RESOURCE_REGISTRY.register(stream, rid, stream); } - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClearPendingPullIntos(controller) { - readableByteStreamControllerInvalidateBYOBRequest(controller); - controller[_pendingPullIntos] = []; + 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; } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClose(controller) { - /** @type {ReadableStream<ArrayBuffer>} */ - 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", - ); + return true; +} +/** + * @template T + * @param {{ [_queue]: Array<ValueWithSize<T | _close>>, [_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<void>} */ + const pullPromise = controller[_pullAlgorithm](controller); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + pullPromise, + () => { + controller[_pulling] = false; + if (controller[_pullAgain]) { + controller[_pullAgain] = false; + readableByteStreamControllerCallPullIfNeeded(controller); + } + }, + (e) => { readableByteStreamControllerError(controller, e); - throw e; - } - } - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(stream); + }, + ), + ); +} + +/** + * @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<ArrayBuffer>} */ + 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<ArrayBuffer>} */ + 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<ArrayBuffer>} */ + const stream = controller[_stream]; + if ( + controller[_closeRequested] || + controller[_stream][_state] !== "readable" + ) { + return; } - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} chunk - */ - function readableByteStreamControllerEnqueue(controller, chunk) { - /** @type {ReadableStream<ArrayBuffer>} */ - const stream = controller[_stream]; - if ( - controller[_closeRequested] || - controller[_stream][_state] !== "readable" - ) { - return; - } - - const { buffer, byteOffset, byteLength } = chunk; - if (isDetachedBuffer(buffer)) { + const { buffer, byteOffset, byteLength } = chunk; + if (isDetachedBuffer(buffer)) { + throw new TypeError( + "chunk's buffer is detached and so cannot be enqueued", + ); + } + 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); + } else if (readableStreamHasBYOBReader(stream)) { + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); + } else { + assert(isReadableStreamLocked(stream) === false); + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueChunkToQueue( + 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); + } + readableByteStreamControllerEnqueueChunkToQueue( controller, - buffer, - byteOffset, + cloneResult, + 0, byteLength, - ) { - ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); - controller[_queueTotalSize] += 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, + ); } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - buffer, - byteOffset, - byteLength, + readableByteStreamControllerShiftPendingPullInto(controller); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {ReadableStreamBYOBRequest | null} + */ +function readableByteStreamControllerGetBYOBRequest(controller) { + if ( + controller[_byobRequest] === null && + controller[_pendingPullIntos].length !== 0 ) { - let cloneResult; - try { - cloneResult = buffer.slice(byteOffset, byteOffset + byteLength); - } catch (e) { - readableByteStreamControllerError(controller, e); - } - readableByteStreamControllerEnqueueChunkToQueue( - controller, - cloneResult, - 0, - byteLength, + 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 - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - pullIntoDescriptor, +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {boolean} + */ +function readableByteStreamControllerShouldCallPull(controller) { + /** @type {ReadableStream<ArrayBuffer>} */ + const stream = controller[_stream]; + if ( + stream[_state] !== "readable" || + controller[_closeRequested] || + !controller[_started] ) { - assert(pullIntoDescriptor.readerType === "none"); - if (pullIntoDescriptor.bytesFilled > 0) { - readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - pullIntoDescriptor.bytesFilled, - ); - } - readableByteStreamControllerShiftPendingPullInto(controller); + return false; } - - /** - * @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]; + if ( + readableStreamHasDefaultReader(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; } - - /** - * @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]; + if ( + readableStreamHasBYOBReader(stream) && + readableStreamGetNumReadIntoRequests(stream) > 0 + ) { + return true; } - - /** - * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container - * @returns {void} - */ - function resetQueue(container) { - container[_queue] = []; - container[_queueTotalSize] = 0; + const desiredSize = readableByteStreamControllerGetDesiredSize(controller); + assert(desiredSize !== null); + return desiredSize > 0; +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @param {ReadRequest<R>} 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<R>} stream + * @param {any=} reason + * @returns {Promise<void>} + */ +function readableStreamCancel(stream, reason) { + stream[_disturbed] = true; + if (stream[_state] === "closed") { + return resolvePromiseWith(undefined); } - - /** - * @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); + 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<void>} */ + const sourceCancelPromise = stream[_controller][_cancelSteps](reason); + return PromisePrototypeThen(sourceCancelPromise, () => undefined); +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @returns {void} + */ +function readableStreamClose(stream) { + assert(stream[_state] === "readable"); + stream[_state] = "closed"; + /** @type {ReadableStreamDefaultReader<R> | undefined} */ + const reader = stream[_reader]; + if (!reader) { + return; + } + if (isReadableStreamDefaultReader(reader)) { + /** @type {Array<ReadRequest<R>>} */ + 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); +} - /** - * @param {ReadableByteStreamController} controller - * @returns {boolean} - */ - function readableByteStreamControllerShouldCallPull(controller) { - /** @type {ReadableStream<ArrayBuffer>} */ - 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; +/** + * @template R + * @param {ReadableStream<R>} stream + * @returns {void} + */ +function readableStreamDisturb(stream) { + stream[_disturbed] = true; +} + +/** @param {ReadableStreamDefaultController<any>} 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); } - const desiredSize = readableByteStreamControllerGetDesiredSize(controller); - assert(desiredSize !== null); - return desiredSize > 0; + }); + uponRejection(pullPromise, (e) => { + readableStreamDefaultControllerError(controller, e); + }); +} + +/** + * @param {ReadableStreamDefaultController<any>} controller + * @returns {boolean} + */ +function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { + const state = controller[_stream][_state]; + if (controller[_closeRequested] === false && state === "readable") { + return true; + } else { + return false; } +} - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {ReadRequest<R>} readRequest - * @returns {void} - */ - function readableStreamAddReadRequest(stream, readRequest) { - assert(isReadableStreamDefaultReader(stream[_reader])); - assert(stream[_state] === "readable"); - ArrayPrototypePush(stream[_reader][_readRequests], readRequest); - } +/** @param {ReadableStreamDefaultController<any>} controller */ +function readableStreamDefaultControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; +} - /** - * @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); +/** @param {ReadableStreamDefaultController<any>} controller */ +function readableStreamDefaultControllerClose(controller) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {any=} reason - * @returns {Promise<void>} - */ - function readableStreamCancel(stream, reason) { - stream[_disturbed] = true; - if (stream[_state] === "closed") { - return resolvePromiseWith(undefined); - } - if (stream[_state] === "errored") { - return PromiseReject(stream[_storedError]); - } + const stream = controller[_stream]; + controller[_closeRequested] = true; + if (controller[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(controller); 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<void>} */ - const sourceCancelPromise = stream[_controller][_cancelSteps](reason); - return PromisePrototypeThen(sourceCancelPromise, () => undefined); } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @returns {void} - */ - function readableStreamClose(stream) { - assert(stream[_state] === "readable"); - stream[_state] = "closed"; - /** @type {ReadableStreamDefaultReader<R> | undefined} */ - const reader = stream[_reader]; - if (!reader) { - return; - } - if (isReadableStreamDefaultReader(reader)) { - /** @type {Array<ReadRequest<R>>} */ - 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<R>} stream - * @returns {void} - */ - function readableStreamDisturb(stream) { - stream[_disturbed] = true; +} + +/** + * @template R + * @param {ReadableStreamDefaultController<R>} controller + * @param {R} chunk + * @returns {void} + */ +function readableStreamDefaultControllerEnqueue(controller, chunk) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; } - - /** @param {ReadableStreamDefaultController<any>} controller */ - function readableStreamDefaultControllerCallPullIfNeeded(controller) { - const shouldPull = readableStreamDefaultcontrollerShouldCallPull( - controller, - ); - if (shouldPull === false) { - return; - } - if (controller[_pulling] === true) { - controller[_pullAgain] = true; - 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; } - 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) => { + + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { readableStreamDefaultControllerError(controller, e); - }); + throw e; + } + } + readableStreamDefaultControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableStreamDefaultController<any>} 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<any>} 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<any>} controller - * @returns {boolean} - */ - function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { - const state = controller[_stream][_state]; - if (controller[_closeRequested] === false && state === "readable") { - return true; - } else { - return false; - } +/** + * @param {ReadableStreamDefaultController<any>} controller + * @returns {boolean} + */ +function readableStreamDefaultcontrollerShouldCallPull(controller) { + const stream = controller[_stream]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return false; } - - /** @param {ReadableStreamDefaultController<any>} controller */ - function readableStreamDefaultControllerClearAlgorithms(controller) { - controller[_pullAlgorithm] = undefined; - controller[_cancelAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; + if (controller[_started] === false) { + return false; } - - /** @param {ReadableStreamDefaultController<any>} 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); - } + if ( + isReadableStreamLocked(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; } - - /** - * @template R - * @param {ReadableStreamDefaultController<R>} controller - * @param {R} chunk - * @returns {void} - */ - function readableStreamDefaultControllerEnqueue(controller, chunk) { - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { + 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 stream = controller[_stream]; + const pullIntoDescriptor = controller[_pendingPullIntos][0]; if ( - isReadableStreamLocked(stream) === true && - readableStreamGetNumReadRequests(stream) > 0 + readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, + ) ) { - 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; - } + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], + pullIntoDescriptor, + ); } - readableStreamDefaultControllerCallPullIfNeeded(controller); } - - /** - * @param {ReadableStreamDefaultController<any>} controller - * @param {any} e - */ - function readableStreamDefaultControllerError(controller, e) { - const stream = controller[_stream]; - if (stream[_state] !== "readable") { +} +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerProcessReadRequestsUsingQueue( + controller, +) { + const reader = controller[_stream][_reader]; + assert(isReadableStreamDefaultReader(reader)); + while (reader[_readRequests].length !== 0) { + if (controller[_queueTotalSize] === 0) { return; } - resetQueue(controller); - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamError(stream, e); - } - - /** - * @param {ReadableStreamDefaultController<any>} 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<any>} 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( + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + readableByteStreamControllerFillReadRequestFromQueue( controller, + readRequest, ); - 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 {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; } + const byteOffset = view.byteOffset; + const byteLength = view.byteLength; - /** - * @param {ReadableStreamBYOBReader} reader - */ - function readableStreamBYOBReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); - } + /** @type {ArrayBufferLike} */ + let buffer; - /** - * @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); - } + try { + buffer = transferArrayBuffer(view.buffer); + } catch (e) { + readIntoRequest.errorSteps(e); + return; } - /** - * @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, - pullIntoDescriptor, - ) - ) { - readableByteStreamControllerShiftPendingPullInto(controller); - readableByteStreamControllerCommitPullIntoDescriptor( - controller[_stream], - pullIntoDescriptor, - ); - } - } + /** @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; } - /** - * @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 readRequest = ArrayPrototypeShift(reader[_readRequests]); - readableByteStreamControllerFillReadRequestFromQueue( - controller, - readRequest, - ); - } + if (stream[_state] === "closed") { + const emptyView = new ctor( + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + 0, + ); + readIntoRequest.closeSteps(emptyView); + return; } - - /** - * @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 (controller[_queueTotalSize] > 0) { 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) + readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, + ) ) { - 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); + const filledView = readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, + ); + readableByteStreamControllerHandleQueueDrain(controller); + readIntoRequest.chunkSteps(filledView); return; } - if (stream[_state] === "closed") { - const emptyView = new ctor( - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - 0, + if (controller[_closeRequested]) { + const e = new TypeError( + "Insufficient bytes to fill elements in the given buffer", ); - readIntoRequest.closeSteps(emptyView); + readableByteStreamControllerError(controller, e); + readIntoRequest.errorSteps(e); 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); } - - /** - * @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"); - } + controller[_pendingPullIntos].push(pullIntoDescriptor); + readableStreamAddReadIntoRequest(stream, readIntoRequest); + readableByteStreamControllerCallPullIfNeeded(controller); +} + +/** + * @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", + ); } - firstDescriptor.buffer = transferArrayBuffer(firstDescriptor.buffer); - readableByteStreamControllerRespondInternal(controller, bytesWritten); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerRespondInReadableState( + } 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( + 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; + } + 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, + ); } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @returns {void} - */ - function readableByteStreamControllerRespondInternal( + pullIntoDescriptor.bytesFilled -= remainderSize; + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], + pullIntoDescriptor, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( 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); + ); +} + +/** + * @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, + ); } - - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerInvalidateBYOBRequest(controller) { - if (controller[_byobRequest] === null) { - return; - } - controller[_byobRequest][_controller] = undefined; - controller[_byobRequest][_view] = null; - controller[_byobRequest] = null; + readableByteStreamControllerCallPullIfNeeded(controller); +} + +/** + * @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); } - - /** - * @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, - ); - } + const stream = controller[_stream]; + if (readableStreamHasBYOBReader(stream)) { + while (readableStreamGetNumReadIntoRequests(stream) > 0) { + const pullIntoDescriptor = + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + stream, + pullIntoDescriptor, + ); } } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {PullIntoDescriptor} pullIntoDescriptor - */ - function readableByteStreamControllerCommitPullIntoDescriptor( - stream, +} + +/** + * @template R + * @param {ReadableStream<R>} 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, - ) { - 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", - ); - } - } - 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 (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", ); } - if ( - (firstDescriptor.bytesFilled + view.byteLength) > - firstDescriptor.byteLength - ) { - throw new RangeError( - "The region specified by view is larger than byobRequest", + } 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", ); } - 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, + if ( + (firstDescriptor.byteOffset + firstDescriptor.bytesFilled) !== + view.byteOffset ) { - const elementSize = pullIntoDescriptor.elementSize; - const currentAlignedBytes = pullIntoDescriptor.bytesFilled - - (pullIntoDescriptor.bytesFilled % elementSize); - const maxBytesToCopy = MathMin( - controller[_queueTotalSize], - pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, + throw new RangeError( + "The region specified by view does not match byobRequest", ); - 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 { - headOfQueue.byteOffset += bytesToCopy; - headOfQueue.byteLength -= bytesToCopy; - } - controller[_queueTotalSize] -= bytesToCopy; - readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - bytesToCopy, - pullIntoDescriptor, - ); - totalBytesToCopyRemaining -= bytesToCopy; - } - if (!ready) { - assert(controller[_queueTotalSize] === 0); - assert(pullIntoDescriptor.bytesFilled > 0); - assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); - } - return ready; } - - /** - * @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, + if (firstDescriptor.bufferByteLength !== view.buffer.byteLength) { + throw new RangeError( + "The buffer of view has different capacity than byobRequest", ); - readRequest.chunkSteps(view); } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} size - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - size, - pullIntoDescriptor, + if ( + (firstDescriptor.bytesFilled + view.byteLength) > + firstDescriptor.byteLength ) { - assert( - controller[_pendingPullIntos].length === 0 || - controller[_pendingPullIntos][0] === pullIntoDescriptor, + throw new RangeError( + "The region specified by view is larger than byobRequest", ); - assert(controller[_byobRequest] === null); - pullIntoDescriptor.bytesFilled += size; } + 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, +) { + 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; - /** - * @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, + const destBuffer = new Uint8Array( + pullIntoDescriptor.buffer, + destStart, + bytesToCopy, ); - } + const srcBuffer = new Uint8Array( + headOfQueue.buffer, + headOfQueue.byteOffset, + bytesToCopy, + ); + destBuffer.set(srcBuffer); - /** - * @template R - * @param {ReadableStreamDefaultReader<R>} reader - * @param {ReadRequest<R>} 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]); + if (headOfQueue.byteLength === bytesToCopy) { + ArrayPrototypeShift(queue); } else { - assert(stream[_state] === "readable"); - stream[_controller][_pullSteps](readRequest); + headOfQueue.byteOffset += bytesToCopy; + headOfQueue.byteLength -= bytesToCopy; } - } - - /** - * @template R - * @param {ReadableStreamDefaultReader<R>} reader - */ - function readableStreamDefaultReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamDefaultReaderErrorReadRequests(reader, e); - } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {any} e - */ - function readableStreamError(stream, e) { + controller[_queueTotalSize] -= bytesToCopy; + readableByteStreamControllerFillHeadPullIntoDescriptor( + controller, + bytesToCopy, + pullIntoDescriptor, + ); + totalBytesToCopyRemaining -= bytesToCopy; + } + if (!ready) { + assert(controller[_queueTotalSize] === 0); + assert(pullIntoDescriptor.bytesFilled > 0); + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); + } + return ready; +} + +/** + * @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<R>} reader + * @param {ReadRequest<R>} 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<R> | undefined} */ - const reader = stream[_reader]; - if (reader === undefined) { - return; - } - /** @type {Deferred<void>} */ - const closedPromise = reader[_closedPromise]; - closedPromise.reject(e); - setPromiseIsHandledToTrue(closedPromise.promise); - if (isReadableStreamDefaultReader(reader)) { - readableStreamDefaultReaderErrorReadRequests(reader, e); - } else { - assert(isReadableStreamBYOBReader(reader)); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); - } - } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadIntoRequest(stream, chunk, done) { - assert(readableStreamHasBYOBReader(stream)); - /** @type {ReadableStreamDefaultReader<R>} */ - 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 {ReadableStream<R>} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadRequest(stream, chunk, done) { - assert(readableStreamHasDefaultReader(stream) === true); - /** @type {ReadableStreamDefaultReader<R>} */ - const reader = stream[_reader]; - assert(reader[_readRequests].length); - /** @type {ReadRequest<R>} */ - const readRequest = ArrayPrototypeShift(reader[_readRequests]); - if (done) { - readRequest.closeSteps(); - } else { - readRequest.chunkSteps(chunk); - } + stream[_controller][_pullSteps](readRequest); + } +} + +/** + * @template R + * @param {ReadableStreamDefaultReader<R>} reader + */ +function readableStreamDefaultReaderRelease(reader) { + readableStreamReaderGenericRelease(reader); + const e = new TypeError("The reader was released."); + readableStreamDefaultReaderErrorReadRequests(reader, e); +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @param {any} e + */ +function readableStreamError(stream, e) { + assert(stream[_state] === "readable"); + stream[_state] = "errored"; + stream[_storedError] = e; + /** @type {ReadableStreamDefaultReader<R> | undefined} */ + const reader = stream[_reader]; + if (reader === undefined) { + return; + } + /** @type {Deferred<void>} */ + 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<R>} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadIntoRequest(stream, chunk, done) { + assert(readableStreamHasBYOBReader(stream)); + /** @type {ReadableStreamDefaultReader<R>} */ + 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 {ReadableStream<R>} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadRequest(stream, chunk, done) { + assert(readableStreamHasDefaultReader(stream) === true); + /** @type {ReadableStreamDefaultReader<R>} */ + const reader = stream[_reader]; + assert(reader[_readRequests].length); + /** @type {ReadRequest<R>} */ + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + if (done) { + readRequest.closeSteps(); + } else { + readRequest.chunkSteps(chunk); + } +} + +/** + * @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; } - - /** - * @param {ReadableStream} stream - * @return {number} - */ - function readableStreamGetNumReadRequests(stream) { - assert(readableStreamHasDefaultReader(stream) === true); - return stream[_reader][_readRequests].length; + if (isReadableStreamBYOBReader(reader)) { + return true; } + return false; +} - /** - * @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 + * @returns {boolean} + */ +function readableStreamHasDefaultReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { return false; } - - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function readableStreamHasDefaultReader(stream) { - const reader = stream[_reader]; - if (reader === undefined) { - return false; - } - if (isReadableStreamDefaultReader(reader)) { - return true; - } - return false; + if (isReadableStreamDefaultReader(reader)) { + return true; } - - /** - * @template T - * @param {ReadableStream<T>} source - * @param {WritableStream<T>} dest - * @param {boolean} preventClose - * @param {boolean} preventAbort - * @param {boolean} preventCancel - * @param {AbortSignal=} signal - * @returns {Promise<void>} - */ - 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<void>} */ - const promise = new Deferred(); - /** @type {() => void} */ - let abortAlgorithm; - if (signal) { - abortAlgorithm = () => { - const error = signal.reason; - /** @type {Array<() => Promise<void>>} */ - 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; + return false; +} + +/** + * @template T + * @param {ReadableStream<T>} source + * @param {WritableStream<T>} dest + * @param {boolean} preventClose + * @param {boolean} preventAbort + * @param {boolean} preventCancel + * @param {AbortSignal=} signal + * @returns {Promise<void>} + */ +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<void>} */ + const promise = new Deferred(); + /** @type {() => void} */ + let abortAlgorithm; + if (signal) { + abortAlgorithm = () => { + const error = signal.reason; + /** @type {Array<() => Promise<void>>} */ + const actions = []; + if (preventAbort === false) { + ArrayPrototypePush(actions, () => { + if (dest[_state] === "writable") { + return writableStreamAbort(dest, error); + } else { + return resolvePromiseWith(undefined); + } + }); } - signal[add](abortAlgorithm); - } - - function pipeLoop() { - return new Promise((resolveLoop, rejectLoop) => { - /** @param {boolean} done */ - function next(done) { - if (done) { - resolveLoop(); + if (preventCancel === false) { + ArrayPrototypePush(actions, () => { + if (source[_state] === "readable") { + return readableStreamCancel(source, error); } else { - uponPromise(pipeStep(), next, rejectLoop); + return resolvePromiseWith(undefined); } - } - next(false); - }); - } - - /** @returns {Promise<boolean>} */ - function pipeStep() { - if (shuttingDown === true) { - return resolvePromiseWith(true); + }); } + shutdownWithAction( + () => SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), + true, + error, + ); + }; - 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 (signal.aborted) { + abortAlgorithm(); + return promise.promise; } + signal[add](abortAlgorithm); + } - isOrBecomesErrored( - source, - reader[_closedPromise].promise, - (storedError) => { - if (preventAbort === false) { - shutdownWithAction( - () => writableStreamAbort(dest, storedError), - true, - storedError, - ); + function pipeLoop() { + return new Promise((resolveLoop, rejectLoop) => { + /** @param {boolean} done */ + function next(done) { + if (done) { + resolveLoop(); } else { - shutdown(true, storedError); + uponPromise(pipeStep(), next, rejectLoop); } - }, - ); - - isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { - if (preventCancel === false) { - shutdownWithAction( - () => readableStreamCancel(source, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); } + next(false); }); + } + + /** @returns {Promise<boolean>} */ + function pipeStep() { + if (shuttingDown === true) { + return resolvePromiseWith(true); + } - isOrBecomesClosed(source, reader[_closedPromise].promise, () => { - if (preventClose === false) { - shutdownWithAction(() => - writableStreamDefaultWriterCloseWithErrorPropagation(writer) + 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, + }, ); - } 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) { + isOrBecomesErrored( + source, + reader[_closedPromise].promise, + (storedError) => { + if (preventAbort === false) { shutdownWithAction( - () => readableStreamCancel(source, destClosed), + () => writableStreamAbort(dest, storedError), true, - destClosed, + storedError, ); } else { - shutdown(true, destClosed); + shutdown(true, storedError); } - } - - setPromiseIsHandledToTrue(pipeLoop()); - - return promise.promise; + }, + ); - /** @returns {Promise<void>} */ - function waitForWritesToFinish() { - const oldCurrentWrite = currentWrite; - return transformPromiseWith( - currentWrite, - () => - oldCurrentWrite !== currentWrite - ? waitForWritesToFinish() - : undefined, + isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, storedError), + true, + storedError, ); + } else { + shutdown(true, storedError); } + }); - /** - * @param {ReadableStream | WritableStream} stream - * @param {Promise<any>} 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<any>} promise - * @param {() => void} action - */ - function isOrBecomesClosed(stream, promise, action) { - if (stream[_state] === "closed") { - action(); - } else { - uponFulfillment(promise, action); - } + isOrBecomesClosed(source, reader[_closedPromise].promise, () => { + if (preventClose === false) { + shutdownWithAction(() => + writableStreamDefaultWriterCloseWithErrorPropagation(writer) + ); + } else { + shutdown(); } + }); - /** - * @param {() => Promise<void[] | void>} 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(); - } + 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); } + } - /** - * @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); - } - } + setPromiseIsHandledToTrue(pipeLoop()); - /** - * @param {boolean=} isError - * @param {any=} error - */ - function finalize(isError, error) { - writableStreamDefaultWriterRelease(writer); - readableStreamDefaultReaderRelease(reader); + return promise.promise; - if (signal !== undefined) { - signal[remove](abortAlgorithm); - } - if (isError) { - promise.reject(error); - } else { - promise.resolve(undefined); - } - } + /** @returns {Promise<void>} */ + function waitForWritesToFinish() { + const oldCurrentWrite = currentWrite; + return transformPromiseWith( + currentWrite, + () => + oldCurrentWrite !== currentWrite ? waitForWritesToFinish() : undefined, + ); } /** - * @param {ReadableStreamGenericReader<any> | ReadableStreamBYOBReader} reader - * @param {any} reason - * @returns {Promise<void>} + * @param {ReadableStream | WritableStream} stream + * @param {Promise<any>} promise + * @param {(e: any) => void} action */ - function readableStreamReaderGenericCancel(reader, reason) { - const stream = reader[_stream]; - assert(stream !== undefined); - return readableStreamCancel(stream, reason); + function isOrBecomesErrored(stream, promise, action) { + if (stream[_state] === "errored") { + action(stream[_storedError]); + } else { + uponRejection(promise, action); + } } /** - * @template R - * @param {ReadableStreamDefaultReader<R> | ReadableStreamBYOBReader} reader - * @param {ReadableStream<R>} stream + * @param {ReadableStream} stream + * @param {Promise<any>} promise + * @param {() => void} action */ - 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); + function isOrBecomesClosed(stream, promise, action) { + if (stream[_state] === "closed") { + action(); } else { - assert(stream[_state] === "errored"); - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(reader[_closedPromise].promise); + uponFulfillment(promise, action); } } /** - * @template R - * @param {ReadableStreamGenericReader<R> | ReadableStreamBYOBReader} reader + * @param {() => Promise<void[] | void>} action + * @param {boolean=} originalIsError + * @param {any=} originalError */ - 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.", - ), + function shutdownWithAction(action, originalIsError, originalError) { + function doTheRest() { + uponPromise( + action(), + () => finalize(originalIsError, originalError), + (newError) => finalize(true, newError), ); } - 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); + if (shuttingDown === true) { + return; + } + shuttingDown = true; + + if ( + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false + ) { + uponFulfillment(waitForWritesToFinish(), doTheRest); + } else { + doTheRest(); } } /** - * @template R - * @param {ReadableStream<R>} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream<R>, ReadableStream<R>]} + * @param {boolean=} isError + * @param {any=} error */ - function readableStreamTee(stream, cloneForBranch2) { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); + function shutdown(isError, error) { + if (shuttingDown) { + return; + } + shuttingDown = true; if ( - ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - ) + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false ) { - return readableByteStreamTee(stream); + uponFulfillment( + waitForWritesToFinish(), + () => finalize(isError, error), + ); } else { - return readableStreamDefaultTee(stream, cloneForBranch2); + finalize(isError, error); } } /** - * @template R - * @param {ReadableStream<R>} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream<R>, ReadableStream<R>]} + * @param {boolean=} isError + * @param {any=} error */ - 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<R>} */ - // deno-lint-ignore prefer-const - let branch1; - /** @type {ReadableStream<R>} */ - // deno-lint-ignore prefer-const - let branch2; + function finalize(isError, error) { + writableStreamDefaultWriterRelease(writer); + readableStreamDefaultReaderRelease(reader); - /** @type {Deferred<void>} */ - const cancelPromise = new Deferred(); + if (signal !== undefined) { + signal[remove](abortAlgorithm); + } + if (isError) { + promise.reject(error); + } else { + promise.resolve(undefined); + } + } +} + +/** + * @param {ReadableStreamGenericReader<any> | ReadableStreamBYOBReader} reader + * @param {any} reason + * @returns {Promise<void>} + */ +function readableStreamReaderGenericCancel(reader, reason) { + const stream = reader[_stream]; + assert(stream !== undefined); + return readableStreamCancel(stream, reason); +} + +/** + * @template R + * @param {ReadableStreamDefaultReader<R> | ReadableStreamBYOBReader} reader + * @param {ReadableStream<R>} 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); + } +} + +/** + * @template R + * @param {ReadableStreamGenericReader<R> | 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<R>} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream<R>, ReadableStream<R>]} + */ +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<R>} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream<R>, ReadableStream<R>]} + */ +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<R>} */ + // deno-lint-ignore prefer-const + let branch1; + /** @type {ReadableStream<R>} */ + // deno-lint-ignore prefer-const + let branch2; + + /** @type {Deferred<void>} */ + const cancelPromise = new Deferred(); + + function pullAlgorithm() { + if (reading === true) { + readAgain = true; + return resolvePromiseWith(undefined); + } + reading = true; + /** @type {ReadRequest<R>} */ + const readRequest = { + chunkSteps(value) { + queueMicrotask(() => { + readAgain = false; + const value1 = value; + const value2 = value; - function pullAlgorithm() { - if (reading === true) { - readAgain = true; - return resolvePromiseWith(undefined); - } - reading = true; - /** @type {ReadRequest<R>} */ - const readRequest = { - chunkSteps(value) { - queueMicrotask(() => { - readAgain = false; - const value1 = value; - const value2 = value; - - // TODO(lucacasonato): respect clonedForBranch2. - - if (canceled1 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController<any>} */ branch1[ - _controller - ], - value1, - ); - } - if (canceled2 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController<any>} */ branch2[ - _controller - ], - value2, - ); - } + // TODO(lucacasonato): respect clonedForBranch2. - reading = false; - if (readAgain === true) { - pullAlgorithm(); - } - }); - }, - closeSteps() { - reading = false; if (canceled1 === false) { - readableStreamDefaultControllerClose( + readableStreamDefaultControllerEnqueue( /** @type {ReadableStreamDefaultController<any>} */ branch1[ _controller ], + value1, ); } if (canceled2 === false) { - readableStreamDefaultControllerClose( + readableStreamDefaultControllerEnqueue( /** @type {ReadableStreamDefaultController<any>} */ branch2[ _controller ], + value2, ); } - if (canceled1 === false || canceled2 === false) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { + reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - return resolvePromiseWith(undefined); + if (readAgain === true) { + pullAlgorithm(); + } + }); + }, + closeSteps() { + reading = false; + if (canceled1 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController<any>} */ branch1[ + _controller + ], + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController<any>} */ branch2[ + _controller + ], + ); + } + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + return resolvePromiseWith(undefined); + } + + /** + * @param {any} reason + * @returns {Promise<void>} + */ + 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<void>} - */ - 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<void>} - */ - 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; + /** + * @param {any} reason + * @returns {Promise<void>} + */ + 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() {} + function startAlgorithm() {} - branch1 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel1Algorithm, + branch1 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel1Algorithm, + ); + branch2 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel2Algorithm, + ); + + uponRejection(reader[_closedPromise].promise, (r) => { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController<any>} */ branch1[ + _controller + ], + r, ); - branch2 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel2Algorithm, + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController<any>} */ branch2[ + _controller + ], + r, ); + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }); - uponRejection(reader[_closedPromise].promise, (r) => { - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController<any>} */ branch1[ - _controller - ], - r, - ); - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController<any>} */ branch2[ - _controller - ], - r, - ); - if (canceled1 === false || canceled2 === false) { + return [branch1, branch2]; +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @returns {[ReadableStream<R>, ReadableStream<R>]} + */ +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<void>} */ + 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); } }); - - return [branch1, branch2]; } - /** - * @template R - * @param {ReadableStream<R>} stream - * @returns {[ReadableStream<R>, ReadableStream<R>]} - */ - 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<void>} */ - 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(); + 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; } - }); - }, - closeSteps() { - reading = false; + } if (!canceled1) { - readableByteStreamControllerClose(branch1[_controller]); + readableByteStreamControllerEnqueue(branch1[_controller], chunk1); } if (!canceled2) { - readableByteStreamControllerClose(branch2[_controller]); - } - if (branch1[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch1[_controller], 0); + readableByteStreamControllerEnqueue(branch2[_controller], chunk2); } - if (branch2[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch2[_controller], 0); - } - if (!canceled1 || !canceled2) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - } + 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; + 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); - } - } - if (!byobCanceled || !otherCanceled) { - cancelPromise.resolve(undefined); + readableByteStreamControllerEnqueue( + otherBranch[_controller], + clonedChunk, + ); + } else if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); } - }, - errorSteps() { reading = false; - }, - }; - readableStreamBYOBReaderRead(reader, view, readIntoRequest); - } + 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); + } - 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); - } + 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); - } + function pull2Algorithm() { + if (reading) { + readAgainForBranch2 = 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; + reading = true; + const byobRequest = readableByteStreamControllerGetBYOBRequest( + branch2[_controller], + ); + if (byobRequest === null) { + pullWithDefaultReader(); + } else { + pullWithBYOBReader(byobRequest[_view], true); } + return PromiseResolve(undefined); + } - 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 cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); } + return cancelPromise.promise; + } - function startAlgorithm() { - return undefined; + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); } + return cancelPromise.promise; + } - branch1 = createReadableByteStream( - startAlgorithm, - pull1Algorithm, - cancel1Algorithm, - ); - branch2 = createReadableByteStream( - startAlgorithm, - pull2Algorithm, - cancel2Algorithm, - ); + function startAlgorithm() { + return undefined; + } - branch1[_original] = stream; - branch2[_original] = stream; + branch1 = createReadableByteStream( + startAlgorithm, + pull1Algorithm, + cancel1Algorithm, + ); + branch2 = createReadableByteStream( + startAlgorithm, + pull2Algorithm, + cancel2Algorithm, + ); - forwardReaderError(reader); - return [branch1, branch2]; + branch1[_original] = stream; + branch2[_original] = stream; + + forwardReaderError(reader); + return [branch1, branch2]; +} + +/** + * @param {ReadableStream<ArrayBuffer>} stream + * @param {ReadableByteStreamController} controller + * @param {() => void} startAlgorithm + * @param {() => Promise<void>} pullAlgorithm + * @param {(reason: any) => Promise<void>} 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<ArrayBuffer>} stream + * @param {UnderlyingSource<ArrayBuffer>} underlyingSource + * @param {UnderlyingSource<ArrayBuffer>} underlyingSourceDict + * @param {number} highWaterMark + */ +function setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, +) { + const controller = webidl.createBranded(ReadableByteStreamController); + /** @type {() => void} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise<void>} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason: any) => Promise<void>} */ + 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'", + }, + ); } - - /** - * @param {ReadableStream<ArrayBuffer>} stream - * @param {ReadableByteStreamController} controller - * @param {() => void} startAlgorithm - * @param {() => Promise<void>} pullAlgorithm - * @param {(reason: any) => Promise<void>} cancelAlgorithm - * @param {number} highWaterMark - * @param {number | undefined} autoAllocateChunkSize - */ - function setUpReadableByteStreamController( + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); + } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise<undefined>"], + { + 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); + ); +} + +/** + * @template R + * @param {ReadableStream<R>} stream + * @param {ReadableStreamDefaultController<R>} controller + * @param {(controller: ReadableStreamDefaultController<R>) => void | Promise<void>} startAlgorithm + * @param {(controller: ReadableStreamDefaultController<R>) => Promise<void>} pullAlgorithm + * @param {(reason: any) => Promise<void>} 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<R>} stream + * @param {UnderlyingSource<R>} underlyingSource + * @param {UnderlyingSource<R>} underlyingSourceDict + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ +function setUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(ReadableStreamDefaultController); + /** @type {() => Promise<void>} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise<void>} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise<void>} */ + 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'", }, - (r) => { - readableByteStreamControllerError(controller, r); + ); + } + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, }, - ), - ); + ); } - - /** - * @param {ReadableStream<ArrayBuffer>} stream - * @param {UnderlyingSource<ArrayBuffer>} underlyingSource - * @param {UnderlyingSource<ArrayBuffer>} underlyingSourceDict - * @param {number} highWaterMark - */ - function setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ) { - const controller = webidl.createBranded(ReadableByteStreamController); - /** @type {() => void} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise<void>} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason: any) => Promise<void>} */ - 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<undefined>"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise<undefined>"], - { - 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, - ); + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {ReadableStreamDefaultController<R>} controller - * @param {(controller: ReadableStreamDefaultController<R>) => void | Promise<void>} startAlgorithm - * @param {(controller: ReadableStreamDefaultController<R>) => Promise<void>} pullAlgorithm - * @param {(reason: any) => Promise<void>} cancelAlgorithm - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultController( + setUpReadableStreamDefaultController( stream, controller, startAlgorithm, @@ -3226,234 +3323,255 @@ cancelAlgorithm, highWaterMark, sizeAlgorithm, + ); +} + +/** + * @template R + * @param {ReadableStreamBYOBReader} reader + * @param {ReadableStream<R>} 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); - }); - } - - /** - * @template R - * @param {ReadableStream<R>} stream - * @param {UnderlyingSource<R>} underlyingSource - * @param {UnderlyingSource<R>} underlyingSourceDict - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(ReadableStreamDefaultController); - /** @type {() => Promise<void>} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise<void>} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise<void>} */ - 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<undefined>"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise<undefined>"], - { - prefix: - "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - } - - /** - * @template R - * @param {ReadableStreamBYOBReader} reader - * @param {ReadableStream<R>} stream - */ - function setUpReadableStreamBYOBReader(reader, stream) { - if (isReadableStreamLocked(stream)) { - throw new TypeError("ReadableStream is locked."); - } - if ( - !(ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - )) - ) { - throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); + throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); + } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readIntoRequests] = []; +} + +/** + * @template R + * @param {ReadableStreamDefaultReader<R>} reader + * @param {ReadableStream<R>} stream + */ +function setUpReadableStreamDefaultReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); + } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readRequests] = []; +} + +/** + * @template O + * @param {TransformStream<any, O>} stream + * @param {TransformStreamDefaultController<O>} controller + * @param {(chunk: O, controller: TransformStreamDefaultController<O>) => Promise<void>} transformAlgorithm + * @param {(controller: TransformStreamDefaultController<O>) => Promise<void>} 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<I, O>} stream + * @param {Transformer<I, O>} transformer + * @param {Transformer<I, O>} transformerDict + */ +function setUpTransformStreamDefaultControllerFromTransformer( + stream, + transformer, + transformerDict, +) { + /** @type {TransformStreamDefaultController<O>} */ + const controller = webidl.createBranded(TransformStreamDefaultController); + /** @type {(chunk: O, controller: TransformStreamDefaultController<O>) => Promise<void>} */ + let transformAlgorithm = (chunk) => { + try { + transformStreamDefaultControllerEnqueue(controller, chunk); + } catch (e) { + return PromiseReject(e); } - readableStreamReaderGenericInitialize(reader, stream); - reader[_readIntoRequests] = []; + return resolvePromiseWith(undefined); + }; + /** @type {(controller: TransformStreamDefaultController<O>) => Promise<void>} */ + let flushAlgorithm = () => resolvePromiseWith(undefined); + if (transformerDict.transform !== undefined) { + transformAlgorithm = (chunk, controller) => + webidl.invokeCallbackFunction( + transformerDict.transform, + [chunk, controller], + transformer, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template R - * @param {ReadableStreamDefaultReader<R>} reader - * @param {ReadableStream<R>} 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<undefined>"], + { + prefix: + "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template O - * @param {TransformStream<any, O>} stream - * @param {TransformStreamDefaultController<O>} controller - * @param {(chunk: O, controller: TransformStreamDefaultController<O>) => Promise<void>} transformAlgorithm - * @param {(controller: TransformStreamDefaultController<O>) => Promise<void>} 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 W + * @param {WritableStream<W>} stream + * @param {WritableStreamDefaultController<W>} controller + * @param {(controller: WritableStreamDefaultController<W>) => Promise<void>} startAlgorithm + * @param {(chunk: W, controller: WritableStreamDefaultController<W>) => Promise<void>} writeAlgorithm + * @param {() => Promise<void>} closeAlgorithm + * @param {(reason?: any) => Promise<void>} 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<W>} stream + * @param {UnderlyingSink<W>} underlyingSink + * @param {UnderlyingSink<W>} underlyingSinkDict + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ +function setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(WritableStreamDefaultController); + /** @type {(controller: WritableStreamDefaultController<W>) => any} */ + let startAlgorithm = () => undefined; + /** @type {(chunk: W, controller: WritableStreamDefaultController<W>) => Promise<void>} */ + let writeAlgorithm = () => resolvePromiseWith(undefined); + let closeAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise<void>} */ + 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'", + }, + ); } - - /** - * @template I - * @template O - * @param {TransformStream<I, O>} stream - * @param {Transformer<I, O>} transformer - * @param {Transformer<I, O>} transformerDict - */ - function setUpTransformStreamDefaultControllerFromTransformer( - stream, - transformer, - transformerDict, - ) { - /** @type {TransformStreamDefaultController<O>} */ - const controller = webidl.createBranded(TransformStreamDefaultController); - /** @type {(chunk: O, controller: TransformStreamDefaultController<O>) => Promise<void>} */ - let transformAlgorithm = (chunk) => { - try { - transformStreamDefaultControllerEnqueue(controller, chunk); - } catch (e) { - return PromiseReject(e); - } - return resolvePromiseWith(undefined); - }; - /** @type {(controller: TransformStreamDefaultController<O>) => Promise<void>} */ - let flushAlgorithm = () => resolvePromiseWith(undefined); - if (transformerDict.transform !== undefined) { - transformAlgorithm = (chunk, controller) => - webidl.invokeCallbackFunction( - transformerDict.transform, - [chunk, controller], - transformer, - webidl.converters["Promise<undefined>"], - { - prefix: - "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (transformerDict.flush !== undefined) { - flushAlgorithm = (controller) => - webidl.invokeCallbackFunction( - transformerDict.flush, - [controller], - transformer, - webidl.converters["Promise<undefined>"], - { - prefix: - "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpTransformStreamDefaultController( - stream, - controller, - transformAlgorithm, - flushAlgorithm, - ); + if (underlyingSinkDict.write !== undefined) { + writeAlgorithm = (chunk) => + webidl.invokeCallbackFunction( + underlyingSinkDict.write, + [chunk, controller], + underlyingSink, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template W - * @param {WritableStream<W>} stream - * @param {WritableStreamDefaultController<W>} controller - * @param {(controller: WritableStreamDefaultController<W>) => Promise<void>} startAlgorithm - * @param {(chunk: W, controller: WritableStreamDefaultController<W>) => Promise<void>} writeAlgorithm - * @param {() => Promise<void>} closeAlgorithm - * @param {(reason?: any) => Promise<void>} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultController( + if (underlyingSinkDict.close !== undefined) { + closeAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.close, + [], + underlyingSink, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSinkDict.abort !== undefined) { + abortAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSinkDict.abort, + [reason], + underlyingSink, + webidl.converters["Promise<undefined>"], + { + prefix: + "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpWritableStreamDefaultController( stream, controller, startAlgorithm, @@ -3462,2794 +3580,2671 @@ 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<W>} stream - * @param {UnderlyingSink<W>} underlyingSink - * @param {UnderlyingSink<W>} underlyingSinkDict - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultControllerFromUnderlyingSink( - stream, - underlyingSink, - underlyingSinkDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(WritableStreamDefaultController); - /** @type {(controller: WritableStreamDefaultController<W>) => any} */ - let startAlgorithm = () => undefined; - /** @type {(chunk: W, controller: WritableStreamDefaultController<W>) => Promise<void>} */ - let writeAlgorithm = () => resolvePromiseWith(undefined); - let closeAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise<void>} */ - 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<undefined>"], - { - prefix: - "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.close !== undefined) { - closeAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSinkDict.close, - [], - underlyingSink, - webidl.converters["Promise<undefined>"], - { - prefix: - "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.abort !== undefined) { - abortAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSinkDict.abort, - [reason], - underlyingSink, - webidl.converters["Promise<undefined>"], - { - prefix: - "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - } - - /** - * @template W - * @param {WritableStreamDefaultWriter<W>} writer - * @param {WritableStream<W>} 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") { + ); +} + +/** + * @template W + * @param {WritableStreamDefaultWriter<W>} writer + * @param {WritableStream<W>} 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(); - 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); + 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; +} + +/** @param {TransformStreamDefaultController} controller */ +function transformStreamDefaultControllerClearAlgorithms(controller) { + controller[_transformAlgorithm] = undefined; + controller[_flushAlgorithm] = undefined; +} + +/** + * @template O + * @param {TransformStreamDefaultController<O>} controller + * @param {O} chunk + */ +function transformStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue( + /** @type {ReadableStreamDefaultController<O>} */ readableController, + ) === false + ) { + throw new TypeError("Readable stream is unavailable."); } - - /** - * @template O - * @param {TransformStreamDefaultController<O>} controller - * @param {O} chunk - */ - function transformStreamDefaultControllerEnqueue(controller, chunk) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue( - /** @type {ReadableStreamDefaultController<O>} */ readableController, - ) === false - ) { - throw new TypeError("Readable stream is unavailable."); - } - try { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController<O>} */ readableController, - chunk, - ); - } catch (e) { - transformStreamErrorWritableAndUnblockWrite(stream, e); - throw stream[_readable][_storedError]; - } - const backpressure = readableStreamDefaultcontrollerHasBackpressure( + try { + readableStreamDefaultControllerEnqueue( /** @type {ReadableStreamDefaultController<O>} */ readableController, + chunk, ); - if (backpressure !== stream[_backpressure]) { - assert(backpressure === true); - transformStreamSetBackpressure(stream, true); - } - } - - /** - * @param {TransformStreamDefaultController} controller - * @param {any=} e - */ - function transformStreamDefaultControllerError(controller, e) { - transformStreamError(controller[_stream], e); + } catch (e) { + transformStreamErrorWritableAndUnblockWrite(stream, e); + throw stream[_readable][_storedError]; } - - /** - * @template O - * @param {TransformStreamDefaultController<O>} controller - * @param {any} chunk - * @returns {Promise<void>} - */ - function transformStreamDefaultControllerPerformTransform(controller, chunk) { - const transformPromise = controller[_transformAlgorithm](chunk, controller); - return transformPromiseWith(transformPromise, undefined, (r) => { - transformStreamError(controller[_stream], r); - throw r; - }); + const backpressure = readableStreamDefaultcontrollerHasBackpressure( + /** @type {ReadableStreamDefaultController<O>} */ readableController, + ); + if (backpressure !== stream[_backpressure]) { + assert(backpressure === true); + transformStreamSetBackpressure(stream, true); } - - /** @param {TransformStreamDefaultController} controller */ - function transformStreamDefaultControllerTerminate(controller) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; +} + +/** + * @param {TransformStreamDefaultController} controller + * @param {any=} e + */ +function transformStreamDefaultControllerError(controller, e) { + transformStreamError(controller[_stream], e); +} + +/** + * @template O + * @param {TransformStreamDefaultController<O>} controller + * @param {any} chunk + * @returns {Promise<void>} + */ +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<void>} + */ +function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { + transformStreamError(stream, reason); + return resolvePromiseWith(undefined); +} + +/** + * @template I + * @template O + * @param {TransformStream<I, O>} stream + * @returns {Promise<void>} + */ +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} */ readableController, + /** @type {ReadableStreamDefaultController} */ readable[_controller], ); - const error = new TypeError("The stream has been terminated."); - transformStreamErrorWritableAndUnblockWrite(stream, error); - } - - /** - * @param {TransformStream} stream - * @param {any=} reason - * @returns {Promise<void>} - */ - function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { - transformStreamError(stream, reason); - return resolvePromiseWith(undefined); - } - - /** - * @template I - * @template O - * @param {TransformStream<I, O>} stream - * @returns {Promise<void>} - */ - 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<I, O>} stream + * @param {I} chunk + * @returns {Promise<void>} + */ +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, ); - }, (r) => { - transformStreamError(stream, r); - throw readable[_storedError]; }); } - - /** - * @template I - * @template O - * @param {TransformStream<I, O>} stream - * @param {I} chunk - * @returns {Promise<void>} - */ - 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<void>} - */ - function transformStreamDefaultSourcePullAlgorithm(stream) { - assert(stream[_backpressure] === true); - assert(stream[_backpressureChangePromise] !== undefined); + return transformStreamDefaultControllerPerformTransform(controller, chunk); +} + +/** + * @param {TransformStream} stream + * @returns {Promise<void>} + */ +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); - 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<void>} + */ +function writableStreamAbort(stream, reason) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); } - - /** - * @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; + stream[_controller][_signal][signalAbort](reason); + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); } - - /** - * @param {WritableStream} stream - * @param {any=} reason - * @returns {Promise<void>} - */ - 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<void> */ - const deferred = new Deferred(); - stream[_pendingAbortRequest] = { - deferred, - reason, - wasAlreadyErroring, - }; - if (wasAlreadyErroring === false) { - writableStreamStartErroring(stream, reason); - } - return deferred.promise; + 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<void> */ + const deferred = new Deferred(); + stream[_pendingAbortRequest] = { + deferred, + reason, + wasAlreadyErroring, + }; + if (wasAlreadyErroring === false) { + writableStreamStartErroring(stream, reason); + } + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise<void>} + */ +function writableStreamAddWriteRequest(stream) { + assert(isWritableStreamLocked(stream) === true); + assert(stream[_state] === "writable"); + /** @type {Deferred<void>} */ + const deferred = new Deferred(); + ArrayPrototypePush(stream[_writeRequests], deferred); + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise<void>} + */ +function writableStreamClose(stream) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return PromiseReject( + new TypeError("Writable stream is closed or errored."), + ); } - - /** - * @param {WritableStream} stream - * @returns {Promise<void>} - */ - function writableStreamAddWriteRequest(stream) { - assert(isWritableStreamLocked(stream) === true); - assert(stream[_state] === "writable"); - /** @type {Deferred<void>} */ - const deferred = new Deferred(); - ArrayPrototypePush(stream[_writeRequests], deferred); - return deferred.promise; + assert(state === "writable" || state === "erroring"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + /** @type {Deferred<void>} */ + 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 - * @returns {Promise<void>} - */ - function writableStreamClose(stream) { +/** + * @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<W>} 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<W>} 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<W>} 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]; - 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<void>} */ - const deferred = new Deferred(); - stream[_closeRequest] = deferred; - const writer = stream[_writer]; + dequeueValue(controller); if ( - writer !== undefined && stream[_backpressure] === true && + writableStreamCloseQueuedOrInFlight(stream) === false && state === "writable" ) { - writer[_readyPromise].resolve(undefined); + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); } - writableStreamDefaultControllerClose(stream[_controller]); - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamCloseQueuedOrInFlight(stream) { - if ( - stream[_closeRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (reason) => { + if (stream[_state] === "writable") { + writableStreamDefaultControllerClearAlgorithms(controller); } - return true; + writableStreamFinishInFlightWriteWithError(stream, reason); + }); +} + +/** + * @template W + * @param {WritableStreamDefaultController<W>} 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" + ) { + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); } - - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamDealWithRejection(stream, error) { - const state = stream[_state]; - if (state === "writable") { - writableStreamStartErroring(stream, error); - return; + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} reason + * @returns {Promise<void>} + */ +function writableStreamDefaultWriterAbort(writer, reason) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamAbort(stream, reason); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise<void>} + */ +function writableStreamDefaultWriterClose(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamClose(stream); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise<void>} + */ +function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + const state = stream[_state]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return resolvePromiseWith(undefined); + } + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable" || state === "erroring"); + return writableStreamDefaultWriterClose(writer); +} + +/** + * @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<W>} writer + * @param {W} chunk + * @returns {Promise<void>} + */ +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.resolve(undefined); + stream[_pendingAbortRequest] = undefined; } - assert(state === "erroring"); + } + 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 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 */ +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) { + assert(stream[_inFlightCloseRequest] === undefined); + assert(stream[_closeRequest] !== undefined); + stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest] = undefined; +} + +/** + * @template W + * @param {WritableStream<W>} 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); + 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); } - - /** - * @template W - * @param {WritableStreamDefaultController<W>} 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); +} + +/** + * @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 { - writableStreamDefaultControllerProcessWrite(controller, value); + assert(backpressure === false); + writer[_readyPromise].resolve(undefined); } } + stream[_backpressure] = backpressure; +} + +/** + * @template T + * @param {T} value + * @param {boolean} done + * @returns {IteratorResult<T>} + */ +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; +} + +/** @type {AsyncIterator<unknown, unknown>} */ +const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); + +const _iteratorNext = Symbol("[[iteratorNext]]"); +const _iteratorFinished = Symbol("[[iteratorFinished]]"); + +/** @type {AsyncIterator<unknown>} */ +const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ + /** @returns {Promise<IteratorResult<unknown>>} */ + next() { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + function nextSteps() { + if (reader[_iteratorFinished]) { + return PromiseResolve(createIteratorResult(undefined, true)); + } - function writableStreamDefaultControllerClearAlgorithms(controller) { - controller[_writeAlgorithm] = undefined; - controller[_closeAlgorithm] = undefined; - controller[_abortAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; - } + if (reader[_stream] === undefined) { + return PromiseReject( + new TypeError( + "Cannot get the next iteration result once the reader has been released.", + ), + ); + } - /** @param {WritableStreamDefaultController} controller */ - function writableStreamDefaultControllerClose(controller) { - enqueueValueWithSize(controller, _close, 0); - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } + /** @type {Deferred<IteratorResult<any>>} */ + 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 {WritableStreamDefaultController} controller - * @param {any} error + * @param {unknown} arg + * @returns {Promise<IteratorResult<unknown>>} */ - function writableStreamDefaultControllerError(controller, error) { - const stream = controller[_stream]; - assert(stream[_state] === "writable"); - writableStreamDefaultControllerClearAlgorithms(controller); - writableStreamStartErroring(stream, error); + 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; } - /** - * @param {WritableStreamDefaultController} controller - * @param {any} error - */ - function writableStreamDefaultControllerErrorIfNeeded(controller, error) { - if (controller[_stream][_state] === "writable") { - writableStreamDefaultControllerError(controller, error); - } + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + return this[_highWaterMark]; } - /** - * @param {WritableStreamDefaultController} controller - * @returns {boolean} - */ - function writableStreamDefaultControllerGetBackpressure(controller) { - const desiredSize = writableStreamDefaultControllerGetDesiredSize( - controller, - ); - return desiredSize <= 0; + /** @returns {(chunk: ArrayBufferView) => number} */ + get size() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + initializeByteLengthSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); } - /** - * @template W - * @param {WritableStreamDefaultController<W>} 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; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ByteLengthQueuingStrategyPrototype, + this, + ), + keys: [ + "highWaterMark", + "size", + ], + })); } +} - /** - * @param {WritableStreamDefaultController} controller - * @returns {number} - */ - function writableStreamDefaultControllerGetDesiredSize(controller) { - return controller[_strategyHWM] - controller[_queueTotalSize]; +webidl.configurePrototype(ByteLengthQueuingStrategy); +const ByteLengthQueuingStrategyPrototype = ByteLengthQueuingStrategy.prototype; + +/** @type {WeakMap<typeof globalThis, (chunk: ArrayBufferView) => number>} */ +const byteSizeFunctionWeakMap = new WeakMap(); + +function initializeByteLengthSizeFunction(globalObject) { + if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { + return; } + const size = (chunk) => chunk.byteLength; + WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); +} - /** @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); +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; } - /** - * @template W - * @param {WritableStreamDefaultController<W>} 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); - }); + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, CountQueuingStrategyPrototype); + return this[_highWaterMark]; } - /** - * @template W - * @param {WritableStreamDefaultController<W>} controller - * @param {W} chunk - * @param {number} chunkSize - */ - function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { - try { - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return; + /** @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<typeof globalThis, () => 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<R>=} underlyingSource + * @param {QueuingStrategy<R>=} 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; } - const stream = controller[_stream]; - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - stream[_state] === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, + 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<ArrayBuffer> + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + ); + } else { + assert(!(ReflectHas(underlyingSourceDict, "type"))); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpReadableStreamDefaultControllerFromUnderlyingSource( + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, ); - writableStreamUpdateBackpressure(stream, backpressure); } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} reason - * @returns {Promise<void>} - */ - function writableStreamDefaultWriterAbort(writer, reason) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamAbort(stream, reason); } - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {Promise<void>} - */ - function writableStreamDefaultWriterClose(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamClose(stream); + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, ReadableStreamPrototype); + return isReadableStreamLocked(this); } /** - * @param {WritableStreamDefaultWriter} writer + * @param {any=} reason * @returns {Promise<void>} */ - function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - const state = stream[_state]; - if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" - ) { - return resolvePromiseWith(undefined); + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); } - if (state === "errored") { - return PromiseReject(stream[_storedError]); + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("Cannot cancel a locked ReadableStream."), + ); } - assert(state === "writable" || state === "erroring"); - return writableStreamDefaultWriterClose(writer); + return readableStreamCancel(this, reason); } /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} error + * @param {ReadableStreamGetReaderOptions=} options + * @returns {ReadableStreamDefaultReader<R> | ReadableStreamBYOBReader} */ - function writableStreamDefaultWriterEnsureClosedPromiseRejected( - writer, - error, - ) { - if (writer[_closedPromise].state === "pending") { - writer[_closedPromise].reject(error); + 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 { - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].reject(error); + options = {}; + } + if (options.mode === undefined) { + return acquireReadableStreamDefaultReader(this); + } else { + assert(options.mode === "byob"); + return acquireReadableStreamBYOBReader(this); } - 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); + * @template T + * @param {{ readable: ReadableStream<T>, writable: WritableStream<R> }} transform + * @param {PipeOptions=} options + * @returns {ReadableStream<T>} + */ + 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", + }); + 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; } /** - * @param {WritableStreamDefaultWriter} writer - * @returns {number | null} + * @param {WritableStream<R>} destination + * @param {PipeOptions=} options + * @returns {Promise<void>} */ - function writableStreamDefaultWriterGetDesiredSize(writer) { - const stream = writer[_stream]; - const state = stream[_state]; - if (state === "errored" || state === "erroring") { - return null; + 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); } - if (state === "closed") { - return 0; + 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 writableStreamDefaultControllerGetDesiredSize(stream[_controller]); + return readableStreamPipeTo( + this, + destination, + preventClose, + preventAbort, + preventCancel, + signal, + ); } - /** @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; + /** @returns {[ReadableStream<R>, ReadableStream<R>]} */ + tee() { + webidl.assertBranded(this, ReadableStreamPrototype); + return readableStreamTee(this, false); } + // TODO(lucacasonato): should be moved to webidl crate /** - * @template W - * @param {WritableStreamDefaultWriter<W>} writer - * @param {W} chunk - * @returns {Promise<void>} + * @param {ReadableStreamIteratorOptions=} options + * @returns {AsyncIterableIterator<R>} */ - 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]); + 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<R>} */ + 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 })}`; + } +} + +// 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); +} + +/** @template R */ +class ReadableStreamDefaultReader { + /** @type {Deferred<void>} */ + [_closedPromise]; + /** @type {ReadableStream<R> | undefined} */ + [_stream]; + /** @type {ReadRequest[]} */ + [_readRequests]; + + /** @param {ReadableStream<R>} 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<ReadableStreamReadResult<R>>} */ + read() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + } catch (err) { + return PromiseReject(err); } - if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" - ) { + if (this[_stream] === undefined) { return PromiseReject( - new TypeError("The stream is closing or is closed."), + new TypeError("Reader has no associated stream."), ); } - if (state === "erroring") { - return PromiseReject(stream[_storedError]); - } - assert(state === "writable"); - const promise = writableStreamAddWriteRequest(stream); - writableStreamDefaultControllerWrite(controller, chunk, chunkSize); - return promise; + /** @type {Deferred<ReadableStreamReadResult<R>>} */ + const promise = new Deferred(); + /** @type {ReadRequest<R>} */ + 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; } - /** @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); + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (this[_stream] === undefined) { return; } - const promise = stream[_controller][_abortSteps](abortRequest.reason); - uponPromise(promise, () => { - abortRequest.deferred.resolve(undefined); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, (reason) => { - abortRequest.deferred.reject(reason); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }); + readableStreamDefaultReaderRelease(this); } - /** @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); + get closed() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + } catch (err) { + return PromiseReject(err); } - assert(stream[_pendingAbortRequest] === undefined); - assert(stream[_storedError] === undefined); + return this[_closedPromise].promise; } /** - * @param {WritableStream} stream - * @param {any=} error + * @param {any} reason + * @returns {Promise<void>} */ - 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; + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); } - writableStreamDealWithRejection(stream, error); - } - /** @param {WritableStream} stream */ - function writableStreamFinishInFlightWrite(stream) { - assert(stream[_inFlightWriteRequest] !== undefined); - stream[_inFlightWriteRequest].resolve(undefined); - stream[_inFlightWriteRequest] = undefined; + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + return readableStreamReaderGenericCancel(this, reason); } - /** - * @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); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; } +} - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamHasOperationMarkedInFlight(stream) { - if ( - stream[_inFlightWriteRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; - } - return true; - } +webidl.configurePrototype(ReadableStreamDefaultReader); +const ReadableStreamDefaultReaderPrototype = + ReadableStreamDefaultReader.prototype; - /** @param {WritableStream} stream */ - function writableStreamMarkCloseRequestInFlight(stream) { - assert(stream[_inFlightCloseRequest] === undefined); - assert(stream[_closeRequest] !== undefined); - stream[_inFlightCloseRequest] = stream[_closeRequest]; - stream[_closeRequest] = undefined; +/** @template R */ +class ReadableStreamBYOBReader { + /** @type {Deferred<void>} */ + [_closedPromise]; + /** @type {ReadableStream<R> | undefined} */ + [_stream]; + /** @type {ReadIntoRequest[]} */ + [_readIntoRequests]; + + /** @param {ReadableStream<R>} 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); } /** - * @template W - * @param {WritableStream<W>} stream + * @param {ArrayBufferView} view + * @returns {Promise<ReadableStreamBYOBReadResult>} */ - function writableStreamMarkFirstWriteRequestInFlight(stream) { - assert(stream[_inFlightWriteRequest] === undefined); - assert(stream[_writeRequests].length); - const writeRequest = stream[_writeRequests].shift(); - stream[_inFlightWriteRequest] = writeRequest; - } + 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); + } - /** @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; + if (view.byteLength === 0) { + return PromiseReject( + new TypeError("view must have non-zero byteLength"), + ); } - const writer = stream[_writer]; - if (writer !== undefined) { - writer[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(writer[_closedPromise].promise); + 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<ReadableStreamBYOBReadResult>} */ + 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; } - /** - * @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); + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (this[_stream] === undefined) { + return; } + readableStreamBYOBReaderRelease(this); } - /** - * @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); - } + get closed() { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + } catch (err) { + return PromiseReject(err); } - stream[_backpressure] = backpressure; + return this[_closedPromise].promise; } /** - * @template T - * @param {T} value - * @param {boolean} done - * @returns {IteratorResult<T>} + * @param {any} reason + * @returns {Promise<void>} */ - 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; - } - - /** @type {AsyncIterator<unknown, unknown>} */ - const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); - - const _iteratorNext = Symbol("[[iteratorNext]]"); - const _iteratorFinished = Symbol("[[iteratorFinished]]"); - - /** @type {AsyncIterator<unknown>} */ - const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ - /** @returns {Promise<IteratorResult<unknown>>} */ - 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<IteratorResult<any>>} */ - 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; - }); + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - - reader[_iteratorNext] = reader[_iteratorNext] - ? PromisePrototypeThen(reader[_iteratorNext], nextSteps, nextSteps) - : nextSteps(); - - return reader[_iteratorNext]; - }, - /** - * @param {unknown} arg - * @returns {Promise<IteratorResult<unknown>>} - */ - 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; + } catch (err) { + return PromiseReject(err); } - /** @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]); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + return readableStreamReaderGenericCancel(this, reason); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ByteLengthQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; } +} - webidl.configurePrototype(ByteLengthQueuingStrategy); - const ByteLengthQueuingStrategyPrototype = - ByteLengthQueuingStrategy.prototype; +webidl.configurePrototype(ReadableStreamBYOBReader); +const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; - /** @type {WeakMap<typeof globalThis, (chunk: ArrayBufferView) => number>} */ - const byteSizeFunctionWeakMap = new WeakMap(); +class ReadableStreamBYOBRequest { + /** @type {ReadableByteStreamController} */ + [_controller]; + /** @type {ArrayBufferView | null} */ + [_view]; - function initializeByteLengthSizeFunction(globalObject) { - if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { - return; - } - const size = (chunk) => chunk.byteLength; - WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); + /** @returns {ArrayBufferView | null} */ + get view() { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + return this[_view]; } - 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; - } + constructor() { + webidl.illegalConstructor(); + } - /** @returns {number} */ - get highWaterMark() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - return this[_highWaterMark]; - } + 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", + }); - /** @returns {(chunk: any) => 1} */ - get size() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - initializeCountSizeFunction(this[_globalObject]); - return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - CountQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); + 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); } - webidl.configurePrototype(CountQueuingStrategy); - const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; - - /** @type {WeakMap<typeof globalThis, () => 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<R>=} underlyingSource - * @param {QueuingStrategy<R>=} 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<ArrayBuffer> - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ); - } else { - assert(!(ReflectHas(underlyingSourceDict, "type"))); - const sizeAlgorithm = extractSizeAlgorithm(strategy); - const highWaterMark = extractHighWaterMark(strategy, 1); - setUpReadableStreamDefaultControllerFromUnderlyingSource( - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ); - } - } + 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", + }); - /** @returns {boolean} */ - get locked() { - webidl.assertBranded(this, ReadableStreamPrototype); - return isReadableStreamLocked(this); + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); } - - /** - * @param {any=} reason - * @returns {Promise<void>} - */ - 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); + if (isDetachedBuffer(view.buffer)) { + throw new TypeError( + "The given view's buffer has been detached and so cannot be used as a response", + ); } - - /** - * @param {ReadableStreamGetReaderOptions=} options - * @returns {ReadableStreamDefaultReader<R> | 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); - } + 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<void>} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise<void>} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {PullIntoDescriptor[]} */ + [_pendingPullIntos]; + /** @type {ReadableByteStreamQueueEntry[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {ReadableStream<ArrayBuffer>} */ + [_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); + } - /** - * @template T - * @param {{ readable: ReadableStream<T>, writable: WritableStream<R> }} transform - * @param {PipeOptions=} options - * @returns {ReadableStream<T>} - */ - 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", - }); - options = webidl.converters.StreamPipeOptions(options, { + /** + * @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: "Argument 2", + context: arg1, }); - 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; } - - /** - * @param {WritableStream<R>} destination - * @param {PipeOptions=} options - * @returns {Promise<void>} - */ - 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, + if (chunk.buffer.byteLength === 0) { + throw webidl.makeException( + TypeError, + "buffer length must be non-zero", + { prefix, context: arg1 }, ); } - - /** @returns {[ReadableStream<R>, ReadableStream<R>]} */ - tee() { - webidl.assertBranded(this, ReadableStreamPrototype); - return readableStreamTee(this, false); + if (this[_closeRequested] === true) { + throw new TypeError( + "Cannot enqueue chunk after a close has been requested.", + ); } - - // TODO(lucacasonato): should be moved to webidl crate - /** - * @param {ReadableStreamIteratorOptions=} options - * @returns {AsyncIterableIterator<R>} - */ - 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<R>} */ - const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); - const reader = acquireReadableStreamDefaultReader(this); - iterator[_reader] = reader; - iterator[_preventCancel] = options.preventCancel; - return iterator; + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "Cannot enqueue chunk when underlying stream is not readable.", + ); } + return readableByteStreamControllerEnqueue(this, chunk); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); } + readableByteStreamControllerError(this, e); } - // 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; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + this, + ), + keys: ["desiredSize"], + })); + } - function errorReadableStream(stream, e) { - readableStreamDefaultControllerError(stream[_controller], e); + /** + * @param {any} reason + * @returns {Promise<void>} + */ + [_cancelSteps](reason) { + readableByteStreamControllerClearPendingPullIntos(this); + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableByteStreamControllerClearAlgorithms(this); + return result; } - /** @template R */ - class ReadableStreamDefaultReader { - /** @type {Deferred<void>} */ - [_closedPromise]; - /** @type {ReadableStream<R> | undefined} */ - [_stream]; - /** @type {ReadRequest[]} */ - [_readRequests]; - - /** @param {ReadableStream<R>} 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); + /** + * @param {ReadRequest<ArrayBuffer>} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + /** @type {ReadableStream<ArrayBuffer>} */ + const stream = this[_stream]; + assert(readableStreamHasDefaultReader(stream)); + if (this[_queueTotalSize] > 0) { + assert(readableStreamGetNumReadRequests(stream) === 0); + readableByteStreamControllerFillReadRequestFromQueue(this, readRequest); + return; } - - /** @returns {Promise<ReadableStreamReadResult<R>>} */ - read() { + const autoAllocateChunkSize = this[_autoAllocateChunkSize]; + if (autoAllocateChunkSize !== undefined) { + let buffer; 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<ReadableStreamReadResult<R>>} */ - const promise = new Deferred(); - /** @type {ReadRequest<R>} */ - 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; - } - - /** @returns {void} */ - releaseLock() { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - if (this[_stream] === undefined) { + buffer = new ArrayBuffer(autoAllocateChunkSize); + } catch (e) { + readRequest.errorSteps(e); return; } - readableStreamDefaultReaderRelease(this); - } - - get closed() { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; - } - - /** - * @param {any} reason - * @returns {Promise<void>} - */ - 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); + /** @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); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + [_releaseSteps]() { + if (this[_pendingPullIntos].length !== 0) { + /** @type {PullIntoDescriptor} */ + const firstPendingPullInto = this[_pendingPullIntos][0]; + firstPendingPullInto.readerType = "none"; + this[_pendingPullIntos] = [firstPendingPullInto]; } } +} - webidl.configurePrototype(ReadableStreamDefaultReader); - const ReadableStreamDefaultReaderPrototype = - ReadableStreamDefaultReader.prototype; +webidl.configurePrototype(ReadableByteStreamController); +const ReadableByteStreamControllerPrototype = + ReadableByteStreamController.prototype; - /** @template R */ - class ReadableStreamBYOBReader { - /** @type {Deferred<void>} */ - [_closedPromise]; - /** @type {ReadableStream<R> | undefined} */ - [_stream]; - /** @type {ReadIntoRequest[]} */ - [_readIntoRequests]; - - /** @param {ReadableStream<R>} 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); - } +/** @template R */ +class ReadableStreamDefaultController { + /** @type {(reason: any) => Promise<void>} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise<void>} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {Array<ValueWithSize<R>>} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: R) => number} */ + [_strategySizeAlgorithm]; + /** @type {ReadableStream<R>} */ + [_stream]; - /** - * @param {ArrayBufferView} view - * @returns {Promise<ReadableStreamBYOBReadResult>} - */ - 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); - } + constructor() { + webidl.illegalConstructor(); + } - 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<ReadableStreamBYOBReadResult>} */ - 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; - } + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + return readableStreamDefaultControllerGetDesiredSize(this); + } - /** @returns {void} */ - releaseLock() { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - if (this[_stream] === undefined) { - return; - } - readableStreamBYOBReaderRelease(this); + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); } + readableStreamDefaultControllerClose(this); + } - get closed() { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; + /** + * @param {R} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); } - - /** - * @param {any} reason - * @returns {Promise<void>} - */ - 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); + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); } + readableStreamDefaultControllerEnqueue(this, chunk); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); } + readableStreamDefaultControllerError(this, e); } - webidl.configurePrototype(ReadableStreamBYOBReader); - const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableStreamDefaultController.prototype, + this, + ), + keys: ["desiredSize"], + })); + } - class ReadableStreamBYOBRequest { - /** @type {ReadableByteStreamController} */ - [_controller]; - /** @type {ArrayBufferView | null} */ - [_view]; + /** + * @param {any} reason + * @returns {Promise<void>} + */ + [_cancelSteps](reason) { + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableStreamDefaultControllerClearAlgorithms(this); + return result; + } - /** @returns {ArrayBufferView | null} */ - get view() { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - return this[_view]; + /** + * @param {ReadRequest<R>} 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); } + } - constructor() { - webidl.illegalConstructor(); - } + [_releaseSteps]() { + return; + } +} - 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", - }); +webidl.configurePrototype(ReadableStreamDefaultController); +const ReadableStreamDefaultControllerPrototype = + ReadableStreamDefaultController.prototype; - 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); - } +/** + * @template I + * @template O + */ +class TransformStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred<void>} */ + [_backpressureChangePromise]; + /** @type {TransformStreamDefaultController<O>} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {ReadableStream<O>} */ + [_readable]; + /** @type {WritableStream<I>} */ + [_writable]; - 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, { + /** + * @param {Transformer<I, O>} transformer + * @param {QueuingStrategy<I>} writableStrategy + * @param {QueuingStrategy<O>} readableStrategy + */ + constructor( + transformer = undefined, + writableStrategy = {}, + readableStrategy = {}, + ) { + const prefix = "Failed to construct 'TransformStream'"; + if (transformer !== undefined) { + transformer = webidl.converters.object(transformer, { 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<void>} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise<void>} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {PullIntoDescriptor[]} */ - [_pendingPullIntos]; - /** @type {ReadableByteStreamQueueEntry[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {ReadableStream<ArrayBuffer>} */ - [_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); + 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; } - - /** - * @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.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); + const transformerDict = webidl.converters.Transformer(transformer, { + prefix, + context: "transformer", + }); + if (transformerDict.readableType !== undefined) { + throw new RangeError( + `${prefix}: readableType transformers not supported.`, + ); } - - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableByteStreamControllerError(this, e); + if (transformerDict.writableType !== undefined) { + throw new RangeError( + `${prefix}: writableType transformers not supported.`, + ); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - this, + const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); + const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); + const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); + const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); + /** @type {Deferred<void>} */ + 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: ["desiredSize"], - })); - } - - /** - * @param {any} reason - * @returns {Promise<void>} - */ - [_cancelSteps](reason) { - readableByteStreamControllerClearPendingPullIntos(this); - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableByteStreamControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest<ArrayBuffer>} readRequest - * @returns {void} - */ - [_pullSteps](readRequest) { - /** @type {ReadableStream<ArrayBuffer>} */ - 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); + ); + } else { + startPromise.resolve(undefined); } + } - [_releaseSteps]() { - if (this[_pendingPullIntos].length !== 0) { - /** @type {PullIntoDescriptor} */ - const firstPendingPullInto = this[_pendingPullIntos][0]; - firstPendingPullInto.readerType = "none"; - this[_pendingPullIntos] = [firstPendingPullInto]; - } - } + /** @returns {ReadableStream<O>} */ + get readable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_readable]; } - webidl.configurePrototype(ReadableByteStreamController); - const ReadableByteStreamControllerPrototype = - ReadableByteStreamController.prototype; - - /** @template R */ - class ReadableStreamDefaultController { - /** @type {(reason: any) => Promise<void>} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise<void>} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {Array<ValueWithSize<R>>} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: R) => number} */ - [_strategySizeAlgorithm]; - /** @type {ReadableStream<R>} */ - [_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); - } + /** @returns {WritableStream<I>} */ + get writable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_writable]; + } - /** - * @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); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ readable: this.readable, writable: this.writable }) + }`; + } +} - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableStreamDefaultControllerError(this, e); - } +webidl.configurePrototype(TransformStream); +const TransformStreamPrototype = TransformStream.prototype; - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableStreamDefaultController.prototype, - this, - ), - keys: ["desiredSize"], - })); - } - - /** - * @param {any} reason - * @returns {Promise<void>} - */ - [_cancelSteps](reason) { - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest<R>} 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); - } - } +/** @template O */ +class TransformStreamDefaultController { + /** @type {(controller: this) => Promise<void>} */ + [_flushAlgorithm]; + /** @type {TransformStream<O>} */ + [_stream]; + /** @type {(chunk: O, controller: this) => Promise<void>} */ + [_transformAlgorithm]; - [_releaseSteps]() { - return; - } + constructor() { + webidl.illegalConstructor(); } - webidl.configurePrototype(ReadableStreamDefaultController); - const ReadableStreamDefaultControllerPrototype = - ReadableStreamDefaultController.prototype; + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + const readableController = this[_stream][_readable][_controller]; + return readableStreamDefaultControllerGetDesiredSize( + /** @type {ReadableStreamDefaultController<O>} */ readableController, + ); + } /** - * @template I - * @template O + * @param {O} chunk + * @returns {void} */ - class TransformStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred<void>} */ - [_backpressureChangePromise]; - /** @type {TransformStreamDefaultController<O>} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {ReadableStream<O>} */ - [_readable]; - /** @type {WritableStream<I>} */ - [_writable]; - - /** - * @param {Transformer<I, O>} transformer - * @param {QueuingStrategy<I>} writableStrategy - * @param {QueuingStrategy<O>} 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<void>} */ - 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'", - }, - ), - ); - } else { - startPromise.resolve(undefined); - } - } - - /** @returns {ReadableStream<O>} */ - get readable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_readable]; - } - - /** @returns {WritableStream<I>} */ - get writable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_writable]; + enqueue(chunk = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); } + transformStreamDefaultControllerEnqueue(this, chunk); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ readable: this.readable, writable: this.writable }) - }`; + /** + * @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.configurePrototype(TransformStream); - const TransformStreamPrototype = TransformStream.prototype; - - /** @template O */ - class TransformStreamDefaultController { - /** @type {(controller: this) => Promise<void>} */ - [_flushAlgorithm]; - /** @type {TransformStream<O>} */ - [_stream]; - /** @type {(chunk: O, controller: this) => Promise<void>} */ - [_transformAlgorithm]; + /** @returns {void} */ + terminate() { + webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); + transformStreamDefaultControllerTerminate(this); + } - constructor() { - webidl.illegalConstructor(); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + TransformStreamDefaultController.prototype, + this, + ), + keys: ["desiredSize"], + })); + } +} + +webidl.configurePrototype(TransformStreamDefaultController); +const TransformStreamDefaultControllerPrototype = + TransformStreamDefaultController.prototype; + +/** @template W */ +class WritableStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred<void> | undefined} */ + [_closeRequest]; + /** @type {WritableStreamDefaultController<W>} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {Deferred<void> | undefined} */ + [_inFlightWriteRequest]; + /** @type {Deferred<void> | undefined} */ + [_inFlightCloseRequest]; + /** @type {PendingAbortRequest | undefined} */ + [_pendingAbortRequest]; + /** @type {"writable" | "closed" | "erroring" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {WritableStreamDefaultWriter<W>} */ + [_writer]; + /** @type {Deferred<void>[]} */ + [_writeRequests]; + + /** + * @param {UnderlyingSink<W>=} underlyingSink + * @param {QueuingStrategy<W>=} strategy + */ + constructor(underlyingSink = undefined, strategy = {}) { + const prefix = "Failed to construct 'WritableStream'"; + if (underlyingSink !== undefined) { + underlyingSink = webidl.converters.object(underlyingSink, { + prefix, + context: "Argument 1", + }); } - - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - const readableController = this[_stream][_readable][_controller]; - return readableStreamDefaultControllerGetDesiredSize( - /** @type {ReadableStreamDefaultController<O>} */ readableController, + 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, + ); + } - /** - * @param {O} chunk - * @returns {void} - */ - enqueue(chunk = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - transformStreamDefaultControllerEnqueue(this, chunk); - } + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, WritableStreamPrototype); + return isWritableStreamLocked(this); + } - /** - * @param {any=} reason - * @returns {void} - */ - error(reason = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - transformStreamDefaultControllerError(this, reason); + /** + * @param {any=} reason + * @returns {Promise<void>} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamPrototype); + } catch (err) { + return PromiseReject(err); } - - /** @returns {void} */ - terminate() { - webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); - transformStreamDefaultControllerTerminate(this); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - TransformStreamDefaultController.prototype, - this, + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be aborted.", ), - keys: ["desiredSize"], - })); - } - } - - webidl.configurePrototype(TransformStreamDefaultController); - const TransformStreamDefaultControllerPrototype = - TransformStreamDefaultController.prototype; - - /** @template W */ - class WritableStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred<void> | undefined} */ - [_closeRequest]; - /** @type {WritableStreamDefaultController<W>} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {Deferred<void> | undefined} */ - [_inFlightWriteRequest]; - /** @type {Deferred<void> | undefined} */ - [_inFlightCloseRequest]; - /** @type {PendingAbortRequest | undefined} */ - [_pendingAbortRequest]; - /** @type {"writable" | "closed" | "erroring" | "errored"} */ - [_state]; - /** @type {any} */ - [_storedError]; - /** @type {WritableStreamDefaultWriter<W>} */ - [_writer]; - /** @type {Deferred<void>[]} */ - [_writeRequests]; - - /** - * @param {UnderlyingSink<W>=} underlyingSink - * @param {QueuingStrategy<W>=} 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, ); } + return writableStreamAbort(this, reason); + } - /** @returns {boolean} */ - get locked() { + /** @returns {Promise<void>} */ + close() { + try { webidl.assertBranded(this, WritableStreamPrototype); - return isWritableStreamLocked(this); + } catch (err) { + return PromiseReject(err); } - - /** - * @param {any=} reason - * @returns {Promise<void>} - */ - 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); + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be closed.", + ), + ); } - - /** @returns {Promise<void>} */ - 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); + if (writableStreamCloseQueuedOrInFlight(this) === true) { + return PromiseReject( + new TypeError("The writable stream is already closing."), + ); } + return writableStreamClose(this); + } - /** @returns {WritableStreamDefaultWriter<W>} */ - getWriter() { - webidl.assertBranded(this, WritableStreamPrototype); - return acquireWritableStreamDefaultWriter(this); - } + /** @returns {WritableStreamDefaultWriter<W>} */ + getWriter() { + webidl.assertBranded(this, WritableStreamPrototype); + return acquireWritableStreamDefaultWriter(this); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; } +} - webidl.configurePrototype(WritableStream); - const WritableStreamPrototype = WritableStream.prototype; +webidl.configurePrototype(WritableStream); +const WritableStreamPrototype = WritableStream.prototype; - /** @template W */ - class WritableStreamDefaultWriter { - /** @type {Deferred<void>} */ - [_closedPromise]; +/** @template W */ +class WritableStreamDefaultWriter { + /** @type {Deferred<void>} */ + [_closedPromise]; - /** @type {Deferred<void>} */ - [_readyPromise]; + /** @type {Deferred<void>} */ + [_readyPromise]; - /** @type {WritableStream<W>} */ - [_stream]; + /** @type {WritableStream<W>} */ + [_stream]; - /** - * @param {WritableStream<W>} 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); + /** + * @param {WritableStream<W>} 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<void>} */ + get closed() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); } + return this[_closedPromise].promise; + } - /** @returns {Promise<void>} */ - 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 {number} */ - get desiredSize() { + /** @returns {Promise<void>} */ + get ready() { + try { webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - if (this[_stream] === undefined) { - throw new TypeError( - "A writable stream is not associated with the writer.", - ); - } - return writableStreamDefaultWriterGetDesiredSize(this); + } catch (err) { + return PromiseReject(err); } + return this[_readyPromise].promise; + } - /** @returns {Promise<void>} */ - get ready() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_readyPromise].promise; + /** + * @param {any} reason + * @returns {Promise<void>} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); } - - /** - * @param {any} reason - * @returns {Promise<void>} - */ - 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); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - - /** @returns {Promise<void>} */ - 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); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); } + return writableStreamDefaultWriterAbort(this, reason); + } - /** @returns {void} */ - releaseLock() { + /** @returns {Promise<void>} */ + close() { + try { webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - const stream = this[_stream]; - if (stream === undefined) { - return; - } - assert(stream[_writer] !== undefined); - writableStreamDefaultWriterRelease(this); + } 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); + } - /** - * @param {W} chunk - * @returns {Promise<void>} - */ - 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); + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + const stream = this[_stream]; + if (stream === undefined) { + return; } + assert(stream[_writer] !== undefined); + writableStreamDefaultWriterRelease(this); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.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<void>} */ - [_abortAlgorithm]; - /** @type {() => Promise<void>} */ - [_closeAlgorithm]; - /** @type {ValueWithSize<W | _close>[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: W) => number} */ - [_strategySizeAlgorithm]; - /** @type {WritableStream<W>} */ - [_stream]; - /** @type {(chunk: W, controller: this) => Promise<void>} */ - [_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; + /** + * @param {W} chunk + * @returns {Promise<void>} + */ + write(chunk = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); } - writableStreamDefaultControllerError(this, e); + } catch (err) { + return PromiseReject(err); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - WritableStreamDefaultController.prototype, - this, - ), - keys: [], - })); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associate with the writer."), + ); } + return writableStreamDefaultWriterWrite(this, chunk); + } - /** - * @param {any=} reason - * @returns {Promise<void>} - */ - [_abortSteps](reason) { - const result = this[_abortAlgorithm](reason); - writableStreamDefaultControllerClearAlgorithms(this); - return result; - } + [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<void>} */ + [_abortAlgorithm]; + /** @type {() => Promise<void>} */ + [_closeAlgorithm]; + /** @type {ValueWithSize<W | _close>[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: W) => number} */ + [_strategySizeAlgorithm]; + /** @type {WritableStream<W>} */ + [_stream]; + /** @type {(chunk: W, controller: this) => Promise<void>} */ + [_writeAlgorithm]; + /** @type {AbortSignal} */ + [_signal]; + + get signal() { + webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); + return this[_signal]; + } + + constructor() { + webidl.illegalConstructor(); + } - [_errorSteps]() { - resetQueue(this); + /** + * @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); } - webidl.configurePrototype(WritableStreamDefaultController); - const WritableStreamDefaultControllerPrototype = - WritableStreamDefaultController.prototype; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + WritableStreamDefaultController.prototype, + this, + ), + keys: [], + })); + } /** - * @param {ReadableStream} stream + * @param {any=} reason + * @returns {Promise<void>} */ - function createProxy(stream) { - return stream.pipeThrough(new TransformStream()); + [_abortSteps](reason) { + const result = this[_abortAlgorithm](reason); + writableStreamDefaultControllerClearAlgorithms(this); + return result; } - webidl.converters.ReadableStream = webidl - .createInterfaceConverter("ReadableStream", ReadableStream.prototype); - webidl.converters.WritableStream = webidl - .createInterfaceConverter("WritableStream", WritableStream.prototype); + [_errorSteps]() { + resetQueue(this); + } +} - webidl.converters.ReadableStreamType = webidl.createEnumConverter( - "ReadableStreamType", - ["bytes"], - ); +webidl.configurePrototype(WritableStreamDefaultController); +const WritableStreamDefaultControllerPrototype = + WritableStreamDefaultController.prototype; - 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 }, - ]); - - 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); +/** + * @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 8de7b949f..f3ad966d0 100644 --- a/ext/web/08_text_encoding.js +++ b/ext/web/08_text_encoding.js @@ -9,437 +9,434 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference lib="esnext" /> -"use strict"; - -((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; - - /** @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, { +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; + +class TextDecoder { + /** @type {string} */ + #encoding; + /** @type {boolean} */ + #fatal; + /** @type {boolean} */ + #ignoreBOM; + /** @type {boolean} */ + #utf8SinglePass; + + /** @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, }); - options = webidl.converters.TextDecoderOptions(options, { + } + let stream = false; + if (options !== undefined) { + options = webidl.converters.TextDecodeOptions(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; + stream = options.stream; } - /** @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); + } } - 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); } - // 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, - ); - } + 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; - } + 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(TextDecoder); - const TextDecoderPrototype = TextDecoder.prototype; +webidl.configurePrototype(TextDecoder); +const TextDecoderPrototype = TextDecoder.prototype; - class TextEncoder { - constructor() { - this[webidl.brand] = webidl.brand; - } +class TextEncoder { + constructor() { + this[webidl.brand] = webidl.brand; + } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderPrototype); - return "utf-8"; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderPrototype); + return "utf-8"; + } - /** - * @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, { - prefix, - context: "Argument 1", - }); - return core.encode(input); - } + /** + * @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, { + 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], - }; - } + /** + * @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); +const encodeIntoBuf = new Uint32Array(2); - webidl.configurePrototype(TextEncoder); - const TextEncoderPrototype = TextEncoder.prototype; +webidl.configurePrototype(TextEncoder); +const TextEncoderPrototype = TextEncoder.prototype; - class TextDecoderStream { - /** @type {TextDecoder} */ - #decoder; - /** @type {TransformStream<BufferSource, string>} */ - #transform; +class TextDecoderStream { + /** @type {TextDecoder} */ + #decoder; + /** @type {TransformStream<BufferSource, string>} */ + #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); + /** + * @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); } - }, - flush: (controller) => { - try { - const final = this.#decoder.decode(); - if (final) { - controller.enqueue(final); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.encoding; - } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.fatal; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.encoding; + } - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.ignoreBOM; - } + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.fatal; + } - /** @returns {ReadableStream<string>} */ - get readable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.readable; - } + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.ignoreBOM; + } - /** @returns {WritableStream<BufferSource>} */ - get writable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.writable; - } + /** @returns {ReadableStream<string>} */ + get readable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.readable; } - webidl.configurePrototype(TextDecoderStream); - const TextDecoderStreamPrototype = TextDecoderStream.prototype; - - class TextEncoderStream { - /** @type {string | null} */ - #pendingHighSurrogate = null; - /** @type {TransformStream<string, Uint8Array>} */ - #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)); - } + /** @returns {WritableStream<BufferSource>} */ + 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<string, Uint8Array>} */ + #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(); - } 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); + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return "utf-8"; - } - - /** @returns {ReadableStream<Uint8Array>} */ - get readable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.readable; - } - - /** @returns {WritableStream<string>} */ - 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, + 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); + } }, - ], - ); - webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( - "TextDecodeOptions", - [ - { - key: "stream", - converter: webidl.converters.boolean, - defaultValue: false, + 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; + } - /** - * @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, TextEncoderStreamPrototype); + return "utf-8"; } - /** - * @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; + /** @returns {ReadableStream<Uint8Array>} */ + get readable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.readable; } - window.__bootstrap.encoding = { - TextEncoder, - TextDecoder, - TextEncoderStream, - TextDecoderStream, - decode, - }; -})(this); + /** @returns {WritableStream<string>} */ + 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 ecdce3e6a..e1be3b4c2 100644 --- a/ext/web/09_file.js +++ b/ext/web/09_file.js @@ -9,630 +9,628 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="./internal.d.ts" /> /// <reference lib="esnext" /> -"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; - - // 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) +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; + +/** + * @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, ); - return { result: StringPrototypeSlice(input, start, position), position }; + position = newPosition; + result += token; } - /** - * @param {string} s - * @returns {string} - */ - function convertLineEndingsToNative(s) { - const nativeLineEnding = isWindows ? "\r\n" : "\n"; - - let { result, position } = collectCodepointsNotCRLF(s, 0); + return result; +} - 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, +/** @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, ); - position = newPosition; - result += token; + 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 result; } - - /** @param {(BlobReference | Blob)[]} parts */ - async function* toIterator(parts) { - for (let i = 0; i < parts.length; ++i) { - yield* parts[i].stream(); + 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; +} - /** @typedef {BufferSource | Blob | string} BlobPart */ +const _type = Symbol("Type"); +const _size = Symbol("Size"); +const _parts = Symbol("Parts"); + +class Blob { + [_type] = ""; + [_size] = 0; + [_parts]; /** - * @param {BlobPart[]} parts - * @param {string} endings - * @returns {{ parts: (BlobReference|Blob)[], size: number }} + * @param {BlobPart[]} blobParts + * @param {BlobPropertyBag} options */ - 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 }; + constructor(blobParts = [], options = {}) { + const prefix = "Failed to construct 'Blob'"; + blobParts = webidl.converters["sequence<BlobPart>"](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); } - /** - * @param {string} str - * @returns {string} - */ - function normalizeType(str) { - let normalizedType = str; - if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { - normalizedType = ""; - } - return StringPrototypeToLowerCase(normalizedType); + /** @returns {number} */ + get size() { + webidl.assertBranded(this, BlobPrototype); + return this[_size]; } - /** - * 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; + /** @returns {string} */ + get type() { + webidl.assertBranded(this, BlobPrototype); + return this[_type]; } - 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<BlobPart>"](blobParts, { + /** + * @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, }); - options = webidl.converters["BlobPropertyBag"](options, { + } + if (end !== undefined) { + end = webidl.converters["long long"](end, { + clamp: true, 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]; + if (contentType !== undefined) { + contentType = webidl.converters["DOMString"](contentType, { + context: "Argument 3", + prefix, + }); } - /** - * @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; + // 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 { - if (start < 0) { - relativeStart = MathMax(O.size + start, 0); - } else { - relativeStart = MathMin(start, O.size); - } + relativeStart = MathMin(start, O.size); } - /** @type {number} */ - let relativeEnd; - if (end === undefined) { - relativeEnd = O.size; + } + /** @type {number} */ + let relativeEnd; + if (end === undefined) { + relativeEnd = O.size; + } else { + if (end < 0) { + relativeEnd = MathMax(O.size + end, 0); } else { - if (end < 0) { - relativeEnd = MathMax(O.size + end, 0); - } else { - relativeEnd = MathMin(end, O.size); - } + 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 - } - } + const span = MathMax(relativeEnd - relativeStart, 0); + const blobParts = []; + let added = 0; - /** @type {string} */ - let relativeContentType; - if (contentType === undefined) { - relativeContentType = ""; + 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 { - relativeContentType = normalizeType(contentType); + 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 } - - const blob = new Blob([], { type: relativeContentType }); - blob[_parts] = blobParts; - blob[_size] = span; - return blob; } - /** - * @returns {ReadableStream<Uint8Array>} - */ - 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; + /** @type {string} */ + let relativeContentType; + if (contentType === undefined) { + relativeContentType = ""; + } else { + relativeContentType = normalizeType(contentType); } - /** - * @returns {Promise<string>} - */ - async text() { - webidl.assertBranded(this, BlobPrototype); - const buffer = await this.#u8Array(this.size); - return core.decode(buffer); - } + const blob = new Blob([], { type: relativeContentType }); + blob[_parts] = blobParts; + blob[_size] = span; + return blob; + } - 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; + /** + * @returns {ReadableStream<Uint8Array>} + */ + 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 bytes; - } - - /** - * @returns {Promise<ArrayBuffer>} - */ - 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", - ], - })); - } + }, + }); + return stream; } - webidl.configurePrototype(Blob); - const BlobPrototype = Blob.prototype; + /** + * @returns {Promise<string>} + */ + async text() { + webidl.assertBranded(this, BlobPrototype); + const buffer = await this.#u8Array(this.size); + return core.decode(buffer); + } - 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); + 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; } } - // 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<BlobPart>"] = 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<BlobPart>"](fileBits, { - context: "Argument 1", - prefix, - }); - fileName = webidl.converters["USVString"](fileName, { - context: "Argument 2", - prefix, - }); - options = webidl.converters["FilePropertyBag"](options, { - context: "Argument 3", - prefix, - }); + return bytes; + } - super(fileBits, options); + /** + * @returns {Promise<ArrayBuffer>} + */ + async arrayBuffer() { + webidl.assertBranded(this, BlobPrototype); + const buf = await this.#u8Array(this.size); + return buf.buffer; + } - /** @type {string} */ - this[_Name] = fileName; - if (options.lastModified === undefined) { - /** @type {number} */ - this[_LastModified] = DatePrototypeGetTime(new Date()); - } else { - /** @type {number} */ - this[_LastModified] = options.lastModified; - } + [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); } - - /** @returns {string} */ - get name() { - webidl.assertBranded(this, FilePrototype); - return this[_Name]; + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - - /** @returns {number} */ - get lastModified() { - webidl.assertBranded(this, FilePrototype); - return this[_LastModified]; + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } } - - 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 + // 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<BlobPart>"] = 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]; /** - * An opaque reference to a blob part in Rust. This could be backed by a file, - * in memory storage, or something else. + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} options */ - 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); - } + constructor(fileBits, fileName, options = {}) { + const prefix = "Failed to construct 'File'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + fileBits = webidl.converters["sequence<BlobPart>"](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); - /** - * 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); + /** @type {string} */ + this[_Name] = fileName; + if (options.lastModified === undefined) { + /** @type {number} */ + this[_LastModified] = DatePrototypeGetTime(new Date()); + } else { + /** @type {number} */ + this[_LastModified] = options.lastModified; } + } - /** - * 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); - } + /** @returns {string} */ + get name() { + webidl.assertBranded(this, FilePrototype); + return this[_Name]; + } - /** - * Read the entire contents of the reference blob. - * @returns {AsyncGenerator<Uint8Array>} - */ - 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); - // } - } + /** @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 { /** - * 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. + * 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. * - * 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 {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 {string} url - * @returns {Blob | null} + * @param {number} start + * @param {number} end + * @returns {BlobReference} */ - function blobFromObjectUrl(url) { - const blobData = ops.op_blob_from_object_url(url); - if (blobData === null) { - return null; - } + slice(start, end) { + const size = end - start; + const id = ops.op_blob_slice_part(this._id, { + start, + len: size, + }); + return new BlobReference(id, size); + } - /** @type {BlobReference[]} */ - const parts = []; - let totalSize = 0; + /** + * Read the entire contents of the reference blob. + * @returns {AsyncGenerator<Uint8Array>} + */ + 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; + } - for (let i = 0; i < blobData.parts.length; ++i) { - const { uuid, size } = blobData.parts[i]; - ArrayPrototypePush(parts, new BlobReference(uuid, size)); - totalSize += size; - } + /** @type {BlobReference[]} */ + const parts = []; + let totalSize = 0; - const blob = webidl.createBranded(Blob); - blob[_type] = blobData.media_type; - blob[_size] = totalSize; - blob[_parts] = parts; - return blob; + for (let i = 0; i < blobData.parts.length; ++i) { + const { uuid, size } = blobData.parts[i]; + ArrayPrototypePush(parts, new BlobReference(uuid, size)); + totalSize += size; } - window.__bootstrap.file = { - blobFromObjectUrl, - getParts, - Blob, - BlobPrototype, - File, - FilePrototype, - }; -})(this); + 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 fb119f43e..7a46dfa9a 100644 --- a/ext/web/10_filereader.js +++ b/ext/web/10_filereader.js @@ -10,487 +10,482 @@ /// <reference path="./internal.d.ts" /> /// <reference lib="esnext" /> -"use strict"; - -((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"); - - 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; - - // 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<ArrayBufferView>*/ = blob.stream(); - - // 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 = []; - - // 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; - - // 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) { +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; + +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; + + /** + * @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 }; + + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream(); + + // 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 = []; + + // 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; + + // 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; - // 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; + 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; } - 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 - } + 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) { + 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; + if (decoder === undefined) { + decoder = new TextDecoder(); } + this[result] = decode(bytes, decoder.encoding); + 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); + case "DataUrl": { + const mediaType = blob.type || "application/octet-stream"; + this[result] = `data:${mediaType};base64,${ + forgivingBase64Encode(bytes) + }`; + break; } - }); - 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; - + } + // 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; - #getEventHandlerFor(name) { - webidl.assertBranded(this, FileReaderPrototype); + // chunkPromise rejected + this[state] = "done"; + this[error] = err; - const maybeMap = this[handlerSymbol]; - if (!maybeMap) return null; + { + const ev = new ProgressEvent("error", {}); + this.dispatchEvent(ev); + } - return MapPrototypeGet(maybeMap, name)?.handler ?? null; - } + //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; + } + } + })(); + } - #setEventHandlerFor(name, value) { - webidl.assertBranded(this, FileReaderPrototype); + #getEventHandlerFor(name) { + 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); - } + const maybeMap = this[handlerSymbol]; + if (!maybeMap) return null; - MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); - } + return MapPrototypeGet(maybeMap, name)?.handler ?? null; + } - constructor() { - super(); - this[webidl.brand] = webidl.brand; - } + #setEventHandlerFor(name, value) { + webidl.assertBranded(this, FileReaderPrototype); - /** @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"); - } + if (!this[handlerSymbol]) { + this[handlerSymbol] = new Map(); } - - get result() { - webidl.assertBranded(this, FileReaderPrototype); - return this[result]; + let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else { + handlerWrapper = makeWrappedHandler(value); + this.addEventListener(name, handlerWrapper); } - get error() { - webidl.assertBranded(this, FileReaderPrototype); - return this[error]; + 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"); } + } - 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; - } + get result() { + webidl.assertBranded(this, FileReaderPrototype); + return this[result]; + } - // Fire a progress event called abort at the context object. - const ev = new ProgressEvent("abort", {}); - this.dispatchEvent(ev); + get error() { + webidl.assertBranded(this, FileReaderPrototype); + return this[error]; + } - // 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); - } + 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; } - - /** @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" }); + // 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; } - - /** @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" }); + // 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; } - /** @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" }); - } + // Fire a progress event called abort at the context object. + const ev = new ProgressEvent("abort", {}); + this.dispatchEvent(ev); - /** - * @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 }); + // 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); } + } - get onerror() { - return this.#getEventHandlerFor("error"); - } - set onerror(value) { - this.#setEventHandlerFor("error", value); - } + /** @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" }); + } - get onloadstart() { - return this.#getEventHandlerFor("loadstart"); - } - set onloadstart(value) { - this.#setEventHandlerFor("loadstart", value); - } + /** @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" }); + } - get onload() { - return this.#getEventHandlerFor("load"); - } - set onload(value) { - this.#setEventHandlerFor("load", value); - } + /** @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" }); + } - get onloadend() { - return this.#getEventHandlerFor("loadend"); - } - set onloadend(value) { - this.#setEventHandlerFor("loadend", value); + /** + * @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 onprogress() { - return this.#getEventHandlerFor("progress"); - } - set onprogress(value) { - this.#setEventHandlerFor("progress", value); - } + get onerror() { + return this.#getEventHandlerFor("error"); + } + set onerror(value) { + this.#setEventHandlerFor("error", value); + } - get onabort() { - return this.#getEventHandlerFor("abort"); - } - set onabort(value) { - this.#setEventHandlerFor("abort", value); - } + get onloadstart() { + return this.#getEventHandlerFor("loadstart"); + } + set onloadstart(value) { + this.#setEventHandlerFor("loadstart", 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), - ); + 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; } - wrappedHandler.handler = handler; - return wrappedHandler; + return FunctionPrototypeCall( + wrappedHandler.handler, + this, + ...new SafeArrayIterator(args), + ); } + wrappedHandler.handler = handler; + return wrappedHandler; +} - window.__bootstrap.fileReader = { - FileReader, - }; -})(this); +export { FileReader }; diff --git a/ext/web/11_blob_url.js b/ext/web/11_blob_url.js index a51a1e718..02551fef6 100644 --- a/ext/web/11_blob_url.js +++ b/ext/web/11_blob_url.js @@ -10,50 +10,42 @@ /// <reference path="../url/lib.deno_url.d.ts" /> /// <reference path="./internal.d.ts" /> /// <reference lib="esnext" /> -"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; - - /** - * @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 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, - }); - - ops.op_blob_revoke_object_url(url); - } - - URL.createObjectURL = createObjectURL; - URL.revokeObjectURL = revokeObjectURL; -})(globalThis); +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, + }); + + return ops.op_blob_create_object_url(blob.type, getParts(blob)); +} + +/** + * @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); +} + +URL.createObjectURL = createObjectURL; +URL.revokeObjectURL = revokeObjectURL; diff --git a/ext/web/12_location.js b/ext/web/12_location.js index 964ca591e..da964eae8 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"; /// <reference path="../../core/internal.d.ts" /> -((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, +// 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; }, - host: { - get() { - return url.host; - }, - set() { - throw new DOMException( - `Cannot set "location.host".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", + ); }, - hostname: { - get() { - return url.hostname; - }, - set() { - throw new DOMException( - `Cannot set "location.hostname".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + host: { + get() { + return url.host; }, - href: { - get() { - return url.href; - }, - set() { - throw new DOMException( - `Cannot set "location.href".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); }, - origin: { - get() { - return url.origin; - }, - enumerable: true, + enumerable: true, + }, + hostname: { + get() { + return url.hostname; }, - pathname: { - get() { - return url.pathname; - }, - set() { - throw new DOMException( - `Cannot set "location.pathname".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.hostname".`, + "NotSupportedError", + ); }, - port: { - get() { - return url.port; - }, - set() { - throw new DOMException( - `Cannot set "location.port".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + href: { + get() { + return url.href; }, - protocol: { - get() { - return url.protocol; - }, - set() { - throw new DOMException( - `Cannot set "location.protocol".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.href".`, + "NotSupportedError", + ); }, - search: { - get() { - return url.search; - }, - set() { - throw new DOMException( - `Cannot set "location.search".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + origin: { + get() { + return url.origin; }, - ancestorOrigins: { - get() { - // TODO(nayeemrmn): Replace with a `DOMStringList` instance. - return { - length: 0, - item: () => null, - contains: () => false, - }; - }, - enumerable: true, + enumerable: true, + }, + pathname: { + get() { + return url.pathname; }, - assign: { - value: function assign() { - throw new DOMException( - `Cannot call "location.assign()".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); }, - reload: { - value: function reload() { - throw new DOMException( - `Cannot call "location.reload()".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + port: { + get() { + return url.port; }, - replace: { - value: function replace() { - throw new DOMException( - `Cannot call "location.replace()".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); }, - toString: { - value: function toString() { - return url.href; - }, - enumerable: true, + enumerable: true, + }, + protocol: { + get() { + return url.protocol; }, - [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)}`; - }, + 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, - }, - }); +ObjectDefineProperties(Location.prototype, { + [SymbolToStringTag]: { + value: "Location", + configurable: true, + }, +}); - const workerLocationUrls = new WeakMap(); +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); +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, +ObjectDefineProperties(WorkerLocation.prototype, { + hash: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hash; }, - hostname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.hostname; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + host: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.host; }, - href: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.href; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + hostname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hostname; }, - origin: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.origin; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + href: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; }, - pathname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.pathname; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + origin: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.origin; }, - port: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.port; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + pathname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.pathname; }, - protocol: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.protocol; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + port: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.port; }, - search: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.search; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + protocol: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.protocol; }, - 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, + configurable: true, + enumerable: true, + }, + search: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.search; }, - [SymbolToStringTag]: { - value: "WorkerLocation", - configurable: true, + configurable: true, + enumerable: true, + }, + toString: { + value: function toString() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; }, - [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)}`; - }, + 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; +let location = undefined; +let workerLocation = undefined; - function setLocationHref(href) { - location = new Location(href, locationConstructorKey); - workerLocation = new WorkerLocation(href, locationConstructorKey); - } +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.`, - ); - } - return workerLocation; - }, - configurable: true, - enumerable: true, - }, - setLocationHref, - getLocationHref() { - return location?.href; - }, - }; -})(this); +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 7ab2beb82..2a784bf3f 100644 --- a/ext/web/13_message_port.js +++ b/ext/web/13_message_port.js @@ -6,338 +6,339 @@ /// <reference path="./internal.d.ts" /> /// <reference path="./lib.deno_web.d.ts" /> -"use strict"; - -((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; - - 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; - } +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; + +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; + } - [SymbolFor("Deno.inspect")](inspect) { - return `MessageChannel ${ - inspect({ port1: this.port1, port2: this.port2 }) - }`; - } + get port1() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port1; } - webidl.configurePrototype(MessageChannel); - const MessageChannelPrototype = MessageChannel.prototype; + get port2() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port2; + } - const _id = Symbol("id"); - const _enabled = Symbol("enabled"); + [SymbolFor("Deno.inspect")](inspect) { + return `MessageChannel ${ + inspect({ port1: this.port1, port2: this.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; +} + +class MessagePort extends EventTarget { + /** @type {number | null} */ + [_id] = null; + /** @type {boolean} */ + [_enabled] = false; + + constructor() { + super(); + webidl.illegalConstructor(); + } /** - * @param {number} id - * @returns {MessagePort} + * @param {any} message + * @param {object[] | StructuredSerializeOptions} transferOrOptions */ - 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(); + 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<object>"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } - - /** - * @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<object>"]( - 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); + 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), - ), - }); + 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; } - this[_enabled] = false; - })(); - } - - close() { - webidl.assertBranded(this, MessagePortPrototype); - if (this[_id] !== null) { - core.close(this[_id]); - this[_id] = null; + const event = new MessageEvent("message", { + data: message, + ports: ArrayPrototypeFilter( + transferables, + (t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t), + ), + }); + this.dispatchEvent(event); } - } + this[_enabled] = false; + })(); } - 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(); + close() { + webidl.assertBranded(this, MessagePortPrototype); + if (this[_id] !== null) { + core.close(this[_id]); + this[_id] = null; + } } - - /** - * @param {globalThis.__bootstrap.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"); +} + +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]; - } + const data = core.deserialize(messageData.data, { + hostObjects, + transferredArrayBuffers, + }); - return [data, transferables]; + for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { + const id = arrayBufferIdsInTransferables[i]; + transferables[id] = transferredArrayBuffers[i]; } - /** - * @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); + 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 {globalThis.__bootstrap.messagePort.Transferable[]} */ - const serializedTransferables = []; + const serializedData = core.serialize(data, { + hostObjects: ArrayPrototypeFilter( + transferables, + (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), + ), + transferredArrayBuffers, + }, (err) => { + throw new DOMException(err, "DataCloneError"); + }); - 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"); + /** @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, - }; } - webidl.converters.StructuredSerializeOptions = webidl - .createDictionaryConverter( - "StructuredSerializeOptions", - [ - { - key: "transfer", - converter: webidl.converters["sequence<object>"], - 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]; - } - - window.__bootstrap.messagePort = { - MessageChannel, - MessagePort, - MessagePortPrototype, - deserializeJsMessageData, - serializeJsMessageData, - structuredClone, + return { + data: serializedData, + transferables: serializedTransferables, }; -})(globalThis); +} + +webidl.converters.StructuredSerializeOptions = webidl + .createDictionaryConverter( + "StructuredSerializeOptions", + [ + { + key: "transfer", + converter: webidl.converters["sequence<object>"], + 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 338f8c803..680da757e 100644 --- a/ext/web/14_compression.js +++ b/ext/web/14_compression.js @@ -5,127 +5,120 @@ /// <reference path="./internal.d.ts" /> /// <reference path="./lib.deno_web.d.ts" /> -"use strict"; - -((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", - ], - ); - - 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", - }); - - 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[webidl.brand] = webidl.brand; - } - - get readable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.readable; - } - - get writable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.writable; - } +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"; + +webidl.converters.CompressionFormat = webidl.createEnumConverter( + "CompressionFormat", + [ + "deflate", + "deflate-raw", + "gzip", + ], +); + +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", + }); + + 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[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; + } + + get readable() { + webidl.assertBranded(this, DecompressionStreamPrototype); + return this.#transform.readable; + } + + 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; +webidl.configurePrototype(DecompressionStream); +const DecompressionStreamPrototype = DecompressionStream.prototype; - window.__bootstrap.compression = { - CompressionStream, - DecompressionStream, - }; -})(globalThis); +export { CompressionStream, DecompressionStream }; diff --git a/ext/web/15_performance.js b/ext/web/15_performance.js index 9107ce75b..6a50f45f8 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 { 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; - - 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); - }; +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 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, + }, + ], + ); - function setTimeOrigin(origin) { - timeOrigin = origin; +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"], + }, + ], + ); - function findMostRecent( - name, - type, - ) { - return ArrayPrototypeFind( - ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), - (entry) => entry.name === name && entry.entryType === type, - ); +webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { + if (webidl.type(V) === "Object" && V !== null) { + return webidl.converters["PerformanceMeasureOptions"](V, opts); } - - 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 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 mark; + return entry.startTime; } - - function filterByNameType( - name, - type, - ) { - return ArrayPrototypeFilter( - performanceEntries, - (entry) => - (name ? entry.name === name : true) && - (type ? entry.entryType === type : true), - ); + 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]; } - const now = opNow; + get entryType() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_entryType]; + } - 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 startTime() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_startTime]; + } - get name() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_name]; - } + get duration() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_duration]; + } - get entryType() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_entryType]; + constructor( + name = null, + entryType = null, + startTime = null, + duration = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } + this[webidl.brand] = webidl.brand; - get startTime() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_startTime]; - } + this[_name] = name; + this[_entryType] = entryType; + this[_startTime] = startTime; + this[_duration] = duration; + } - get duration() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_duration]; - } + toJSON() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return { + name: this[_name], + entryType: this[_entryType], + startTime: this[_startTime], + duration: this[_duration], + }; + } - constructor( - name = null, - entryType = null, - startTime = null, - duration = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PerformanceEntryPrototype, + this, + ), + keys: [ + "name", + "entryType", + "startTime", + "duration", + ], + })); + } +} +webidl.configurePrototype(PerformanceEntry); +const PerformanceEntryPrototype = PerformanceEntry.prototype; - this[_name] = name; - this[_entryType] = entryType; - this[_startTime] = startTime; - this[_duration] = duration; - } +const _detail = Symbol("[[detail]]"); +class PerformanceMark extends PerformanceEntry { + [_detail] = null; - toJSON() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return { - name: this[_name], - entryType: this[_entryType], - startTime: this[_startTime], - duration: this[_duration], - }; - } + get detail() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return this[_detail]; + } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceEntryPrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - ], - })); - } + get entryType() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return "mark"; } - webidl.configurePrototype(PerformanceEntry); - const PerformanceEntryPrototype = PerformanceEntry.prototype; - const _detail = Symbol("[[detail]]"); - class PerformanceMark extends PerformanceEntry { - [_detail] = null; + constructor( + name, + options = {}, + ) { + const prefix = "Failed to construct 'PerformanceMark'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - get detail() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return this[_detail]; - } + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); - get entryType() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return "mark"; - } + options = webidl.converters.PerformanceMarkOptions(options, { + prefix, + context: "Argument 2", + }); - constructor( - name, - options = {}, - ) { - const prefix = "Failed to construct 'PerformanceMark'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + const { detail = null, startTime = now() } = options; - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); + 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); + } - options = webidl.converters.PerformanceMarkOptions(options, { - prefix, - context: "Argument 2", - }); + toJSON() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } - const { detail = null, startTime = now() } = options; + [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]; + } - 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); - } + get entryType() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return "measure"; + } - toJSON() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; + constructor( + name = null, + startTime = null, + duration = null, + detail = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } + super(name, "measure", startTime, duration, key); + this[webidl.brand] = webidl.brand; + this[_detail] = structuredClone(detail); } - webidl.configurePrototype(PerformanceMark); - const PerformanceMarkPrototype = PerformanceMark.prototype; - class PerformanceMeasure extends PerformanceEntry { - [_detail] = null; - get detail() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return this[_detail]; - } + toJSON() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } - get entryType() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return "measure"; + [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(); } - constructor( - name = null, - startTime = null, - duration = null, - detail = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } + super(); + this[webidl.brand] = webidl.brand; + } - super(name, "measure", startTime, duration, key); - this[webidl.brand] = webidl.brand; - this[_detail] = structuredClone(detail); - } + get timeOrigin() { + webidl.assertBranded(this, PerformancePrototype); + return timeOrigin; + } - toJSON() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } + 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", + }); - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceMeasurePrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => !(entry.name === markName && entry.entryType === "mark"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "mark", + ); } } - 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; - } + 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", + }); - 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", - ); - } + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => + !(entry.name === measureName && entry.entryType === "measure"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "measure", + ); } + } - 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(); + } - 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 }); - 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", + }); - name = webidl.converters.DOMString(name, { + if (type !== undefined) { + type = webidl.converters.DOMString(type, { prefix, - context: "Argument 1", + context: "Argument 2", }); + } - if (type !== undefined) { - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 2", - }); - } + return filterByNameType(name, type); + } - return filterByNameType(name, type); - } + getEntriesByType(type) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - 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", + }); - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 1", - }); + return filterByNameType(undefined, type); + } - 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; + } - mark( - markName, - markOptions = {}, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'mark' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + measure( + measureName, + startOrMeasureOptions = {}, + endMark = undefined, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'measure' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - markName = webidl.converters.DOMString(markName, { - prefix, - context: "Argument 1", - }); + measureName = webidl.converters.DOMString(measureName, { + prefix, + context: "Argument 1", + }); - markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { + startOrMeasureOptions = webidl.converters + ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { 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, { + if (endMark !== undefined) { + endMark = webidl.converters.DOMString(endMark, { prefix, - context: "Argument 1", + context: "Argument 3", }); + } - 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 ( - 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") + !ReflectHas(startOrMeasureOptions, "start") && + !ReflectHas(startOrMeasureOptions, "end") ) { - const start = convertMarkToTimestamp(startOrMeasureOptions.start); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - endTime = start + duration; - } else { - endTime = now(); + throw new TypeError( + "A start or end mark must be supplied in options.", + ); } - let startTime; if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "start") - ) { - startTime = convertMarkToTimestamp(startOrMeasureOptions.start); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "end") && - ReflectHas(startOrMeasureOptions, "duration") + ReflectHas(startOrMeasureOptions, "start") && + ReflectHas(startOrMeasureOptions, "duration") && + ReflectHas(startOrMeasureOptions, "end") ) { - const end = convertMarkToTimestamp(startOrMeasureOptions.end); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - startTime = end - duration; - } else if (typeof startOrMeasureOptions === "string") { - startTime = convertMarkToTimestamp(startOrMeasureOptions); - } else { - startTime = 0; + throw new TypeError( + "Cannot specify start, end, and duration together in options.", + ); } - 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, - }; } + 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; + } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this), - keys: [], - })); - } + now() { + webidl.assertBranded(this, PerformancePrototype); + return now(); } - webidl.configurePrototype(Performance); - const PerformancePrototype = Performance.prototype; - webidl.converters["Performance"] = webidl.createInterfaceConverter( - "Performance", - PerformancePrototype, - ); + toJSON() { + webidl.assertBranded(this, PerformancePrototype); + return { + timeOrigin: this.timeOrigin, + }; + } - window.__bootstrap.performance = { - PerformanceEntry, - PerformanceMark, - PerformanceMeasure, - Performance, - performance: new Performance(illegalConstructorKey), - setTimeOrigin, - }; -})(this); + [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 254ea4455..f8ad57c4f 100644 --- a/ext/web/benches/encoding.rs +++ b/ext/web/benches/encoding.rs @@ -29,11 +29,12 @@ fn setup() -> Vec<Extension> { deno_console::init(), deno_web::init::<Permissions>(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 b28b1ae1d..a2af22982 100644 --- a/ext/web/benches/timers_ops.rs +++ b/ext/web/benches/timers_ops.rs @@ -28,9 +28,10 @@ fn setup() -> Vec<Extension> { deno_console::init(), deno_web::init::<Permissions>(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 9bb89d98e..fe0c8ac07 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 - /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> -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 var domException: { - DOMException: typeof DOMException; - }; +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 namespace mimesniff { - declare interface MimeType { - type: string; - subtype: string; - parameters: Map<string, string>; - } - 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_dom_exception.js" { + export = DOMException; +} - declare var eventTarget: { - EventTarget: typeof EventTarget; - }; +declare module "internal:ext/web/01_mimesniff.js" { + interface MimeType { + type: string; + subtype: string; + parameters: Map<string, string>; + } + 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 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/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 location: { - getLocationHref(): string | undefined; - }; +declare module "internal:ext/web/12_location.js" { + function getLocationHref(): string | undefined; +} - declare var base64: { - atob(data: string): string; - btoa(data: string): string; - }; +declare module "internal:ext/web/05_base64.js" { + function atob(data: string): string; + function btoa(data: string): string; +} - declare var file: { - blobFromObjectUrl(url: string): Blob | null; - getParts(blob: Blob): string[]; - Blob: typeof Blob; - File: typeof File; - }; +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 streams: { - ReadableStream: typeof ReadableStream; - isReadableStreamDisturbed(stream: ReadableStream): boolean; - createProxy<T>(stream: ReadableStream<T>): ReadableStream<T>; - }; +declare module "internal:ext/web/06_streams.js" { + const ReadableStream: typeof ReadableStream; + function isReadableStreamDisturbed(stream: ReadableStream): boolean; + function createProxy<T>(stream: ReadableStream<T>): ReadableStream<T>; +} - 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 c677bb8e9..4fcc06ef4 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -64,7 +64,7 @@ pub fn init<P: TimersPermission + 'static>( ) -> 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 23e8b24f0..58b713099 100644 --- a/ext/webgpu/src/01_webgpu.js +++ b/ext/webgpu/src/01_webgpu.js @@ -6,5260 +6,5241 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="./lib.deno_webgpu.d.ts" /> -"use strict"; - -((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 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; + +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 {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 {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; + } +} - /** - * @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", - ); +const illegalConstructorKey = Symbol("illegalConstructorKey"); +class GPUError extends Error { + constructor(key = null) { + super(); + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } - 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, { + [_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: resourceContext, + context: "Argument 1", }); - if (resourceDevice.rid !== self.rid) { - throw new DOMException( - `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, - "OperationError", - ); - } - return { ...resourceDevice, rid: resourceDevice.rid }; + 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; - /** - * @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; +class GPU { + [webidl.brand] = webidl.brand; + + constructor() { + webidl.illegalConstructor(); } /** - * @param {number[] | GPUExtent3DDict} data - * @returns {GPUExtent3DDict} + * @param {GPURequestAdapterOptions} options */ - function normalizeGPUExtent3D(data) { - if (ArrayIsArray(data)) { - return { - width: data[0], - height: data[1], - depthOrArrayLayers: data[2], - }; + 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 data; + 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), + }; + 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 {number[] | GPUOrigin3DDict} data - * @returns {GPUOrigin3DDict} + * @param {GPUDeviceDescriptor} descriptor + * @returns {Promise<GPUDevice>} */ - function normalizeGPUOrigin3D(data) { - if (ArrayIsArray(data)) { - return { - x: data[0], - y: data[1], - z: data[2], - }; - } else { - return data; + 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 {number[] | GPUColor} data - * @returns {GPUColor} + * @param {string[]} unmaskHints + * @returns {Promise<GPUAdapterInfo>} */ - function normalizeGPUColor(data) { - if (ArrayIsArray(data)) { - return { - r: data[0], - g: data[1], - b: data[2], - a: data[3], - }; - } else { - return data; - } - } + async requestAdapterInfo(unmaskHints = []) { + webidl.assertBranded(this, GPUAdapterPrototype); + const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; + unmaskHints = webidl.converters["sequence<DOMString>"](unmaskHints, { + prefix, + context: "Argument 1", + }); - const illegalConstructorKey = Symbol("illegalConstructorKey"); - class GPUError extends Error { - constructor(key = null) { - super(); - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - } + const { + vendor, + architecture, + device, + description, + } = await core.opAsync( + "op_webgpu_request_adapter_info", + this[_adapter].rid, + ); - [_message]; - get message() { - webidl.assertBranded(this, GPUErrorPrototype); - return this[_message]; - } + 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; } - 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; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + limits: this.limits, + }) + }`; } - 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 GPUAdapterPrototype = GPUAdapter.prototype; + +class GPUAdapterInfo { + /** @type {string} */ + [_vendor]; + /** @returns {string} */ + get vendor() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_vendor]; } - 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", - }); + /** @type {string} */ + [_architecture]; + /** @returns {string} */ + get architecture() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_architecture]; + } - const { err, ...data } = await core.opAsync( - "op_webgpu_request_adapter", - options.powerPreference, - options.forceFallbackAdapter, - ); + /** @type {string} */ + [_device]; + /** @returns {string} */ + get device() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_device]; + } - if (err) { - return null; - } else { - return createGPUAdapter(data); - } - } + /** @type {string} */ + [_description]; + /** @returns {string} */ + get description() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_description]; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } + [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(); } - const GPUPrototype = GPU.prototype; - /** - * @typedef InnerGPUAdapter - * @property {number} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits - * @property {boolean} isFallbackAdapter - */ + 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; + } - /** - * @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; + [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(); } - class GPUAdapter { - /** @type {InnerGPUAdapter} */ - [_adapter]; + get reason() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_reason]; + } + get message() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_message]; + } - /** @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; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ reason: this[_reason], message: this[_message] }) + }`; + } +} - constructor() { - webidl.illegalConstructor(); - } +const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; +/** + * @param {string} name + * @param {any} type + */ +function GPUObjectBaseMixin(name, type) { + type.prototype[_label] = null; + ObjectDefineProperty(type.prototype, "label", { /** - * @param {GPUDeviceDescriptor} descriptor - * @returns {Promise<GPUDevice>} + * @return {string | null} */ - 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), - ); - } - + get() { + webidl.assertBranded(this, type.prototype); + return this[_label]; + }, /** - * @param {string[]} unmaskHints - * @returns {Promise<GPUAdapterInfo>} + * @param {string | null} label */ - async requestAdapterInfo(unmaskHints = []) { - webidl.assertBranded(this, GPUAdapterPrototype); - const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; - unmaskHints = webidl.converters["sequence<DOMString>"](unmaskHints, { - prefix, + 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; + }, + }); +} + +/** + * @typedef ErrorScope + * @property {string} filter + * @property {Promise<void>[]} operations + */ + +/** + * @typedef InnerGPUDeviceOptions + * @property {GPUAdapter} adapter + * @property {number | undefined} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + */ + +class InnerGPUDevice { + /** @type {GPUAdapter} */ + adapter; + /** @type {number | undefined} */ + rid; + /** @type {GPUSupportedFeatures} */ + features; + /** @type {GPUSupportedLimits} */ + limits; + /** @type {WeakRef<any>[]} */ + resources; + /** @type {boolean} */ + isLost; + /** @type {Promise<GPUDeviceLostInfo>} */ + lost; + /** @type {(info: GPUDeviceLostInfo) => void} */ + resolveLost; + /** @type {ErrorScope[]} */ + errorScopeStack; - 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, - }) - }`; - } + /** + * @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 = []; } - 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, - }) - }`; - } + /** @param {any} resource */ + trackResource(resource) { + ArrayPrototypePush(this.resources, new WeakRef(resource)); } - const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; - function createGPUSupportedLimits(limits) { - /** @type {GPUSupportedLimits} */ - const adapterFeatures = webidl.createBranded(GPUSupportedLimits); - adapterFeatures[_limits] = limits; - return adapterFeatures; + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + this.pushErrorPromise(PromiseResolve(err)); } - /** - * @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 {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 GPUSupportedLimits { - /** @type {InnerAdapterLimits} */ - [_limits]; - constructor() { - webidl.illegalConstructor(); + 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) => {}); - 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; + 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); + } + return PromiseResolve(); + }); + if (oomScope) { + ArrayPrototypePush(oomScope.operations, oomFilteredPromise); + } else { + PromisePrototypeCatch(oomFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); } - get maxComputeWorkgroupsPerDimension() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupsPerDimension; + // 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 { + /** @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](); + } } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect(this[_limits])}`; + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; } } - 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, - ); + 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]; } - class GPUSupportedFeatures { - constructor() { - webidl.illegalConstructor(); - } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect([...new SafeArrayIterator(this.values())]) - }`; - } + constructor() { + webidl.illegalConstructor(); + super(); } - const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; + destroy() { + webidl.assertBranded(this, GPUDevicePrototype); + this[_cleanup](); + } /** - * @param {string | undefined} reason - * @param {string} message - * @returns {GPUDeviceLostInfo} + * @param {GPUBufferDescriptor} descriptor + * @returns {GPUBuffer} */ - 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] }) - }`; + 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; } - const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; - /** - * @param {string} name - * @param {any} type + * @param {GPUTextureDescriptor} descriptor + * @returns {GPUTexture} */ - 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; - }, + 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; } /** - * @typedef ErrorScope - * @property {string} filter - * @property {Promise<void>[]} operations + * @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; + } /** - * @typedef InnerGPUDeviceOptions - * @property {GPUAdapter} adapter - * @property {number | undefined} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits + * @param {GPUBindGroupLayoutDescriptor} descriptor + * @returns {GPUBindGroupLayout} */ - - class InnerGPUDevice { - /** @type {GPUAdapter} */ - adapter; - /** @type {number | undefined} */ - rid; - /** @type {GPUSupportedFeatures} */ - features; - /** @type {GPUSupportedLimits} */ - limits; - /** @type {WeakRef<any>[]} */ - resources; - /** @type {boolean} */ - isLost; - /** @type {Promise<GPUDeviceLostInfo>} */ - 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)); + 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 + } } - /** @param {{ type: string, value: string | null } | undefined} err */ - pushError(err) { - this.pushErrorPromise(PromiseResolve(err)); - } + const { rid, err } = ops.op_webgpu_create_bind_group_layout( + device.rid, + descriptor.label, + descriptor.entries, + ); + device.pushError(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 bindGroupLayout = createGPUBindGroupLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } - 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 + /** + * @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, }); - } - // prevent uncaptured promise rejections - PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + return rid; + }, + ); + const { rid, err } = ops.op_webgpu_create_pipeline_layout( + device.rid, + descriptor.label, + bindGroupLayouts, + ); + device.pushError(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); - } - return PromiseResolve(); - }); - if (oomScope) { - ArrayPrototypePush(oomScope.operations, oomFilteredPromise); - } else { - PromisePrototypeCatch(oomFilteredPromise, () => { - // TODO(lucacasonato): emit an UncapturedErrorEvent - }); - } - // prevent uncaptured promise rejections - PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); - } + const pipelineLayout = createGPUPipelineLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(pipelineLayout); + return pipelineLayout; } /** - * @param {string | null} label - * @param {InnerGPUDevice} inner - * @param {GPUQueue} queue - * @returns {GPUDevice} + * @param {GPUBindGroupDescriptor} descriptor + * @returns {GPUBindGroup} */ - 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", + 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 { - options = { - mapping: null, - mappedRanges: null, - mappingRange: null, - state: "unmapped", + 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 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 { rid, err } = ops.op_webgpu_create_bind_group( + device.rid, + descriptor.label, + layout, + entries, + ); + device.pushError(err); - const texture = createGPUTexture( - descriptor, - device, - rid, - ); - device.trackResource(texture); - return texture; - } + const bindGroup = createGPUBindGroup( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroup); + return bindGroup; + } - /** - * @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); + /** + * @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 sampler = createGPUSampler( - descriptor.label, - device, - rid, - ); - device.trackResource(sampler); - return sampler; - } + const shaderModule = createGPUShaderModule( + descriptor.label, + device, + rid, + ); + device.trackResource(shaderModule); + return shaderModule; + } - /** - * @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, { + /** + * @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, - context: "Argument 1", + resourceContext: context, + selfContext: "this", }); - 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; } + const module = assertResource(descriptor.compute.module, { + prefix, + context: "compute shader module", + }); + assertDeviceMatch(device, descriptor.compute.module, { + prefix, + resourceContext: "compute shader module", + selfContext: "this", + }); - /** - * @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 { 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 pipelineLayout = createGPUPipelineLayout( - descriptor.label, - device, - rid, - ); - device.trackResource(pipelineLayout); - return pipelineLayout; - } + const computePipeline = createGPUComputePipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(computePipeline); + return computePipeline; + } - /** - * @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, { + /** + * @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, - context: "Argument 1", + resourceContext: context, + selfContext: "this", }); - const device = assertDevice(this, { prefix, context: "this" }); - const layout = assertResource(descriptor.layout, { + } + 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: "layout", + context: "fragment shader module", }); - assertDeviceMatch(device, descriptor.layout, { + assertDeviceMatch(device, descriptor.fragment.module, { prefix, - resourceContext: "layout", + resourceContext: "fragment shader module", 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, - }; - } - }); + fragment = { + module, + entryPoint: descriptor.fragment.entryPoint, + targets: descriptor.fragment.targets, + }; + } - const { rid, err } = ops.op_webgpu_create_bind_group( - device.rid, - descriptor.label, - layout, - entries, - ); - device.pushError(err); + 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 bindGroup = createGPUBindGroup( - descriptor.label, - device, - rid, - ); - device.trackResource(bindGroup); - return bindGroup; - } + const renderPipeline = createGPURenderPipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(renderPipeline); + return renderPipeline; + } - /** - * @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); + createComputePipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createComputePipeline(descriptor)); + } - const shaderModule = createGPUShaderModule( - descriptor.label, - device, - rid, - ); - device.trackResource(shaderModule); - return shaderModule; - } + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createRenderPipeline(descriptor)); + } - /** - * @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, { + /** + * @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" }); - 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 device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); - const computePipeline = createGPUComputePipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(computePipeline); - return computePipeline; - } + const renderBundleEncoder = createGPURenderBundleEncoder( + descriptor.label, + device, + rid, + ); + device.trackResource(renderBundleEncoder); + return renderBundleEncoder; + } - /** - * @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, { + /** + * @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" }); - 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 device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_query_set({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); - const renderPipeline = createGPURenderPipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(renderPipeline); - return renderPipeline; - } + const querySet = createGPUQuerySet( + descriptor.label, + device, + rid, + descriptor, + ); + device.trackResource(querySet); + return querySet; + } - createComputePipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createComputePipeline(descriptor)); + get lost() { + webidl.assertBranded(this, GPUDevicePrototype); + const device = this[_device]; + if (!device) { + return PromiseResolve(true); } - - createRenderPipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createRenderPipeline(descriptor)); + if (device.rid === undefined) { + return PromiseResolve(true); } + return device.lost; + } - /** - * @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); + /** + * @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: [] }); + } - const commandEncoder = createGPUCommandEncoder( - descriptor.label, - device, - rid, + /** + * @returns {Promise<GPUError | null>} + */ + // 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", ); - device.trackResource(commandEncoder); - return commandEncoder; } + const operations = SafePromiseAll(scope.operations); + return PromisePrototypeThen( + operations, + () => PromiseResolve(null), + (err) => PromiseResolve(err), + ); + } - /** - * @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; - } + [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 {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, - { + /** + * @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<GPUCommandBuffer>"]( + 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, - 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; + 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); + } - 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; - } + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueuePrototype); + return PromiseResolve(); + } - /** - * @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, { + /** + * @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 1", + context: "Argument 5", }); - const device = assertDevice(this, { prefix, context: "this" }); - ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); - } + 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); + } - /** - * @returns {Promise<GPUError | null>} - */ - // 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", - ); + /** + * @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 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, - }) - }`; + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; } + this[_state] = "destroy"; } - 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; + constructor() { + webidl.illegalConstructor(); } - class GPUQueue { - /** @type {InnerGPUDevice} */ - [_device]; + get size() { + webidl.assertBranded(this, GPUBufferPrototype); + return this[_size]; + } - constructor() { - webidl.illegalConstructor(); + 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 {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<GPUCommandBuffer>"]( - commandBuffers, - { prefix, context: "Argument 1" }, + /** + * @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", ); - 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; - }, + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", ); - 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 ((offset + rangeSize) > this[_size]) { + throw new DOMException( + `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + "OperationError", + ); } - - /** - * @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), + if (this[_state] !== "unmapped") { + throw new DOMException( + `${prefix}: GPUBuffer is not currently unmapped.`, + "OperationError", ); - 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), + 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", ); - device.pushError(err); } - - copyImageBitmapToTexture(_source, _destination, _copySize) { - throw new Error("Not yet implemented"); + 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", + ); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + 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] = []; } - 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} offset * @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]; + 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", + }); } - - get usage() { - webidl.assertBranded(this, GPUBufferPrototype); - return this[_usage]; + 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; } - get mapState() { - webidl.assertBranded(this, GPUBufferPrototype); - const state = this[_state]; - if (state === "mapped at creation") { - return "mapped"; - } else { - return state; - } + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); } - - /** - * @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)) { + 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}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + `${prefix}: requested buffer overlaps with another mapped range.`, "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", - }); - } - 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), + ); - 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)) - ) { + 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}: requested buffer overlaps with another mapped range.`, + `${prefix}: invalid state.`, "OperationError", ); } + if ((mapMode & 0x0002) === 0x0002) { + write = true; + } } - 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 mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "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", + 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)] : []), ); - } 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; + device.pushError(err); + if (err) return; } - - 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(); - } - 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; - } + destroy() { + webidl.assertBranded(this, GPUBufferPrototype); + this[_cleanup](); } - class GPUMapMode { - constructor() { - webidl.illegalConstructor(); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBuffer", GPUBuffer); +const GPUBufferPrototype = GPUBuffer.prototype; - static get READ() { - return 0x0001; - } - static get WRITE() { - return 0x0002; - } +class GPUBufferUsage { + constructor() { + webidl.illegalConstructor(); } - /** - * @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; + 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 GPUTexture { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {WeakRef<GPUTextureView>[]} */ - [_views]; +class GPUMapMode { + constructor() { + webidl.illegalConstructor(); + } - /** @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; + 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<GPUTextureView>[]} */ + [_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](); } } - - 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; + 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]; - } + /** + * @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); - get height() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_height]; - } + const textureView = createGPUTextureView( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_views], new WeakRef(textureView)); + return textureView; + } - get depthOrArrayLayers() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_depthOrArrayLayers]; - } + destroy() { + webidl.assertBranded(this, GPUTexturePrototype); + this[_cleanup](); + } - get mipLevelCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_mipLevelCount]; - } + get width() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_width]; + } - get sampleCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_sampleCount]; - } + get height() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_height]; + } - get dimension() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_dimension]; - } + get depthOrArrayLayers() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_depthOrArrayLayers]; + } - get format() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_format]; - } + get mipLevelCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_mipLevelCount]; + } - get usage() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_usage]; - } + get sampleCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_sampleCount]; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + get dimension() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_dimension]; } - GPUObjectBaseMixin("GPUTexture", GPUTexture); - const GPUTexturePrototype = GPUTexture.prototype; - class GPUTextureUsage { - constructor() { - webidl.illegalConstructor(); - } + get format() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_format]; + } - 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; - } + get usage() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_usage]; } - /** - * @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; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; } - 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("GPUTexture", GPUTexture); +const GPUTexturePrototype = GPUTexture.prototype; - constructor() { - webidl.illegalConstructor(); - } +class GPUTextureUsage { + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + static get COPY_SRC() { + return 0x01; } - 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; + static get COPY_DST() { + return 0x02; } - 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(); + 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; } + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + constructor() { + webidl.illegalConstructor(); } - 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; + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; } - 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; - } +} +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(); - } + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [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; } } - 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; + constructor() { + webidl.illegalConstructor(); } - 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("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; } + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + constructor() { + webidl.illegalConstructor(); } - 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; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; } - 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; - } +} +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(); - } + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [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; } } - 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; + constructor() { + webidl.illegalConstructor(); } - 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"); + } - compilationInfo() { - throw new Error("Not yet implemented"); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } +class GPUShaderStage { + constructor() { + webidl.illegalConstructor(); } - GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); - class GPUShaderStage { - constructor() { - webidl.illegalConstructor(); - } + static get VERTEX() { + return 0x1; + } - static get VERTEX() { - return 0x1; - } + static get FRAGMENT() { + return 0x2; + } - 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; } + } - static get COMPUTE() { - return 0x4; - } + constructor() { + webidl.illegalConstructor(); } /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUComputePipeline} + * @param {number} index + * @returns {GPUBindGroupLayout} */ - 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(); - } + 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); - /** - * @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, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } - const bindGroupLayout = createGPUBindGroupLayout( - label, - device, - rid, - ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +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; +} +class GPURenderPipeline { + /** @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, - }) - }`; - } + constructor() { + webidl.illegalConstructor(); } - GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); - const GPUComputePipelinePrototype = GPUComputePipeline.prototype; /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderPipeline} + * @param {number} index */ - 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]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @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); - - 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("GPURenderPipeline", GPURenderPipeline); - const GPURenderPipelinePrototype = GPURenderPipeline.prototype; - class GPUColorWrite { - constructor() { - webidl.illegalConstructor(); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); +const GPURenderPipelinePrototype = GPURenderPipeline.prototype; - static get RED() { - return 0x1; - } - static get GREEN() { - return 0x2; - } - static get BLUE() { - return 0x4; - } - static get ALPHA() { - return 0x8; +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<GPURenderPassEncoder | GPUComputePassEncoder>[]} */ + [_encoders]; + + [_cleanup]() { + const encoders = this[_encoders]; + while (encoders.length > 0) { + const encoder = ArrayPrototypePop(encoders)?.deref(); + if (encoder) { + encoder[_cleanup](); + } } - static get ALL() { - return 0xF; + 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 {GPUCommandEncoder} + * @param {GPURenderPassDescriptor} descriptor + * @return {GPURenderPassEncoder} */ - 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<GPURenderPassEncoder | GPUComputePassEncoder>[]} */ - [_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; - } - } + 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", + }); - constructor() { - webidl.illegalConstructor(); + if (this[_rid] === undefined) { + throw new DOMException( + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", + "OperationError", + ); } - /** - * @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, { + let depthStencilAttachment; + if (descriptor.depthStencilAttachment) { + const view = assertResource(descriptor.depthStencilAttachment.view, { prefix, - context: "this", + context: "texture view for depth stencil attachment", }); + assertDeviceMatch( + device, + descriptor.depthStencilAttachment.view[_texture], + { + prefix, + resourceContext: "texture view for depth stencil attachment", + selfContext: "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, { + 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 view for depth stencil attachment", + 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, { - prefix, - context: `texture view for ${context}`, - }); - assertResource(colorAttachment.view[_texture], { + 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 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; - } + 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", - }); + /** + * @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", - }); + 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 { 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; - } + 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, + /** + * @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, - destination, + destinationRid, 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); - } + ); + 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", - }); + /** + * @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); - } + 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 {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 {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 {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); - } + /** + * @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); - } + 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 {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} 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, + /** + * @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, - destination, + destinationRid, 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); - } + ); + 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); + /** + * @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, + }) + }`; + } +} +GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); +const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; + +/** + * @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; +} + +class GPURenderPassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); /** @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, - }) - }`; - } + constructor() { + webidl.illegalConstructor(); } - GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); - const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPURenderPassEncoder} + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth */ - function createGPURenderPassEncoder(label, encoder, rid) { - /** @type {GPURenderPassEncoder} */ - const passEncoder = webidl.createBranded(GPURenderPassEncoder); - passEncoder[_label] = label; - passEncoder[_encoder] = encoder; - passEncoder[_rid] = rid; - return passEncoder; - } - - class GPURenderPassEncoder { - /** @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(); - } + 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 - * @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 {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 {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, + ); + } - /** - * @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"); + } - beginOcclusionQuery(_queryIndex) { - throw new Error("Not yet implemented"); - } + endOcclusionQuery() { + 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, + ); + } - /** - * @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); + } - 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 {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, { + /** + * @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<GPURenderBundle>"](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: "Argument 1", + resourceContext: context, selfContext: "this", }); - ops.op_webgpu_render_pass_write_timestamp( - renderPassRid, - querySetRid, - queryIndex, - ); - } + return rid; + }); + ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids); + } - /** - * @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<GPURenderBundle>"](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; + } - 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, 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 {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); + } - /** - * @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); - } + 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 {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 {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 {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 {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 {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, { + /** + * @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", }); - 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, - ); } + 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} 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, { + /** + * @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", }); - 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, - ); } + 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 {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, - ); - } + /** + * @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, + ); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + /** + * @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, + ); } - GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); - const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPUComputePassEncoder} + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset */ - function createGPUComputePassEncoder(label, encoder, rid) { - /** @type {GPUComputePassEncoder} */ - const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); - computePassEncoder[_label] = label; - computePassEncoder[_encoder] = encoder; - computePassEncoder[_rid] = rid; - return computePassEncoder; + 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, + ); } - class GPUComputePassEncoder { - /** @type {GPUCommandEncoder} */ - [_encoder]; + /** + * @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, + ); + } - /** @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("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(); - } + 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 {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( + /** + * @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 = 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, - ); - } + 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 {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, - ); - } + /** + * @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); - } + 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, - ); - } + /** + * @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; - } + 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( + // 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, - bindGroup, + bindGroupRid, 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, - ); - } + /** + * @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); + } - [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("GPUComputePassEncoder", GPUComputePassEncoder); - const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUCommandBuffer} + * @param {string} markerLabel */ - function createGPUCommandBuffer(label, device, rid) { - /** @type {GPUCommandBuffer} */ - const commandBuffer = webidl.createBranded(GPUCommandBuffer); - commandBuffer[_label] = label; - commandBuffer[_device] = device; - commandBuffer[_rid] = rid; - return commandBuffer; + 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 GPUCommandBuffer { - /** @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("GPUComputePassEncoder", GPUComputePassEncoder); +const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; + +/** + * @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; +} + +class GPUCommandBuffer { + /** @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(); - } + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [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; } } - GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + + constructor() { + webidl.illegalConstructor(); + } /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundleEncoder} + * @param {GPURenderBundleDescriptor} descriptor */ - 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(); - } + 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; - /** - * @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; + } - 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; } - - // TODO(lucacasonato): has an overload - setBindGroup( + ops.op_webgpu_render_bundle_encoder_set_bind_group( + renderBundleEncoderRid, 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, 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, - ); - } + 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 {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 {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 {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} 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} 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( + /** + * @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 = 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, - }) - }`; - } + instanceCount, + firstIndex, + baseVertex, + firstInstance, + ); } - GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); - const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundle} + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset */ - 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; - } - } + 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, + ); + } - constructor() { - webidl.illegalConstructor(); - } + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [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; } } - 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(); + } - 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; } + } - destroy() { - webidl.assertBranded(this, GPUQuerySetPrototype); - this[_cleanup](); - } + constructor() { + webidl.illegalConstructor(); + } - get type() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_type](); - } + destroy() { + webidl.assertBranded(this, GPUQuerySetPrototype); + this[_cleanup](); + } - get count() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_count](); - } + get type() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_type](); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } + get count() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_count](); } - 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); + + [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 c927f10a5..daafdfa4b 100644 --- a/ext/webgpu/src/02_idl_types.js +++ b/ext/webgpu/src/02_idl_types.js @@ -3,2038 +3,2034 @@ // @ts-check /// <reference path="../web/internal.d.ts" /> -"use strict"; - -((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", +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; + +// 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: 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<GPUIntegerCoordinate>"] = 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<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); - } - return webidl.converters["GPUExtent3DDict"](V, opts); +// 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", + 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<GPUIntegerCoordinate>"] = 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<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); } - throw webidl.makeException( - TypeError, - "can not be converted to sequence<GPUIntegerCoordinate> 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["GPUTextureDescriptor"] = webidl.createDictionaryConverter( - "GPUTextureDescriptor", + return webidl.converters["GPUExtent3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> 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["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, - 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, - dictMembersGPUTextureViewDescriptor, - ); - - // 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", + dictMembersGPUTextureViewDescriptor, + ); + +// 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, + ); + +// 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, - 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, - ); - - // 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<USVString, GPUPipelineConstantValue>"] = 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<USVString, GPUPipelineConstantValue>"], - }, - ]; - 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"], + 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<USVString, GPUPipelineConstantValue>"] = 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<USVString, GPUPipelineConstantValue>"], + }, +]; +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"], ), - 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"], ), - 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<GPUCommandBuffer>"] = 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, + ), + 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<GPUCommandBuffer>"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + +// DICTIONARY: GPUCommandBufferDescriptor +const dictMembersGPUCommandBufferDescriptor = []; +webidl.converters["GPUCommandBufferDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandBufferDescriptor, ); - // TYPEDEF: GPUOrigin3D - webidl.converters["GPUOrigin3D"] = (V, opts) => { - // Union for (sequence<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); - } - return webidl.converters["GPUOrigin3DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence<GPUIntegerCoordinate> 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, - ); +// INTERFACE: GPUCommandEncoder +webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( + "GPUCommandEncoder", + GPUCommandEncoder.prototype, +); - // 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<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); - } - return webidl.converters["GPUOrigin2DDict"](V, opts); +// 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<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); } - throw webidl.makeException( - TypeError, - "can not be converted to sequence<GPUIntegerCoordinate> 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<double> or GPUColorDict) - if (V === null || V === undefined) { - return webidl.converters["GPUColorDict"](V, opts); + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> 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<GPUIntegerCoordinate> 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<GPUIntegerCoordinate>"](V, opts); } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence<double>"](V, opts); - } - return webidl.converters["GPUColorDict"](V, opts); + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<GPUIntegerCoordinate> 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<double> 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<double>"](V, opts); } - throw webidl.makeException( - TypeError, - "can not be converted to sequence<double> 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<GPURenderBundle>"] = 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"], + return webidl.converters["GPUColorDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence<double> 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"], ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( - "GPUQuerySetDescriptor", + ), + required: true, + }, + { + key: "depthStencilAttachment", + converter: webidl.converters["GPURenderPassDepthStencilAttachment"], + }, +]; +webidl.converters["GPURenderPassDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPassDescriptor", 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"]; -})(this); + dictMembersGPURenderPassDescriptor, + ); + +// INTERFACE: GPURenderBundle +webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + GPURenderBundle.prototype, +); +webidl.converters["sequence<GPURenderBundle>"] = 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 f9e422e82..b46db047c 100644 --- a/ext/webgpu/src/03_surface.js +++ b/ext/webgpu/src/03_surface.js @@ -6,144 +6,141 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="./lib.deno_webgpu.d.ts" /> -"use strict"; - -((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]; - - get canvas() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - return this[_canvas]; - } - - 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", - }); - - 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, - }); - - device.pushError(err); - } - - unconfigure() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - - this[_configuration] = null; - this[_device] = null; - } +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"; + +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]; + } - getCurrentTexture() { - webidl.assertBranded(this, GPUCanvasContextPrototype); - const prefix = - "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; + constructor() { + webidl.illegalConstructor(); + } - if (this[_configuration] === null) { - throw new DOMException( - "context is not configured.", - "InvalidStateError", - ); - } + 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", + }); + + 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); + } - const device = assertDevice(this, { prefix, context: "this" }); + unconfigure() { + webidl.assertBranded(this, GPUCanvasContextPrototype); - if (this[_currentTexture]) { - return this[_currentTexture]; - } + this[_configuration] = null; + this[_device] = null; + } - const { rid } = ops.op_webgpu_surface_get_current_texture( - device.rid, - this[_surfaceRid], - ); + getCurrentTexture() { + webidl.assertBranded(this, GPUCanvasContextPrototype); + const prefix = + "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; - 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, - }, - device, - rid, + if (this[_configuration] === null) { + throw new DOMException( + "context is not configured.", + "InvalidStateError", ); - 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 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, + }, + mipLevelCount: 1, + sampleCount: 1, + dimension: "2d", + format: this[_configuration].format, + usage: this[_configuration].usage, + }, + device, + rid, + ); + device.trackResource(texture); + this[_currentTexture] = texture; + return texture; } - const GPUCanvasContextPrototype = GPUCanvasContext.prototype; - function createCanvasContext(options) { - const canvasContext = webidl.createBranded(GPUCanvasContext); - canvasContext[_surfaceRid] = options.surfaceRid; - canvasContext[_canvas] = options.canvas; - return canvasContext; + // 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; +} - window.__bootstrap.webgpu = { - ...window.__bootstrap.webgpu, - GPUCanvasContext, - createCanvasContext, - }; -})(this); +export { createCanvasContext, GPUCanvasContext }; diff --git a/ext/webgpu/src/04_surface_idl_types.js b/ext/webgpu/src/04_surface_idl_types.js index 9dcfa767e..f9665afa6 100644 --- a/ext/webgpu/src/04_surface_idl_types.js +++ b/ext/webgpu/src/04_surface_idl_types.js @@ -6,81 +6,77 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="./lib.deno_webgpu.d.ts" /> -"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", - ], - ); +// 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", - }, +// 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, + // 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 []; }, - { - key: "viewFormats", - converter: webidl.createSequenceConverter( - webidl.converters["GPUTextureFormat"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUCanvasConfiguration"] = webidl - .createDictionaryConverter( - "GPUCanvasConfiguration", - dictMembersGPUCanvasConfiguration, - ); -})(this); + }, +]; +webidl.converters["GPUCanvasConfiguration"] = webidl + .createDictionaryConverter( + "GPUCanvasConfiguration", + dictMembersGPUCanvasConfiguration, + ); diff --git a/ext/webgpu/src/lib.rs b/ext/webgpu/src/lib.rs index 8e4077e7f..83326b71a 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 2ce9cf448..84d17b38d 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 bb90d973e..1357d308e 100644 --- a/ext/webidl/00_webidl.js +++ b/ext/webidl/00_webidl.js @@ -6,1186 +6,1182 @@ /// <reference path="../../core/internal.d.ts" /> -"use strict"; - -((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; +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; + +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 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"; + } +} - 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)); } - // 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 censorNegativeZero(MathRound(x)); +} - return censorNegativeZero(MathRound(x)); - } +function integerPart(n) { + return censorNegativeZero(MathTrunc(n)); +} - function integerPart(n) { - return censorNegativeZero(MathTrunc(n)); - } +function sign(x) { + return x < 0 ? -1 : 1; +} - 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; } - - 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; + 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 censorNegativeZero(x) { - return x === 0 ? 0 : x; - } + const twoToTheBitLength = MathPow(2, bitLength); + const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); - 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; + 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; } - const twoToTheBitLength = MathPow(2, bitLength); - const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } + // 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; + } - x = integerPart(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; + }; +} - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } +function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = NumberMAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; - return x; - } + return (V, opts = {}) => { + let x = toNumber(V); + x = censorNegativeZero(x); - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); } - 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; + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); } - // 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; + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); + if (!NumberIsFinite(x) || x === 0) { + return 0; + } - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; +} - x = integerPart(x); +const converters = []; - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } +converters.any = (V) => { + return V; +}; - return x; - } +converters.boolean = function (val) { + return !!val; +}; - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } +converters.byte = createIntegerConversion(8, { unsigned: false }); +converters.octet = createIntegerConversion(8, { unsigned: true }); - if (!NumberIsFinite(x) || x === 0) { - return 0; - } +converters.short = createIntegerConversion(16, { unsigned: false }); +converters["unsigned short"] = createIntegerConversion(16, { + unsigned: true, +}); - let xBigInt = BigInt(integerPart(x)); - xBigInt = asBigIntN(bitLength, xBigInt); - return Number(xBigInt); - }; +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, + ); } - const converters = []; + if (ObjectIs(x, -0)) { + return x; + } - converters.any = (V) => { - return V; - }; + const y = MathFround(x); - converters.boolean = function (val) { - return !!val; - }; + if (!NumberIsFinite(y)) { + throw makeException( + TypeError, + "is outside the range of a single-precision floating-point value", + opts, + ); + } - converters.byte = createIntegerConversion(8, { unsigned: false }); - converters.octet = createIntegerConversion(8, { unsigned: true }); + return y; +}; - converters.short = createIntegerConversion(16, { unsigned: false }); - converters["unsigned short"] = createIntegerConversion(16, { - unsigned: true, - }); +converters["unrestricted float"] = (V, _opts) => { + const x = toNumber(V); - converters.long = createIntegerConversion(32, { unsigned: false }); - converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + if (isNaN(x)) { + return x; + } - converters["long long"] = createLongLongConversion(64, { unsigned: false }); - converters["unsigned long long"] = createLongLongConversion(64, { - unsigned: true, - }); + if (ObjectIs(x, -0)) { + return x; + } - converters.float = (V, opts) => { - const x = toNumber(V); + return MathFround(x); +}; - if (!NumberIsFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } +converters.double = (V, opts) => { + const x = toNumber(V); - if (ObjectIs(x, -0)) { - return x; - } + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } - const y = MathFround(x); + return x; +}; - if (!NumberIsFinite(y)) { - throw makeException( - TypeError, - "is outside the range of a single-precision floating-point value", - opts, - ); - } +converters["unrestricted double"] = (V, _opts) => { + const x = toNumber(V); - return y; - }; + return x; +}; - converters["unrestricted float"] = (V, _opts) => { - const x = toNumber(V); +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, + ); + } - if (isNaN(x)) { - return x; - } + return String(V); +}; - if (ObjectIs(x, -0)) { - return x; +// 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; +}; - return MathFround(x); - }; +converters.object = (V, opts) => { + if (type(V) !== "Object") { + throw makeException(TypeError, "is not an object", opts); + } - converters.double = (V, opts) => { - const x = toNumber(V); + return V; +}; - if (!NumberIsFinite(x)) { +// 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 not a finite floating-point value", + "is not an ArrayBuffer or SharedArrayBuffer", opts, ); } + throw makeException(TypeError, "is not an ArrayBuffer", opts); + } - return x; - }; + return V; +}; - converters["unrestricted double"] = (V, _opts) => { - const x = toNumber(V); +converters.DataView = (V, opts = {}) => { + if (!isDataView(V)) { + throw makeException(TypeError, "is not a DataView", opts); + } - return x; - }; + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is backed by a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + 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.DOMString = function (V, opts = {}) { - if (typeof V === "string") { return V; - } else if (V === null && opts.treatNullAsEmptyString) { - return ""; - } else if (typeof V === "symbol") { + }; + }, +); + +// 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 a symbol, which cannot be converted to a string", + "is a view on a SharedArrayBuffer, which is not allowed", opts, ); } - return String(V); - }; + return V; + } + + 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, + ); + } - // 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 V; +}; + +converters.DOMTimeStamp = converters["unsigned long long"]; +converters.DOMHighResTimeStamp = converters["double"]; + +converters.Function = convertCallbackFunction; + +converters.VoidFunction = convertCallbackFunction; + +converters["UVString?"] = createNullableConverter( + converters.USVString, +); +converters["sequence<double>"] = createSequenceConverter( + converters.double, +); +converters["sequence<object>"] = createSequenceConverter( + converters.object, +); +converters["Promise<undefined>"] = createPromiseConverter(() => undefined); + +converters["sequence<ByteString>"] = createSequenceConverter( + converters.ByteString, +); +converters["sequence<sequence<ByteString>>"] = createSequenceConverter( + converters["sequence<ByteString>"], +); +converters["record<ByteString, ByteString>"] = createRecordConverter( + converters.ByteString, + converters.ByteString, +); + +converters["sequence<USVString>"] = createSequenceConverter( + converters.USVString, +); +converters["sequence<sequence<USVString>>"] = createSequenceConverter( + converters["sequence<USVString>"], +); +converters["record<USVString, USVString>"] = createRecordConverter( + converters.USVString, + converters.USVString, +); + +converters["sequence<DOMString>"] = 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); } - return x; - }; + } + ArrayPrototypeSort(allMembers, (a, b) => { + if (a.key == b.key) { + return 0; + } + return a.key < b.key ? -1 : 1; + }); - 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); + 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 { - 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); - } + ObjectDefineProperty(defaultValues, member.key, { + get() { + return member.converter(idlMemberValue, member.defaultValue); + }, + enumerable: true, + }); } } - return U; - }; + } - converters.object = (V, opts) => { - if (type(V) !== "Object") { - throw makeException(TypeError, "is not an object", 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 V; - }; - - // Not exported, but used in Function and VoidFunction. + const idlDict = ObjectAssign({}, defaultValues); - // 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); + // NOTE: fast path Null and Undefined. + if ((V === undefined || V === null) && !hasRequiredKey) { + return idlDict; } - return V; - } - - function isDataView(V) { - return ArrayBufferIsView(V) && - TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; - } - function isNonSharedArrayBuffer(V) { - return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); - } + for (let i = 0; i < allMembers.length; ++i) { + const member = allMembers[i]; + const key = member.key; - function isSharedArrayBuffer(V) { - // deno-lint-ignore prefer-primordials - return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); - } + let esMemberValue; + if (typeV === "Undefined" || typeV === "Null") { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } - converters.ArrayBuffer = (V, opts = {}) => { - if (!isNonSharedArrayBuffer(V)) { - if (opts.allowShared && !isSharedArrayBuffer(V)) { + 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, - "is not an ArrayBuffer or SharedArrayBuffer", + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, opts, ); } - throw makeException(TypeError, "is not an ArrayBuffer", opts); } - return V; + return idlDict; }; +} - converters.DataView = (V, opts = {}) => { - if (!isDataView(V)) { - throw makeException(TypeError, "is not a DataView", opts); - } +// https://heycam.github.io/webidl/#es-enumeration +function createEnumConverter(name, values) { + const E = new Set(values); - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is backed by a SharedArrayBuffer, which is not allowed", - opts, + 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 V; + 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); + }; +} - // 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, - ); - } - - return V; - }; - }, - ); - - // Common definitions - - converters.ArrayBufferView = (V, opts = {}) => { - if (!ArrayBufferIsView(V)) { +// https://heycam.github.io/webidl/#es-sequence +function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (type(V) !== "Object") { throw makeException( TypeError, - "is not a view on an ArrayBuffer or SharedArrayBuffer", + "can not be converted to sequence.", opts, ); } - - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { throw makeException( TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", + "can not be converted to sequence.", opts, ); } - - return V; - }; - - converters.BufferSource = (V, opts = {}) => { - if (ArrayBufferIsView(V)) { - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { throw makeException( TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", + "can not be converted to sequence.", opts, ); } - - return V; + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); } + return array; + }; +} - 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) - ) { +function createRecordConverter(keyConverter, valueConverter) { + return (V, opts) => { + if (type(V) !== "Object") { throw makeException( TypeError, - "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + "can not be converted to dictionary.", opts, ); } - - return V; - }; - - converters.DOMTimeStamp = converters["unsigned long long"]; - converters.DOMHighResTimeStamp = converters["double"]; - - converters.Function = convertCallbackFunction; - - converters.VoidFunction = convertCallbackFunction; - - converters["UVString?"] = createNullableConverter( - converters.USVString, - ); - converters["sequence<double>"] = createSequenceConverter( - converters.double, - ); - converters["sequence<object>"] = createSequenceConverter( - converters.object, - ); - converters["Promise<undefined>"] = createPromiseConverter(() => undefined); - - converters["sequence<ByteString>"] = createSequenceConverter( - converters.ByteString, - ); - converters["sequence<sequence<ByteString>>"] = createSequenceConverter( - converters["sequence<ByteString>"], - ); - converters["record<ByteString, ByteString>"] = createRecordConverter( - converters.ByteString, - converters.ByteString, - ); - - converters["sequence<USVString>"] = createSequenceConverter( - converters.USVString, - ); - converters["sequence<sequence<USVString>>"] = createSequenceConverter( - converters["sequence<USVString>"], - ); - converters["record<USVString, USVString>"] = createRecordConverter( - converters.USVString, - converters.USVString, - ); - - converters["sequence<DOMString>"] = 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; + const result = {}; + // Fast path for common case (not a Proxy) + if (!core.isProxy(V)) { + for (const key in V) { + if (!ObjectPrototypeHasOwnProperty(V, key)) { + continue; } - ArrayPrototypePush(allMembers, member); + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; } + return result; } - ArrayPrototypeSort(allMembers, (a, b) => { - if (a.key == b.key) { - return 0; + // 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; } - return a.key < b.key ? -1 : 1; + } + 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", }); - - 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, - }); - } - } + } catch (err) { + if (opts.returnsPromise === true) { + return PromiseReject(err); } + throw err; + } +} - 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; - } - - for (let i = 0; i < allMembers.length; ++i) { - const member = allMembers[i]; - 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, - ); - } - } +const brand = Symbol("[[webidl.brand]]"); - return idlDict; - }; +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)}`); + } } +} - // https://heycam.github.io/webidl/#es-enumeration - function createEnumConverter(name, values) { - const E = new Set(values); +const _iteratorInternal = Symbol("iterator internal"); - return function (V, opts = {}) { - const S = String(V); +const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); - if (!E.has(S)) { +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( - `${ - opts.prefix ? opts.prefix + ": " : "" - }The provided value '${S}' is not a valid enum value of type ${name}.`, + `next() called on a value that is not a ${name} iterator object`, ); } - - return S; - }; + 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 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); - }; + function entries() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "key+value"); } - // 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, - ); - } - if (res.done === true) break; - const val = converter(res.value, { - ...opts, - context: `${opts.context}, index ${array.length}`, + 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", }); - 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, + idlCallback = FunctionPrototypeBind( + idlCallback, + thisArg ?? globalThis, ); - } - 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; + const pairs = this[dataSymbol]; + for (let i = 0; i < pairs.length; i++) { + const entry = pairs[i]; + idlCallback(entry[valueKey], entry[keyKey], this); } - 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; - } - } - 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; + }, + 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; } - } - - 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 (key === "constructor") continue; + const descriptor = descriptors[key]; if ( - !ObjectPrototypeIsPrototypeOf(prototype, self) || self[brand] !== brand + ReflectHas(descriptor, "value") && + typeof descriptor.value === "function" ) { - 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 globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); - - 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 }, + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + writable: true, + configurable: true, + }); + } else if (ReflectHas(descriptor, "get")) { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, configurable: true, }); - return iterator; } + } + ObjectDefineProperty(prototype.prototype, SymbolToStringTag, { + value: prototype.name, + enumerable: false, + configurable: true, + writable: false, + }); +} - function entries() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key+value"); - } +const setlikeInner = Symbol("[[set]]"); - const properties = { - entries: { - value: entries, - writable: true, - enumerable: true, - configurable: true, +// 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]: { - value: entries, - writable: true, - enumerable: false, - configurable: true, + }, + [SymbolIterator]: { + configurable: true, + enumerable: false, + writable: true, + value() { + assertBranded(this, objPrototype); + return obj[setlikeInner][SymbolIterator](); }, - keys: { - value: function keys() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key"); - }, - writable: true, - enumerable: true, - configurable: true, + }, + entries: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeEntries(obj[setlikeInner]); }, - values: { - value: function values() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "value"); - }, - writable: true, - enumerable: true, - configurable: true, + }, + keys: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeKeys(obj[setlikeInner]); }, - 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, + }, + values: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeValues(obj[setlikeInner]); }, - }; - 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, + }, + forEach: { configurable: true, - writable: false, - }); - } - - const setlikeInner = Symbol("[[set]]"); - - // 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; - }, + enumerable: true, + writable: true, + value(callbackfn, thisArg) { + assertBranded(this, objPrototype); + return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg); }, - [SymbolIterator]: { - configurable: true, - enumerable: false, - writable: true, - value() { - assertBranded(this, objPrototype); - return obj[setlikeInner][SymbolIterator](); - }, + }, + has: { + configurable: true, + enumerable: true, + writable: true, + value(value) { + assertBranded(this, objPrototype); + return SetPrototypeHas(obj[setlikeInner], value); }, - entries: { + }, + }); + + if (!readonly) { + ObjectDefineProperties(obj, { + add: { configurable: true, enumerable: true, writable: true, - value() { + value(value) { assertBranded(this, objPrototype); - return SetPrototypeEntries(obj[setlikeInner]); + return SetPrototypeAdd(obj[setlikeInner], value); }, }, - keys: { + delete: { configurable: true, enumerable: true, writable: true, - value() { + value(value) { assertBranded(this, objPrototype); - return SetPrototypeKeys(obj[setlikeInner]); + return SetPrototypeDelete(obj[setlikeInner], value); }, }, - values: { + clear: { 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); + 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 353a630eb..b53326de9 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 e7df8af62..1400a00ed 100644 --- a/ext/webidl/benches/dict.rs +++ b/ext/webidl/benches/dict.rs @@ -11,7 +11,7 @@ fn setup() -> Vec<Extension> { 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 4ab9e33a9..9f47c42e5 100644 --- a/ext/webidl/internal.d.ts +++ b/ext/webidl/internal.d.ts @@ -4,338 +4,334 @@ /// <reference no-default-lib="true" /> /// <reference lib="esnext" /> -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<double>"](v: any, opts?: ValueConverterOpts): number[]; +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<double>"](v: any, opts?: ValueConverterOpts): number[]; - [type: string]: (v: any, opts: ValueConverterOpts) => any; - }; + [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; - } + /** + * 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. - */ - declare function createDictionaryConverter<T>( - name: string, - ...dictionaries: Dictionary[] - ): (v: any, opts: ValueConverterOpts) => T; + /** + * Create a converter for dictionaries. + */ + function createDictionaryConverter<T>( + 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 for enums. + */ + function createEnumConverter( + name: string, + values: string[], + ): (v: any, opts: ValueConverterOpts) => string; - /** - * Create a converter that makes the contained type nullable. - */ - declare function createNullableConverter<T>( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T | null; + /** + * Create a converter that makes the contained type nullable. + */ + function createNullableConverter<T>( + 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<T>( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T[]; + /** + * Create a converter that converts a sequence of the inner type. + */ + function createSequenceConverter<T>( + 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<T>( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => Promise<T>; + /** + * Create a converter that converts a Promise of the inner type. + */ + function createPromiseConverter<T>( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => Promise<T>; - /** - * Invoke a callback function. - */ - declare function invokeCallbackFunction<T>( - callable: (...args: any) => any, - args: any[], - thisArg: any, - returnValueConverter: (v: any, opts: ValueConverterOpts) => T, - opts: ConverterOpts & { returnsPromise?: boolean }, - ): T; + /** + * Invoke a callback function. + */ + function invokeCallbackFunction<T>( + 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; + /** + * Throw an illegal constructor error. + */ + function illegalConstructor(): never; - /** - * The branding symbol. - */ - declare const brand: unique symbol; + /** + * The branding symbol. + */ + const brand: unique symbol; - /** - * Create a branded instance of an interface. - */ - declare function createBranded(self: any): any; + /** + * Create a branded instance of an interface. + */ + function createBranded(self: any): any; - /** - * Assert that self is branded. - */ - declare function assertBranded(self: any, type: any): void; + /** + * Assert that self is branded. + */ + function assertBranded(self: any, type: any): void; - /** - * Create a converter for interfaces. - */ - declare function createInterfaceConverter( - name: string, - prototype: any, - ): (v: any, opts: ValueConverterOpts) => any; + /** + * Create a converter for interfaces. + */ + 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<K, V>, - 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<K, V>, + 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; + /** + * 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. - */ - declare function configurePrototype(prototype: any); + /** + * Configure prototype properties enumerability / writability / configurability. + */ + 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"; - } - } + /** + * 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 ae25f04c7..4e21ef796 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 9b7c45e70..305cf75a7 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"; /// <reference path="../../core/internal.d.ts" /> -((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; - - webidl.converters["sequence<DOMString> or DOMString"] = (V, opts) => { - // Union for (sequence<DOMString> or DOMString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence<DOMString>"](V, opts); - } +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<DOMString> or DOMString"] = (V, opts) => { + // Union for (sequence<DOMString> or DOMString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence<DOMString>"](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); - } - 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); - } +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); } - return webidl.converters["USVString"](V, opts); - }; - - 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 _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]; + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } + } + return webidl.converters["USVString"](V, opts); +}; + +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 _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]; + } - 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]; + } + + [_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; } + } + + [_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, { + prefix, + }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + protocols = webidl.converters["sequence<DOMString> or DOMString"]( + protocols, + { + prefix, + context: "Argument 2", + }, + ); - [_protocol] = ""; - get protocol() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_protocol]; + let wsURL; + + try { + wsURL = new URL(url); + } catch (e) { + throw new DOMException(e.message, "SyntaxError"); } - [_url] = ""; - get url() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_url]; + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); } - [_binaryType] = "blob"; - get binaryType() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_binaryType]; + if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", + ); } - 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; - } + + this[_url] = wsURL.href; + + ops.op_ws_check_permission_and_cancel_handle( + "WebSocket.abort()", + this[_url], + false, + ); + + if (typeof protocols === "string") { + protocols = [protocols]; } - [_bufferedAmount] = 0; - get bufferedAmount() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_bufferedAmount]; + if ( + protocols.length !== + new Set( + ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), + ).size + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); } - 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<DOMString> or DOMString"]( + if ( + ArrayPrototypeSome( protocols, - { - prefix, - context: "Argument 2", - }, + (protocol) => !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), + ) + ) { + throw new DOMException( + "Invalid protocol value.", + "SyntaxError", ); + } - let wsURL; - - try { - wsURL = new URL(url); - } catch (e) { - throw new DOMException(e.message, "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); - if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { - throw new DOMException( - "Only ws & wss schemes are allowed in a WebSocket URL.", - "SyntaxError", - ); - } + this[_eventLoop](); + } + }, + (err) => { + this[_readyState] = CLOSED; - if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { - throw new DOMException( - "Fragments are not allowed in a WebSocket URL.", - "SyntaxError", + const errorEv = new ErrorEvent( + "error", + { error: err, message: ErrorPrototypeToString(err) }, ); - } - - this[_url] = wsURL.href; + this.dispatchEvent(errorEv); - ops.op_ws_check_permission_and_cancel_handle( - "WebSocket.abort()", - this[_url], - false, - ); + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + }, + ); + } - if (typeof protocols === "string") { - protocols = [protocols]; - } + send(data) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'send' on 'WebSocket'"; - if ( - protocols.length !== - new Set( - ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), - ).size - ) { - throw new DOMException( - "Can't supply multiple times the same protocol.", - "SyntaxError", - ); - } + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + data = webidl.converters.WebSocketSend(data, { + prefix, + context: "Argument 1", + }); - if ( - ArrayPrototypeSome( - protocols, - (protocol) => - !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), - ) - ) { - throw new DOMException( - "Invalid protocol value.", - "SyntaxError", - ); - } + if (this[_readyState] !== OPEN) { + throw new DOMException("readyState not OPEN", "InvalidStateError"); + } + const sendTypedArray = (ta) => { + this[_bufferedAmount] += ta.byteLength; 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](); - } + core.opAsync("op_ws_send", this[_rid], { + kind: "binary", + value: ta, + }), + () => { + this[_bufferedAmount] -= ta.byteLength; }, - (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); + 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; }, ); } + } - send(data) { - webidl.assertBranded(this, WebSocketPrototype); - const prefix = "Failed to execute 'send' on 'WebSocket'"; + close(code = undefined, reason = undefined) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'close' on 'WebSocket'"; - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - data = webidl.converters.WebSocketSend(data, { + if (code !== undefined) { + code = webidl.converters["unsigned short"](code, { prefix, + clamp: true, 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 (reason !== undefined) { + reason = webidl.converters.USVString(reason, { + prefix, + context: "Argument 2", + }); + } - 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; - }, + 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", ); } } - 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 && core.encode(reason).byteLength > 123) { + throw new DOMException( + "The close reason may not be longer than 123 bytes.", + "SyntaxError", + ); + } - if (reason !== undefined) { - reason = webidl.converters.USVString(reason, { - prefix, - context: "Argument 2", - }); - } + if (this[_readyState] === CONNECTING) { + this[_readyState] = CLOSING; + } else if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; - 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", - ); - } - } + PromisePrototypeCatch( + core.opAsync("op_ws_close", this[_rid], code, reason), + (err) => { + this[_readyState] = CLOSED; - if (reason !== undefined && core.encode(reason).byteLength > 123) { - throw new DOMException( - "The close reason may not be longer than 123 bytes.", - "SyntaxError", - ); - } + const errorEv = new ErrorEvent("error", { + error: err, + message: ErrorPrototypeToString(err), + }); + this.dispatchEvent(errorEv); - 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]); - }, - ); - } + 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], - ); + 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; - switch (kind) { - case "string": { - this[_serverHandleIdleTimeout](); - const event = new MessageEvent("message", { - data: value, - origin: this[_url], - }); - this.dispatchEvent(event); - break; + if (this.binaryType === "blob") { + data = new Blob([value]); + } else { + data = value.buffer; } - 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 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; - } + 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; - [_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); - } else { - clearTimeout(this[_idleTimeoutTimeout]); - } - }, (this[_idleTimeoutDuration] / 2) * 1000); + const errorEv = new ErrorEvent("error", { + message: value, + }); + this.dispatchEvent(errorEv); + + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + core.tryClose(this[_rid]); + break; + } } } + } - [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, - }) - }`; + [_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); + } else { + clearTimeout(this[_idleTimeoutTimeout]); + } + }, (this[_idleTimeoutDuration] / 2) * 1000); } } - 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; - - 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 5d7e47cc4..b3d21297f 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"; /// <reference path="../../core/internal.d.ts" /> -((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 { - 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<USVString>"], - get defaultValue() { - return []; - }, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - { - key: "headers", - converter: webidl.converters.HeadersInit, +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; + +webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( + "WebSocketStreamOptions", + [ + { + key: "protocols", + converter: webidl.converters["sequence<USVString>"], + get defaultValue() { + return []; }, - ], - ); - 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 _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]; + }, + { + 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 _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]; + } + + 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, - ); - - if (kind === "close") { - break; - } - } - })(), - () => { - const err = new DOMException( - "Closed while connecting", - "NetworkError", + ).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 (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", + + if (kind === "close") { + break; + } + } + })(), + () => { + const err = new DOMException( + "Closed while connecting", + "NetworkError", + ); + 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", ); - this[_connection].reject(err); - this[_closed].reject(err); - }, + } + }, + 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], ); - } 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", - ); - } - }, - close: async (reason) => { + + 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[_closed].reject(err); + controller.error(err); + core.tryClose(this[_rid]); + break; + } + } + + 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; - }, - abort: async (reason) => { try { - this.close(reason?.code !== undefined ? reason : {}); + PromisePrototypeCatch( + writableStreamClose(writable), + () => {}, + ); } catch (_) { - this.close(); + // needed to ignore warnings & assertions } - 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[_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) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); } + await this.closed; + }, + }); + + 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); + }, + ); + } + } - if ( - this[_closeSent].state === "fulfilled" && - this[_closed].state === "pending" - ) { - if ( - new Date().getTime() - await this[_closeSent].promise <= - CLOSE_RESPONSE_TIMEOUT - ) { - return pull(controller); - } + [_connection] = new Deferred(); + get connection() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_connection].promise; + } - this[_closed].resolve(value); - core.tryClose(this[_rid]); - } - }; - 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 - } - }); + [_earlyClose] = false; + [_closed] = new Deferred(); + [_closeSent] = new Deferred(); + get closed() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_closed].promise; + } - 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; - }, - }); - - 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); - }, - ); - } + 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", + ); } - [_connection] = new Deferred(); - get connection() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_connection].promise; + 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", + ); } - [_earlyClose] = false; - [_closed] = new Deferred(); - [_closeSent] = new Deferred(); - get closed() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_closed].promise; + let code = closeInfo.code; + if (closeInfo.reason && code === undefined) { + code = 1000; } - 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); - }, - ); - } + 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, - }) - }`; - } + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + }) + }`; } +} - const WebSocketStreamPrototype = WebSocketStream.prototype; +const WebSocketStreamPrototype = WebSocketStream.prototype; - window.__bootstrap.webSocket.WebSocketStream = WebSocketStream; -})(this); +export { WebSocketStream }; diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 82a2c5918..da6a48e45 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -504,7 +504,7 @@ pub fn init<P: WebSocketPermissions + 'static>( ) -> 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 27d7aac09..d7abdfbd8 100644 --- a/ext/webstorage/01_webstorage.js +++ b/ext/webstorage/01_webstorage.js @@ -2,191 +2,189 @@ /// <reference path="../../core/internal.d.ts" /> -((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 _persistent = Symbol("[[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]); - } +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]]"); + +class Storage { + [_persistent]; + + constructor() { + webidl.illegalConstructor(); } - 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 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, - }; - }, + 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", }); - proxy[SymbolFor("Deno.customInspect")] = function (inspect) { - return `${this.constructor.name} ${ - inspect({ - length: this.length, - ...ObjectFromEntries(ObjectEntries(proxy)), - }) - }`; - }; + 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", + }); - return proxy; + ops.op_webstorage_set(key, value, this[_persistent]); } - let localStorage; - let sessionStorage; + 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; - window.__bootstrap.webStorage = { - localStorage() { - if (!localStorage) { - localStorage = createStorage(true); + 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 29deaee84..d878ad701 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<PathBuf>) -> 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", )) |