diff options
Diffstat (limited to 'cli/js/web')
44 files changed, 0 insertions, 10963 deletions
diff --git a/cli/js/web/README.md b/cli/js/web/README.md deleted file mode 100644 index 01672fe76..000000000 --- a/cli/js/web/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Deno Web APIs - -This directory facilities Web APIs that are available in Deno. - -Please note, that some implementations might not be completely aligned with -specification. - -Some Web APIs are using ops under the hood, eg. `console`, `performance`. - -## Implemented Web APIs - -- [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob): for - representing opaque binary data -- [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console): for - logging purposes -- [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent), - [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) - and - [EventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventListener): - to work with DOM events - - **Implementation notes:** There is no DOM hierarchy in Deno, so there is no - tree for Events to bubble/capture through. -- [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch), - [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), - [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), - [Body](https://developer.mozilla.org/en-US/docs/Web/API/Body) and - [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers): modern - Promise-based HTTP Request API -- [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData): access - to a `multipart/form-data` serialization -- [Performance](https://developer.mozilla.org/en-US/docs/Web/API/Performance): - retrieving current time with a high precision -- [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout), - [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval), - [clearTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout): - scheduling callbacks in future and - [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) -- [Stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) for - creating, composing, and consuming streams of data -- [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) and - [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams): - to construct and parse URLSs -- [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker): executing - additional code in a separate thread - - **Implementation notes:** Blob URLs are not supported, object ownership - cannot be transferred, posted data is serialized to JSON instead of - [structured cloning](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). diff --git a/cli/js/web/abort_controller.ts b/cli/js/web/abort_controller.ts deleted file mode 100644 index 376376092..000000000 --- a/cli/js/web/abort_controller.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { AbortSignalImpl, signalAbort } from "./abort_signal.ts"; - -export class AbortControllerImpl implements AbortController { - #signal = new AbortSignalImpl(); - - get signal(): AbortSignal { - return this.#signal; - } - - abort(): void { - this.#signal[signalAbort](); - } - - get [Symbol.toStringTag](): string { - return "AbortController"; - } -} - -Object.defineProperty(AbortControllerImpl, "name", { - value: "AbortController", - configurable: true, -}); diff --git a/cli/js/web/abort_signal.ts b/cli/js/web/abort_signal.ts deleted file mode 100644 index b741d6534..000000000 --- a/cli/js/web/abort_signal.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { EventImpl } from "./event.ts"; -import { EventTargetImpl } from "./event_target.ts"; - -export const add = Symbol("add"); -export const signalAbort = Symbol("signalAbort"); -export const remove = Symbol("remove"); - -export class AbortSignalImpl extends EventTargetImpl implements AbortSignal { - #aborted?: boolean; - #abortAlgorithms = new Set<() => void>(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onabort: ((this: AbortSignal, ev: Event) => any) | null = null; - - [add](algorithm: () => void): void { - this.#abortAlgorithms.add(algorithm); - } - - [signalAbort](): void { - if (this.#aborted) { - return; - } - this.#aborted = true; - for (const algorithm of this.#abortAlgorithms) { - algorithm(); - } - this.#abortAlgorithms.clear(); - this.dispatchEvent(new EventImpl("abort")); - } - - [remove](algorithm: () => void): void { - this.#abortAlgorithms.delete(algorithm); - } - - constructor() { - super(); - this.addEventListener("abort", (evt: Event) => { - const { onabort } = this; - if (typeof onabort === "function") { - onabort.call(this, evt); - } - }); - } - - get aborted(): boolean { - return Boolean(this.#aborted); - } - - get [Symbol.toStringTag](): string { - return "AbortSignal"; - } -} - -Object.defineProperty(AbortSignalImpl, "name", { - value: "AbortSignal", - configurable: true, -}); diff --git a/cli/js/web/base64.ts b/cli/js/web/base64.ts deleted file mode 100644 index 6f2459b92..000000000 --- a/cli/js/web/base64.ts +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// Forked from https://github.com/beatgammit/base64-js -// Copyright (c) 2014 Jameson Little. MIT License. - -const lookup: string[] = []; -const revLookup: number[] = []; - -const code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -for (let i = 0, len = code.length; i < len; ++i) { - lookup[i] = code[i]; - revLookup[code.charCodeAt(i)] = i; -} - -// Support decoding URL-safe base64 strings, as Node.js does. -// See: https://en.wikipedia.org/wiki/Base64#URL_applications -revLookup["-".charCodeAt(0)] = 62; -revLookup["_".charCodeAt(0)] = 63; - -function getLens(b64: string): [number, number] { - const len = b64.length; - - if (len % 4 > 0) { - throw new Error("Invalid string. Length must be a multiple of 4"); - } - - // Trim off extra bytes after placeholder bytes are found - // See: https://github.com/beatgammit/base64-js/issues/42 - let validLen = b64.indexOf("="); - if (validLen === -1) validLen = len; - - const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4); - - return [validLen, placeHoldersLen]; -} - -// base64 is 4/3 + up to two characters of the original data -export function byteLength(b64: string): number { - const lens = getLens(b64); - const validLen = lens[0]; - const placeHoldersLen = lens[1]; - return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; -} - -function _byteLength( - b64: string, - validLen: number, - placeHoldersLen: number, -): number { - return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; -} - -export function toByteArray(b64: string): Uint8Array { - let tmp; - const lens = getLens(b64); - const validLen = lens[0]; - const placeHoldersLen = lens[1]; - - const arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen)); - - let curByte = 0; - - // if there are placeholders, only get up to the last complete 4 chars - const len = placeHoldersLen > 0 ? validLen - 4 : validLen; - - let i; - for (i = 0; i < len; i += 4) { - tmp = (revLookup[b64.charCodeAt(i)] << 18) | - (revLookup[b64.charCodeAt(i + 1)] << 12) | - (revLookup[b64.charCodeAt(i + 2)] << 6) | - revLookup[b64.charCodeAt(i + 3)]; - arr[curByte++] = (tmp >> 16) & 0xff; - arr[curByte++] = (tmp >> 8) & 0xff; - arr[curByte++] = tmp & 0xff; - } - - if (placeHoldersLen === 2) { - tmp = (revLookup[b64.charCodeAt(i)] << 2) | - (revLookup[b64.charCodeAt(i + 1)] >> 4); - arr[curByte++] = tmp & 0xff; - } - - if (placeHoldersLen === 1) { - tmp = (revLookup[b64.charCodeAt(i)] << 10) | - (revLookup[b64.charCodeAt(i + 1)] << 4) | - (revLookup[b64.charCodeAt(i + 2)] >> 2); - arr[curByte++] = (tmp >> 8) & 0xff; - arr[curByte++] = tmp & 0xff; - } - - return arr; -} - -function tripletToBase64(num: number): string { - return ( - lookup[(num >> 18) & 0x3f] + - lookup[(num >> 12) & 0x3f] + - lookup[(num >> 6) & 0x3f] + - lookup[num & 0x3f] - ); -} - -function encodeChunk(uint8: Uint8Array, start: number, end: number): string { - let tmp; - const output = []; - for (let i = start; i < end; i += 3) { - tmp = ((uint8[i] << 16) & 0xff0000) + - ((uint8[i + 1] << 8) & 0xff00) + - (uint8[i + 2] & 0xff); - output.push(tripletToBase64(tmp)); - } - return output.join(""); -} - -export function fromByteArray(uint8: Uint8Array): string { - let tmp; - const len = uint8.length; - const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes - const parts = []; - const maxChunkLength = 16383; // must be multiple of 3 - - // go through the array every three bytes, we'll deal with trailing stuff later - for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { - parts.push( - encodeChunk( - uint8, - i, - i + maxChunkLength > len2 ? len2 : i + maxChunkLength, - ), - ); - } - - // pad the end with zeros, but make sure to not forget the extra bytes - if (extraBytes === 1) { - tmp = uint8[len - 1]; - parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + "=="); - } else if (extraBytes === 2) { - tmp = (uint8[len - 2] << 8) + uint8[len - 1]; - parts.push( - lookup[tmp >> 10] + - lookup[(tmp >> 4) & 0x3f] + - lookup[(tmp << 2) & 0x3f] + - "=", - ); - } - - return parts.join(""); -} diff --git a/cli/js/web/blob.ts b/cli/js/web/blob.ts deleted file mode 100644 index 7034da723..000000000 --- a/cli/js/web/blob.ts +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { TextDecoder, TextEncoder } from "./text_encoding.ts"; -import { build } from "../build.ts"; -import { ReadableStreamImpl } from "./streams/readable_stream.ts"; - -export const bytesSymbol = Symbol("bytes"); - -export function containsOnlyASCII(str: string): boolean { - if (typeof str !== "string") { - return false; - } - return /^[\x00-\x7F]*$/.test(str); -} - -function convertLineEndingsToNative(s: string): string { - const nativeLineEnd = build.os == "windows" ? "\r\n" : "\n"; - - let position = 0; - - let collectionResult = collectSequenceNotCRLF(s, position); - - let token = collectionResult.collected; - position = collectionResult.newPosition; - - let result = token; - - while (position < s.length) { - const c = s.charAt(position); - if (c == "\r") { - result += nativeLineEnd; - position++; - if (position < s.length && s.charAt(position) == "\n") { - position++; - } - } else if (c == "\n") { - position++; - result += nativeLineEnd; - } - - collectionResult = collectSequenceNotCRLF(s, position); - - token = collectionResult.collected; - position = collectionResult.newPosition; - - result += token; - } - - return result; -} - -function collectSequenceNotCRLF( - s: string, - position: number, -): { collected: string; newPosition: number } { - const start = position; - for ( - let c = s.charAt(position); - position < s.length && !(c == "\r" || c == "\n"); - c = s.charAt(++position) - ); - return { collected: s.slice(start, position), newPosition: position }; -} - -function toUint8Arrays( - blobParts: BlobPart[], - doNormalizeLineEndingsToNative: boolean, -): Uint8Array[] { - const ret: Uint8Array[] = []; - const enc = new TextEncoder(); - for (const element of blobParts) { - if (typeof element === "string") { - let str = element; - if (doNormalizeLineEndingsToNative) { - str = convertLineEndingsToNative(element); - } - ret.push(enc.encode(str)); - // eslint-disable-next-line @typescript-eslint/no-use-before-define - } else if (element instanceof DenoBlob) { - ret.push(element[bytesSymbol]); - } else if (element instanceof Uint8Array) { - ret.push(element); - } else if (element instanceof Uint16Array) { - const uint8 = new Uint8Array(element.buffer); - ret.push(uint8); - } else if (element instanceof Uint32Array) { - const uint8 = new Uint8Array(element.buffer); - ret.push(uint8); - } else if (ArrayBuffer.isView(element)) { - // Convert view to Uint8Array. - const uint8 = new Uint8Array(element.buffer); - ret.push(uint8); - } else if (element instanceof ArrayBuffer) { - // Create a new Uint8Array view for the given ArrayBuffer. - const uint8 = new Uint8Array(element); - ret.push(uint8); - } else { - ret.push(enc.encode(String(element))); - } - } - return ret; -} - -function processBlobParts( - blobParts: BlobPart[], - options: BlobPropertyBag, -): Uint8Array { - const normalizeLineEndingsToNative = options.ending === "native"; - // ArrayBuffer.transfer is not yet implemented in V8, so we just have to - // pre compute size of the array buffer and do some sort of static allocation - // instead of dynamic allocation. - const uint8Arrays = toUint8Arrays(blobParts, normalizeLineEndingsToNative); - const byteLength = uint8Arrays - .map((u8): number => u8.byteLength) - .reduce((a, b): number => a + b, 0); - const ab = new ArrayBuffer(byteLength); - const bytes = new Uint8Array(ab); - let courser = 0; - for (const u8 of uint8Arrays) { - bytes.set(u8, courser); - courser += u8.byteLength; - } - - return bytes; -} - -function getStream(blobBytes: Uint8Array): ReadableStream<ArrayBufferView> { - // TODO: Align to spec https://fetch.spec.whatwg.org/#concept-construct-readablestream - return new ReadableStreamImpl({ - type: "bytes", - start: (controller: ReadableByteStreamController): void => { - controller.enqueue(blobBytes); - controller.close(); - }, - }); -} - -async function readBytes( - reader: ReadableStreamReader<ArrayBufferView>, -): Promise<ArrayBuffer> { - const chunks: Uint8Array[] = []; - while (true) { - const { done, value } = await reader.read(); - if (!done && value instanceof Uint8Array) { - chunks.push(value); - } else if (done) { - const size = chunks.reduce((p, i) => p + i.byteLength, 0); - const bytes = new Uint8Array(size); - let offs = 0; - for (const chunk of chunks) { - bytes.set(chunk, offs); - offs += chunk.byteLength; - } - return bytes; - } else { - throw new TypeError("Invalid reader result."); - } - } -} - -// A WeakMap holding blob to byte array mapping. -// Ensures it does not impact garbage collection. -export const blobBytesWeakMap = new WeakMap<Blob, Uint8Array>(); - -class DenoBlob implements Blob { - [bytesSymbol]: Uint8Array; - readonly size: number = 0; - readonly type: string = ""; - - constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) { - if (arguments.length === 0) { - this[bytesSymbol] = new Uint8Array(); - return; - } - - const { ending = "transparent", type = "" } = options ?? {}; - // Normalize options.type. - let normalizedType = type; - if (!containsOnlyASCII(type)) { - normalizedType = ""; - } else { - if (type.length) { - for (let i = 0; i < type.length; ++i) { - const char = type[i]; - if (char < "\u0020" || char > "\u007E") { - normalizedType = ""; - break; - } - } - normalizedType = type.toLowerCase(); - } - } - const bytes = processBlobParts(blobParts!, { ending, type }); - // Set Blob object's properties. - this[bytesSymbol] = bytes; - this.size = bytes.byteLength; - this.type = normalizedType; - } - - slice(start?: number, end?: number, contentType?: string): DenoBlob { - return new DenoBlob([this[bytesSymbol].slice(start, end)], { - type: contentType || this.type, - }); - } - - stream(): ReadableStream<ArrayBufferView> { - return getStream(this[bytesSymbol]); - } - - async text(): Promise<string> { - const reader = getStream(this[bytesSymbol]).getReader(); - const decoder = new TextDecoder(); - return decoder.decode(await readBytes(reader)); - } - - arrayBuffer(): Promise<ArrayBuffer> { - return readBytes(getStream(this[bytesSymbol]).getReader()); - } -} - -// we want the Base class name to be the name of the class. -Object.defineProperty(DenoBlob, "name", { - value: "Blob", - configurable: true, -}); - -export { DenoBlob }; diff --git a/cli/js/web/body.ts b/cli/js/web/body.ts deleted file mode 100644 index a7a120ad6..000000000 --- a/cli/js/web/body.ts +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import * as blob from "./blob.ts"; -import * as encoding from "./text_encoding.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { ReadableStreamImpl } from "./streams/readable_stream.ts"; -import { isReadableStreamDisturbed } from "./streams/internals.ts"; -import { Buffer } from "../buffer.ts"; - -import { - getHeaderValueParams, - hasHeaderValueOf, - isTypedArray, -} from "./util.ts"; -import { MultipartParser } from "./fetch/multipart.ts"; - -// only namespace imports work for now, plucking out what we need -const { TextEncoder, TextDecoder } = encoding; -const DenoBlob = blob.DenoBlob; - -interface BodyMeta { - contentType: string; - size?: number; -} - -function validateBodyType(owner: Body, bodySource: BodyInit | null): boolean { - if (isTypedArray(bodySource)) { - return true; - } else if (bodySource instanceof ArrayBuffer) { - return true; - } else if (typeof bodySource === "string") { - return true; - } else if (bodySource instanceof ReadableStreamImpl) { - return true; - } else if (bodySource instanceof FormData) { - return true; - } else if (bodySource instanceof URLSearchParams) { - return true; - } else if (!bodySource) { - return true; // null body is fine - } - throw new Error( - `Bad ${owner.constructor.name} body type: ${bodySource.constructor.name}`, - ); -} - -async function bufferFromStream( - stream: ReadableStreamReader, - size?: number, -): Promise<ArrayBuffer> { - const encoder = new TextEncoder(); - const buffer = new Buffer(); - - if (size) { - // grow to avoid unnecessary allocations & copies - buffer.grow(size); - } - - while (true) { - const { done, value } = await stream.read(); - - if (done) break; - - if (typeof value === "string") { - buffer.writeSync(encoder.encode(value)); - } else if (value instanceof ArrayBuffer) { - buffer.writeSync(new Uint8Array(value)); - } else if (value instanceof Uint8Array) { - buffer.writeSync(value); - } else if (!value) { - // noop for undefined - } else { - throw new Error("unhandled type on stream read"); - } - } - - return buffer.bytes().buffer; -} - -export const BodyUsedError = - "Failed to execute 'clone' on 'Body': body is already used"; - -export class Body implements domTypes.Body { - protected _stream: ReadableStreamImpl<string | ArrayBuffer> | null; - #contentType: string; - #size: number | undefined; - constructor(protected _bodySource: BodyInit | null, meta: BodyMeta) { - validateBodyType(this, _bodySource); - this._bodySource = _bodySource; - this.#contentType = meta.contentType; - this.#size = meta.size; - this._stream = null; - } - - get body(): ReadableStream | null { - if (this._stream) { - return this._stream; - } - - if (this._bodySource instanceof ReadableStreamImpl) { - this._stream = this._bodySource; - } - if (typeof this._bodySource === "string") { - const bodySource = this._bodySource; - this._stream = new ReadableStreamImpl<string | ArrayBuffer>({ - start(controller: ReadableStreamDefaultController): void { - controller.enqueue(bodySource); - controller.close(); - }, - }); - } - return this._stream; - } - - get bodyUsed(): boolean { - if (this.body && isReadableStreamDisturbed(this.body)) { - return true; - } - return false; - } - - public async blob(): Promise<Blob> { - return new DenoBlob([await this.arrayBuffer()], { - type: this.#contentType, - }); - } - - // ref: https://fetch.spec.whatwg.org/#body-mixin - public async formData(): Promise<FormData> { - const formData = new FormData(); - if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) { - const params = getHeaderValueParams(this.#contentType); - - // ref: https://tools.ietf.org/html/rfc2046#section-5.1 - const boundary = params.get("boundary")!; - const body = new Uint8Array(await this.arrayBuffer()); - const multipartParser = new MultipartParser(body, boundary); - - return multipartParser.parse(); - } else if ( - hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded") - ) { - // From https://github.com/github/fetch/blob/master/fetch.js - // Copyright (c) 2014-2016 GitHub, Inc. MIT License - const body = await this.text(); - try { - body - .trim() - .split("&") - .forEach((bytes): void => { - if (bytes) { - const split = bytes.split("="); - const name = split.shift()!.replace(/\+/g, " "); - const value = split.join("=").replace(/\+/g, " "); - formData.append( - decodeURIComponent(name), - decodeURIComponent(value), - ); - } - }); - } catch (e) { - throw new TypeError("Invalid form urlencoded format"); - } - return formData; - } else { - throw new TypeError("Invalid form data"); - } - } - - public async text(): Promise<string> { - if (typeof this._bodySource === "string") { - return this._bodySource; - } - - const ab = await this.arrayBuffer(); - const decoder = new TextDecoder("utf-8"); - return decoder.decode(ab); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public async json(): Promise<any> { - const raw = await this.text(); - return JSON.parse(raw); - } - - public arrayBuffer(): Promise<ArrayBuffer> { - if (isTypedArray(this._bodySource)) { - return Promise.resolve(this._bodySource.buffer as ArrayBuffer); - } else if (this._bodySource instanceof ArrayBuffer) { - return Promise.resolve(this._bodySource); - } else if (typeof this._bodySource === "string") { - const enc = new TextEncoder(); - return Promise.resolve( - enc.encode(this._bodySource).buffer as ArrayBuffer, - ); - } else if (this._bodySource instanceof ReadableStreamImpl) { - return bufferFromStream(this._bodySource.getReader(), this.#size); - } else if ( - this._bodySource instanceof FormData || - this._bodySource instanceof URLSearchParams - ) { - const enc = new TextEncoder(); - return Promise.resolve( - enc.encode(this._bodySource.toString()).buffer as ArrayBuffer, - ); - } else if (!this._bodySource) { - return Promise.resolve(new ArrayBuffer(0)); - } - throw new Error( - `Body type not yet implemented: ${this._bodySource.constructor.name}`, - ); - } -} diff --git a/cli/js/web/console.ts b/cli/js/web/console.ts deleted file mode 100644 index 181cdb664..000000000 --- a/cli/js/web/console.ts +++ /dev/null @@ -1,1072 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { isInvalidDate, isTypedArray, TypedArray } from "./util.ts"; -import { cliTable } from "./console_table.ts"; -import { exposeForTest } from "../internals.ts"; -import { PromiseState } from "./promise.ts"; -import { - stripColor, - yellow, - dim, - cyan, - red, - green, - magenta, - bold, -} from "../colors.ts"; - -type ConsoleContext = Set<unknown>; - -export interface InspectOptions { - depth?: number; - indentLevel?: number; - sorted?: boolean; - trailingComma?: boolean; - compact?: boolean; - iterableLimit?: number; -} - -const DEFAULT_INSPECT_OPTIONS: Required<InspectOptions> = { - depth: 4, - indentLevel: 0, - sorted: false, - trailingComma: false, - compact: true, - iterableLimit: 100, -}; - -const DEFAULT_INDENT = " "; // Default indent string - -const LINE_BREAKING_LENGTH = 80; -const MIN_GROUP_LENGTH = 6; -const STR_ABBREVIATE_SIZE = 100; -// Char codes -const CHAR_PERCENT = 37; /* % */ -const CHAR_LOWERCASE_S = 115; /* s */ -const CHAR_LOWERCASE_D = 100; /* d */ -const CHAR_LOWERCASE_I = 105; /* i */ -const CHAR_LOWERCASE_F = 102; /* f */ -const CHAR_LOWERCASE_O = 111; /* o */ -const CHAR_UPPERCASE_O = 79; /* O */ -const CHAR_LOWERCASE_C = 99; /* c */ - -const PROMISE_STRING_BASE_LENGTH = 12; - -export class CSI { - static kClear = "\x1b[1;1H"; - static kClearScreenDown = "\x1b[0J"; -} - -/* eslint-disable @typescript-eslint/no-use-before-define */ - -function getClassInstanceName(instance: unknown): string { - if (typeof instance !== "object") { - return ""; - } - if (!instance) { - return ""; - } - - const proto = Object.getPrototypeOf(instance); - if (proto && proto.constructor) { - return proto.constructor.name; // could be "Object" or "Array" - } - - return ""; -} - -function inspectFunction(value: Function, _ctx: ConsoleContext): string { - // Might be Function/AsyncFunction/GeneratorFunction - const cstrName = Object.getPrototypeOf(value).constructor.name; - if (value.name && value.name !== "anonymous") { - // from MDN spec - return `[${cstrName}: ${value.name}]`; - } - return `[${cstrName}]`; -} - -interface InspectIterableOptions<T> { - typeName: string; - displayName: string; - delims: [string, string]; - entryHandler: ( - entry: [unknown, T], - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, - next: () => IteratorResult<[unknown, T], unknown>, - ) => string; - group: boolean; - sort: boolean; -} -type IterableEntries<T> = Iterable<T> & { - entries(): IterableIterator<[unknown, T]>; -}; -function inspectIterable<T>( - value: IterableEntries<T>, - ctx: ConsoleContext, - level: number, - options: InspectIterableOptions<T>, - inspectOptions: Required<InspectOptions>, -): string { - if (level >= inspectOptions.depth) { - return cyan(`[${options.typeName}]`); - } - ctx.add(value); - - const entries: string[] = []; - - const iter = value.entries(); - let entriesLength = 0; - const next = (): IteratorResult<[unknown, T], unknown> => { - return iter.next(); - }; - for (const el of iter) { - if (entriesLength < inspectOptions.iterableLimit) { - entries.push( - options.entryHandler( - el, - ctx, - level + 1, - inspectOptions, - next.bind(iter), - ), - ); - } - entriesLength++; - } - ctx.delete(value); - - if (options.sort) { - entries.sort(); - } - - if (entriesLength > inspectOptions.iterableLimit) { - const nmore = entriesLength - inspectOptions.iterableLimit; - entries.push(`... ${nmore} more items`); - } - - const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; - - const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`; - const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`; - const closingIndentation = `${inspectOptions.trailingComma ? "," : ""}\n${ - DEFAULT_INDENT.repeat(level) - }`; - - let iContent: string; - if (options.group && entries.length > MIN_GROUP_LENGTH) { - const groups = groupEntries(entries, level, value); - iContent = `${initIndentation}${ - groups.join(entryIndentation) - }${closingIndentation}`; - } else { - iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `; - if ( - stripColor(iContent).length > LINE_BREAKING_LENGTH || - !inspectOptions.compact - ) { - iContent = `${initIndentation}${ - entries.join(entryIndentation) - }${closingIndentation}`; - } - } - - return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`; -} - -// Ported from Node.js -// Copyright Node.js contributors. All rights reserved. -function groupEntries<T>( - entries: string[], - level: number, - value: Iterable<T>, - iterableLimit = 100, -): string[] { - 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 = 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 = Math.sqrt(actualMax - totalLength / entries.length); - const biasedMax = Math.max(actualMax - 3 - averageBias, 1); - // Dynamically check how many columns seem possible. - const columns = Math.min( - // 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. - Math.round( - Math.sqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax, - ), - // Do not exceed the breakLength. - Math.floor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax), - // Limit the columns to a maximum of fifteen. - 15, - ); - // Return with the original output if no grouping should happen. - if (columns <= 1) { - return entries; - } - const tmp = []; - const maxLineLength = []; - for (let i = 0; i < columns; i++) { - let lineMaxLength = 0; - for (let j = i; j < entries.length; j += columns) { - if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j]; - } - lineMaxLength += separatorSpace; - maxLineLength[i] = lineMaxLength; - } - let order: "padStart" | "padEnd" = "padStart"; - if (value !== undefined) { - for (let i = 0; i < entries.length; i++) { - /* eslint-disable @typescript-eslint/no-explicit-any */ - if ( - typeof (value as any)[i] !== "number" && - typeof (value as any)[i] !== "bigint" - ) { - order = "padEnd"; - break; - } - /* eslint-enable */ - } - } - // 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 = Math.min(i + columns, entriesLength); - let str = ""; - let j = i; - for (; j < max - 1; j++) { - // In future, colors should be taken here into the account - const padding = maxLineLength[j - i]; - str += `${entries[j]}, `[order](padding, " "); - } - if (order === "padStart") { - const padding = maxLineLength[j - i] + - entries[j].length - - dataLen[j] - - separatorSpace; - str += entries[j].padStart(padding, " "); - } else { - str += entries[j]; - } - tmp.push(str); - } - if (iterableLimit < entries.length) { - tmp.push(entries[entriesLength]); - } - entries = tmp; - } - return entries; -} - -function inspectValue( - value: unknown, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - switch (typeof value) { - case "string": - return value; - case "number": // Numbers are yellow - // Special handling of -0 - return yellow(Object.is(value, -0) ? "-0" : `${value}`); - case "boolean": // booleans are yellow - return yellow(String(value)); - case "undefined": // undefined is dim - return dim(String(value)); - case "symbol": // Symbols are green - return green(String(value)); - case "bigint": // Bigints are yellow - return yellow(`${value}n`); - case "function": // Function string is cyan - return cyan(inspectFunction(value as Function, ctx)); - case "object": // null is bold - if (value === null) { - return bold("null"); - } - - if (ctx.has(value)) { - // Circular string is cyan - return cyan("[Circular]"); - } - - return inspectObject(value, ctx, level, inspectOptions); - default: - // Not implemented is red - return red("[Not Implemented]"); - } -} - -// 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: string): string { - const quote = QUOTES.find((c) => !string.includes(c)) ?? QUOTES[0]; - const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); - return `${quote}${string.replace(escapePattern, "\\")}${quote}`; -} - -// Print strings when they are inside of arrays or objects with quotes -function inspectValueWithQuotes( - value: unknown, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - switch (typeof value) { - case "string": - const trunc = value.length > STR_ABBREVIATE_SIZE - ? value.slice(0, STR_ABBREVIATE_SIZE) + "..." - : value; - return green(quoteString(trunc)); // Quoted strings are green - default: - return inspectValue(value, ctx, level, inspectOptions); - } -} - -function inspectArray( - value: unknown[], - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - const options: InspectIterableOptions<unknown> = { - typeName: "Array", - displayName: "", - delims: ["[", "]"], - entryHandler: (entry, ctx, level, inspectOptions, next): string => { - const [index, val] = entry as [number, unknown]; - let i = index; - if (!value.hasOwnProperty(i)) { - i++; - while (!value.hasOwnProperty(i) && i < value.length) { - next(); - i++; - } - const emptyItems = i - index; - const ending = emptyItems > 1 ? "s" : ""; - return dim(`<${emptyItems} empty item${ending}>`); - } else { - return inspectValueWithQuotes(val, ctx, level, inspectOptions); - } - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, ctx, level, options, inspectOptions); -} - -function inspectTypedArray( - typedArrayName: string, - value: TypedArray, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - const valueLength = value.length; - const options: InspectIterableOptions<unknown> = { - typeName: typedArrayName, - displayName: `${typedArrayName}(${valueLength})`, - delims: ["[", "]"], - entryHandler: (entry, ctx, level, inspectOptions): string => { - const val = entry[1]; - return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions); - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, ctx, level, options, inspectOptions); -} - -function inspectSet( - value: Set<unknown>, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - const options: InspectIterableOptions<unknown> = { - typeName: "Set", - displayName: "Set", - delims: ["{", "}"], - entryHandler: (entry, ctx, level, inspectOptions): string => { - const val = entry[1]; - return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions); - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable(value, ctx, level, options, inspectOptions); -} - -function inspectMap( - value: Map<unknown, unknown>, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - const options: InspectIterableOptions<[unknown]> = { - typeName: "Map", - displayName: "Map", - delims: ["{", "}"], - entryHandler: (entry, ctx, level, inspectOptions): string => { - const [key, val] = entry; - return `${ - inspectValueWithQuotes( - key, - ctx, - level + 1, - inspectOptions, - ) - } => ${inspectValueWithQuotes(val, ctx, level + 1, inspectOptions)}`; - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value as any, - ctx, - level, - options, - inspectOptions, - ); -} - -function inspectWeakSet(): string { - return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color -} - -function inspectWeakMap(): string { - return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color -} - -function inspectDate(value: Date): string { - // without quotes, ISO format, in magenta like before - return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString()); -} - -function inspectRegExp(value: RegExp): string { - return red(value.toString()); // RegExps are red -} - -/* eslint-disable @typescript-eslint/ban-types */ - -function inspectStringObject(value: String): string { - return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan -} - -function inspectBooleanObject(value: Boolean): string { - return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan -} - -function inspectNumberObject(value: Number): string { - return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan -} - -/* eslint-enable @typescript-eslint/ban-types */ - -function inspectPromise( - value: Promise<unknown>, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - const [state, result] = Deno.core.getPromiseDetails(value); - - if (state === PromiseState.Pending) { - return `Promise { ${cyan("<pending>")} }`; - } - - const prefix = state === PromiseState.Fulfilled - ? "" - : `${red("<rejected>")} `; - - const str = `${prefix}${ - inspectValueWithQuotes( - result, - ctx, - level + 1, - inspectOptions, - ) - }`; - - if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { - return `Promise {\n${DEFAULT_INDENT.repeat(level + 1)}${str}\n}`; - } - - return `Promise { ${str} }`; -} - -// TODO: Proxy - -function inspectRawObject( - value: Record<string, unknown>, - ctx: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - if (level >= inspectOptions.depth) { - return cyan("[Object]"); // wrappers are in cyan - } - ctx.add(value); - - let baseString: string; - - let shouldShowDisplayName = false; - let displayName = (value as { [Symbol.toStringTag]: string })[ - Symbol.toStringTag - ]; - if (!displayName) { - displayName = getClassInstanceName(value); - } - if (displayName && displayName !== "Object" && displayName !== "anonymous") { - shouldShowDisplayName = true; - } - - const entries: string[] = []; - const stringKeys = Object.keys(value); - const symbolKeys = Object.getOwnPropertySymbols(value); - if (inspectOptions.sorted) { - stringKeys.sort(); - symbolKeys.sort((s1, s2) => - (s1.description ?? "").localeCompare(s2.description ?? "") - ); - } - - for (const key of stringKeys) { - entries.push( - `${key}: ${ - inspectValueWithQuotes( - value[key], - ctx, - level + 1, - inspectOptions, - ) - }`, - ); - } - for (const key of symbolKeys) { - entries.push( - `${key.toString()}: ${ - inspectValueWithQuotes( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value[key as any], - ctx, - level + 1, - inspectOptions, - ) - }`, - ); - } - // Making sure color codes are ignored when calculating the total length - const totalLength = entries.length + level + - stripColor(entries.join("")).length; - - ctx.delete(value); - - if (entries.length === 0) { - baseString = "{}"; - } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) { - const entryIndent = DEFAULT_INDENT.repeat(level + 1); - const closingIndent = DEFAULT_INDENT.repeat(level); - baseString = `{\n${entryIndent}${entries.join(`,\n${entryIndent}`)}${ - inspectOptions.trailingComma ? "," : "" - }\n${closingIndent}}`; - } else { - baseString = `{ ${entries.join(", ")} }`; - } - - if (shouldShowDisplayName) { - baseString = `${displayName} ${baseString}`; - } - - return baseString; -} - -function inspectObject( - value: {}, - consoleContext: ConsoleContext, - level: number, - inspectOptions: Required<InspectOptions>, -): string { - if (customInspect in value && typeof value[customInspect] === "function") { - try { - return String(value[customInspect]!()); - } catch {} - } - if (value instanceof Error) { - return String(value.stack); - } else if (Array.isArray(value)) { - return inspectArray(value, consoleContext, level, inspectOptions); - } else if (value instanceof Number) { - return inspectNumberObject(value); - } else if (value instanceof Boolean) { - return inspectBooleanObject(value); - } else if (value instanceof String) { - return inspectStringObject(value); - } else if (value instanceof Promise) { - return inspectPromise(value, consoleContext, level, inspectOptions); - } else if (value instanceof RegExp) { - return inspectRegExp(value); - } else if (value instanceof Date) { - return inspectDate(value); - } else if (value instanceof Set) { - return inspectSet(value, consoleContext, level, inspectOptions); - } else if (value instanceof Map) { - return inspectMap(value, consoleContext, level, inspectOptions); - } else if (value instanceof WeakSet) { - return inspectWeakSet(); - } else if (value instanceof WeakMap) { - return inspectWeakMap(); - } else if (isTypedArray(value)) { - return inspectTypedArray( - Object.getPrototypeOf(value).constructor.name, - value, - consoleContext, - level, - inspectOptions, - ); - } else { - // Otherwise, default object formatting - return inspectRawObject(value, consoleContext, level, inspectOptions); - } -} - -export function inspectArgs( - args: unknown[], - inspectOptions: InspectOptions = {}, -): string { - const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; - const first = args[0]; - let a = 0; - let str = ""; - let join = ""; - - if (typeof first === "string") { - let tempStr: string; - let lastPos = 0; - - for (let i = 0; i < first.length - 1; i++) { - if (first.charCodeAt(i) === CHAR_PERCENT) { - const nextChar = first.charCodeAt(++i); - if (a + 1 !== args.length) { - switch (nextChar) { - case CHAR_LOWERCASE_S: - // format as a string - tempStr = String(args[++a]); - break; - case CHAR_LOWERCASE_D: - case CHAR_LOWERCASE_I: - // format as an integer - const tempInteger = args[++a]; - if (typeof tempInteger === "bigint") { - tempStr = `${tempInteger}n`; - } else if (typeof tempInteger === "symbol") { - tempStr = "NaN"; - } else { - tempStr = `${parseInt(String(tempInteger), 10)}`; - } - break; - case CHAR_LOWERCASE_F: - // format as a floating point value - const tempFloat = args[++a]; - if (typeof tempFloat === "symbol") { - tempStr = "NaN"; - } else { - tempStr = `${parseFloat(String(tempFloat))}`; - } - break; - case CHAR_LOWERCASE_O: - case CHAR_UPPERCASE_O: - // format as an object - tempStr = inspectValue( - args[++a], - new Set<unknown>(), - 0, - rInspectOptions, - ); - break; - case CHAR_PERCENT: - str += first.slice(lastPos, i); - lastPos = i + 1; - continue; - case CHAR_LOWERCASE_C: - // TODO: applies CSS style rules to the output string as specified - continue; - default: - // any other character is not a correct placeholder - continue; - } - - if (lastPos !== i - 1) { - str += first.slice(lastPos, i - 1); - } - - str += tempStr; - lastPos = i + 1; - } else if (nextChar === CHAR_PERCENT) { - str += first.slice(lastPos, i); - lastPos = i + 1; - } - } - } - - if (lastPos !== 0) { - a++; - join = " "; - if (lastPos < first.length) { - str += first.slice(lastPos); - } - } - } - - while (a < args.length) { - const value = args[a]; - str += join; - if (typeof value === "string") { - str += value; - } else { - // use default maximum depth for null or undefined argument - str += inspectValue(value, new Set<unknown>(), 0, rInspectOptions); - } - join = " "; - a++; - } - - if (rInspectOptions.indentLevel > 0) { - const groupIndent = DEFAULT_INDENT.repeat(rInspectOptions.indentLevel); - if (str.indexOf("\n") !== -1) { - str = str.replace(/\n/g, `\n${groupIndent}`); - } - str = groupIndent + str; - } - - return str; -} - -type PrintFunc = (x: string, isErr?: boolean) => void; - -const countMap = new Map<string, number>(); -const timerMap = new Map<string, number>(); -const isConsoleInstance = Symbol("isConsoleInstance"); - -export class Console { - readonly #printFunc: PrintFunc; - indentLevel: number; - [isConsoleInstance] = false; - - constructor(printFunc: 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 = Object.create({}) as Console; - Object.assign(console, this); - return console; - } - - log = (...args: unknown[]): void => { - this.#printFunc( - inspectArgs(args, { - indentLevel: this.indentLevel, - }) + "\n", - false, - ); - }; - - debug = this.log; - info = this.log; - - dir = (obj: unknown, options: InspectOptions = {}): void => { - this.#printFunc(inspectArgs([obj], options) + "\n", false); - }; - - dirxml = this.dir; - - warn = (...args: unknown[]): void => { - this.#printFunc( - inspectArgs(args, { - indentLevel: this.indentLevel, - }) + "\n", - true, - ); - }; - - error = this.warn; - - assert = (condition = false, ...args: unknown[]): void => { - if (condition) { - return; - } - - if (args.length === 0) { - this.error("Assertion failed"); - return; - } - - const [first, ...rest] = args; - - if (typeof first === "string") { - this.error(`Assertion failed: ${first}`, ...rest); - return; - } - - this.error(`Assertion failed:`, ...args); - }; - - count = (label = "default"): void => { - label = String(label); - - if (countMap.has(label)) { - const current = countMap.get(label) || 0; - countMap.set(label, current + 1); - } else { - countMap.set(label, 1); - } - - this.info(`${label}: ${countMap.get(label)}`); - }; - - countReset = (label = "default"): void => { - label = String(label); - - if (countMap.has(label)) { - countMap.set(label, 0); - } else { - this.warn(`Count for '${label}' does not exist`); - } - }; - - table = (data: unknown, properties?: string[]): void => { - if (properties !== undefined && !Array.isArray(properties)) { - throw new Error( - "The 'properties' argument must be of type Array. " + - "Received type string", - ); - } - - if (data === null || typeof data !== "object") { - return this.log(data); - } - - const objectValues: { [key: string]: string[] } = {}; - const indexKeys: string[] = []; - const values: string[] = []; - - const stringifyValue = (value: unknown): string => - inspectValueWithQuotes(value, new Set<unknown>(), 0, { - ...DEFAULT_INSPECT_OPTIONS, - depth: 1, - }); - const toTable = (header: string[], body: string[][]): void => - this.log(cliTable(header, body)); - const createColumn = (value: unknown, shift?: number): string[] => [ - ...(shift ? [...new Array(shift)].map((): string => "") : []), - stringifyValue(value), - ]; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let resultData: any; - const isSet = data instanceof Set; - const isMap = data instanceof Map; - const valuesKey = "Values"; - const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; - - if (data instanceof Set) { - resultData = [...data]; - } else if (data instanceof Map) { - let idx = 0; - resultData = {}; - - data.forEach((v: unknown, k: unknown): void => { - resultData[idx] = { Key: k, Values: v }; - idx++; - }); - } else { - resultData = data!; - } - - let hasPrimitives = false; - Object.keys(resultData).forEach((k, idx): void => { - const value: unknown = resultData[k]!; - const primitive = value === null || - (typeof value !== "function" && typeof value !== "object"); - if (properties === undefined && primitive) { - hasPrimitives = true; - values.push(stringifyValue(value)); - } else { - const valueObj = (value as { [key: string]: unknown }) || {}; - const keys = properties || Object.keys(valueObj); - for (const k of keys) { - if (primitive || !valueObj.hasOwnProperty(k)) { - if (objectValues[k]) { - // fill with blanks for idx to avoid misplacing from later values - objectValues[k].push(""); - } - } else { - if (objectValues[k]) { - objectValues[k].push(stringifyValue(valueObj[k])); - } else { - objectValues[k] = createColumn(valueObj[k], idx); - } - } - } - values.push(""); - } - - indexKeys.push(k); - }); - - const headerKeys = Object.keys(objectValues); - const bodyValues = Object.values(objectValues); - const header = [ - indexKey, - ...(properties || [...headerKeys, !isMap && hasPrimitives && valuesKey]), - ].filter(Boolean) as string[]; - const body = [indexKeys, ...bodyValues, values]; - - toTable(header, body); - }; - - time = (label = "default"): void => { - label = String(label); - - if (timerMap.has(label)) { - this.warn(`Timer '${label}' already exists`); - return; - } - - timerMap.set(label, Date.now()); - }; - - timeLog = (label = "default", ...args: unknown[]): void => { - label = String(label); - - if (!timerMap.has(label)) { - this.warn(`Timer '${label}' does not exists`); - return; - } - - const startTime = timerMap.get(label) as number; - const duration = Date.now() - startTime; - - this.info(`${label}: ${duration}ms`, ...args); - }; - - timeEnd = (label = "default"): void => { - label = String(label); - - if (!timerMap.has(label)) { - this.warn(`Timer '${label}' does not exists`); - return; - } - - const startTime = timerMap.get(label) as number; - timerMap.delete(label); - const duration = Date.now() - startTime; - - this.info(`${label}: ${duration}ms`); - }; - - group = (...label: unknown[]): void => { - if (label.length > 0) { - this.log(...label); - } - this.indentLevel += 2; - }; - - groupCollapsed = this.group; - - groupEnd = (): void => { - if (this.indentLevel > 0) { - this.indentLevel -= 2; - } - }; - - clear = (): void => { - this.indentLevel = 0; - this.#printFunc(CSI.kClear, false); - this.#printFunc(CSI.kClearScreenDown, false); - }; - - trace = (...args: unknown[]): void => { - const message = inspectArgs(args, { indentLevel: 0 }); - const err = { - name: "Trace", - message, - }; - Error.captureStackTrace(err, this.trace); - this.error((err as Error).stack); - }; - - static [Symbol.hasInstance](instance: Console): boolean { - return instance[isConsoleInstance]; - } -} - -export const customInspect = Symbol("Deno.customInspect"); - -export function inspect( - value: unknown, - inspectOptions: InspectOptions = {}, -): string { - if (typeof value === "string") { - return value; - } else { - return inspectValue(value, new Set<unknown>(), 0, { - ...DEFAULT_INSPECT_OPTIONS, - ...inspectOptions, - // TODO(nayeemrmn): Indent level is not supported. - indentLevel: 0, - }); - } -} - -// Expose these fields to internalObject for tests. -exposeForTest("Console", Console); -exposeForTest("inspectArgs", inspectArgs); diff --git a/cli/js/web/console_table.ts b/cli/js/web/console_table.ts deleted file mode 100644 index 42667d998..000000000 --- a/cli/js/web/console_table.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// Copyright Joyent, Inc. and other Node contributors. MIT license. -// Forked from Node's lib/internal/cli_table.js - -import { hasOwnProperty } from "./util.ts"; -import { stripColor } from "../colors.ts"; - -const tableChars = { - middleMiddle: "─", - rowMiddle: "┼", - topRight: "┐", - topLeft: "┌", - leftMiddle: "├", - topMiddle: "┬", - bottomRight: "┘", - bottomLeft: "└", - bottomMiddle: "┴", - rightMiddle: "┤", - left: "│ ", - right: " │", - middle: " │ ", -}; - -function isFullWidthCodePoint(code: number): boolean { - // 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: string): number { - str = stripColor(str).normalize("NFC"); - let width = 0; - - for (const ch of str) { - width += isFullWidthCodePoint(ch.codePointAt(0)!) ? 2 : 1; - } - - return width; -} - -function renderRow(row: string[], columnWidths: number[]): string { - let out = tableChars.left; - for (let i = 0; i < row.length; i++) { - const cell = row[i]; - const len = getStringWidth(cell); - const needed = (columnWidths[i] - len) / 2; - // round(needed) + ceil(needed) will always add up to the amount - // of spaces we need while also left justifying the output. - out += `${" ".repeat(needed)}${cell}${" ".repeat(Math.ceil(needed))}`; - if (i !== row.length - 1) { - out += tableChars.middle; - } - } - out += tableChars.right; - return out; -} - -export function cliTable(head: string[], columns: string[][]): string { - const rows: string[][] = []; - const columnWidths = head.map((h: string): number => getStringWidth(h)); - const longestColumn = columns.reduce( - (n: number, a: string[]): number => Math.max(n, a.length), - 0, - ); - - 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] = Math.max(width, counted); - } - } - - const divider = columnWidths.map((i: number): string => - tableChars.middleMiddle.repeat(i + 2) - ); - - let result = `${tableChars.topLeft}${divider.join(tableChars.topMiddle)}` + - `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + - `${tableChars.leftMiddle}${divider.join(tableChars.rowMiddle)}` + - `${tableChars.rightMiddle}\n`; - - for (const row of rows) { - result += `${renderRow(row, columnWidths)}\n`; - } - - result += `${tableChars.bottomLeft}${divider.join(tableChars.bottomMiddle)}` + - tableChars.bottomRight; - - return result; -} diff --git a/cli/js/web/custom_event.ts b/cli/js/web/custom_event.ts deleted file mode 100644 index dad89f650..000000000 --- a/cli/js/web/custom_event.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { EventImpl as Event } from "./event.ts"; -import { requiredArguments } from "./util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class CustomEventImpl<T = any> extends Event implements CustomEvent { - readonly #detail: T; - - constructor(type: string, eventInitDict: CustomEventInit<T> = {}) { - super(type, eventInitDict); - requiredArguments("CustomEvent", arguments.length, 1); - const { detail } = eventInitDict; - this.#detail = detail as T; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get detail(): T { - return this.#detail; - } - - get [Symbol.toStringTag](): string { - return "CustomEvent"; - } -} - -Reflect.defineProperty(CustomEventImpl.prototype, "detail", { - enumerable: true, -}); diff --git a/cli/js/web/decode_utf8.ts b/cli/js/web/decode_utf8.ts deleted file mode 100644 index ca190134c..000000000 --- a/cli/js/web/decode_utf8.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// This module is based on Bjoern Hoehrmann's DFA UTF-8 decoder. -// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -// -// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// `.apply` can actually take a typed array, though the type system doesn't -// really support it, so we have to "hack" it a bit to get past some of the -// strict type checks. -declare global { - interface CallableFunction extends Function { - apply<T, R>( - this: (this: T, ...args: number[]) => R, - thisArg: T, - args: Uint16Array, - ): R; - } -} - -export function decodeUtf8( - input: Uint8Array, - fatal: boolean, - ignoreBOM: boolean, -): string { - let outString = ""; - - // Prepare a buffer so that we don't have to do a lot of string concats, which - // are very slow. - const outBufferLength: number = Math.min(1024, input.length); - const outBuffer = new Uint16Array(outBufferLength); - let outIndex = 0; - - let state = 0; - let codepoint = 0; - let type: number; - - let i = - ignoreBOM && input[0] === 0xef && input[1] === 0xbb && input[2] === 0xbf - ? 3 - : 0; - - for (; i < input.length; ++i) { - // Encoding error handling - if (state === 12 || (state !== 0 && (input[i] & 0xc0) !== 0x80)) { - if (fatal) { - throw new TypeError( - `Decoder error. Invalid byte in sequence at position ${i} in data.`, - ); - } - outBuffer[outIndex++] = 0xfffd; // Replacement character - if (outIndex === outBufferLength) { - outString += String.fromCharCode.apply(null, outBuffer); - outIndex = 0; - } - state = 0; - } - - // deno-fmt-ignore - type = [ - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8 - ][input[i]]; - codepoint = state !== 0 - ? (input[i] & 0x3f) | (codepoint << 6) - : (0xff >> type) & input[i]; - // deno-fmt-ignore - state = [ - 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, - 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, - 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, - 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, - 12,36,12,12,12,12,12,12,12,12,12,12 - ][state + type]; - - if (state !== 0) continue; - - // Add codepoint to buffer (as charcodes for utf-16), and flush buffer to - // string if needed. - if (codepoint > 0xffff) { - outBuffer[outIndex++] = 0xd7c0 + (codepoint >> 10); - if (outIndex === outBufferLength) { - outString += String.fromCharCode.apply(null, outBuffer); - outIndex = 0; - } - outBuffer[outIndex++] = 0xdc00 | (codepoint & 0x3ff); - if (outIndex === outBufferLength) { - outString += String.fromCharCode.apply(null, outBuffer); - outIndex = 0; - } - } else { - outBuffer[outIndex++] = codepoint; - if (outIndex === outBufferLength) { - outString += String.fromCharCode.apply(null, outBuffer); - outIndex = 0; - } - } - } - - // Add a replacement character if we ended in the middle of a sequence or - // encountered an invalid code at the end. - if (state !== 0) { - if (fatal) throw new TypeError(`Decoder error. Unexpected end of data.`); - outBuffer[outIndex++] = 0xfffd; // Replacement character - } - - // Final flush of buffer - outString += String.fromCharCode.apply(null, outBuffer.subarray(0, outIndex)); - - return outString; -} diff --git a/cli/js/web/dom_exception.ts b/cli/js/web/dom_exception.ts deleted file mode 100644 index 5e7d5ee6f..000000000 --- a/cli/js/web/dom_exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -export class DOMExceptionImpl extends Error implements DOMException { - readonly #name: string; - - constructor(message = "", name = "Error") { - super(message); - this.#name = name; - } - - get name(): string { - return this.#name; - } -} diff --git a/cli/js/web/dom_file.ts b/cli/js/web/dom_file.ts deleted file mode 100644 index 907337e59..000000000 --- a/cli/js/web/dom_file.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import * as blob from "./blob.ts"; - -export class DomFileImpl extends blob.DenoBlob implements File { - lastModified: number; - name: string; - - constructor( - fileBits: BlobPart[], - fileName: string, - options?: FilePropertyBag, - ) { - const { lastModified = Date.now(), ...blobPropertyBag } = options ?? {}; - super(fileBits, blobPropertyBag); - - // 4.1.2.1 Replace any "/" character (U+002F SOLIDUS) - // with a ":" (U + 003A COLON) - this.name = String(fileName).replace(/\u002F/g, "\u003A"); - // 4.1.3.3 If lastModified is not provided, set lastModified to the current - // date and time represented in number of milliseconds since the Unix Epoch. - this.lastModified = lastModified; - } -} diff --git a/cli/js/web/dom_iterable.ts b/cli/js/web/dom_iterable.ts deleted file mode 100644 index 7e26a12a4..000000000 --- a/cli/js/web/dom_iterable.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { requiredArguments } from "./util.ts"; -import { exposeForTest } from "../internals.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Constructor<T = {}> = new (...args: any[]) => T; - -export interface DomIterable<K, V> { - keys(): IterableIterator<K>; - values(): IterableIterator<V>; - entries(): IterableIterator<[K, V]>; - [Symbol.iterator](): IterableIterator<[K, V]>; - forEach( - callback: (value: V, key: K, parent: this) => void, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - thisArg?: any, - ): void; -} - -export function DomIterableMixin<K, V, TBase extends Constructor>( - Base: TBase, - dataSymbol: symbol, -): TBase & Constructor<DomIterable<K, V>> { - // we have to cast `this` as `any` because there is no way to describe the - // Base class in a way where the Symbol `dataSymbol` is defined. So the - // runtime code works, but we do lose a little bit of type safety. - - // Additionally, we have to not use .keys() nor .values() since the internal - // slot differs in type - some have a Map, which yields [K, V] in - // Symbol.iterator, and some have an Array, which yields V, in this case - // [K, V] too as they are arrays of tuples. - - const DomIterable = class extends Base { - *entries(): IterableIterator<[K, V]> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for (const entry of (this as any)[dataSymbol]) { - yield entry; - } - } - - *keys(): IterableIterator<K> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for (const [key] of (this as any)[dataSymbol]) { - yield key; - } - } - - *values(): IterableIterator<V> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for (const [, value] of (this as any)[dataSymbol]) { - yield value; - } - } - - forEach( - callbackfn: (value: V, key: K, parent: this) => void, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - thisArg?: any, - ): void { - requiredArguments( - `${this.constructor.name}.forEach`, - arguments.length, - 1, - ); - callbackfn = callbackfn.bind( - thisArg == null ? globalThis : Object(thisArg), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for (const [key, value] of (this as any)[dataSymbol]) { - callbackfn(value, key, this); - } - } - - *[Symbol.iterator](): IterableIterator<[K, V]> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for (const entry of (this as any)[dataSymbol]) { - yield entry; - } - } - }; - - // we want the Base class name to be the name of the class. - Object.defineProperty(DomIterable, "name", { - value: Base.name, - configurable: true, - }); - - return DomIterable; -} - -exposeForTest("DomIterableMixin", DomIterableMixin); diff --git a/cli/js/web/dom_types.d.ts b/cli/js/web/dom_types.d.ts deleted file mode 100644 index b8636b7d1..000000000 --- a/cli/js/web/dom_types.d.ts +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -/*! **************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -*******************************************************************************/ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export type RequestInfo = Request | string; - -export interface ProgressEventInit extends EventInit { - lengthComputable?: boolean; - loaded?: number; - total?: number; -} - -export interface UIEventInit extends EventInit { - detail?: number; - // adjust Window -> Node - view?: Node | null; -} - -export class UIEvent extends Event { - constructor(type: string, eventInitDict?: UIEventInit); - readonly detail: number; - // adjust Window -> Node - readonly view: Node | null; -} - -export interface FocusEventInit extends UIEventInit { - relatedTarget?: EventTarget | null; -} - -export class FocusEvent extends UIEvent { - constructor(type: string, eventInitDict?: FocusEventInit); - readonly relatedTarget: EventTarget | null; -} - -export interface EventModifierInit extends UIEventInit { - altKey?: boolean; - ctrlKey?: boolean; - metaKey?: boolean; - modifierAltGraph?: boolean; - modifierCapsLock?: boolean; - modifierFn?: boolean; - modifierFnLock?: boolean; - modifierHyper?: boolean; - modifierNumLock?: boolean; - modifierScrollLock?: boolean; - modifierSuper?: boolean; - modifierSymbol?: boolean; - modifierSymbolLock?: boolean; - shiftKey?: boolean; -} - -export interface MouseEventInit extends EventModifierInit { - button?: number; - buttons?: number; - clientX?: number; - clientY?: number; - movementX?: number; - movementY?: number; - relatedTarget?: EventTarget | null; - screenX?: number; - screenY?: number; -} - -export class MouseEvent extends UIEvent { - constructor(type: string, eventInitDict?: MouseEventInit); - readonly altKey: boolean; - readonly button: number; - readonly buttons: number; - readonly clientX: number; - readonly clientY: number; - readonly ctrlKey: boolean; - readonly metaKey: boolean; - readonly movementX: number; - readonly movementY: number; - readonly offsetX: number; - readonly offsetY: number; - readonly pageX: number; - readonly pageY: number; - readonly relatedTarget: EventTarget | null; - readonly screenX: number; - readonly screenY: number; - readonly shiftKey: boolean; - readonly x: number; - readonly y: number; - getModifierState(keyArg: string): boolean; -} - -interface GetRootNodeOptions { - composed?: boolean; -} - -export class Node extends EventTarget { - readonly baseURI: string; - readonly childNodes: NodeListOf<ChildNode>; - readonly firstChild: ChildNode | null; - readonly isConnected: boolean; - readonly lastChild: ChildNode | null; - readonly nextSibling: ChildNode | null; - readonly nodeName: string; - readonly nodeType: number; - nodeValue: string | null; - // adjusted: Document -> Node - readonly ownerDocument: Node | null; - // adjusted: HTMLElement -> Node - readonly parentElement: Node | null; - readonly parentNode: (Node & ParentNode) | null; - readonly previousSibling: ChildNode | null; - textContent: string | null; - appendChild<T extends Node>(newChild: T): T; - cloneNode(deep?: boolean): Node; - compareDocumentPosition(other: Node): number; - contains(other: Node | null): boolean; - getRootNode(options?: GetRootNodeOptions): Node; - hasChildNodes(): boolean; - insertBefore<T extends Node>(newChild: T, refChild: Node | null): T; - isDefaultNamespace(namespace: string | null): boolean; - isEqualNode(otherNode: Node | null): boolean; - isSameNode(otherNode: Node | null): boolean; - lookupNamespaceURI(prefix: string | null): string | null; - lookupPrefix(namespace: string | null): string | null; - normalize(): void; - removeChild<T extends Node>(oldChild: T): T; - replaceChild<T extends Node>(newChild: Node, oldChild: T): T; - readonly ATTRIBUTE_NODE: number; - readonly CDATA_SECTION_NODE: number; - readonly COMMENT_NODE: number; - readonly DOCUMENT_FRAGMENT_NODE: number; - readonly DOCUMENT_NODE: number; - readonly DOCUMENT_POSITION_CONTAINED_BY: number; - readonly DOCUMENT_POSITION_CONTAINS: number; - readonly DOCUMENT_POSITION_DISCONNECTED: number; - readonly DOCUMENT_POSITION_FOLLOWING: number; - readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; - readonly DOCUMENT_POSITION_PRECEDING: number; - readonly DOCUMENT_TYPE_NODE: number; - readonly ELEMENT_NODE: number; - readonly ENTITY_NODE: number; - readonly ENTITY_REFERENCE_NODE: number; - readonly NOTATION_NODE: number; - readonly PROCESSING_INSTRUCTION_NODE: number; - readonly TEXT_NODE: number; - static readonly ATTRIBUTE_NODE: number; - static readonly CDATA_SECTION_NODE: number; - static readonly COMMENT_NODE: number; - static readonly DOCUMENT_FRAGMENT_NODE: number; - static readonly DOCUMENT_NODE: number; - static readonly DOCUMENT_POSITION_CONTAINED_BY: number; - static readonly DOCUMENT_POSITION_CONTAINS: number; - static readonly DOCUMENT_POSITION_DISCONNECTED: number; - static readonly DOCUMENT_POSITION_FOLLOWING: number; - static readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; - static readonly DOCUMENT_POSITION_PRECEDING: number; - static readonly DOCUMENT_TYPE_NODE: number; - static readonly ELEMENT_NODE: number; - static readonly ENTITY_NODE: number; - static readonly ENTITY_REFERENCE_NODE: number; - static readonly NOTATION_NODE: number; - static readonly PROCESSING_INSTRUCTION_NODE: number; - static readonly TEXT_NODE: number; -} - -interface Slotable { - // adjusted: HTMLSlotElement -> Node - readonly assignedSlot: Node | null; -} - -interface ChildNode extends Node { - after(...nodes: Array<Node | string>): void; - before(...nodes: Array<Node | string>): void; - remove(): void; - replaceWith(...nodes: Array<Node | string>): void; -} - -interface ParentNode { - readonly childElementCount: number; - // not currently supported - // readonly children: HTMLCollection; - // adjusted: Element -> Node - readonly firstElementChild: Node | null; - // adjusted: Element -> Node - readonly lastElementChild: Node | null; - append(...nodes: Array<Node | string>): void; - prepend(...nodes: Array<Node | string>): void; - // not currently supported - // querySelector<K extends keyof HTMLElementTagNameMap>( - // selectors: K, - // ): HTMLElementTagNameMap[K] | null; - // querySelector<K extends keyof SVGElementTagNameMap>( - // selectors: K, - // ): SVGElementTagNameMap[K] | null; - // querySelector<E extends Element = Element>(selectors: string): E | null; - // querySelectorAll<K extends keyof HTMLElementTagNameMap>( - // selectors: K, - // ): NodeListOf<HTMLElementTagNameMap[K]>; - // querySelectorAll<K extends keyof SVGElementTagNameMap>( - // selectors: K, - // ): NodeListOf<SVGElementTagNameMap[K]>; - // querySelectorAll<E extends Element = Element>( - // selectors: string, - // ): NodeListOf<E>; -} - -interface NodeList { - readonly length: number; - item(index: number): Node | null; - forEach( - callbackfn: (value: Node, key: number, parent: NodeList) => void, - thisArg?: any, - ): void; - [index: number]: Node; - [Symbol.iterator](): IterableIterator<Node>; - entries(): IterableIterator<[number, Node]>; - keys(): IterableIterator<number>; - values(): IterableIterator<Node>; -} - -interface NodeListOf<TNode extends Node> extends NodeList { - length: number; - item(index: number): TNode; - forEach( - callbackfn: (value: TNode, key: number, parent: NodeListOf<TNode>) => void, - thisArg?: any, - ): void; - [index: number]: TNode; - [Symbol.iterator](): IterableIterator<TNode>; - entries(): IterableIterator<[number, TNode]>; - keys(): IterableIterator<number>; - values(): IterableIterator<TNode>; -} - -export interface Body { - readonly body: ReadableStream<Uint8Array> | null; - readonly bodyUsed: boolean; - arrayBuffer(): Promise<ArrayBuffer>; - blob(): Promise<Blob>; - formData(): Promise<FormData>; - json(): Promise<any>; - text(): Promise<string>; -} - -export interface RequestInit { - body?: BodyInit | null; - cache?: RequestCache; - credentials?: RequestCredentials; - headers?: HeadersInit; - integrity?: string; - keepalive?: boolean; - method?: string; - mode?: RequestMode; - redirect?: RequestRedirect; - referrer?: string; - referrerPolicy?: ReferrerPolicy; - signal?: AbortSignal | null; - window?: any; -} - -export interface ResponseInit { - headers?: HeadersInit; - status?: number; - statusText?: string; -} - -export interface Request extends Body { - readonly cache?: RequestCache; - readonly credentials?: RequestCredentials; - readonly destination?: RequestDestination; - readonly headers: Headers; - readonly integrity?: string; - readonly isHistoryNavigation?: boolean; - readonly isReloadNavigation?: boolean; - readonly keepalive?: boolean; - readonly method: string; - readonly mode?: RequestMode; - readonly redirect?: RequestRedirect; - readonly referrer?: string; - readonly referrerPolicy?: ReferrerPolicy; - readonly signal?: AbortSignal; - readonly url: string; - clone(): Request; -} - -export interface RequestConstructor { - new (input: RequestInfo, init?: RequestInit): Request; - prototype: Request; -} - -export interface Response extends Body { - readonly headers: Headers; - readonly ok: boolean; - readonly redirected: boolean; - readonly status: number; - readonly statusText: string; - readonly type: ResponseType; - readonly url: string; - clone(): Response; -} - -export interface ResponseConstructor { - prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; - error(): Response; - redirect(url: string, status?: number): Response; -} diff --git a/cli/js/web/dom_util.ts b/cli/js/web/dom_util.ts deleted file mode 100644 index 4b9ce3f50..000000000 --- a/cli/js/web/dom_util.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -export function getDOMStringList(arr: string[]): DOMStringList { - Object.defineProperties(arr, { - contains: { - value(searchElement: string): boolean { - return arr.includes(searchElement); - }, - enumerable: true, - }, - item: { - value(idx: number): string | null { - return idx in arr ? arr[idx] : null; - }, - }, - }); - return arr as string[] & DOMStringList; -} diff --git a/cli/js/web/error_event.ts b/cli/js/web/error_event.ts deleted file mode 100644 index c04a49545..000000000 --- a/cli/js/web/error_event.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { EventImpl as Event } from "./event.ts"; -import { defineEnumerableProps } from "./util.ts"; - -export class ErrorEventImpl extends Event implements ErrorEvent { - readonly #message: string; - readonly #filename: string; - readonly #lineno: number; - readonly #colno: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly #error: any; - - get message(): string { - return this.#message; - } - get filename(): string { - return this.#filename; - } - get lineno(): number { - return this.#lineno; - } - get colno(): number { - return this.#colno; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get error(): any { - return this.#error; - } - - constructor( - type: string, - { - bubbles, - cancelable, - composed, - message = "", - filename = "", - lineno = 0, - colno = 0, - error = null, - }: ErrorEventInit = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); - - this.#message = message; - this.#filename = filename; - this.#lineno = lineno; - this.#colno = colno; - this.#error = error; - } - - get [Symbol.toStringTag](): string { - return "ErrorEvent"; - } -} - -defineEnumerableProps(ErrorEventImpl, [ - "message", - "filename", - "lineno", - "colno", - "error", -]); diff --git a/cli/js/web/event.ts b/cli/js/web/event.ts deleted file mode 100644 index d22d41c29..000000000 --- a/cli/js/web/event.ts +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import type * as domTypes from "./dom_types.d.ts"; -import { defineEnumerableProps, requiredArguments } from "./util.ts"; -import { assert } from "../util.ts"; - -/** Stores a non-accessible view of the event path which is used internally in - * the logic for determining the path of an event. */ -export interface EventPath { - item: EventTarget; - itemInShadowTree: boolean; - relatedTarget: EventTarget | null; - rootOfClosedTree: boolean; - slotInClosedTree: boolean; - target: EventTarget | null; - touchTargetList: EventTarget[]; -} - -interface EventAttributes { - type: string; - bubbles: boolean; - cancelable: boolean; - composed: boolean; - currentTarget: EventTarget | null; - eventPhase: number; - target: EventTarget | null; - timeStamp: number; -} - -interface EventData { - dispatched: boolean; - inPassiveListener: boolean; - isTrusted: boolean; - path: EventPath[]; - stopImmediatePropagation: boolean; -} - -const eventData = new WeakMap<Event, EventData>(); - -// accessors for non runtime visible data - -export function getDispatched(event: Event): boolean { - return Boolean(eventData.get(event)?.dispatched); -} - -export function getPath(event: Event): EventPath[] { - return eventData.get(event)?.path ?? []; -} - -export function getStopImmediatePropagation(event: Event): boolean { - return Boolean(eventData.get(event)?.stopImmediatePropagation); -} - -export function setCurrentTarget( - event: Event, - value: EventTarget | null, -): void { - (event as EventImpl).currentTarget = value; -} - -export function setDispatched(event: Event, value: boolean): void { - const data = eventData.get(event as Event); - if (data) { - data.dispatched = value; - } -} - -export function setEventPhase(event: Event, value: number): void { - (event as EventImpl).eventPhase = value; -} - -export function setInPassiveListener(event: Event, value: boolean): void { - const data = eventData.get(event as Event); - if (data) { - data.inPassiveListener = value; - } -} - -export function setPath(event: Event, value: EventPath[]): void { - const data = eventData.get(event as Event); - if (data) { - data.path = value; - } -} - -export function setRelatedTarget<T extends Event>( - event: T, - value: EventTarget | null, -): void { - if ("relatedTarget" in event) { - (event as T & { - relatedTarget: EventTarget | null; - }).relatedTarget = value; - } -} - -export function setTarget(event: Event, value: EventTarget | null): void { - (event as EventImpl).target = value; -} - -export function setStopImmediatePropagation( - event: Event, - value: boolean, -): void { - const data = eventData.get(event as Event); - if (data) { - data.stopImmediatePropagation = value; - } -} - -// Type guards that widen the event type - -export function hasRelatedTarget( - event: Event, -): event is domTypes.FocusEvent | domTypes.MouseEvent { - return "relatedTarget" in event; -} - -function isTrusted(this: Event): boolean { - return eventData.get(this)!.isTrusted; -} - -export class EventImpl implements Event { - // The default value is `false`. - // Use `defineProperty` to define on each instance, NOT on the prototype. - isTrusted!: boolean; - - #canceledFlag = false; - #stopPropagationFlag = false; - #attributes: EventAttributes; - - constructor(type: string, eventInitDict: EventInit = {}) { - requiredArguments("Event", arguments.length, 1); - type = String(type); - this.#attributes = { - type, - bubbles: eventInitDict.bubbles ?? false, - cancelable: eventInitDict.cancelable ?? false, - composed: eventInitDict.composed ?? false, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: Date.now(), - }; - eventData.set(this, { - dispatched: false, - inPassiveListener: false, - isTrusted: false, - path: [], - stopImmediatePropagation: false, - }); - Reflect.defineProperty(this, "isTrusted", { - enumerable: true, - get: isTrusted, - }); - } - - get bubbles(): boolean { - return this.#attributes.bubbles; - } - - get cancelBubble(): boolean { - return this.#stopPropagationFlag; - } - - set cancelBubble(value: boolean) { - this.#stopPropagationFlag = value; - } - - get cancelable(): boolean { - return this.#attributes.cancelable; - } - - get composed(): boolean { - return this.#attributes.composed; - } - - get currentTarget(): EventTarget | null { - return this.#attributes.currentTarget; - } - - set currentTarget(value: EventTarget | null) { - this.#attributes = { - type: this.type, - bubbles: this.bubbles, - cancelable: this.cancelable, - composed: this.composed, - currentTarget: value, - eventPhase: this.eventPhase, - target: this.target, - timeStamp: this.timeStamp, - }; - } - - get defaultPrevented(): boolean { - return this.#canceledFlag; - } - - get eventPhase(): number { - return this.#attributes.eventPhase; - } - - set eventPhase(value: number) { - this.#attributes = { - type: this.type, - bubbles: this.bubbles, - cancelable: this.cancelable, - composed: this.composed, - currentTarget: this.currentTarget, - eventPhase: value, - target: this.target, - timeStamp: this.timeStamp, - }; - } - - get initialized(): boolean { - return true; - } - - get target(): EventTarget | null { - return this.#attributes.target; - } - - set target(value: EventTarget | null) { - this.#attributes = { - type: this.type, - bubbles: this.bubbles, - cancelable: this.cancelable, - composed: this.composed, - currentTarget: this.currentTarget, - eventPhase: this.eventPhase, - target: value, - timeStamp: this.timeStamp, - }; - } - - get timeStamp(): number { - return this.#attributes.timeStamp; - } - - get type(): string { - return this.#attributes.type; - } - - composedPath(): EventTarget[] { - const path = eventData.get(this)!.path; - if (path.length === 0) { - return []; - } - - assert(this.currentTarget); - const composedPath: EventPath[] = [ - { - item: this.currentTarget, - 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) { - composedPath.unshift({ - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (slotInClosedTree) { - currentHiddenLevel--; - - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } - } - } - - currentHiddenLevel = currentTargetHiddenSubtreeLevel; - maxHiddenLevel = currentTargetHiddenSubtreeLevel; - - for (let index = currentTargetIndex + 1; index < path.length; index++) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - - if (slotInClosedTree) { - currentHiddenLevel++; - } - - if (currentHiddenLevel <= maxHiddenLevel) { - composedPath.push({ - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (rootOfClosedTree) { - currentHiddenLevel--; - - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } - } - } - return composedPath.map((p) => p.item); - } - - preventDefault(): void { - if (this.cancelable && !eventData.get(this)!.inPassiveListener) { - this.#canceledFlag = true; - } - } - - stopPropagation(): void { - this.#stopPropagationFlag = true; - } - - stopImmediatePropagation(): void { - this.#stopPropagationFlag = true; - eventData.get(this)!.stopImmediatePropagation = true; - } - - get NONE(): number { - return Event.NONE; - } - - get CAPTURING_PHASE(): number { - return Event.CAPTURING_PHASE; - } - - get AT_TARGET(): number { - return Event.AT_TARGET; - } - - get BUBBLING_PHASE(): number { - return Event.BUBBLING_PHASE; - } - - static get NONE(): number { - return 0; - } - - static get CAPTURING_PHASE(): number { - return 1; - } - - static get AT_TARGET(): number { - return 2; - } - - static get BUBBLING_PHASE(): number { - return 3; - } -} - -defineEnumerableProps(EventImpl, [ - "bubbles", - "cancelable", - "composed", - "currentTarget", - "defaultPrevented", - "eventPhase", - "target", - "timeStamp", - "type", -]); diff --git a/cli/js/web/event_target.ts b/cli/js/web/event_target.ts deleted file mode 100644 index 82935dd9c..000000000 --- a/cli/js/web/event_target.ts +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// This module follows most of the WHATWG Living Standard for the DOM logic. -// 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. - -import { DOMExceptionImpl as DOMException } from "./dom_exception.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { - EventImpl as Event, - EventPath, - getDispatched, - getPath, - getStopImmediatePropagation, - hasRelatedTarget, - setCurrentTarget, - setDispatched, - setEventPhase, - setInPassiveListener, - setPath, - setRelatedTarget, - setStopImmediatePropagation, - setTarget, -} from "./event.ts"; -import { defineEnumerableProps, requiredArguments } from "./util.ts"; - -// 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: EventTarget): EventTarget | null { - return isNode(eventTarget) ? eventTarget.parentNode : null; -} - -function getRoot(eventTarget: EventTarget): EventTarget | null { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; -} - -function isNode<T extends EventTarget>( - eventTarget: T | null, -): eventTarget is T & domTypes.Node { - return Boolean(eventTarget && "nodeType" in eventTarget); -} - -// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor -function isShadowInclusiveAncestor( - ancestor: EventTarget | null, - node: EventTarget | null, -): boolean { - while (isNode(node)) { - if (node === ancestor) { - return true; - } - - if (isShadowRoot(node)) { - node = node && getHost(node); - } else { - node = getParent(node); - } - } - - return false; -} - -function isShadowRoot(nodeImpl: EventTarget | null): boolean { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); -} - -function isSlotable<T extends EventTarget>( - nodeImpl: T | null, -): nodeImpl is T & domTypes.Node & domTypes.Slotable { - return Boolean(isNode(nodeImpl) && "assignedSlot" in nodeImpl); -} - -// 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: Event, - target: EventTarget, - targetOverride: EventTarget | null, - relatedTarget: EventTarget | null, - touchTargets: EventTarget[], - slotInClosedTree: boolean, -): void { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = isShadowRoot(target) && getMode(target) === "closed"; - - getPath(eventImpl).push({ - item: target, - itemInShadowTree, - target: targetOverride, - relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); -} - -function dispatch( - targetImpl: EventTarget, - eventImpl: Event, - targetOverride?: EventTarget, -): boolean { - let clearTargets = false; - let activationTarget: EventTarget | null = 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: EventTarget[] = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; - - if (isActivationEvent && getHasActivationBehavior(targetImpl)) { - activationTarget = targetImpl; - } - - let slotInClosedTree = false; - let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) - ? targetImpl - : null; - let parent = getParent(targetImpl); - - // Populate event path - // https://dom.spec.whatwg.org/#event-path - while (parent !== null) { - if (slotable !== null) { - slotable = null; - - const parentRoot = getRoot(parent); - if ( - isShadowRoot(parentRoot) && - parentRoot && - getMode(parentRoot) === "closed" - ) { - slotInClosedTree = true; - } - } - - relatedTarget = retarget(eventRelatedTarget, parent); - - if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) - ) { - 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); - } - } - - for (let i = 0; i < path.length; i++) { - const tuple = path[i]; - - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } - - if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET - ) { - invokeEventListeners(tuple, eventImpl); - } - } - } - - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); - - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); - } - - // TODO: 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: Event, - targetListeners: Record<string, Listener[]>, -): boolean { - let found = false; - - const { type } = eventImpl; - - if (!targetListeners || !targetListeners[type]) { - return found; - } - - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = targetListeners[type].slice(); - - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; - - let capture, once, passive; - if (typeof listener.options === "boolean") { - capture = listener.options; - once = false; - passive = false; - } else { - capture = listener.options.capture; - once = listener.options.once; - passive = listener.options.passive; - } - - // Check if the event listener has been removed since the listeners has been cloned. - if (!targetListeners[type].includes(listener)) { - continue; - } - - found = true; - - if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) - ) { - continue; - } - - if (once) { - targetListeners[type].splice(targetListeners[type].indexOf(listener), 1); - } - - if (passive) { - setInPassiveListener(eventImpl, true); - } - - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - listener.callback.call(eventImpl.currentTarget, eventImpl); - } - - setInPassiveListener(eventImpl, false); - - if (getStopImmediatePropagation(eventImpl)) { - return found; - } - } - - 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: EventPath, eventImpl: Event): void { - const path = getPath(eventImpl); - const tupleIndex = path.indexOf(tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = path[i]; - if (t.target) { - setTarget(eventImpl, t.target); - break; - } - } - - setRelatedTarget(eventImpl, tuple.relatedTarget); - - if (eventImpl.cancelBubble) { - return; - } - - setCurrentTarget(eventImpl, tuple.item); - - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); -} - -function normalizeAddEventHandlerOptions( - options: boolean | AddEventListenerOptions | undefined, -): AddEventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - once: false, - passive: false, - }; - } else { - return options; - } -} - -function normalizeEventHandlerOptions( - options: boolean | EventListenerOptions | undefined, -): EventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; - } else { - return options; - } -} - -/** Retarget the target following the spec logic. - * - * Ref: https://dom.spec.whatwg.org/#retarget */ -function retarget(a: EventTarget | null, b: EventTarget): EventTarget | null { - while (true) { - if (!isNode(a)) { - return a; - } - - const aRoot = a.getRootNode(); - - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } - - a = getHost(aRoot); - } - } -} - -// Non-public state information for an event target that needs to held onto. -// Some of the information should be moved to other entities (like Node, -// ShowRoot, UIElement, etc.). -interface EventTargetData { - assignedSlot: boolean; - hasActivationBehavior: boolean; - host: EventTarget | null; - listeners: Record<string, Listener[]>; - mode: string; -} - -interface Listener { - callback: EventListenerOrEventListenerObject; - options: AddEventListenerOptions; -} - -// Accessors for non-public data - -export const eventTargetData = new WeakMap<EventTarget, EventTargetData>(); - -function getAssignedSlot(target: EventTarget): boolean { - return Boolean(eventTargetData.get(target as EventTarget)?.assignedSlot); -} - -function getHasActivationBehavior(target: EventTarget): boolean { - return Boolean( - eventTargetData.get(target as EventTarget)?.hasActivationBehavior, - ); -} - -function getHost(target: EventTarget): EventTarget | null { - return eventTargetData.get(target as EventTarget)?.host ?? null; -} - -function getListeners(target: EventTarget): Record<string, Listener[]> { - return eventTargetData.get(target as EventTarget)?.listeners ?? {}; -} - -function getMode(target: EventTarget): string | null { - return eventTargetData.get(target as EventTarget)?.mode ?? null; -} - -export function getDefaultTargetData(): Readonly<EventTargetData> { - return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: Object.create(null), - mode: "", - }; -} - -export class EventTargetImpl implements EventTarget { - constructor() { - eventTargetData.set(this, getDefaultTargetData()); - } - - public addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void { - requiredArguments("EventTarget.addEventListener", arguments.length, 2); - if (callback === null) { - return; - } - - options = normalizeAddEventHandlerOptions(options); - const { listeners } = eventTargetData.get(this ?? globalThis)!; - - if (!(type in listeners)) { - listeners[type] = []; - } - - for (const listener of listeners[type]) { - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - return; - } - } - - listeners[type].push({ callback, options }); - } - - public removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void { - requiredArguments("EventTarget.removeEventListener", arguments.length, 2); - - const listeners = eventTargetData.get(this ?? globalThis)!.listeners; - if (callback !== null && type in listeners) { - listeners[type] = listeners[type].filter( - (listener) => listener.callback !== callback, - ); - } else if (callback === null || !listeners[type]) { - return; - } - - options = normalizeEventHandlerOptions(options); - - for (let i = 0; i < listeners[type].length; ++i) { - const listener = listeners[type][i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - listeners[type].splice(i, 1); - break; - } - } - } - - public dispatchEvent(event: Event): boolean { - requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); - const self = this ?? globalThis; - - const listeners = eventTargetData.get(self)!.listeners; - if (!(event.type in listeners)) { - return true; - } - - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - return dispatch(self, event); - } - - get [Symbol.toStringTag](): string { - return "EventTarget"; - } - - protected getParent(_event: Event): EventTarget | null { - return null; - } -} - -defineEnumerableProps(EventTargetImpl, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", -]); diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts deleted file mode 100644 index 4fe525cde..000000000 --- a/cli/js/web/fetch.ts +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { notImplemented } from "../util.ts"; -import { isTypedArray } from "./util.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { TextEncoder } from "./text_encoding.ts"; -import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob.ts"; -import { read } from "../ops/io.ts"; -import { close } from "../ops/resources.ts"; -import { fetch as opFetch } from "../ops/fetch.ts"; -import type { FetchResponse } from "../ops/fetch.ts"; -import * as Body from "./body.ts"; -import { getHeaderValueParams } from "./util.ts"; -import { ReadableStreamImpl } from "./streams/readable_stream.ts"; -import { MultipartBuilder } from "./fetch/multipart.ts"; - -const NULL_BODY_STATUS = [101, 204, 205, 304]; -const REDIRECT_STATUS = [301, 302, 303, 307, 308]; - -const responseData = new WeakMap(); -export class Response extends Body.Body implements domTypes.Response { - readonly type: ResponseType; - readonly redirected: boolean; - readonly url: string; - readonly status: number; - readonly statusText: string; - headers: Headers; - - constructor(body: BodyInit | null = null, init?: domTypes.ResponseInit) { - init = init ?? {}; - - if (typeof init !== "object") { - throw new TypeError(`'init' is not an object`); - } - - const extraInit = responseData.get(init) || {}; - let { type = "default", url = "" } = extraInit; - - let status = init.status === undefined ? 200 : Number(init.status || 0); - let statusText = init.statusText ?? ""; - let headers = init.headers instanceof Headers - ? init.headers - : new Headers(init.headers); - - if (init.status !== undefined && (status < 200 || status > 599)) { - throw new RangeError( - `The status provided (${init.status}) is outside the range [200, 599]`, - ); - } - - // null body status - if (body && NULL_BODY_STATUS.includes(status)) { - throw new TypeError("Response with null body status cannot have body"); - } - - if (!type) { - type = "default"; - } else { - if (type == "error") { - // spec: https://fetch.spec.whatwg.org/#concept-network-error - status = 0; - statusText = ""; - headers = new Headers(); - body = null; - /* spec for other Response types: - https://fetch.spec.whatwg.org/#concept-filtered-response-basic - Please note that type "basic" is not the same thing as "default".*/ - } else if (type == "basic") { - for (const h of headers) { - /* Forbidden Response-Header Names: - https://fetch.spec.whatwg.org/#forbidden-response-header-name */ - if (["set-cookie", "set-cookie2"].includes(h[0].toLowerCase())) { - headers.delete(h[0]); - } - } - } else if (type == "cors") { - /* CORS-safelisted Response-Header Names: - https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name */ - const allowedHeaders = [ - "Cache-Control", - "Content-Language", - "Content-Length", - "Content-Type", - "Expires", - "Last-Modified", - "Pragma", - ].map((c: string) => c.toLowerCase()); - for (const h of headers) { - /* Technically this is still not standards compliant because we are - supposed to allow headers allowed in the - 'Access-Control-Expose-Headers' header in the 'internal response' - However, this implementation of response doesn't seem to have an - easy way to access the internal response, so we ignore that - header. - TODO(serverhiccups): change how internal responses are handled - so we can do this properly. */ - if (!allowedHeaders.includes(h[0].toLowerCase())) { - headers.delete(h[0]); - } - } - /* TODO(serverhiccups): Once I fix the 'internal response' thing, - these actually need to treat the internal response differently */ - } else if (type == "opaque" || type == "opaqueredirect") { - url = ""; - status = 0; - statusText = ""; - headers = new Headers(); - body = null; - } - } - - const contentType = headers.get("content-type") || ""; - const size = Number(headers.get("content-length")) || undefined; - - super(body, { contentType, size }); - - this.url = url; - this.statusText = statusText; - this.status = extraInit.status || status; - this.headers = headers; - this.redirected = extraInit.redirected || false; - this.type = type; - } - - get ok(): boolean { - return 200 <= this.status && this.status < 300; - } - - public clone(): domTypes.Response { - if (this.bodyUsed) { - throw TypeError(Body.BodyUsedError); - } - - const iterators = this.headers.entries(); - const headersList: Array<[string, string]> = []; - for (const header of iterators) { - headersList.push(header); - } - - let resBody = this._bodySource; - - if (this._bodySource instanceof ReadableStreamImpl) { - const tees = this._bodySource.tee(); - this._stream = this._bodySource = tees[0]; - resBody = tees[1]; - } - - return new Response(resBody, { - status: this.status, - statusText: this.statusText, - headers: new Headers(headersList), - }); - } - - static redirect(url: URL | string, status: number): domTypes.Response { - if (![301, 302, 303, 307, 308].includes(status)) { - throw new RangeError( - "The redirection status must be one of 301, 302, 303, 307 and 308.", - ); - } - return new Response(null, { - status, - statusText: "", - headers: [["Location", typeof url === "string" ? url : url.toString()]], - }); - } -} - -function sendFetchReq( - url: string, - method: string | null, - headers: Headers | null, - body: ArrayBufferView | undefined, -): Promise<FetchResponse> { - let headerArray: Array<[string, string]> = []; - if (headers) { - headerArray = Array.from(headers.entries()); - } - - const args = { - method, - url, - headers: headerArray, - }; - - return opFetch(args, body); -} - -export async function fetch( - input: (domTypes.Request & { _bodySource?: unknown }) | URL | string, - init?: domTypes.RequestInit, -): Promise<Response> { - let url: string; - let method: string | null = null; - let headers: Headers | null = null; - let body: ArrayBufferView | undefined; - let redirected = false; - let remRedirectCount = 20; // TODO: use a better way to handle - - if (typeof input === "string" || input instanceof URL) { - url = typeof input === "string" ? (input as string) : (input as URL).href; - if (init != null) { - method = init.method || null; - if (init.headers) { - headers = init.headers instanceof Headers - ? init.headers - : new Headers(init.headers); - } else { - headers = null; - } - - // ref: https://fetch.spec.whatwg.org/#body-mixin - // Body should have been a mixin - // but we are treating it as a separate class - if (init.body) { - if (!headers) { - headers = new Headers(); - } - let contentType = ""; - if (typeof init.body === "string") { - body = new TextEncoder().encode(init.body); - contentType = "text/plain;charset=UTF-8"; - } else if (isTypedArray(init.body)) { - body = init.body; - } else if (init.body instanceof ArrayBuffer) { - body = new Uint8Array(init.body); - } else if (init.body instanceof URLSearchParams) { - body = new TextEncoder().encode(init.body.toString()); - contentType = "application/x-www-form-urlencoded;charset=UTF-8"; - } else if (init.body instanceof DenoBlob) { - body = init.body[blobBytesSymbol]; - contentType = init.body.type; - } else if (init.body instanceof FormData) { - let boundary; - if (headers.has("content-type")) { - const params = getHeaderValueParams("content-type"); - boundary = params.get("boundary")!; - } - const multipartBuilder = new MultipartBuilder(init.body, boundary); - body = multipartBuilder.getBody(); - contentType = multipartBuilder.getContentType(); - } else { - // TODO: ReadableStream - notImplemented(); - } - if (contentType && !headers.has("content-type")) { - headers.set("content-type", contentType); - } - } - } - } else { - url = input.url; - method = input.method; - headers = input.headers; - - if (input._bodySource) { - body = new DataView(await input.arrayBuffer()); - } - } - - let responseBody; - let responseInit: domTypes.ResponseInit = {}; - while (remRedirectCount) { - const fetchResponse = await sendFetchReq(url, method, headers, body); - - if ( - NULL_BODY_STATUS.includes(fetchResponse.status) || - REDIRECT_STATUS.includes(fetchResponse.status) - ) { - // We won't use body of received response, so close it now - // otherwise it will be kept in resource table. - close(fetchResponse.bodyRid); - responseBody = null; - } else { - responseBody = new ReadableStreamImpl({ - async pull(controller: ReadableStreamDefaultController): Promise<void> { - try { - const b = new Uint8Array(1024 * 32); - const result = await read(fetchResponse.bodyRid, b); - if (result === null) { - controller.close(); - return close(fetchResponse.bodyRid); - } - - controller.enqueue(b.subarray(0, result)); - } catch (e) { - controller.error(e); - controller.close(); - close(fetchResponse.bodyRid); - } - }, - cancel(): void { - // When reader.cancel() is called - close(fetchResponse.bodyRid); - }, - }); - } - - responseInit = { - status: 200, - statusText: fetchResponse.statusText, - headers: fetchResponse.headers, - }; - - responseData.set(responseInit, { - redirected, - rid: fetchResponse.bodyRid, - status: fetchResponse.status, - url, - }); - - const response = new Response(responseBody, responseInit); - - if (REDIRECT_STATUS.includes(fetchResponse.status)) { - // We're in a redirect status - switch ((init && init.redirect) || "follow") { - case "error": - responseInit = {}; - responseData.set(responseInit, { - type: "error", - redirected: false, - url: "", - }); - return new Response(null, responseInit); - case "manual": - responseInit = {}; - responseData.set(responseInit, { - type: "opaqueredirect", - redirected: false, - url: "", - }); - return new Response(null, responseInit); - case "follow": - default: - let redirectUrl = response.headers.get("Location"); - if (redirectUrl == null) { - return response; // Unspecified - } - if ( - !redirectUrl.startsWith("http://") && - !redirectUrl.startsWith("https://") - ) { - redirectUrl = new URL(redirectUrl, url).href; - } - url = redirectUrl; - redirected = true; - remRedirectCount--; - } - } else { - return response; - } - } - - responseData.set(responseInit, { - type: "error", - redirected: false, - url: "", - }); - - return new Response(null, responseInit); -} diff --git a/cli/js/web/fetch/multipart.ts b/cli/js/web/fetch/multipart.ts deleted file mode 100644 index f30975e5e..000000000 --- a/cli/js/web/fetch/multipart.ts +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { Buffer } from "../../buffer.ts"; -import { bytesSymbol } from "../blob.ts"; -import { DomFileImpl } from "../dom_file.ts"; -import { DenoBlob } from "../blob.ts"; -import { TextEncoder, TextDecoder } from "../text_encoding.ts"; -import { getHeaderValueParams } from "../util.ts"; - -const decoder = new TextDecoder(); -const encoder = new TextEncoder(); -const CR = "\r".charCodeAt(0); -const LF = "\n".charCodeAt(0); - -interface MultipartHeaders { - headers: Headers; - disposition: Map<string, string>; -} - -export class MultipartBuilder { - readonly boundary: string; - readonly writer = new Buffer(); - constructor(readonly formData: FormData, boundary?: string) { - this.boundary = boundary ?? this.#createBoundary(); - } - - getContentType(): string { - return `multipart/form-data; boundary=${this.boundary}`; - } - - getBody(): Uint8Array { - for (const [fieldName, fieldValue] of this.formData.entries()) { - if (fieldValue instanceof DomFileImpl) { - this.#writeFile(fieldName, fieldValue); - } else this.#writeField(fieldName, fieldValue as string); - } - - this.writer.writeSync(encoder.encode(`\r\n--${this.boundary}--`)); - - return this.writer.bytes(); - } - - #createBoundary = (): string => { - return ( - "----------" + - Array.from(Array(32)) - .map(() => Math.random().toString(36)[2] || 0) - .join("") - ); - }; - - #writeHeaders = (headers: string[][]): void => { - let buf = this.writer.empty() ? "" : "\r\n"; - - buf += `--${this.boundary}\r\n`; - for (const [key, value] of headers) { - buf += `${key}: ${value}\r\n`; - } - buf += `\r\n`; - - this.writer.write(encoder.encode(buf)); - }; - - #writeFileHeaders = ( - field: string, - filename: string, - type?: string, - ): void => { - const headers = [ - [ - "Content-Disposition", - `form-data; name="${field}"; filename="${filename}"`, - ], - ["Content-Type", type || "application/octet-stream"], - ]; - return this.#writeHeaders(headers); - }; - - #writeFieldHeaders = (field: string): void => { - const headers = [["Content-Disposition", `form-data; name="${field}"`]]; - return this.#writeHeaders(headers); - }; - - #writeField = (field: string, value: string): void => { - this.#writeFieldHeaders(field); - this.writer.writeSync(encoder.encode(value)); - }; - - #writeFile = (field: string, value: DomFileImpl): void => { - this.#writeFileHeaders(field, value.name, value.type); - this.writer.writeSync(value[bytesSymbol]); - }; -} - -export class MultipartParser { - readonly boundary: string; - readonly boundaryChars: Uint8Array; - readonly body: Uint8Array; - constructor(body: Uint8Array, boundary: string) { - if (!boundary) { - throw new TypeError("multipart/form-data must provide a boundary"); - } - - this.boundary = `--${boundary}`; - this.body = body; - this.boundaryChars = encoder.encode(this.boundary); - } - - #parseHeaders = (headersText: string): MultipartHeaders => { - const headers = new Headers(); - const rawHeaders = headersText.split("\r\n"); - for (const rawHeader of rawHeaders) { - const sepIndex = rawHeader.indexOf(":"); - if (sepIndex < 0) { - continue; // Skip this header - } - const key = rawHeader.slice(0, sepIndex); - const value = rawHeader.slice(sepIndex + 1); - headers.set(key, value); - } - - return { - headers, - disposition: getHeaderValueParams( - headers.get("Content-Disposition") ?? "", - ), - }; - }; - - parse(): FormData { - const formData = new FormData(); - let headerText = ""; - let boundaryIndex = 0; - let state = 0; - let fileStart = 0; - - for (let i = 0; i < this.body.length; i++) { - const byte = this.body[i]; - const prevByte = this.body[i - 1]; - const isNewLine = byte === LF && prevByte === CR; - - if (state === 1 || state === 2 || state == 3) { - headerText += String.fromCharCode(byte); - } - if (state === 0 && isNewLine) { - state = 1; - } else if (state === 1 && isNewLine) { - state = 2; - const headersDone = this.body[i + 1] === CR && this.body[i + 2] === LF; - - if (headersDone) { - state = 3; - } - } else if (state === 2 && isNewLine) { - state = 3; - } else if (state === 3 && isNewLine) { - state = 4; - fileStart = i + 1; - } else if (state === 4) { - if (this.boundaryChars[boundaryIndex] !== byte) { - boundaryIndex = 0; - } else { - boundaryIndex++; - } - - if (boundaryIndex >= this.boundary.length) { - const { headers, disposition } = this.#parseHeaders(headerText); - const content = this.body.subarray(fileStart, i - boundaryIndex - 1); - // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata - const filename = disposition.get("filename"); - const name = disposition.get("name"); - - state = 5; - // Reset - boundaryIndex = 0; - headerText = ""; - - if (!name) { - continue; // Skip, unknown name - } - - if (filename) { - const blob = new DenoBlob([content], { - type: headers.get("Content-Type") || "application/octet-stream", - }); - formData.append(name, blob, filename); - } else { - formData.append(name, decoder.decode(content)); - } - } - } else if (state === 5 && isNewLine) { - state = 1; - } - } - - return formData; - } -} diff --git a/cli/js/web/form_data.ts b/cli/js/web/form_data.ts deleted file mode 100644 index 1a0622638..000000000 --- a/cli/js/web/form_data.ts +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import * as blob from "./blob.ts"; -import * as domFile from "./dom_file.ts"; -import { DomIterableMixin } from "./dom_iterable.ts"; -import { requiredArguments } from "./util.ts"; - -const dataSymbol = Symbol("data"); - -class FormDataBase { - [dataSymbol]: Array<[string, FormDataEntryValue]> = []; - - append(name: string, value: string): void; - append(name: string, value: domFile.DomFileImpl): void; - append(name: string, value: blob.DenoBlob, filename?: string): void; - append( - name: string, - value: string | blob.DenoBlob | domFile.DomFileImpl, - filename?: string, - ): void { - requiredArguments("FormData.append", arguments.length, 2); - name = String(name); - if (value instanceof domFile.DomFileImpl) { - this[dataSymbol].push([name, value]); - } else if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || "blob", { - type: value.type, - }); - this[dataSymbol].push([name, dfile]); - } else { - this[dataSymbol].push([name, String(value)]); - } - } - - delete(name: string): void { - requiredArguments("FormData.delete", arguments.length, 1); - name = String(name); - let i = 0; - while (i < this[dataSymbol].length) { - if (this[dataSymbol][i][0] === name) { - this[dataSymbol].splice(i, 1); - } else { - i++; - } - } - } - - getAll(name: string): FormDataEntryValue[] { - requiredArguments("FormData.getAll", arguments.length, 1); - name = String(name); - const values = []; - for (const entry of this[dataSymbol]) { - if (entry[0] === name) { - values.push(entry[1]); - } - } - - return values; - } - - get(name: string): FormDataEntryValue | null { - requiredArguments("FormData.get", arguments.length, 1); - name = String(name); - for (const entry of this[dataSymbol]) { - if (entry[0] === name) { - return entry[1]; - } - } - - return null; - } - - has(name: string): boolean { - requiredArguments("FormData.has", arguments.length, 1); - name = String(name); - return this[dataSymbol].some((entry): boolean => entry[0] === name); - } - - set(name: string, value: string): void; - set(name: string, value: domFile.DomFileImpl): void; - set(name: string, value: blob.DenoBlob, filename?: string): void; - set( - name: string, - value: string | blob.DenoBlob | domFile.DomFileImpl, - filename?: string, - ): void { - requiredArguments("FormData.set", arguments.length, 2); - name = String(name); - - // If there are any entries in the context object’s entry list whose name - // is name, replace the first such entry with entry and remove the others - let found = false; - let i = 0; - while (i < this[dataSymbol].length) { - if (this[dataSymbol][i][0] === name) { - if (!found) { - if (value instanceof domFile.DomFileImpl) { - this[dataSymbol][i][1] = value; - } else if (value instanceof blob.DenoBlob) { - this[dataSymbol][i][1] = new domFile.DomFileImpl( - [value], - filename || "blob", - { - type: value.type, - }, - ); - } else { - this[dataSymbol][i][1] = String(value); - } - found = true; - } else { - this[dataSymbol].splice(i, 1); - continue; - } - } - i++; - } - - // Otherwise, append entry to the context object’s entry list. - if (!found) { - if (value instanceof domFile.DomFileImpl) { - this[dataSymbol].push([name, value]); - } else if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || "blob", { - type: value.type, - }); - this[dataSymbol].push([name, dfile]); - } else { - this[dataSymbol].push([name, String(value)]); - } - } - } - - get [Symbol.toStringTag](): string { - return "FormData"; - } -} - -export class FormDataImpl extends DomIterableMixin< - string, - FormDataEntryValue, - typeof FormDataBase ->(FormDataBase, dataSymbol) {} - -Object.defineProperty(FormDataImpl, "name", { - value: "FormData", - configurable: true, -}); diff --git a/cli/js/web/headers.ts b/cli/js/web/headers.ts deleted file mode 100644 index d75f87adc..000000000 --- a/cli/js/web/headers.ts +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { DomIterableMixin } from "./dom_iterable.ts"; -import { requiredArguments } from "./util.ts"; -import { customInspect } from "./console.ts"; - -// From node-fetch -// Copyright (c) 2016 David Frank. MIT License. -const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/; -const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isHeaders(value: any): value is Headers { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return value instanceof Headers; -} - -const headersData = Symbol("headers data"); - -// TODO: headerGuard? Investigate if it is needed -// node-fetch did not implement this but it is in the spec -function normalizeParams(name: string, value?: string): string[] { - name = String(name).toLowerCase(); - value = String(value).trim(); - return [name, value]; -} - -// The following name/value validations are copied from -// https://github.com/bitinn/node-fetch/blob/master/src/headers.js -// Copyright (c) 2016 David Frank. MIT License. -function validateName(name: string): void { - if (invalidTokenRegex.test(name) || name === "") { - throw new TypeError(`${name} is not a legal HTTP header name`); - } -} - -function validateValue(value: string): void { - if (invalidHeaderCharRegex.test(value)) { - throw new TypeError(`${value} is not a legal HTTP header value`); - } -} - -/** Appends a key and value to the header list. - * - * The spec indicates that when a key already exists, the append adds the new - * value onto the end of the existing value. The behaviour of this though - * varies when the key is `set-cookie`. In this case, if the key of the cookie - * already exists, the value is replaced, but if the key of the cookie does not - * exist, and additional `set-cookie` header is added. - * - * The browser specification of `Headers` is written for clients, and not - * servers, and Deno is a server, meaning that it needs to follow the patterns - * expected for servers, of which a `set-cookie` header is expected for each - * unique cookie key, but duplicate cookie keys should not exist. */ -function dataAppend( - data: Array<[string, string]>, - key: string, - value: string, -): void { - for (let i = 0; i < data.length; i++) { - const [dataKey] = data[i]; - if (key === "set-cookie" && dataKey === "set-cookie") { - const [, dataValue] = data[i]; - const [dataCookieKey] = dataValue.split("="); - const [cookieKey] = value.split("="); - if (dataCookieKey === cookieKey) { - data[i][1] = value; - return; - } - } else { - if (dataKey === key) { - data[i][1] += `, ${value}`; - return; - } - } - } - data.push([key, value]); -} - -/** Gets a value of a key in the headers list. - * - * This varies slightly from spec behaviour in that when the key is `set-cookie` - * the value returned will look like a concatenated value, when in fact, if the - * headers were iterated over, each individual `set-cookie` value is a unique - * entry in the headers list. */ -function dataGet( - data: Array<[string, string]>, - key: string, -): string | undefined { - const setCookieValues = []; - for (const [dataKey, value] of data) { - if (dataKey === key) { - if (key === "set-cookie") { - setCookieValues.push(value); - } else { - return value; - } - } - } - if (setCookieValues.length) { - return setCookieValues.join(", "); - } - return undefined; -} - -/** Sets a value of a key in the headers list. - * - * The spec indicates that the value should be replaced if the key already - * exists. The behaviour here varies, where if the key is `set-cookie` the key - * of the cookie is inspected, and if the key of the cookie already exists, - * then the value is replaced. If the key of the cookie is not found, then - * the value of the `set-cookie` is added to the list of headers. - * - * The browser specification of `Headers` is written for clients, and not - * servers, and Deno is a server, meaning that it needs to follow the patterns - * expected for servers, of which a `set-cookie` header is expected for each - * unique cookie key, but duplicate cookie keys should not exist. */ -function dataSet( - data: Array<[string, string]>, - key: string, - value: string, -): void { - for (let i = 0; i < data.length; i++) { - const [dataKey] = data[i]; - if (dataKey === key) { - // there could be multiple set-cookie headers, but all others are unique - if (key === "set-cookie") { - const [, dataValue] = data[i]; - const [dataCookieKey] = dataValue.split("="); - const [cookieKey] = value.split("="); - if (cookieKey === dataCookieKey) { - data[i][1] = value; - return; - } - } else { - data[i][1] = value; - return; - } - } - } - data.push([key, value]); -} - -function dataDelete(data: Array<[string, string]>, key: string): void { - let i = 0; - while (i < data.length) { - const [dataKey] = data[i]; - if (dataKey === key) { - data.splice(i, 1); - } else { - i++; - } - } -} - -function dataHas(data: Array<[string, string]>, key: string): boolean { - for (const [dataKey] of data) { - if (dataKey === key) { - return true; - } - } - return false; -} - -// ref: https://fetch.spec.whatwg.org/#dom-headers -class HeadersBase { - [headersData]: Array<[string, string]>; - - constructor(init?: HeadersInit) { - if (init === null) { - throw new TypeError( - "Failed to construct 'Headers'; The provided value was not valid", - ); - } else if (isHeaders(init)) { - this[headersData] = [...init]; - } else { - this[headersData] = []; - if (Array.isArray(init)) { - for (const tuple of init) { - // If header does not contain exactly two items, - // then throw a TypeError. - // ref: https://fetch.spec.whatwg.org/#concept-headers-fill - requiredArguments( - "Headers.constructor tuple array argument", - tuple.length, - 2, - ); - - this.append(tuple[0], tuple[1]); - } - } else if (init) { - for (const [rawName, rawValue] of Object.entries(init)) { - this.append(rawName, rawValue); - } - } - } - } - - [customInspect](): string { - let length = this[headersData].length; - let output = ""; - for (const [key, value] of this[headersData]) { - const prefix = length === this[headersData].length ? " " : ""; - const postfix = length === 1 ? " " : ", "; - output = output + `${prefix}${key}: ${value}${postfix}`; - length--; - } - return `Headers {${output}}`; - } - - // ref: https://fetch.spec.whatwg.org/#concept-headers-append - append(name: string, value: string): void { - requiredArguments("Headers.append", arguments.length, 2); - const [newname, newvalue] = normalizeParams(name, value); - validateName(newname); - validateValue(newvalue); - dataAppend(this[headersData], newname, newvalue); - } - - delete(name: string): void { - requiredArguments("Headers.delete", arguments.length, 1); - const [newname] = normalizeParams(name); - validateName(newname); - dataDelete(this[headersData], newname); - } - - get(name: string): string | null { - requiredArguments("Headers.get", arguments.length, 1); - const [newname] = normalizeParams(name); - validateName(newname); - return dataGet(this[headersData], newname) ?? null; - } - - has(name: string): boolean { - requiredArguments("Headers.has", arguments.length, 1); - const [newname] = normalizeParams(name); - validateName(newname); - return dataHas(this[headersData], newname); - } - - set(name: string, value: string): void { - requiredArguments("Headers.set", arguments.length, 2); - const [newname, newvalue] = normalizeParams(name, value); - validateName(newname); - validateValue(newvalue); - dataSet(this[headersData], newname, newvalue); - } - - get [Symbol.toStringTag](): string { - return "Headers"; - } -} - -// @internal -export class HeadersImpl extends DomIterableMixin< - string, - string, - typeof HeadersBase ->(HeadersBase, headersData) {} - -Object.defineProperty(HeadersImpl, "name", { - value: "Headers", - configurable: true, -}); diff --git a/cli/js/web/performance.ts b/cli/js/web/performance.ts deleted file mode 100644 index 1acff9f75..000000000 --- a/cli/js/web/performance.ts +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { now as opNow } from "../ops/timers.ts"; -import { customInspect, inspect } from "./console.ts"; -import { cloneValue, setFunctionName } from "./util.ts"; - -let performanceEntries: PerformanceEntryList = []; - -function findMostRecent( - name: string, - type: "mark" | "measure", -): PerformanceEntry | undefined { - return performanceEntries - .slice() - .reverse() - .find((entry) => entry.name === name && entry.entryType === type); -} - -function convertMarkToTimestamp(mark: string | number): number { - if (typeof mark === "string") { - const entry = findMostRecent(mark, "mark"); - if (!entry) { - throw new SyntaxError(`Cannot find mark: "${mark}".`); - } - return entry.startTime; - } - if (mark < 0) { - throw new TypeError("Mark cannot be negative."); - } - return mark; -} - -function filterByNameType( - name?: string, - type?: "mark" | "measure", -): PerformanceEntryList { - return performanceEntries.filter( - (entry) => - (name ? entry.name === name : true) && - (type ? entry.entryType === type : true), - ); -} - -function now(): number { - const res = opNow(); - return res.seconds * 1e3 + res.subsecNanos / 1e6; -} - -export class PerformanceEntryImpl implements PerformanceEntry { - #name: string; - #entryType: string; - #startTime: number; - #duration: number; - - get name(): string { - return this.#name; - } - - get entryType(): string { - return this.#entryType; - } - - get startTime(): number { - return this.#startTime; - } - - get duration(): number { - return this.#duration; - } - - constructor( - name: string, - entryType: string, - startTime: number, - duration: number, - ) { - this.#name = name; - this.#entryType = entryType; - this.#startTime = startTime; - this.#duration = duration; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return { - name: this.#name, - entryType: this.#entryType, - startTime: this.#startTime, - duration: this.#duration, - }; - } - - [customInspect](): string { - return `${this.constructor.name} { name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; - } -} - -export class PerformanceMarkImpl extends PerformanceEntryImpl - implements PerformanceMark { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - #detail: any; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get detail(): any { - return this.#detail; - } - - get entryType(): "mark" { - return "mark"; - } - - constructor( - name: string, - { detail = null, startTime = now() }: PerformanceMarkOptions = {}, - ) { - super(name, "mark", startTime, 0); - if (startTime < 0) { - throw new TypeError("startTime cannot be negative"); - } - this.#detail = cloneValue(detail); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](): string { - return this.detail - ? `${this.constructor.name} {\n detail: ${ - inspect(this.detail, { depth: 3 }) - },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}` - : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; - } -} - -export class PerformanceMeasureImpl extends PerformanceEntryImpl - implements PerformanceMeasure { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - #detail: any; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get detail(): any { - return this.#detail; - } - - get entryType(): "measure" { - return "measure"; - } - - constructor( - name: string, - startTime: number, - duration: number, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - detail: any = null, - ) { - super(name, "measure", startTime, duration); - this.#detail = cloneValue(detail); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](): string { - return this.detail - ? `${this.constructor.name} {\n detail: ${ - inspect(this.detail, { depth: 3 }) - },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}` - : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; - } -} - -export class PerformanceImpl implements Performance { - clearMarks(markName?: string): void { - if (markName == null) { - performanceEntries = performanceEntries.filter( - (entry) => entry.entryType !== "mark", - ); - } else { - performanceEntries = performanceEntries.filter( - (entry) => !(entry.name === markName && entry.entryType === "mark"), - ); - } - } - - clearMeasures(measureName?: string): void { - if (measureName == null) { - performanceEntries = performanceEntries.filter( - (entry) => entry.entryType !== "measure", - ); - } else { - performanceEntries = performanceEntries.filter( - (entry) => - !(entry.name === measureName && entry.entryType === "measure"), - ); - } - } - - getEntries(): PerformanceEntryList { - return filterByNameType(); - } - getEntriesByName( - name: string, - type?: "mark" | "measure", - ): PerformanceEntryList { - return filterByNameType(name, type); - } - getEntriesByType(type: "mark" | "measure"): PerformanceEntryList { - return filterByNameType(undefined, type); - } - - mark( - markName: string, - options: PerformanceMarkOptions = {}, - ): PerformanceMark { - // 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 PerformanceMarkImpl(markName, options); - // 3.1.1.7 Queue entry - not implemented - performanceEntries.push(entry); - return entry; - } - - measure( - measureName: string, - options?: PerformanceMeasureOptions, - ): PerformanceMeasure; - measure( - measureName: string, - startMark?: string, - endMark?: string, - ): PerformanceMeasure; - measure( - measureName: string, - startOrMeasureOptions: string | PerformanceMeasureOptions = {}, - endMark?: string, - ): PerformanceMeasure { - if (startOrMeasureOptions && typeof startOrMeasureOptions === "object") { - if (endMark) { - throw new TypeError("Options cannot be passed with endMark."); - } - if ( - !("start" in startOrMeasureOptions) && - !("end" in startOrMeasureOptions) - ) { - throw new TypeError("A start or end mark must be supplied in options."); - } - if ( - "start" in startOrMeasureOptions && - "duration" in startOrMeasureOptions && - "end" in startOrMeasureOptions - ) { - throw new TypeError( - "Cannot specify start, end, and duration together in options.", - ); - } - } - let endTime: number; - if (endMark) { - endTime = convertMarkToTimestamp(endMark); - } else if ( - typeof startOrMeasureOptions === "object" && - "end" in startOrMeasureOptions - ) { - endTime = convertMarkToTimestamp(startOrMeasureOptions.end!); - } else if ( - typeof startOrMeasureOptions === "object" && - "start" in startOrMeasureOptions && - "duration" in startOrMeasureOptions - ) { - const start = convertMarkToTimestamp(startOrMeasureOptions.start!); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration!); - endTime = start + duration; - } else { - endTime = now(); - } - let startTime: number; - if ( - typeof startOrMeasureOptions === "object" && - "start" in startOrMeasureOptions - ) { - startTime = convertMarkToTimestamp(startOrMeasureOptions.start!); - } else if ( - typeof startOrMeasureOptions === "object" && - "end" in startOrMeasureOptions && - "duration" in startOrMeasureOptions - ) { - 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 PerformanceMeasureImpl( - measureName, - startTime, - endTime - startTime, - typeof startOrMeasureOptions === "object" - ? startOrMeasureOptions.detail ?? null - : null, - ); - performanceEntries.push(entry); - return entry; - } - - now(): number { - return now(); - } -} - -setFunctionName(PerformanceEntryImpl, "PerformanceEntry"); -setFunctionName(PerformanceMarkImpl, "PerformanceMark"); -setFunctionName(PerformanceMeasureImpl, "PerformanceMeasure"); -setFunctionName(PerformanceImpl, "Performance"); diff --git a/cli/js/web/promise.ts b/cli/js/web/promise.ts deleted file mode 100644 index a24e8ed51..000000000 --- a/cli/js/web/promise.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -export enum PromiseState { - Pending, - Fulfilled, - Rejected, -} - -export type PromiseDetails<T> = [PromiseState, T | undefined]; diff --git a/cli/js/web/request.ts b/cli/js/web/request.ts deleted file mode 100644 index f65b6a363..000000000 --- a/cli/js/web/request.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import * as body from "./body.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { ReadableStreamImpl } from "./streams/readable_stream.ts"; - -function byteUpperCase(s: string): string { - return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c): string { - return c.toUpperCase(); - }); -} - -function normalizeMethod(m: string): string { - const u = byteUpperCase(m); - if ( - u === "DELETE" || - u === "GET" || - u === "HEAD" || - u === "OPTIONS" || - u === "POST" || - u === "PUT" - ) { - return u; - } - return m; -} - -export class Request extends body.Body implements domTypes.Request { - public method: string; - public url: string; - public credentials?: "omit" | "same-origin" | "include"; - public headers: Headers; - - constructor(input: domTypes.RequestInfo, init?: domTypes.RequestInit) { - if (arguments.length < 1) { - throw TypeError("Not enough arguments"); - } - - if (!init) { - init = {}; - } - - let b: BodyInit; - - // prefer body from init - if (init.body) { - b = init.body; - } else if (input instanceof Request && input._bodySource) { - if (input.bodyUsed) { - throw TypeError(body.BodyUsedError); - } - b = input._bodySource; - } else if (typeof input === "object" && "body" in input && input.body) { - if (input.bodyUsed) { - throw TypeError(body.BodyUsedError); - } - b = input.body; - } else { - b = ""; - } - - let headers: Headers; - - // prefer headers from init - if (init.headers) { - headers = new Headers(init.headers); - } else if (input instanceof Request) { - headers = input.headers; - } else { - headers = new Headers(); - } - - const contentType = headers.get("content-type") || ""; - super(b, { contentType }); - this.headers = headers; - - // readonly attribute ByteString method; - this.method = "GET"; - - // readonly attribute USVString url; - this.url = ""; - - // readonly attribute RequestCredentials credentials; - this.credentials = "omit"; - - if (input instanceof Request) { - if (input.bodyUsed) { - throw TypeError(body.BodyUsedError); - } - this.method = input.method; - this.url = input.url; - this.headers = new Headers(input.headers); - this.credentials = input.credentials; - this._stream = input._stream; - } else if (typeof input === "string") { - this.url = input; - } - - if (init && "method" in init) { - this.method = normalizeMethod(init.method as string); - } - - if ( - init && - "credentials" in init && - init.credentials && - ["omit", "same-origin", "include"].indexOf(init.credentials) !== -1 - ) { - this.credentials = init.credentials; - } - } - - public clone(): domTypes.Request { - if (this.bodyUsed) { - throw TypeError(body.BodyUsedError); - } - - const iterators = this.headers.entries(); - const headersList: Array<[string, string]> = []; - for (const header of iterators) { - headersList.push(header); - } - - let body2 = this._bodySource; - - if (this._bodySource instanceof ReadableStreamImpl) { - const tees = this._bodySource.tee(); - this._stream = this._bodySource = tees[0]; - body2 = tees[1]; - } - - return new Request(this.url, { - body: body2, - method: this.method, - headers: new Headers(headersList), - credentials: this.credentials, - }); - } -} diff --git a/cli/js/web/streams/internals.ts b/cli/js/web/streams/internals.ts deleted file mode 100644 index 06c5e304d..000000000 --- a/cli/js/web/streams/internals.ts +++ /dev/null @@ -1,2405 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// This code closely follows the WHATWG Stream Specification -// See: https://streams.spec.whatwg.org/ -// -// There are some parts that are not fully implemented, and there are some -// comments which point to steps of the specification that are not implemented. - -/* eslint-disable @typescript-eslint/no-explicit-any,require-await */ -import { ReadableByteStreamControllerImpl } from "./readable_byte_stream_controller.ts"; -import { ReadableStreamDefaultControllerImpl } from "./readable_stream_default_controller.ts"; -import { ReadableStreamDefaultReaderImpl } from "./readable_stream_default_reader.ts"; -import { ReadableStreamImpl } from "./readable_stream.ts"; -import * as sym from "./symbols.ts"; -import type { TransformStreamImpl } from "./transform_stream.ts"; -import { TransformStreamDefaultControllerImpl } from "./transform_stream_default_controller.ts"; -import { WritableStreamDefaultControllerImpl } from "./writable_stream_default_controller.ts"; -import { WritableStreamDefaultWriterImpl } from "./writable_stream_default_writer.ts"; -import { WritableStreamImpl } from "./writable_stream.ts"; -import { AbortSignalImpl } from "../abort_signal.ts"; -import { DOMExceptionImpl as DOMException } from "../dom_exception.ts"; -import { cloneValue, setFunctionName } from "../util.ts"; -import { assert, AssertionError } from "../../util.ts"; - -export type AbortAlgorithm = (reason?: any) => PromiseLike<void>; -export interface AbortRequest { - promise: Deferred<void>; - reason?: any; - wasAlreadyErroring: boolean; -} -export interface BufferQueueItem extends Pair<ArrayBuffer | SharedArrayBuffer> { - offset: number; -} -export type CancelAlgorithm = (reason?: any) => PromiseLike<void>; -export type CloseAlgorithm = () => PromiseLike<void>; -type Container<R = any> = { - [sym.queue]: Array<Pair<R> | BufferQueueItem>; - [sym.queueTotalSize]: number; -}; -export type FlushAlgorithm = () => Promise<void>; -export type Pair<R> = { value: R; size: number }; -export type PullAlgorithm = () => PromiseLike<void>; -export type SizeAlgorithm<T> = (chunk: T) => number; -export type StartAlgorithm = () => void | PromiseLike<void>; -export type TransformAlgorithm<I> = (chunk: I) => Promise<void>; -export type WriteAlgorithm<W> = (chunk: W) => Promise<void>; -export interface Deferred<T> { - promise: Promise<T>; - resolve?: (value?: T | PromiseLike<T>) => void; - reject?: (reason?: any) => void; -} - -export interface ReadableStreamGenericReader<R = any> - extends ReadableStreamReader<R> { - [sym.closedPromise]: Deferred<void>; - [sym.forAuthorCode]: boolean; - [sym.ownerReadableStream]: ReadableStreamImpl<R>; - [sym.readRequests]: Array<Deferred<ReadableStreamReadResult<R>>>; -} - -export interface ReadableStreamAsyncIterator<T = any> extends AsyncIterator<T> { - [sym.asyncIteratorReader]: ReadableStreamDefaultReaderImpl<T>; - [sym.preventCancel]: boolean; - return(value?: any | PromiseLike<any>): Promise<IteratorResult<T>>; -} - -export function acquireReadableStreamDefaultReader<T>( - stream: ReadableStreamImpl<T>, - forAuthorCode = false, -): ReadableStreamDefaultReaderImpl<T> { - const reader = new ReadableStreamDefaultReaderImpl(stream); - reader[sym.forAuthorCode] = forAuthorCode; - return reader; -} - -export function acquireWritableStreamDefaultWriter<W>( - stream: WritableStreamImpl<W>, -): WritableStreamDefaultWriterImpl<W> { - return new WritableStreamDefaultWriterImpl(stream); -} - -export function call<F extends (...args: any[]) => any>( - fn: F, - v: ThisType<F>, - args: Parameters<F>, -): ReturnType<F> { - return Function.prototype.apply.call(fn, v, args); -} - -function createAlgorithmFromUnderlyingMethod< - O extends UnderlyingByteSource | UnderlyingSource | Transformer, - P extends keyof O, ->( - underlyingObject: O, - methodName: P, - algoArgCount: 0, - ...extraArgs: any[] -): () => Promise<void>; - -function createAlgorithmFromUnderlyingMethod< - O extends UnderlyingByteSource | UnderlyingSource | Transformer, - P extends keyof O, ->( - underlyingObject: O, - methodName: P, - algoArgCount: 1, - ...extraArgs: any[] -): (arg: any) => Promise<void>; -function createAlgorithmFromUnderlyingMethod< - O extends UnderlyingByteSource | UnderlyingSource | Transformer, - P extends keyof O, ->( - underlyingObject: O, - methodName: P, - algoArgCount: 0 | 1, - ...extraArgs: any[] -): (() => Promise<void>) | ((arg: any) => Promise<void>) { - const method = underlyingObject[methodName]; - if (method) { - if (!isCallable(method)) { - throw new TypeError("method is not callable"); - } - if (algoArgCount === 0) { - return async (): Promise<void> => - call(method, underlyingObject, extraArgs as any); - } else { - return async (arg: any): Promise<void> => { - const fullArgs = [arg, ...extraArgs]; - return call(method, underlyingObject, fullArgs as any); - }; - } - } - return async (): Promise<void> => undefined; -} - -function createReadableStream<T>( - startAlgorithm: StartAlgorithm, - pullAlgorithm: PullAlgorithm, - cancelAlgorithm: CancelAlgorithm, - highWaterMark = 1, - sizeAlgorithm: SizeAlgorithm<T> = (): number => 1, -): ReadableStreamImpl<T> { - highWaterMark = validateAndNormalizeHighWaterMark(highWaterMark); - const stream: ReadableStreamImpl<T> = Object.create( - ReadableStreamImpl.prototype, - ); - initializeReadableStream(stream); - const controller: ReadableStreamDefaultControllerImpl<T> = Object.create( - ReadableStreamDefaultControllerImpl.prototype, - ); - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - return stream; -} - -function createWritableStream<W>( - startAlgorithm: StartAlgorithm, - writeAlgorithm: WriteAlgorithm<W>, - closeAlgorithm: CloseAlgorithm, - abortAlgorithm: AbortAlgorithm, - highWaterMark = 1, - sizeAlgorithm: SizeAlgorithm<W> = (): number => 1, -): WritableStreamImpl<W> { - highWaterMark = validateAndNormalizeHighWaterMark(highWaterMark); - const stream = Object.create(WritableStreamImpl.prototype); - initializeWritableStream(stream); - const controller = Object.create( - WritableStreamDefaultControllerImpl.prototype, - ); - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - return stream; -} - -export function dequeueValue<R>(container: Container<R>): R { - assert(sym.queue in container && sym.queueTotalSize in container); - assert(container[sym.queue].length); - const pair = container[sym.queue].shift()!; - container[sym.queueTotalSize] -= pair.size; - if (container[sym.queueTotalSize] <= 0) { - container[sym.queueTotalSize] = 0; - } - return pair.value as R; -} - -function enqueueValueWithSize<R>( - container: Container<R>, - value: R, - size: number, -): void { - assert(sym.queue in container && sym.queueTotalSize in container); - size = Number(size); - if (!isFiniteNonNegativeNumber(size)) { - throw new RangeError("size must be a finite non-negative number."); - } - container[sym.queue].push({ value, size }); - container[sym.queueTotalSize] += size; -} - -/** Non-spec mechanism to "unwrap" a promise and store it to be resolved - * later. */ -export function getDeferred<T>(): Required<Deferred<T>> { - let resolve: (value?: T | PromiseLike<T>) => void; - let reject: (reason?: any) => void; - const promise = new Promise<T>((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve: resolve!, reject: reject! }; -} - -export function initializeReadableStream<R>( - stream: ReadableStreamImpl<R>, -): void { - stream[sym.state] = "readable"; - stream[sym.reader] = stream[sym.storedError] = undefined; - stream[sym.disturbed] = false; -} - -export function initializeTransformStream<I, O>( - stream: TransformStreamImpl<I, O>, - startPromise: Promise<void>, - writableHighWaterMark: number, - writableSizeAlgorithm: SizeAlgorithm<I>, - readableHighWaterMark: number, - readableSizeAlgorithm: SizeAlgorithm<O>, -): void { - const startAlgorithm = (): Promise<void> => startPromise; - const writeAlgorithm = (chunk: any): Promise<void> => - transformStreamDefaultSinkWriteAlgorithm(stream, chunk); - const abortAlgorithm = (reason: any): Promise<void> => - transformStreamDefaultSinkAbortAlgorithm(stream, reason); - const closeAlgorithm = (): Promise<void> => - transformStreamDefaultSinkCloseAlgorithm(stream); - stream[sym.writable] = createWritableStream( - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - writableHighWaterMark, - writableSizeAlgorithm, - ); - const pullAlgorithm = (): PromiseLike<void> => - transformStreamDefaultSourcePullAlgorithm(stream); - const cancelAlgorithm = (reason: any): Promise<void> => { - transformStreamErrorWritableAndUnblockWrite(stream, reason); - return Promise.resolve(undefined); - }; - stream[sym.readable] = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ); - stream[sym.backpressure] = stream[sym.backpressureChangePromise] = undefined; - transformStreamSetBackpressure(stream, true); - Object.defineProperty(stream, sym.transformStreamController, { - value: undefined, - configurable: true, - }); -} - -export function initializeWritableStream<W>( - stream: WritableStreamImpl<W>, -): void { - stream[sym.state] = "writable"; - stream[sym.storedError] = stream[sym.writer] = stream[ - sym.writableStreamController - ] = stream[sym.inFlightWriteRequest] = stream[sym.closeRequest] = stream[ - sym.inFlightCloseRequest - ] = stream[sym.pendingAbortRequest] = undefined; - stream[sym.writeRequests] = []; - stream[sym.backpressure] = false; -} - -export function invokeOrNoop<O extends Record<string, any>, P extends keyof O>( - o: O, - p: P, - ...args: Parameters<O[P]> -): ReturnType<O[P]> | undefined { - assert(o); - const method = o[p]; - if (!method) { - return undefined; - } - return call(method, o, args); -} - -function isCallable(value: unknown): value is (...args: any) => any { - return typeof value === "function"; -} - -export function isDetachedBuffer(value: object): boolean { - return sym.isFakeDetached in value; -} - -function isFiniteNonNegativeNumber(v: unknown): v is number { - return Number.isFinite(v) && (v as number) >= 0; -} - -export function isReadableByteStreamController( - x: unknown, -): x is ReadableByteStreamControllerImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.controlledReadableByteStream in x) - ); -} - -export function isReadableStream(x: unknown): x is ReadableStreamImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.readableStreamController in x) - ); -} - -export function isReadableStreamAsyncIterator( - x: unknown, -): x is ReadableStreamAsyncIterator { - if (typeof x !== "object" || x === null) { - return false; - } - return sym.asyncIteratorReader in x; -} - -export function isReadableStreamDefaultController( - x: unknown, -): x is ReadableStreamDefaultControllerImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.controlledReadableStream in x) - ); -} - -export function isReadableStreamDefaultReader<T>( - x: unknown, -): x is ReadableStreamDefaultReaderImpl<T> { - return !(typeof x !== "object" || x === null || !(sym.readRequests in x)); -} - -export function isReadableStreamLocked(stream: ReadableStreamImpl): boolean { - assert(isReadableStream(stream)); - return !!stream[sym.reader]; -} - -export function isReadableStreamDisturbed(stream: ReadableStream): boolean { - assert(isReadableStream(stream)); - return !!stream[sym.disturbed]; -} - -export function isTransformStream(x: unknown): x is TransformStreamImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.transformStreamController in x) - ); -} - -export function isTransformStreamDefaultController( - x: unknown, -): x is TransformStreamDefaultControllerImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.controlledTransformStream in x) - ); -} - -export function isUnderlyingByteSource( - underlyingSource: UnderlyingByteSource | UnderlyingSource, -): underlyingSource is UnderlyingByteSource { - const { type } = underlyingSource; - const typeString = String(type); - return typeString === "bytes"; -} - -export function isWritableStream(x: unknown): x is WritableStreamImpl { - return !( - typeof x !== "object" || - x === null || - !(sym.writableStreamController in x) - ); -} - -export function isWritableStreamDefaultController( - x: unknown, -): x is WritableStreamDefaultControllerImpl<any> { - return !( - typeof x !== "object" || - x === null || - !(sym.controlledWritableStream in x) - ); -} - -export function isWritableStreamDefaultWriter( - x: unknown, -): x is WritableStreamDefaultWriterImpl<any> { - return !( - typeof x !== "object" || - x === null || - !(sym.ownerWritableStream in x) - ); -} - -export function isWritableStreamLocked(stream: WritableStreamImpl): boolean { - assert(isWritableStream(stream)); - return stream[sym.writer] !== undefined; -} - -export function makeSizeAlgorithmFromSizeFunction<T>( - size: QueuingStrategySizeCallback<T> | undefined, -): SizeAlgorithm<T> { - if (size === undefined) { - return (): number => 1; - } - if (typeof size !== "function") { - throw new TypeError("size must be callable."); - } - return (chunk: T): number => { - return size.call(undefined, chunk); - }; -} - -function peekQueueValue<T>(container: Container<T>): T | "close" { - assert(sym.queue in container && sym.queueTotalSize in container); - assert(container[sym.queue].length); - const [pair] = container[sym.queue]; - return pair.value as T; -} - -function readableByteStreamControllerShouldCallPull( - controller: ReadableByteStreamControllerImpl, -): boolean { - const stream = controller[sym.controlledReadableByteStream]; - if ( - stream[sym.state] !== "readable" || - controller[sym.closeRequested] || - !controller[sym.started] - ) { - return false; - } - if ( - readableStreamHasDefaultReader(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; - } - // 3.13.25.6 If ! ReadableStreamHasBYOBReader(stream) is true and ! - // ReadableStreamGetNumReadIntoRequests(stream) > 0, return true. - const desiredSize = readableByteStreamControllerGetDesiredSize(controller); - assert(desiredSize !== null); - return desiredSize > 0; -} - -export function readableByteStreamControllerCallPullIfNeeded( - controller: ReadableByteStreamControllerImpl, -): void { - const shouldPull = readableByteStreamControllerShouldCallPull(controller); - if (!shouldPull) { - return; - } - if (controller[sym.pulling]) { - controller[sym.pullAgain] = true; - return; - } - assert(controller[sym.pullAgain] === false); - controller[sym.pulling] = true; - const pullPromise = controller[sym.pullAlgorithm](); - setPromiseIsHandledToTrue( - pullPromise.then( - () => { - controller[sym.pulling] = false; - if (controller[sym.pullAgain]) { - controller[sym.pullAgain] = false; - readableByteStreamControllerCallPullIfNeeded(controller); - } - }, - (e) => { - readableByteStreamControllerError(controller, e); - }, - ), - ); -} - -export function readableByteStreamControllerClearAlgorithms( - controller: ReadableByteStreamControllerImpl, -): void { - (controller as any)[sym.pullAlgorithm] = undefined; - (controller as any)[sym.cancelAlgorithm] = undefined; -} - -export function readableByteStreamControllerClose( - controller: ReadableByteStreamControllerImpl, -): void { - const stream = controller[sym.controlledReadableByteStream]; - if (controller[sym.closeRequested] || stream[sym.state] !== "readable") { - return; - } - if (controller[sym.queueTotalSize] > 0) { - controller[sym.closeRequested] = true; - return; - } - // 3.13.6.4 If controller.[[pendingPullIntos]] is not empty, (BYOB Support) - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(stream); -} - -export function readableByteStreamControllerEnqueue( - controller: ReadableByteStreamControllerImpl, - chunk: ArrayBufferView, -): void { - const stream = controller[sym.controlledReadableByteStream]; - if (controller[sym.closeRequested] || stream[sym.state] !== "readable") { - return; - } - const { buffer, byteOffset, byteLength } = chunk; - const transferredBuffer = transferArrayBuffer(buffer); - if (readableStreamHasDefaultReader(stream)) { - if (readableStreamGetNumReadRequests(stream) === 0) { - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, - ); - } else { - assert(controller[sym.queue].length === 0); - const transferredView = new Uint8Array( - transferredBuffer, - byteOffset, - byteLength, - ); - readableStreamFulfillReadRequest(stream, transferredView, false); - } - // 3.13.9.8 Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true - } else { - assert(!isReadableStreamLocked(stream)); - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, - ); - } - readableByteStreamControllerCallPullIfNeeded(controller); -} - -function readableByteStreamControllerEnqueueChunkToQueue( - controller: ReadableByteStreamControllerImpl, - buffer: ArrayBuffer | SharedArrayBuffer, - byteOffset: number, - byteLength: number, -): void { - controller[sym.queue].push({ - value: buffer, - offset: byteOffset, - size: byteLength, - }); - controller[sym.queueTotalSize] += byteLength; -} - -export function readableByteStreamControllerError( - controller: ReadableByteStreamControllerImpl, - e: any, -): void { - const stream = controller[sym.controlledReadableByteStream]; - if (stream[sym.state] !== "readable") { - return; - } - // 3.13.11.3 Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). - resetQueue(controller); - readableByteStreamControllerClearAlgorithms(controller); - readableStreamError(stream, e); -} - -export function readableByteStreamControllerGetDesiredSize( - controller: ReadableByteStreamControllerImpl, -): number | null { - const stream = controller[sym.controlledReadableByteStream]; - const state = stream[sym.state]; - if (state === "errored") { - return null; - } - if (state === "closed") { - return 0; - } - return controller[sym.strategyHWM] - controller[sym.queueTotalSize]; -} - -export function readableByteStreamControllerHandleQueueDrain( - controller: ReadableByteStreamControllerImpl, -): void { - assert( - controller[sym.controlledReadableByteStream][sym.state] === "readable", - ); - if (controller[sym.queueTotalSize] === 0 && controller[sym.closeRequested]) { - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(controller[sym.controlledReadableByteStream]); - } else { - readableByteStreamControllerCallPullIfNeeded(controller); - } -} - -export function readableStreamAddReadRequest<R>( - stream: ReadableStreamImpl<R>, -): Promise<ReadableStreamReadResult<R>> { - assert(isReadableStreamDefaultReader(stream[sym.reader])); - assert(stream[sym.state] === "readable"); - const promise = getDeferred<ReadableStreamReadResult<R>>(); - stream[sym.reader]![sym.readRequests].push(promise); - return promise.promise; -} - -export function readableStreamCancel<T>( - stream: ReadableStreamImpl<T>, - reason: any, -): Promise<void> { - stream[sym.disturbed] = true; - if (stream[sym.state] === "closed") { - return Promise.resolve(); - } - if (stream[sym.state] === "errored") { - return Promise.reject(stream[sym.storedError]); - } - readableStreamClose(stream); - return stream[sym.readableStreamController].then( - () => undefined, - ) as Promise<void>; -} - -export function readableStreamClose<T>(stream: ReadableStreamImpl<T>): void { - assert(stream[sym.state] === "readable"); - stream[sym.state] = "closed"; - const reader = stream[sym.reader]; - if (!reader) { - return; - } - if (isReadableStreamDefaultReader<T>(reader)) { - for (const readRequest of reader[sym.readRequests]) { - assert(readRequest.resolve); - readRequest.resolve( - readableStreamCreateReadResult<T>( - undefined, - true, - reader[sym.forAuthorCode], - ), - ); - } - reader[sym.readRequests] = []; - } - const resolve = reader[sym.closedPromise].resolve; - assert(resolve); - resolve(); -} - -export function readableStreamCreateReadResult<T>( - value: T | undefined, - done: boolean, - forAuthorCode: boolean, -): ReadableStreamReadResult<T> { - const prototype = forAuthorCode ? Object.prototype : null; - assert(typeof done === "boolean"); - const obj: ReadableStreamReadResult<T> = Object.create(prototype); - Object.defineProperties(obj, { - value: { value, writable: true, enumerable: true, configurable: true }, - done: { value: done, writable: true, enumerable: true, configurable: true }, - }); - return obj; -} - -export function readableStreamDefaultControllerCallPullIfNeeded<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): void { - const shouldPull = readableStreamDefaultControllerShouldCallPull(controller); - if (!shouldPull) { - return; - } - if (controller[sym.pulling]) { - controller[sym.pullAgain] = true; - return; - } - assert(controller[sym.pullAgain] === false); - controller[sym.pulling] = true; - const pullPromise = controller[sym.pullAlgorithm](); - pullPromise.then( - () => { - controller[sym.pulling] = false; - if (controller[sym.pullAgain]) { - controller[sym.pullAgain] = false; - readableStreamDefaultControllerCallPullIfNeeded(controller); - } - }, - (e) => { - readableStreamDefaultControllerError(controller, e); - }, - ); -} - -export function readableStreamDefaultControllerCanCloseOrEnqueue<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): boolean { - const state = controller[sym.controlledReadableStream][sym.state]; - return !controller[sym.closeRequested] && state === "readable"; -} - -export function readableStreamDefaultControllerClearAlgorithms<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): void { - (controller as any)[sym.pullAlgorithm] = undefined; - (controller as any)[sym.cancelAlgorithm] = undefined; - (controller as any)[sym.strategySizeAlgorithm] = undefined; -} - -export function readableStreamDefaultControllerClose<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): void { - if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller)) { - return; - } - const stream = controller[sym.controlledReadableStream]; - controller[sym.closeRequested] = true; - if (controller[sym.queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamClose(stream); - } -} - -export function readableStreamDefaultControllerEnqueue<T>( - controller: ReadableStreamDefaultControllerImpl<T>, - chunk: T, -): void { - if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller)) { - return; - } - const stream = controller[sym.controlledReadableStream]; - if ( - isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - readableStreamFulfillReadRequest(stream, chunk, false); - } else { - try { - const chunkSize = controller[sym.strategySizeAlgorithm](chunk); - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (err) { - readableStreamDefaultControllerError(controller, err); - throw err; - } - } - readableStreamDefaultControllerCallPullIfNeeded(controller); -} - -export function readableStreamDefaultControllerGetDesiredSize<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): number | null { - const stream = controller[sym.controlledReadableStream]; - const state = stream[sym.state]; - if (state === "errored") { - return null; - } - if (state === "closed") { - return 0; - } - return controller[sym.strategyHWM] - controller[sym.queueTotalSize]; -} - -export function readableStreamDefaultControllerError<T>( - controller: ReadableStreamDefaultControllerImpl<T>, - e: any, -): void { - const stream = controller[sym.controlledReadableStream]; - if (stream[sym.state] !== "readable") { - return; - } - resetQueue(controller); - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamError(stream, e); -} - -function readableStreamDefaultControllerHasBackpressure<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): boolean { - return readableStreamDefaultControllerShouldCallPull(controller); -} - -function readableStreamDefaultControllerShouldCallPull<T>( - controller: ReadableStreamDefaultControllerImpl<T>, -): boolean { - const stream = controller[sym.controlledReadableStream]; - if ( - !readableStreamDefaultControllerCanCloseOrEnqueue(controller) || - controller[sym.started] === false - ) { - return false; - } - if ( - isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; - } - const desiredSize = readableStreamDefaultControllerGetDesiredSize(controller); - assert(desiredSize !== null); - return desiredSize > 0; -} - -export function readableStreamDefaultReaderRead<R>( - reader: ReadableStreamDefaultReaderImpl<R>, -): Promise<ReadableStreamReadResult<R>> { - const stream = reader[sym.ownerReadableStream]; - assert(stream); - stream[sym.disturbed] = true; - if (stream[sym.state] === "closed") { - return Promise.resolve( - readableStreamCreateReadResult<R>( - undefined, - true, - reader[sym.forAuthorCode], - ), - ); - } - if (stream[sym.state] === "errored") { - return Promise.reject(stream[sym.storedError]); - } - assert(stream[sym.state] === "readable"); - return (stream[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl)[sym.pullSteps](); -} - -export function readableStreamError(stream: ReadableStreamImpl, e: any): void { - assert(isReadableStream(stream)); - assert(stream[sym.state] === "readable"); - stream[sym.state] = "errored"; - stream[sym.storedError] = e; - const reader = stream[sym.reader]; - if (reader === undefined) { - return; - } - if (isReadableStreamDefaultReader(reader)) { - for (const readRequest of reader[sym.readRequests]) { - assert(readRequest.reject); - readRequest.reject(e); - readRequest.reject = undefined; - readRequest.resolve = undefined; - } - reader[sym.readRequests] = []; - } - // 3.5.6.8 Otherwise, support BYOB Reader - reader[sym.closedPromise].reject!(e); - reader[sym.closedPromise].reject = undefined; - reader[sym.closedPromise].resolve = undefined; - setPromiseIsHandledToTrue(reader[sym.closedPromise].promise); -} - -export function readableStreamFulfillReadRequest<R>( - stream: ReadableStreamImpl<R>, - chunk: R, - done: boolean, -): void { - const reader = stream[sym.reader]!; - const readRequest = reader[sym.readRequests].shift()!; - assert(readRequest.resolve); - readRequest.resolve( - readableStreamCreateReadResult(chunk, done, reader[sym.forAuthorCode]), - ); -} - -export function readableStreamGetNumReadRequests( - stream: ReadableStreamImpl, -): number { - return stream[sym.reader]?.[sym.readRequests].length ?? 0; -} - -export function readableStreamHasDefaultReader( - stream: ReadableStreamImpl, -): boolean { - const reader = stream[sym.reader]; - return !(reader === undefined || !isReadableStreamDefaultReader(reader)); -} - -export function readableStreamPipeTo<T>( - source: ReadableStreamImpl<T>, - dest: WritableStreamImpl<T>, - preventClose: boolean, - preventAbort: boolean, - preventCancel: boolean, - signal: AbortSignalImpl | undefined, -): Promise<void> { - assert(isReadableStream(source)); - assert(isWritableStream(dest)); - assert( - typeof preventClose === "boolean" && - typeof preventAbort === "boolean" && - typeof preventCancel === "boolean", - ); - assert(signal === undefined || signal instanceof AbortSignalImpl); - assert(!isReadableStreamLocked(source)); - assert(!isWritableStreamLocked(dest)); - const reader = acquireReadableStreamDefaultReader(source); - const writer = acquireWritableStreamDefaultWriter(dest); - source[sym.disturbed] = true; - let shuttingDown = false; - const promise = getDeferred<void>(); - let abortAlgorithm: () => void; - if (signal) { - abortAlgorithm = (): void => { - const error = new DOMException("Abort signal received.", "AbortSignal"); - const actions: Array<() => Promise<void>> = []; - if (!preventAbort) { - actions.push(() => { - if (dest[sym.state] === "writable") { - return writableStreamAbort(dest, error); - } else { - return Promise.resolve(undefined); - } - }); - } - if (!preventCancel) { - actions.push(() => { - if (source[sym.state] === "readable") { - return readableStreamCancel(source, error); - } else { - return Promise.resolve(undefined); - } - }); - } - shutdownWithAction( - () => Promise.all(actions.map((action) => action())), - true, - error, - ); - }; - if (signal.aborted) { - abortAlgorithm(); - return promise.promise; - } - signal.addEventListener("abort", abortAlgorithm); - } - - let currentWrite = Promise.resolve(); - - // At this point, the spec becomes non-specific and vague. Most of the rest - // of this code is based on the reference implementation that is part of the - // specification. This is why the functions are only scoped to this function - // to ensure they don't leak into the spec compliant parts. - - function isOrBecomesClosed( - stream: ReadableStreamImpl | WritableStreamImpl, - promise: Promise<void>, - action: () => void, - ): void { - if (stream[sym.state] === "closed") { - action(); - } else { - setPromiseIsHandledToTrue(promise.then(action)); - } - } - - function isOrBecomesErrored( - stream: ReadableStreamImpl | WritableStreamImpl, - promise: Promise<void>, - action: (error: any) => void, - ): void { - if (stream[sym.state] === "errored") { - action(stream[sym.storedError]); - } else { - setPromiseIsHandledToTrue(promise.catch((error) => action(error))); - } - } - - function finalize(isError?: boolean, error?: any): void { - writableStreamDefaultWriterRelease(writer); - readableStreamReaderGenericRelease(reader); - - if (signal) { - signal.removeEventListener("abort", abortAlgorithm); - } - if (isError) { - promise.reject(error); - } else { - promise.resolve(); - } - } - - function waitForWritesToFinish(): Promise<void> { - const oldCurrentWrite = currentWrite; - return currentWrite.then(() => - oldCurrentWrite !== currentWrite ? waitForWritesToFinish() : undefined - ); - } - - function shutdownWithAction( - action: () => Promise<any>, - originalIsError?: boolean, - originalError?: any, - ): void { - function doTheRest(): void { - setPromiseIsHandledToTrue( - action().then( - () => finalize(originalIsError, originalError), - (newError) => finalize(true, newError), - ), - ); - } - - if (shuttingDown) { - return; - } - shuttingDown = true; - - if ( - dest[sym.state] === "writable" && - writableStreamCloseQueuedOrInFlight(dest) === false - ) { - setPromiseIsHandledToTrue(waitForWritesToFinish().then(doTheRest)); - } else { - doTheRest(); - } - } - - function shutdown(isError: boolean, error?: any): void { - if (shuttingDown) { - return; - } - shuttingDown = true; - - if ( - dest[sym.state] === "writable" && - !writableStreamCloseQueuedOrInFlight(dest) - ) { - setPromiseIsHandledToTrue( - waitForWritesToFinish().then(() => finalize(isError, error)), - ); - } - finalize(isError, error); - } - - function pipeStep(): Promise<boolean> { - if (shuttingDown) { - return Promise.resolve(true); - } - return writer[sym.readyPromise].promise.then(() => { - return readableStreamDefaultReaderRead(reader).then(({ value, done }) => { - if (done === true) { - return true; - } - currentWrite = writableStreamDefaultWriterWrite( - writer, - value!, - ).then(undefined, () => {}); - return false; - }); - }); - } - - function pipeLoop(): Promise<void> { - return new Promise((resolveLoop, rejectLoop) => { - function next(done: boolean): void { - if (done) { - resolveLoop(undefined); - } else { - setPromiseIsHandledToTrue(pipeStep().then(next, rejectLoop)); - } - } - next(false); - }); - } - - isOrBecomesErrored( - source, - reader[sym.closedPromise].promise, - (storedError) => { - if (!preventAbort) { - shutdownWithAction( - () => writableStreamAbort(dest, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); - } - }, - ); - - isOrBecomesErrored(dest, writer[sym.closedPromise].promise, (storedError) => { - if (!preventCancel) { - shutdownWithAction( - () => readableStreamCancel(source, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); - } - }); - - isOrBecomesClosed(source, reader[sym.closedPromise].promise, () => { - if (!preventClose) { - shutdownWithAction(() => - writableStreamDefaultWriterCloseWithErrorPropagation(writer) - ); - } - }); - - if ( - writableStreamCloseQueuedOrInFlight(dest) || - dest[sym.state] === "closed" - ) { - const destClosed = new TypeError( - "The destination writable stream closed before all data could be piped to it.", - ); - if (!preventCancel) { - shutdownWithAction( - () => readableStreamCancel(source, destClosed), - true, - destClosed, - ); - } else { - shutdown(true, destClosed); - } - } - - setPromiseIsHandledToTrue(pipeLoop()); - return promise.promise; -} - -export function readableStreamReaderGenericCancel<R = any>( - reader: ReadableStreamGenericReader<R>, - reason: any, -): Promise<void> { - const stream = reader[sym.ownerReadableStream]; - assert(stream); - return readableStreamCancel(stream, reason); -} - -export function readableStreamReaderGenericInitialize<R = any>( - reader: ReadableStreamGenericReader<R>, - stream: ReadableStreamImpl<R>, -): void { - reader[sym.forAuthorCode] = true; - reader[sym.ownerReadableStream] = stream; - stream[sym.reader] = reader; - if (stream[sym.state] === "readable") { - reader[sym.closedPromise] = getDeferred(); - } else if (stream[sym.state] === "closed") { - reader[sym.closedPromise] = { promise: Promise.resolve() }; - } else { - assert(stream[sym.state] === "errored"); - reader[sym.closedPromise] = { - promise: Promise.reject(stream[sym.storedError]), - }; - setPromiseIsHandledToTrue(reader[sym.closedPromise].promise); - } -} - -export function readableStreamReaderGenericRelease<R = any>( - reader: ReadableStreamGenericReader<R>, -): void { - assert(reader[sym.ownerReadableStream]); - assert(reader[sym.ownerReadableStream][sym.reader] === reader); - const closedPromise = reader[sym.closedPromise]; - if (reader[sym.ownerReadableStream][sym.state] === "readable") { - assert(closedPromise.reject); - closedPromise.reject(new TypeError("ReadableStream state is readable.")); - } else { - closedPromise.promise = Promise.reject(new TypeError("Reading is closed.")); - delete closedPromise.reject; - delete closedPromise.resolve; - } - setPromiseIsHandledToTrue(closedPromise.promise); - reader[sym.ownerReadableStream][sym.reader] = undefined; - (reader as any)[sym.ownerReadableStream] = undefined; -} - -export function readableStreamTee<T>( - stream: ReadableStreamImpl<T>, - cloneForBranch2: boolean, -): [ReadableStreamImpl<T>, ReadableStreamImpl<T>] { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); - const reader = acquireReadableStreamDefaultReader(stream); - let reading = false; - let canceled1 = false; - let canceled2 = false; - let reason1: any = undefined; - let reason2: any = undefined; - /* eslint-disable prefer-const */ - let branch1: ReadableStreamImpl<T>; - let branch2: ReadableStreamImpl<T>; - /* eslint-enable prefer-const */ - const cancelPromise = getDeferred<void>(); - const pullAlgorithm = (): PromiseLike<void> => { - if (reading) { - return Promise.resolve(); - } - reading = true; - const readPromise = readableStreamDefaultReaderRead(reader).then( - (result) => { - reading = false; - assert(typeof result === "object"); - const { done } = result; - assert(typeof done === "boolean"); - if (done) { - if (!canceled1) { - readableStreamDefaultControllerClose( - branch1[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - ); - } - if (!canceled2) { - readableStreamDefaultControllerClose( - branch2[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - ); - } - return; - } - const { value } = result; - const value1 = value!; - let value2 = value!; - if (!canceled2 && cloneForBranch2) { - value2 = cloneValue(value2); - } - if (!canceled1) { - readableStreamDefaultControllerEnqueue( - branch1[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - value1, - ); - } - if (!canceled2) { - readableStreamDefaultControllerEnqueue( - branch2[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - value2, - ); - } - }, - ); - setPromiseIsHandledToTrue(readPromise); - return Promise.resolve(); - }; - const cancel1Algorithm = (reason?: any): PromiseLike<void> => { - canceled1 = true; - reason1 = reason; - if (canceled2) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - }; - const cancel2Algorithm = (reason?: any): PromiseLike<void> => { - canceled2 = true; - reason2 = reason; - if (canceled1) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - }; - const startAlgorithm = (): void => undefined; - branch1 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel1Algorithm, - ); - branch2 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel2Algorithm, - ); - setPromiseIsHandledToTrue( - reader[sym.closedPromise].promise.catch((r) => { - readableStreamDefaultControllerError( - branch1[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - r, - ); - readableStreamDefaultControllerError( - branch2[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl, - r, - ); - }), - ); - return [branch1, branch2]; -} - -export function resetQueue<R>(container: Container<R>): void { - assert(sym.queue in container && sym.queueTotalSize in container); - container[sym.queue] = []; - container[sym.queueTotalSize] = 0; -} - -/** An internal function which mimics the behavior of setting the promise to - * handled in JavaScript. In this situation, an assertion failure, which - * shouldn't happen will get thrown, instead of swallowed. */ -export function setPromiseIsHandledToTrue(promise: PromiseLike<unknown>): void { - promise.then(undefined, (e) => { - if (e && e instanceof AssertionError) { - queueMicrotask(() => { - throw e; - }); - } - }); -} - -function setUpReadableByteStreamController( - stream: ReadableStreamImpl, - controller: ReadableByteStreamControllerImpl, - startAlgorithm: StartAlgorithm, - pullAlgorithm: PullAlgorithm, - cancelAlgorithm: CancelAlgorithm, - highWaterMark: number, - autoAllocateChunkSize: number | undefined, -): void { - assert(stream[sym.readableStreamController] === undefined); - if (autoAllocateChunkSize !== undefined) { - assert(Number.isInteger(autoAllocateChunkSize)); - assert(autoAllocateChunkSize >= 0); - } - controller[sym.controlledReadableByteStream] = stream; - controller[sym.pulling] = controller[sym.pullAgain] = false; - controller[sym.byobRequest] = undefined; - controller[sym.queue] = []; - controller[sym.queueTotalSize] = 0; - controller[sym.closeRequested] = controller[sym.started] = false; - controller[sym.strategyHWM] = validateAndNormalizeHighWaterMark( - highWaterMark, - ); - controller[sym.pullAlgorithm] = pullAlgorithm; - controller[sym.cancelAlgorithm] = cancelAlgorithm; - controller[sym.autoAllocateChunkSize] = autoAllocateChunkSize; - // 3.13.26.12 Set controller.[[pendingPullIntos]] to a new empty List. - stream[sym.readableStreamController] = controller; - const startResult = startAlgorithm(); - const startPromise = Promise.resolve(startResult); - setPromiseIsHandledToTrue( - startPromise.then( - () => { - controller[sym.started] = true; - assert(!controller[sym.pulling]); - assert(!controller[sym.pullAgain]); - readableByteStreamControllerCallPullIfNeeded(controller); - }, - (r) => { - readableByteStreamControllerError(controller, r); - }, - ), - ); -} - -export function setUpReadableByteStreamControllerFromUnderlyingSource( - stream: ReadableStreamImpl, - underlyingByteSource: UnderlyingByteSource, - highWaterMark: number, -): void { - assert(underlyingByteSource); - const controller: ReadableByteStreamControllerImpl = Object.create( - ReadableByteStreamControllerImpl.prototype, - ); - const startAlgorithm: StartAlgorithm = () => { - return invokeOrNoop(underlyingByteSource, "start", controller); - }; - const pullAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingByteSource, - "pull", - 0, - controller, - ); - setFunctionName(pullAlgorithm, "[[pullAlgorithm]]"); - const cancelAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingByteSource, - "cancel", - 1, - ); - setFunctionName(cancelAlgorithm, "[[cancelAlgorithm]]"); - // 3.13.27.6 Let autoAllocateChunkSize be ? GetV(underlyingByteSource, "autoAllocateChunkSize"). - const autoAllocateChunkSize = undefined; - setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - autoAllocateChunkSize, - ); -} - -function setUpReadableStreamDefaultController<T>( - stream: ReadableStreamImpl<T>, - controller: ReadableStreamDefaultControllerImpl<T>, - startAlgorithm: StartAlgorithm, - pullAlgorithm: PullAlgorithm, - cancelAlgorithm: CancelAlgorithm, - highWaterMark: number, - sizeAlgorithm: SizeAlgorithm<T>, -): void { - assert(stream[sym.readableStreamController] === undefined); - controller[sym.controlledReadableStream] = stream; - controller[sym.queue] = []; - controller[sym.queueTotalSize] = 0; - controller[sym.started] = controller[sym.closeRequested] = controller[ - sym.pullAgain - ] = controller[sym.pulling] = false; - controller[sym.strategySizeAlgorithm] = sizeAlgorithm; - controller[sym.strategyHWM] = highWaterMark; - controller[sym.pullAlgorithm] = pullAlgorithm; - controller[sym.cancelAlgorithm] = cancelAlgorithm; - stream[sym.readableStreamController] = controller; - const startResult = startAlgorithm(); - const startPromise = Promise.resolve(startResult); - setPromiseIsHandledToTrue( - startPromise.then( - () => { - controller[sym.started] = true; - assert(controller[sym.pulling] === false); - assert(controller[sym.pullAgain] === false); - readableStreamDefaultControllerCallPullIfNeeded(controller); - }, - (r) => { - readableStreamDefaultControllerError(controller, r); - }, - ), - ); -} - -export function setUpReadableStreamDefaultControllerFromUnderlyingSource<T>( - stream: ReadableStreamImpl<T>, - underlyingSource: UnderlyingSource<T>, - highWaterMark: number, - sizeAlgorithm: SizeAlgorithm<T>, -): void { - assert(underlyingSource); - const controller: ReadableStreamDefaultControllerImpl<T> = Object.create( - ReadableStreamDefaultControllerImpl.prototype, - ); - const startAlgorithm: StartAlgorithm = (): void | PromiseLike<void> => - invokeOrNoop(underlyingSource, "start", controller); - const pullAlgorithm: PullAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingSource, - "pull", - 0, - controller, - ); - setFunctionName(pullAlgorithm, "[[pullAlgorithm]]"); - const cancelAlgorithm: CancelAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingSource, - "cancel", - 1, - ); - setFunctionName(cancelAlgorithm, "[[cancelAlgorithm]]"); - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); -} - -function setUpTransformStreamDefaultController<I, O>( - stream: TransformStreamImpl<I, O>, - controller: TransformStreamDefaultControllerImpl<I, O>, - transformAlgorithm: TransformAlgorithm<I>, - flushAlgorithm: FlushAlgorithm, -): void { - assert(isTransformStream(stream)); - assert(stream[sym.transformStreamController] === undefined); - controller[sym.controlledTransformStream] = stream; - stream[sym.transformStreamController] = controller; - controller[sym.transformAlgorithm] = transformAlgorithm; - controller[sym.flushAlgorithm] = flushAlgorithm; -} - -export function setUpTransformStreamDefaultControllerFromTransformer<I, O>( - stream: TransformStreamImpl<I, O>, - transformer: Transformer<I, O>, -): void { - assert(transformer); - const controller = Object.create( - TransformStreamDefaultControllerImpl.prototype, - ) as TransformStreamDefaultControllerImpl<I, O>; - let transformAlgorithm: TransformAlgorithm<I> = (chunk) => { - try { - transformStreamDefaultControllerEnqueue( - controller, - // it defaults to no tranformation, so I is assumed to be O - (chunk as unknown) as O, - ); - } catch (e) { - return Promise.reject(e); - } - return Promise.resolve(); - }; - const transformMethod = transformer.transform; - if (transformMethod) { - if (typeof transformMethod !== "function") { - throw new TypeError("tranformer.transform must be callable."); - } - transformAlgorithm = async (chunk): Promise<void> => - call(transformMethod, transformer, [chunk, controller]); - } - const flushAlgorithm = createAlgorithmFromUnderlyingMethod( - transformer, - "flush", - 0, - controller, - ); - setUpTransformStreamDefaultController( - stream, - controller, - transformAlgorithm, - flushAlgorithm, - ); -} - -function setUpWritableStreamDefaultController<W>( - stream: WritableStreamImpl<W>, - controller: WritableStreamDefaultControllerImpl<W>, - startAlgorithm: StartAlgorithm, - writeAlgorithm: WriteAlgorithm<W>, - closeAlgorithm: CloseAlgorithm, - abortAlgorithm: AbortAlgorithm, - highWaterMark: number, - sizeAlgorithm: SizeAlgorithm<W>, -): void { - assert(isWritableStream(stream)); - assert(stream[sym.writableStreamController] === undefined); - controller[sym.controlledWritableStream] = stream; - stream[sym.writableStreamController] = controller; - controller[sym.queue] = []; - controller[sym.queueTotalSize] = 0; - controller[sym.started] = false; - controller[sym.strategySizeAlgorithm] = sizeAlgorithm; - controller[sym.strategyHWM] = highWaterMark; - controller[sym.writeAlgorithm] = writeAlgorithm; - controller[sym.closeAlgorithm] = closeAlgorithm; - controller[sym.abortAlgorithm] = abortAlgorithm; - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - const startResult = startAlgorithm(); - const startPromise = Promise.resolve(startResult); - setPromiseIsHandledToTrue( - startPromise.then( - () => { - assert( - stream[sym.state] === "writable" || stream[sym.state] === "erroring", - ); - controller[sym.started] = true; - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, - (r) => { - assert( - stream[sym.state] === "writable" || stream[sym.state] === "erroring", - ); - controller[sym.started] = true; - writableStreamDealWithRejection(stream, r); - }, - ), - ); -} - -export function setUpWritableStreamDefaultControllerFromUnderlyingSink<W>( - stream: WritableStreamImpl<W>, - underlyingSink: UnderlyingSink<W>, - highWaterMark: number, - sizeAlgorithm: SizeAlgorithm<W>, -): void { - assert(underlyingSink); - const controller = Object.create( - WritableStreamDefaultControllerImpl.prototype, - ); - const startAlgorithm = (): void | PromiseLike<void> => { - return invokeOrNoop(underlyingSink, "start", controller); - }; - const writeAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingSink, - "write", - 1, - controller, - ); - setFunctionName(writeAlgorithm, "[[writeAlgorithm]]"); - const closeAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingSink, - "close", - 0, - ); - setFunctionName(closeAlgorithm, "[[closeAlgorithm]]"); - const abortAlgorithm = createAlgorithmFromUnderlyingMethod( - underlyingSink, - "abort", - 1, - ); - setFunctionName(abortAlgorithm, "[[abortAlgorithm]]"); - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); -} - -function transformStreamDefaultControllerClearAlgorithms<I, O>( - controller: TransformStreamDefaultControllerImpl<I, O>, -): void { - (controller as any)[sym.transformAlgorithm] = undefined; - (controller as any)[sym.flushAlgorithm] = undefined; -} - -export function transformStreamDefaultControllerEnqueue<I, O>( - controller: TransformStreamDefaultControllerImpl<I, O>, - chunk: O, -): void { - const stream = controller[sym.controlledTransformStream]; - const readableController = stream[sym.readable][ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl<O>; - if (!readableStreamDefaultControllerCanCloseOrEnqueue(readableController)) { - throw new TypeError( - "TransformStream's readable controller cannot be closed or enqueued.", - ); - } - try { - readableStreamDefaultControllerEnqueue(readableController, chunk); - } catch (e) { - transformStreamErrorWritableAndUnblockWrite(stream, e); - throw stream[sym.readable][sym.storedError]; - } - const backpressure = readableStreamDefaultControllerHasBackpressure( - readableController, - ); - if (backpressure) { - transformStreamSetBackpressure(stream, true); - } -} - -export function transformStreamDefaultControllerError<I, O>( - controller: TransformStreamDefaultControllerImpl<I, O>, - e: any, -): void { - transformStreamError(controller[sym.controlledTransformStream], e); -} - -function transformStreamDefaultControllerPerformTransform<I, O>( - controller: TransformStreamDefaultControllerImpl<I, O>, - chunk: I, -): Promise<void> { - const transformPromise = controller[sym.transformAlgorithm](chunk); - return transformPromise.then(undefined, (r) => { - transformStreamError(controller[sym.controlledTransformStream], r); - throw r; - }); -} - -function transformStreamDefaultSinkAbortAlgorithm<I, O>( - stream: TransformStreamImpl<I, O>, - reason: any, -): Promise<void> { - transformStreamError(stream, reason); - return Promise.resolve(undefined); -} - -function transformStreamDefaultSinkCloseAlgorithm<I, O>( - stream: TransformStreamImpl<I, O>, -): Promise<void> { - const readable = stream[sym.readable]; - const controller = stream[sym.transformStreamController]; - const flushPromise = controller[sym.flushAlgorithm](); - transformStreamDefaultControllerClearAlgorithms(controller); - return flushPromise.then( - () => { - if (readable[sym.state] === "errored") { - throw readable[sym.storedError]; - } - const readableController = readable[ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl<O>; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(readableController) - ) { - readableStreamDefaultControllerClose(readableController); - } - }, - (r) => { - transformStreamError(stream, r); - throw readable[sym.storedError]; - }, - ); -} - -function transformStreamDefaultSinkWriteAlgorithm<I, O>( - stream: TransformStreamImpl<I, O>, - chunk: I, -): Promise<void> { - assert(stream[sym.writable][sym.state] === "writable"); - const controller = stream[sym.transformStreamController]; - if (stream[sym.backpressure]) { - const backpressureChangePromise = stream[sym.backpressureChangePromise]; - assert(backpressureChangePromise); - return backpressureChangePromise.promise.then(() => { - const writable = stream[sym.writable]; - const state = writable[sym.state]; - if (state === "erroring") { - throw writable[sym.storedError]; - } - assert(state === "writable"); - return transformStreamDefaultControllerPerformTransform( - controller, - chunk, - ); - }); - } - return transformStreamDefaultControllerPerformTransform(controller, chunk); -} - -function transformStreamDefaultSourcePullAlgorithm<I, O>( - stream: TransformStreamImpl<I, O>, -): Promise<void> { - assert(stream[sym.backpressure] === true); - assert(stream[sym.backpressureChangePromise] !== undefined); - transformStreamSetBackpressure(stream, false); - return stream[sym.backpressureChangePromise]!.promise; -} - -function transformStreamError<I, O>( - stream: TransformStreamImpl<I, O>, - e: any, -): void { - readableStreamDefaultControllerError( - stream[sym.readable][ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl<O>, - e, - ); - transformStreamErrorWritableAndUnblockWrite(stream, e); -} - -export function transformStreamDefaultControllerTerminate<I, O>( - controller: TransformStreamDefaultControllerImpl<I, O>, -): void { - const stream = controller[sym.controlledTransformStream]; - const readableController = stream[sym.readable][ - sym.readableStreamController - ] as ReadableStreamDefaultControllerImpl<O>; - readableStreamDefaultControllerClose(readableController); - const error = new TypeError("TransformStream is closed."); - transformStreamErrorWritableAndUnblockWrite(stream, error); -} - -function transformStreamErrorWritableAndUnblockWrite<I, O>( - stream: TransformStreamImpl<I, O>, - e: any, -): void { - transformStreamDefaultControllerClearAlgorithms( - stream[sym.transformStreamController], - ); - writableStreamDefaultControllerErrorIfNeeded( - stream[sym.writable][sym.writableStreamController]!, - e, - ); - if (stream[sym.backpressure]) { - transformStreamSetBackpressure(stream, false); - } -} - -function transformStreamSetBackpressure<I, O>( - stream: TransformStreamImpl<I, O>, - backpressure: boolean, -): void { - assert(stream[sym.backpressure] !== backpressure); - if (stream[sym.backpressureChangePromise] !== undefined) { - stream[sym.backpressureChangePromise]!.resolve!(undefined); - } - stream[sym.backpressureChangePromise] = getDeferred<void>(); - stream[sym.backpressure] = backpressure; -} - -function transferArrayBuffer(buffer: ArrayBuffer): ArrayBuffer { - assert(!isDetachedBuffer(buffer)); - const transferredIshVersion = buffer.slice(0); - - Object.defineProperty(buffer, "byteLength", { - get(): number { - return 0; - }, - }); - (buffer as any)[sym.isFakeDetached] = true; - - return transferredIshVersion; -} - -export function validateAndNormalizeHighWaterMark( - highWaterMark: number, -): number { - highWaterMark = Number(highWaterMark); - if (Number.isNaN(highWaterMark) || highWaterMark < 0) { - throw new RangeError( - `highWaterMark must be a positive number or Infinity. Received: ${highWaterMark}.`, - ); - } - return highWaterMark; -} - -export function writableStreamAbort<W>( - stream: WritableStreamImpl<W>, - reason: any, -): Promise<void> { - const state = stream[sym.state]; - if (state === "closed" || state === "errored") { - return Promise.resolve(undefined); - } - if (stream[sym.pendingAbortRequest]) { - return stream[sym.pendingAbortRequest]!.promise.promise; - } - assert(state === "writable" || state === "erroring"); - let wasAlreadyErroring = false; - if (state === "erroring") { - wasAlreadyErroring = true; - reason = undefined; - } - const promise = getDeferred<void>(); - stream[sym.pendingAbortRequest] = { promise, reason, wasAlreadyErroring }; - - if (wasAlreadyErroring === false) { - writableStreamStartErroring(stream, reason); - } - return promise.promise; -} - -function writableStreamAddWriteRequest<W>( - stream: WritableStreamImpl<W>, -): Promise<void> { - assert(isWritableStream(stream)); - assert(stream[sym.state] === "writable"); - const promise = getDeferred<void>(); - stream[sym.writeRequests].push(promise); - return promise.promise; -} - -export function writableStreamClose<W>( - stream: WritableStreamImpl<W>, -): Promise<void> { - const state = stream[sym.state]; - if (state === "closed" || state === "errored") { - return Promise.reject( - new TypeError( - "Cannot close an already closed or errored WritableStream.", - ), - ); - } - assert(!writableStreamCloseQueuedOrInFlight(stream)); - const promise = getDeferred<void>(); - stream[sym.closeRequest] = promise; - const writer = stream[sym.writer]; - if (writer && stream[sym.backpressure] && state === "writable") { - writer[sym.readyPromise].resolve!(); - writer[sym.readyPromise].resolve = undefined; - writer[sym.readyPromise].reject = undefined; - } - writableStreamDefaultControllerClose(stream[sym.writableStreamController]!); - return promise.promise; -} - -export function writableStreamCloseQueuedOrInFlight<W>( - stream: WritableStreamImpl<W>, -): boolean { - return !( - stream[sym.closeRequest] === undefined && - stream[sym.inFlightCloseRequest] === undefined - ); -} - -function writableStreamDealWithRejection<W>( - stream: WritableStreamImpl<W>, - error: any, -): void { - const state = stream[sym.state]; - if (state === "writable") { - writableStreamStartErroring(stream, error); - return; - } - assert(state === "erroring"); - writableStreamFinishErroring(stream); -} - -function writableStreamDefaultControllerAdvanceQueueIfNeeded<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): void { - const stream = controller[sym.controlledWritableStream]; - if (!controller[sym.started]) { - return; - } - if (stream[sym.inFlightWriteRequest]) { - return; - } - const state = stream[sym.state]; - assert(state !== "closed" && state !== "errored"); - if (state === "erroring") { - writableStreamFinishErroring(stream); - return; - } - if (!controller[sym.queue].length) { - return; - } - const writeRecord = peekQueueValue(controller); - if (writeRecord === "close") { - writableStreamDefaultControllerProcessClose(controller); - } else { - writableStreamDefaultControllerProcessWrite(controller, writeRecord.chunk); - } -} - -export function writableStreamDefaultControllerClearAlgorithms<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): void { - (controller as any)[sym.writeAlgorithm] = undefined; - (controller as any)[sym.closeAlgorithm] = undefined; - (controller as any)[sym.abortAlgorithm] = undefined; - (controller as any)[sym.strategySizeAlgorithm] = undefined; -} - -function writableStreamDefaultControllerClose<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): void { - enqueueValueWithSize(controller, "close", 0); - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); -} - -export function writableStreamDefaultControllerError<W>( - controller: WritableStreamDefaultControllerImpl<W>, - error: any, -): void { - const stream = controller[sym.controlledWritableStream]; - assert(stream[sym.state] === "writable"); - writableStreamDefaultControllerClearAlgorithms(controller); - writableStreamStartErroring(stream, error); -} - -function writableStreamDefaultControllerErrorIfNeeded<W>( - controller: WritableStreamDefaultControllerImpl<W>, - error: any, -): void { - if (controller[sym.controlledWritableStream][sym.state] === "writable") { - writableStreamDefaultControllerError(controller, error); - } -} - -function writableStreamDefaultControllerGetBackpressure<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): boolean { - const desiredSize = writableStreamDefaultControllerGetDesiredSize(controller); - return desiredSize <= 0; -} - -function writableStreamDefaultControllerGetChunkSize<W>( - controller: WritableStreamDefaultControllerImpl<W>, - chunk: W, -): number { - let returnValue: number; - try { - returnValue = controller[sym.strategySizeAlgorithm](chunk); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return 1; - } - return returnValue; -} - -function writableStreamDefaultControllerGetDesiredSize<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): number { - return controller[sym.strategyHWM] - controller[sym.queueTotalSize]; -} - -function writableStreamDefaultControllerProcessClose<W>( - controller: WritableStreamDefaultControllerImpl<W>, -): void { - const stream = controller[sym.controlledWritableStream]; - writableStreamMarkCloseRequestInFlight(stream); - dequeueValue(controller); - assert(controller[sym.queue].length === 0); - const sinkClosePromise = controller[sym.closeAlgorithm](); - writableStreamDefaultControllerClearAlgorithms(controller); - setPromiseIsHandledToTrue( - sinkClosePromise.then( - () => { - writableStreamFinishInFlightClose(stream); - }, - (reason) => { - writableStreamFinishInFlightCloseWithError(stream, reason); - }, - ), - ); -} - -function writableStreamDefaultControllerProcessWrite<W>( - controller: WritableStreamDefaultControllerImpl<W>, - chunk: W, -): void { - const stream = controller[sym.controlledWritableStream]; - writableStreamMarkFirstWriteRequestInFlight(stream); - const sinkWritePromise = controller[sym.writeAlgorithm](chunk); - setPromiseIsHandledToTrue( - sinkWritePromise.then( - () => { - writableStreamFinishInFlightWrite(stream); - const state = stream[sym.state]; - assert(state === "writable" || state === "erroring"); - dequeueValue(controller); - if ( - !writableStreamCloseQueuedOrInFlight(stream) && - state === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, - (reason) => { - if (stream[sym.state] === "writable") { - writableStreamDefaultControllerClearAlgorithms(controller); - } - writableStreamFinishInFlightWriteWithError(stream, reason); - }, - ), - ); -} - -function writableStreamDefaultControllerWrite<W>( - controller: WritableStreamDefaultControllerImpl<W>, - chunk: W, - chunkSize: number, -): void { - const writeRecord = { chunk }; - try { - enqueueValueWithSize(controller, writeRecord, chunkSize); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return; - } - const stream = controller[sym.controlledWritableStream]; - if ( - !writableStreamCloseQueuedOrInFlight(stream) && - stream[sym.state] === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); -} - -export function writableStreamDefaultWriterAbort<W>( - writer: WritableStreamDefaultWriterImpl<W>, - reason: any, -): Promise<void> { - const stream = writer[sym.ownerWritableStream]; - assert(stream); - return writableStreamAbort(stream, reason); -} - -export function writableStreamDefaultWriterClose<W>( - writer: WritableStreamDefaultWriterImpl<W>, -): Promise<void> { - const stream = writer[sym.ownerWritableStream]; - assert(stream); - return writableStreamClose(stream); -} - -function writableStreamDefaultWriterCloseWithErrorPropagation<W>( - writer: WritableStreamDefaultWriterImpl<W>, -): Promise<void> { - const stream = writer[sym.ownerWritableStream]; - assert(stream); - const state = stream[sym.state]; - if (writableStreamCloseQueuedOrInFlight(stream) || state === "closed") { - return Promise.resolve(); - } - if (state === "errored") { - return Promise.reject(stream[sym.storedError]); - } - assert(state === "writable" || state === "erroring"); - return writableStreamDefaultWriterClose(writer); -} - -function writableStreamDefaultWriterEnsureClosePromiseRejected<W>( - writer: WritableStreamDefaultWriterImpl<W>, - error: any, -): void { - if (writer[sym.closedPromise].reject) { - writer[sym.closedPromise].reject!(error); - } else { - writer[sym.closedPromise] = { - promise: Promise.reject(error), - }; - } - setPromiseIsHandledToTrue(writer[sym.closedPromise].promise); -} - -function writableStreamDefaultWriterEnsureReadyPromiseRejected<W>( - writer: WritableStreamDefaultWriterImpl<W>, - error: any, -): void { - if (writer[sym.readyPromise].reject) { - writer[sym.readyPromise].reject!(error); - writer[sym.readyPromise].reject = undefined; - writer[sym.readyPromise].resolve = undefined; - } else { - writer[sym.readyPromise] = { - promise: Promise.reject(error), - }; - } - setPromiseIsHandledToTrue(writer[sym.readyPromise].promise); -} - -export function writableStreamDefaultWriterWrite<W>( - writer: WritableStreamDefaultWriterImpl<W>, - chunk: W, -): Promise<void> { - const stream = writer[sym.ownerWritableStream]; - assert(stream); - const controller = stream[sym.writableStreamController]; - assert(controller); - const chunkSize = writableStreamDefaultControllerGetChunkSize( - controller, - chunk, - ); - if (stream !== writer[sym.ownerWritableStream]) { - return Promise.reject("Writer has incorrect WritableStream."); - } - const state = stream[sym.state]; - if (state === "errored") { - return Promise.reject(stream[sym.storedError]); - } - if (writableStreamCloseQueuedOrInFlight(stream) || state === "closed") { - return Promise.reject(new TypeError("The stream is closed or closing.")); - } - if (state === "erroring") { - return Promise.reject(stream[sym.storedError]); - } - assert(state === "writable"); - const promise = writableStreamAddWriteRequest(stream); - writableStreamDefaultControllerWrite(controller, chunk, chunkSize); - return promise; -} - -export function writableStreamDefaultWriterGetDesiredSize<W>( - writer: WritableStreamDefaultWriterImpl<W>, -): number | null { - const stream = writer[sym.ownerWritableStream]; - const state = stream[sym.state]; - if (state === "errored" || state === "erroring") { - return null; - } - if (state === "closed") { - return 0; - } - return writableStreamDefaultControllerGetDesiredSize( - stream[sym.writableStreamController]!, - ); -} - -export function writableStreamDefaultWriterRelease<W>( - writer: WritableStreamDefaultWriterImpl<W>, -): void { - const stream = writer[sym.ownerWritableStream]; - assert(stream); - assert(stream[sym.writer] === writer); - const releasedError = new TypeError( - "Writer was released and can no longer be used to monitor the stream's closedness.", - ); - writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError); - writableStreamDefaultWriterEnsureClosePromiseRejected(writer, releasedError); - stream[sym.writer] = undefined; - (writer as any)[sym.ownerWritableStream] = undefined; -} - -function writableStreamFinishErroring<W>(stream: WritableStreamImpl<W>): void { - assert(stream[sym.state] === "erroring"); - assert(!writableStreamHasOperationMarkedInFlight(stream)); - stream[sym.state] = "errored"; - stream[sym.writableStreamController]![sym.errorSteps](); - const storedError = stream[sym.storedError]; - for (const writeRequest of stream[sym.writeRequests]) { - assert(writeRequest.reject); - writeRequest.reject(storedError); - } - stream[sym.writeRequests] = []; - if (!stream[sym.pendingAbortRequest]) { - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const abortRequest = stream[sym.pendingAbortRequest]; - assert(abortRequest); - stream[sym.pendingAbortRequest] = undefined; - if (abortRequest.wasAlreadyErroring) { - assert(abortRequest.promise.reject); - abortRequest.promise.reject(storedError); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const promise = stream[sym.writableStreamController]; - setPromiseIsHandledToTrue( - promise.then( - () => { - assert(abortRequest.promise.resolve); - abortRequest.promise.resolve(); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, - (reason) => { - assert(abortRequest.promise.reject); - abortRequest.promise.reject(reason); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, - ), - ); -} - -function writableStreamFinishInFlightClose<W>( - stream: WritableStreamImpl<W>, -): void { - assert(stream[sym.inFlightCloseRequest]); - stream[sym.inFlightCloseRequest]?.resolve!(); - stream[sym.inFlightCloseRequest] = undefined; - const state = stream[sym.state]; - assert(state === "writable" || state === "erroring"); - if (state === "erroring") { - stream[sym.storedError] = undefined; - if (stream[sym.pendingAbortRequest]) { - stream[sym.pendingAbortRequest]!.promise.resolve!(); - stream[sym.pendingAbortRequest] = undefined; - } - } - stream[sym.state] = "closed"; - const writer = stream[sym.writer]; - if (writer) { - writer[sym.closedPromise].resolve!(); - } - assert(stream[sym.pendingAbortRequest] === undefined); - assert(stream[sym.storedError] === undefined); -} - -function writableStreamFinishInFlightCloseWithError<W>( - stream: WritableStreamImpl<W>, - error: any, -): void { - assert(stream[sym.inFlightCloseRequest]); - stream[sym.inFlightCloseRequest]?.reject!(error); - stream[sym.inFlightCloseRequest] = undefined; - assert(stream[sym.state] === "writable" || stream[sym.state] === "erroring"); - if (stream[sym.pendingAbortRequest]) { - stream[sym.pendingAbortRequest]?.promise.reject!(error); - stream[sym.pendingAbortRequest] = undefined; - } - writableStreamDealWithRejection(stream, error); -} - -function writableStreamFinishInFlightWrite<W>( - stream: WritableStreamImpl<W>, -): void { - assert(stream[sym.inFlightWriteRequest]); - stream[sym.inFlightWriteRequest]!.resolve(); - stream[sym.inFlightWriteRequest] = undefined; -} - -function writableStreamFinishInFlightWriteWithError<W>( - stream: WritableStreamImpl<W>, - error: any, -): void { - assert(stream[sym.inFlightWriteRequest]); - stream[sym.inFlightWriteRequest]!.reject!(error); - stream[sym.inFlightWriteRequest] = undefined; - assert(stream[sym.state] === "writable" || stream[sym.state] === "erroring"); - writableStreamDealWithRejection(stream, error); -} - -function writableStreamHasOperationMarkedInFlight<W>( - stream: WritableStreamImpl<W>, -): boolean { - return !( - stream[sym.inFlightWriteRequest] === undefined && - stream[sym.inFlightCloseRequest] === undefined - ); -} - -function writableStreamMarkCloseRequestInFlight<W>( - stream: WritableStreamImpl<W>, -): void { - assert(stream[sym.inFlightCloseRequest] === undefined); - assert(stream[sym.closeRequest] !== undefined); - stream[sym.inFlightCloseRequest] = stream[sym.closeRequest]; - stream[sym.closeRequest] = undefined; -} - -function writableStreamMarkFirstWriteRequestInFlight<W>( - stream: WritableStreamImpl<W>, -): void { - assert(stream[sym.inFlightWriteRequest] === undefined); - assert(stream[sym.writeRequests].length); - const writeRequest = stream[sym.writeRequests].shift(); - stream[sym.inFlightWriteRequest] = writeRequest; -} - -function writableStreamRejectCloseAndClosedPromiseIfNeeded<W>( - stream: WritableStreamImpl<W>, -): void { - assert(stream[sym.state] === "errored"); - if (stream[sym.closeRequest]) { - assert(stream[sym.inFlightCloseRequest] === undefined); - stream[sym.closeRequest]!.reject!(stream[sym.storedError]); - stream[sym.closeRequest] = undefined; - } - const writer = stream[sym.writer]; - if (writer) { - writer[sym.closedPromise].reject!(stream[sym.storedError]); - setPromiseIsHandledToTrue(writer[sym.closedPromise].promise); - } -} - -function writableStreamStartErroring<W>( - stream: WritableStreamImpl<W>, - reason: any, -): void { - assert(stream[sym.storedError] === undefined); - assert(stream[sym.state] === "writable"); - const controller = stream[sym.writableStreamController]; - assert(controller); - stream[sym.state] = "erroring"; - stream[sym.storedError] = reason; - const writer = stream[sym.writer]; - if (writer) { - writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); - } - if ( - !writableStreamHasOperationMarkedInFlight(stream) && - controller[sym.started] - ) { - writableStreamFinishErroring(stream); - } -} - -function writableStreamUpdateBackpressure<W>( - stream: WritableStreamImpl<W>, - backpressure: boolean, -): void { - assert(stream[sym.state] === "writable"); - assert(!writableStreamCloseQueuedOrInFlight(stream)); - const writer = stream[sym.writer]; - if (writer && backpressure !== stream[sym.backpressure]) { - if (backpressure) { - writer[sym.readyPromise] = getDeferred(); - } else { - assert(backpressure === false); - writer[sym.readyPromise].resolve!(); - writer[sym.readyPromise].resolve = undefined; - writer[sym.readyPromise].reject = undefined; - } - } - stream[sym.backpressure] = backpressure; -} - -/* eslint-enable */ diff --git a/cli/js/web/streams/queuing_strategy.ts b/cli/js/web/streams/queuing_strategy.ts deleted file mode 100644 index 818f0d51b..000000000 --- a/cli/js/web/streams/queuing_strategy.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -export class CountQueuingStrategyImpl implements CountQueuingStrategy { - highWaterMark: number; - - constructor({ highWaterMark }: { highWaterMark: number }) { - this.highWaterMark = highWaterMark; - } - - size(): 1 { - return 1; - } - - [customInspect](): string { - return `${this.constructor.name} { highWaterMark: ${ - String(this.highWaterMark) - }, size: f }`; - } -} - -Object.defineProperty(CountQueuingStrategyImpl.prototype, "size", { - enumerable: true, -}); - -setFunctionName(CountQueuingStrategyImpl, "CountQueuingStrategy"); - -export class ByteLengthQueuingStrategyImpl - implements ByteLengthQueuingStrategy { - highWaterMark: number; - - constructor({ highWaterMark }: { highWaterMark: number }) { - this.highWaterMark = highWaterMark; - } - - size(chunk: ArrayBufferView): number { - return chunk.byteLength; - } - - [customInspect](): string { - return `${this.constructor.name} { highWaterMark: ${ - String(this.highWaterMark) - }, size: f }`; - } -} - -Object.defineProperty(ByteLengthQueuingStrategyImpl.prototype, "size", { - enumerable: true, -}); - -setFunctionName(CountQueuingStrategyImpl, "CountQueuingStrategy"); diff --git a/cli/js/web/streams/readable_byte_stream_controller.ts b/cli/js/web/streams/readable_byte_stream_controller.ts deleted file mode 100644 index eb4374604..000000000 --- a/cli/js/web/streams/readable_byte_stream_controller.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - BufferQueueItem, - CancelAlgorithm, - isDetachedBuffer, - isReadableByteStreamController, - PullAlgorithm, - resetQueue, - readableByteStreamControllerCallPullIfNeeded, - readableByteStreamControllerClearAlgorithms, - readableByteStreamControllerClose, - readableByteStreamControllerEnqueue, - readableByteStreamControllerError, - readableByteStreamControllerGetDesiredSize, - readableByteStreamControllerHandleQueueDrain, - readableStreamAddReadRequest, - readableStreamHasDefaultReader, - readableStreamGetNumReadRequests, - readableStreamCreateReadResult, -} from "./internals.ts"; -import type { ReadableStreamImpl } from "./readable_stream.ts"; -import * as sym from "./symbols.ts"; -import { assert } from "../../util.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -export class ReadableByteStreamControllerImpl - implements ReadableByteStreamController { - [sym.autoAllocateChunkSize]: number | undefined; - [sym.byobRequest]: undefined; - [sym.cancelAlgorithm]: CancelAlgorithm; - [sym.closeRequested]: boolean; - [sym.controlledReadableByteStream]: ReadableStreamImpl<Uint8Array>; - [sym.pullAgain]: boolean; - [sym.pullAlgorithm]: PullAlgorithm; - [sym.pulling]: boolean; - [sym.queue]: BufferQueueItem[]; - [sym.queueTotalSize]: number; - [sym.started]: boolean; - [sym.strategyHWM]: number; - - private constructor() { - throw new TypeError( - "ReadableByteStreamController's constructor cannot be called.", - ); - } - - get byobRequest(): undefined { - return undefined; - } - - get desiredSize(): number | null { - if (!isReadableByteStreamController(this)) { - throw new TypeError("Invalid ReadableByteStreamController."); - } - return readableByteStreamControllerGetDesiredSize(this); - } - - close(): void { - if (!isReadableByteStreamController(this)) { - throw new TypeError("Invalid ReadableByteStreamController."); - } - if (this[sym.closeRequested]) { - throw new TypeError("Closed already requested."); - } - if (this[sym.controlledReadableByteStream][sym.state] !== "readable") { - throw new TypeError( - "ReadableByteStreamController's stream is not in a readable state.", - ); - } - readableByteStreamControllerClose(this); - } - - enqueue(chunk: ArrayBufferView): void { - if (!isReadableByteStreamController(this)) { - throw new TypeError("Invalid ReadableByteStreamController."); - } - if (this[sym.closeRequested]) { - throw new TypeError("Closed already requested."); - } - if (this[sym.controlledReadableByteStream][sym.state] !== "readable") { - throw new TypeError( - "ReadableByteStreamController's stream is not in a readable state.", - ); - } - if (!ArrayBuffer.isView(chunk)) { - throw new TypeError( - "You can only enqueue array buffer views when using a ReadableByteStreamController", - ); - } - if (isDetachedBuffer(chunk.buffer)) { - throw new TypeError("Cannot enqueue a view onto a detached ArrayBuffer"); - } - readableByteStreamControllerEnqueue(this, chunk); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error(error?: any): void { - if (!isReadableByteStreamController(this)) { - throw new TypeError("Invalid ReadableByteStreamController."); - } - readableByteStreamControllerError(this, error); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [sym.cancelSteps](reason: any): PromiseLike<void> { - // 3.11.5.1.1 If this.[[pendingPullIntos]] is not empty, - resetQueue(this); - const result = this[sym.cancelAlgorithm](reason); - readableByteStreamControllerClearAlgorithms(this); - return result; - } - - [sym.pullSteps](): Promise<ReadableStreamReadResult<Uint8Array>> { - const stream = this[sym.controlledReadableByteStream]; - assert(readableStreamHasDefaultReader(stream)); - if (this[sym.queueTotalSize] > 0) { - assert(readableStreamGetNumReadRequests(stream) === 0); - const entry = this[sym.queue].shift(); - assert(entry); - this[sym.queueTotalSize] -= entry.size; - readableByteStreamControllerHandleQueueDrain(this); - const view = new Uint8Array(entry.value, entry.offset, entry.size); - return Promise.resolve( - readableStreamCreateReadResult( - view, - false, - stream[sym.reader]![sym.forAuthorCode], - ), - ); - } - // 3.11.5.2.5 If autoAllocateChunkSize is not undefined, - const promise = readableStreamAddReadRequest(stream); - readableByteStreamControllerCallPullIfNeeded(this); - return promise; - } - - [customInspect](): string { - return `${this.constructor.name} { byobRequest: ${ - String(this.byobRequest) - }, desiredSize: ${String(this.desiredSize)} }`; - } -} - -setFunctionName( - ReadableByteStreamControllerImpl, - "ReadableByteStreamController", -); diff --git a/cli/js/web/streams/readable_stream.ts b/cli/js/web/streams/readable_stream.ts deleted file mode 100644 index 086535bd2..000000000 --- a/cli/js/web/streams/readable_stream.ts +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - acquireReadableStreamDefaultReader, - initializeReadableStream, - isReadableStream, - isReadableStreamLocked, - isUnderlyingByteSource, - isWritableStream, - isWritableStreamLocked, - makeSizeAlgorithmFromSizeFunction, - setPromiseIsHandledToTrue, - readableStreamCancel, - ReadableStreamGenericReader, - readableStreamPipeTo, - readableStreamTee, - setUpReadableByteStreamControllerFromUnderlyingSource, - setUpReadableStreamDefaultControllerFromUnderlyingSource, - validateAndNormalizeHighWaterMark, -} from "./internals.ts"; -import type { ReadableByteStreamControllerImpl } from "./readable_byte_stream_controller.ts"; -import { ReadableStreamAsyncIteratorPrototype } from "./readable_stream_async_iterator.ts"; -import type { ReadableStreamDefaultControllerImpl } from "./readable_stream_default_controller.ts"; -import * as sym from "./symbols.ts"; -import { customInspect } from "../console.ts"; -import { AbortSignalImpl } from "../abort_signal.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class ReadableStreamImpl<R = any> implements ReadableStream<R> { - [sym.disturbed]: boolean; - [sym.readableStreamController]: - | ReadableStreamDefaultControllerImpl<R> - | ReadableByteStreamControllerImpl; - [sym.reader]: ReadableStreamGenericReader<R> | undefined; - [sym.state]: "readable" | "closed" | "errored"; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [sym.storedError]: any; - - constructor( - underlyingSource: UnderlyingByteSource | UnderlyingSource<R> = {}, - strategy: - | { - highWaterMark?: number; - size?: undefined; - } - | QueuingStrategy<R> = {}, - ) { - initializeReadableStream(this); - const { size } = strategy; - let { highWaterMark } = strategy; - const { type } = underlyingSource; - - if (isUnderlyingByteSource(underlyingSource)) { - if (size !== undefined) { - throw new RangeError( - `When underlying source is "bytes", strategy.size must be undefined.`, - ); - } - highWaterMark = validateAndNormalizeHighWaterMark(highWaterMark ?? 0); - setUpReadableByteStreamControllerFromUnderlyingSource( - this, - underlyingSource, - highWaterMark, - ); - } else if (type === undefined) { - const sizeAlgorithm = makeSizeAlgorithmFromSizeFunction(size); - highWaterMark = validateAndNormalizeHighWaterMark(highWaterMark ?? 1); - setUpReadableStreamDefaultControllerFromUnderlyingSource( - this, - underlyingSource, - highWaterMark, - sizeAlgorithm, - ); - } else { - throw new RangeError( - `Valid values for underlyingSource are "bytes" or undefined. Received: "${type}".`, - ); - } - } - - get locked(): boolean { - if (!isReadableStream(this)) { - throw new TypeError("Invalid ReadableStream."); - } - return isReadableStreamLocked(this); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - cancel(reason?: any): Promise<void> { - if (!isReadableStream(this)) { - return Promise.reject(new TypeError("Invalid ReadableStream.")); - } - if (isReadableStreamLocked(this)) { - return Promise.reject( - new TypeError("Cannot cancel a locked ReadableStream."), - ); - } - return readableStreamCancel(this, reason); - } - - getIterator({ - preventCancel, - }: { preventCancel?: boolean } = {}): AsyncIterableIterator<R> { - if (!isReadableStream(this)) { - throw new TypeError("Invalid ReadableStream."); - } - const reader = acquireReadableStreamDefaultReader(this); - const iterator = Object.create(ReadableStreamAsyncIteratorPrototype); - iterator[sym.asyncIteratorReader] = reader; - iterator[sym.preventCancel] = Boolean(preventCancel); - return iterator; - } - - getReader({ mode }: { mode?: string } = {}): ReadableStreamDefaultReader<R> { - if (!isReadableStream(this)) { - throw new TypeError("Invalid ReadableStream."); - } - if (mode === undefined) { - return acquireReadableStreamDefaultReader(this, true); - } - mode = String(mode); - // 3.2.5.4.4 If mode is "byob", return ? AcquireReadableStreamBYOBReader(this, true). - throw new RangeError(`Unsupported mode "${mode}"`); - } - - pipeThrough<T>( - { - writable, - readable, - }: { - writable: WritableStream<R>; - readable: ReadableStream<T>; - }, - { preventClose, preventAbort, preventCancel, signal }: PipeOptions = {}, - ): ReadableStream<T> { - if (!isReadableStream(this)) { - throw new TypeError("Invalid ReadableStream."); - } - if (!isWritableStream(writable)) { - throw new TypeError("writable is not a valid WritableStream."); - } - if (!isReadableStream(readable)) { - throw new TypeError("readable is not a valid ReadableStream."); - } - preventClose = Boolean(preventClose); - preventAbort = Boolean(preventAbort); - preventCancel = Boolean(preventCancel); - if (signal && !(signal instanceof AbortSignalImpl)) { - throw new TypeError("Invalid signal."); - } - if (isReadableStreamLocked(this)) { - throw new TypeError("ReadableStream is locked."); - } - if (isWritableStreamLocked(writable)) { - throw new TypeError("writable is locked."); - } - const promise = readableStreamPipeTo( - this, - writable, - preventClose, - preventAbort, - preventCancel, - signal, - ); - setPromiseIsHandledToTrue(promise); - return readable; - } - - pipeTo( - dest: WritableStream<R>, - { preventClose, preventAbort, preventCancel, signal }: PipeOptions = {}, - ): Promise<void> { - if (!isReadableStream(this)) { - return Promise.reject(new TypeError("Invalid ReadableStream.")); - } - if (!isWritableStream(dest)) { - return Promise.reject( - new TypeError("dest is not a valid WritableStream."), - ); - } - preventClose = Boolean(preventClose); - preventAbort = Boolean(preventAbort); - preventCancel = Boolean(preventCancel); - if (signal && !(signal instanceof AbortSignalImpl)) { - return Promise.reject(new TypeError("Invalid signal.")); - } - if (isReadableStreamLocked(this)) { - return Promise.reject(new TypeError("ReadableStream is locked.")); - } - if (isWritableStreamLocked(dest)) { - return Promise.reject(new TypeError("dest is locked.")); - } - return readableStreamPipeTo( - this, - dest, - preventClose, - preventAbort, - preventCancel, - signal, - ); - } - - tee(): [ReadableStreamImpl<R>, ReadableStreamImpl<R>] { - if (!isReadableStream(this)) { - throw new TypeError("Invalid ReadableStream."); - } - return readableStreamTee(this, false); - } - - [customInspect](): string { - return `${this.constructor.name} { locked: ${String(this.locked)} }`; - } - - [Symbol.asyncIterator]( - options: { - preventCancel?: boolean; - } = {}, - ): AsyncIterableIterator<R> { - return this.getIterator(options); - } -} - -setFunctionName(ReadableStreamImpl, "ReadableStream"); diff --git a/cli/js/web/streams/readable_stream_async_iterator.ts b/cli/js/web/streams/readable_stream_async_iterator.ts deleted file mode 100644 index c6b9759a5..000000000 --- a/cli/js/web/streams/readable_stream_async_iterator.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import * as sym from "./symbols.ts"; -import { - isReadableStreamAsyncIterator, - ReadableStreamAsyncIterator, - readableStreamCreateReadResult, - readableStreamReaderGenericCancel, - readableStreamReaderGenericRelease, - readableStreamDefaultReaderRead, -} from "./internals.ts"; -import { assert } from "../../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const AsyncIteratorPrototype: AsyncIterableIterator<any> = Object - .getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype); - -export const ReadableStreamAsyncIteratorPrototype: ReadableStreamAsyncIterator = - Object.setPrototypeOf({ - next( - this: ReadableStreamAsyncIterator, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ): Promise<ReadableStreamReadResult<any>> { - if (!isReadableStreamAsyncIterator(this)) { - return Promise.reject( - new TypeError("invalid ReadableStreamAsyncIterator."), - ); - } - const reader = this[sym.asyncIteratorReader]; - if (!reader[sym.ownerReadableStream]) { - return Promise.reject( - new TypeError("reader owner ReadableStream is undefined."), - ); - } - return readableStreamDefaultReaderRead(reader).then((result) => { - assert(typeof result === "object"); - const { done } = result; - assert(typeof done === "boolean"); - if (done) { - readableStreamReaderGenericRelease(reader); - } - const { value } = result; - return readableStreamCreateReadResult(value, done, true); - }); - }, - return( - this: ReadableStreamAsyncIterator, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value?: any | PromiseLike<any>, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ): Promise<ReadableStreamReadResult<any>> { - if (!isReadableStreamAsyncIterator(this)) { - return Promise.reject( - new TypeError("invalid ReadableStreamAsyncIterator."), - ); - } - const reader = this[sym.asyncIteratorReader]; - if (!reader[sym.ownerReadableStream]) { - return Promise.reject( - new TypeError("reader owner ReadableStream is undefined."), - ); - } - if (reader[sym.readRequests].length) { - return Promise.reject( - new TypeError("reader has outstanding read requests."), - ); - } - if (!this[sym.preventCancel]) { - const result = readableStreamReaderGenericCancel(reader, value); - readableStreamReaderGenericRelease(reader); - return result.then(() => - readableStreamCreateReadResult(value, true, true) - ); - } - readableStreamReaderGenericRelease(reader); - return Promise.resolve( - readableStreamCreateReadResult(value, true, true), - ); - }, - }, AsyncIteratorPrototype); diff --git a/cli/js/web/streams/readable_stream_default_controller.ts b/cli/js/web/streams/readable_stream_default_controller.ts deleted file mode 100644 index 8536712b9..000000000 --- a/cli/js/web/streams/readable_stream_default_controller.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - CancelAlgorithm, - dequeueValue, - isReadableStreamDefaultController, - Pair, - PullAlgorithm, - readableStreamAddReadRequest, - readableStreamClose, - readableStreamCreateReadResult, - readableStreamDefaultControllerCallPullIfNeeded, - readableStreamDefaultControllerCanCloseOrEnqueue, - readableStreamDefaultControllerClearAlgorithms, - readableStreamDefaultControllerClose, - readableStreamDefaultControllerEnqueue, - readableStreamDefaultControllerError, - readableStreamDefaultControllerGetDesiredSize, - resetQueue, - SizeAlgorithm, -} from "./internals.ts"; -import type { ReadableStreamImpl } from "./readable_stream.ts"; -import * as sym from "./symbols.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class ReadableStreamDefaultControllerImpl<R = any> - implements ReadableStreamDefaultController<R> { - [sym.cancelAlgorithm]: CancelAlgorithm; - [sym.closeRequested]: boolean; - [sym.controlledReadableStream]: ReadableStreamImpl<R>; - [sym.pullAgain]: boolean; - [sym.pullAlgorithm]: PullAlgorithm; - [sym.pulling]: boolean; - [sym.queue]: Array<Pair<R>>; - [sym.queueTotalSize]: number; - [sym.started]: boolean; - [sym.strategyHWM]: number; - [sym.strategySizeAlgorithm]: SizeAlgorithm<R>; - - private constructor() { - throw new TypeError( - "ReadableStreamDefaultController's constructor cannot be called.", - ); - } - - get desiredSize(): number | null { - if (!isReadableStreamDefaultController(this)) { - throw new TypeError("Invalid ReadableStreamDefaultController."); - } - return readableStreamDefaultControllerGetDesiredSize(this); - } - - close(): void { - if (!isReadableStreamDefaultController(this)) { - throw new TypeError("Invalid ReadableStreamDefaultController."); - } - if (!readableStreamDefaultControllerCanCloseOrEnqueue(this)) { - throw new TypeError( - "ReadableStreamDefaultController cannot close or enqueue.", - ); - } - readableStreamDefaultControllerClose(this); - } - - enqueue(chunk: R): void { - if (!isReadableStreamDefaultController(this)) { - throw new TypeError("Invalid ReadableStreamDefaultController."); - } - if (!readableStreamDefaultControllerCanCloseOrEnqueue(this)) { - throw new TypeError("ReadableSteamController cannot enqueue."); - } - return readableStreamDefaultControllerEnqueue(this, chunk); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error(error?: any): void { - if (!isReadableStreamDefaultController(this)) { - throw new TypeError("Invalid ReadableStreamDefaultController."); - } - readableStreamDefaultControllerError(this, error); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [sym.cancelSteps](reason?: any): PromiseLike<void> { - resetQueue(this); - const result = this[sym.cancelAlgorithm](reason); - readableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - [sym.pullSteps](): Promise<ReadableStreamReadResult<R>> { - const stream = this[sym.controlledReadableStream]; - if (this[sym.queue].length) { - const chunk = dequeueValue<R>(this); - if (this[sym.closeRequested] && this[sym.queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(this); - readableStreamClose(stream); - } else { - readableStreamDefaultControllerCallPullIfNeeded(this); - } - return Promise.resolve( - readableStreamCreateReadResult( - chunk, - false, - stream[sym.reader]![sym.forAuthorCode], - ), - ); - } - const pendingPromise = readableStreamAddReadRequest(stream); - readableStreamDefaultControllerCallPullIfNeeded(this); - return pendingPromise; - } - - [customInspect](): string { - return `${this.constructor.name} { desiredSize: ${ - String(this.desiredSize) - } }`; - } -} - -setFunctionName( - ReadableStreamDefaultControllerImpl, - "ReadableStreamDefaultController", -); diff --git a/cli/js/web/streams/readable_stream_default_reader.ts b/cli/js/web/streams/readable_stream_default_reader.ts deleted file mode 100644 index 88260effb..000000000 --- a/cli/js/web/streams/readable_stream_default_reader.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - Deferred, - isReadableStream, - isReadableStreamDefaultReader, - isReadableStreamLocked, - readableStreamDefaultReaderRead, - readableStreamReaderGenericCancel, - readableStreamReaderGenericInitialize, - readableStreamReaderGenericRelease, -} from "./internals.ts"; -import type { ReadableStreamImpl } from "./readable_stream.ts"; -import * as sym from "./symbols.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class ReadableStreamDefaultReaderImpl<R = any> - implements ReadableStreamDefaultReader<R> { - [sym.closedPromise]: Deferred<void>; - [sym.forAuthorCode]: boolean; - [sym.ownerReadableStream]: ReadableStreamImpl<R>; - [sym.readRequests]: Array<Deferred<ReadableStreamReadResult<R>>>; - - constructor(stream: ReadableStream<R>) { - if (!isReadableStream(stream)) { - throw new TypeError("stream is not a ReadableStream."); - } - if (isReadableStreamLocked(stream)) { - throw new TypeError("stream is locked."); - } - readableStreamReaderGenericInitialize(this, stream); - this[sym.readRequests] = []; - } - - get closed(): Promise<void> { - if (!isReadableStreamDefaultReader(this)) { - return Promise.reject( - new TypeError("Invalid ReadableStreamDefaultReader."), - ); - } - return ( - this[sym.closedPromise].promise ?? - Promise.reject(new TypeError("Invalid reader.")) - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - cancel(reason?: any): Promise<void> { - if (!isReadableStreamDefaultReader(this)) { - return Promise.reject( - new TypeError("Invalid ReadableStreamDefaultReader."), - ); - } - if (!this[sym.ownerReadableStream]) { - return Promise.reject(new TypeError("Invalid reader.")); - } - return readableStreamReaderGenericCancel(this, reason); - } - - read(): Promise<ReadableStreamReadResult<R>> { - if (!isReadableStreamDefaultReader(this)) { - return Promise.reject( - new TypeError("Invalid ReadableStreamDefaultReader."), - ); - } - if (!this[sym.ownerReadableStream]) { - return Promise.reject(new TypeError("Invalid reader.")); - } - return readableStreamDefaultReaderRead(this); - } - - releaseLock(): void { - if (!isReadableStreamDefaultReader(this)) { - throw new TypeError("Invalid ReadableStreamDefaultReader."); - } - if (this[sym.ownerReadableStream] === undefined) { - return; - } - if (this[sym.readRequests].length) { - throw new TypeError("Cannot release lock with pending read requests."); - } - readableStreamReaderGenericRelease(this); - } - - [customInspect](): string { - return `${this.constructor.name} { closed: Promise }`; - } -} - -setFunctionName(ReadableStreamDefaultReaderImpl, "ReadableStreamDefaultReader"); diff --git a/cli/js/web/streams/symbols.ts b/cli/js/web/streams/symbols.ts deleted file mode 100644 index 82d668598..000000000 --- a/cli/js/web/streams/symbols.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// The specification refers to internal slots. In most cases, ECMAScript -// Private Fields are not sufficient for these, as they are often accessed -// outside of the class itself and using a WeakMap gets really complex to hide -// this data from the public, therefore we will use unique symbols which are -// not available in the runtime. - -export const abortAlgorithm = Symbol("abortAlgorithm"); -export const abortSteps = Symbol("abortSteps"); -export const asyncIteratorReader = Symbol("asyncIteratorReader"); -export const autoAllocateChunkSize = Symbol("autoAllocateChunkSize"); -export const backpressure = Symbol("backpressure"); -export const backpressureChangePromise = Symbol("backpressureChangePromise"); -export const byobRequest = Symbol("byobRequest"); -export const cancelAlgorithm = Symbol("cancelAlgorithm"); -export const cancelSteps = Symbol("cancelSteps"); -export const closeAlgorithm = Symbol("closeAlgorithm"); -export const closedPromise = Symbol("closedPromise"); -export const closeRequest = Symbol("closeRequest"); -export const closeRequested = Symbol("closeRequested"); -export const controlledReadableByteStream = Symbol( - "controlledReadableByteStream", -); -export const controlledReadableStream = Symbol("controlledReadableStream"); -export const controlledTransformStream = Symbol("controlledTransformStream"); -export const controlledWritableStream = Symbol("controlledWritableStream"); -export const disturbed = Symbol("disturbed"); -export const errorSteps = Symbol("errorSteps"); -export const flushAlgorithm = Symbol("flushAlgorithm"); -export const forAuthorCode = Symbol("forAuthorCode"); -export const inFlightWriteRequest = Symbol("inFlightWriteRequest"); -export const inFlightCloseRequest = Symbol("inFlightCloseRequest"); -export const isFakeDetached = Symbol("isFakeDetached"); -export const ownerReadableStream = Symbol("ownerReadableStream"); -export const ownerWritableStream = Symbol("ownerWritableStream"); -export const pendingAbortRequest = Symbol("pendingAbortRequest"); -export const preventCancel = Symbol("preventCancel"); -export const pullAgain = Symbol("pullAgain"); -export const pullAlgorithm = Symbol("pullAlgorithm"); -export const pulling = Symbol("pulling"); -export const pullSteps = Symbol("pullSteps"); -export const queue = Symbol("queue"); -export const queueTotalSize = Symbol("queueTotalSize"); -export const readable = Symbol("readable"); -export const readableStreamController = Symbol("readableStreamController"); -export const reader = Symbol("reader"); -export const readRequests = Symbol("readRequests"); -export const readyPromise = Symbol("readyPromise"); -export const started = Symbol("started"); -export const state = Symbol("state"); -export const storedError = Symbol("storedError"); -export const strategyHWM = Symbol("strategyHWM"); -export const strategySizeAlgorithm = Symbol("strategySizeAlgorithm"); -export const transformAlgorithm = Symbol("transformAlgorithm"); -export const transformStreamController = Symbol("transformStreamController"); -export const writableStreamController = Symbol("writableStreamController"); -export const writeAlgorithm = Symbol("writeAlgorithm"); -export const writable = Symbol("writable"); -export const writer = Symbol("writer"); -export const writeRequests = Symbol("writeRequests"); diff --git a/cli/js/web/streams/transform_stream.ts b/cli/js/web/streams/transform_stream.ts deleted file mode 100644 index f6924aead..000000000 --- a/cli/js/web/streams/transform_stream.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - Deferred, - getDeferred, - initializeTransformStream, - invokeOrNoop, - isTransformStream, - makeSizeAlgorithmFromSizeFunction, - setUpTransformStreamDefaultControllerFromTransformer, - validateAndNormalizeHighWaterMark, -} from "./internals.ts"; -import type { ReadableStreamImpl } from "./readable_stream.ts"; -import * as sym from "./symbols.ts"; -import type { TransformStreamDefaultControllerImpl } from "./transform_stream_default_controller.ts"; -import type { WritableStreamImpl } from "./writable_stream.ts"; -import { customInspect, inspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class TransformStreamImpl<I = any, O = any> - implements TransformStream<I, O> { - [sym.backpressure]?: boolean; - [sym.backpressureChangePromise]?: Deferred<void>; - [sym.readable]: ReadableStreamImpl<O>; - [sym.transformStreamController]: TransformStreamDefaultControllerImpl<I, O>; - [sym.writable]: WritableStreamImpl<I>; - - constructor( - transformer: Transformer<I, O> = {}, - writableStrategy: QueuingStrategy<I> = {}, - readableStrategy: QueuingStrategy<O> = {}, - ) { - const writableSizeFunction = writableStrategy.size; - let writableHighWaterMark = writableStrategy.highWaterMark; - const readableSizeFunction = readableStrategy.size; - let readableHighWaterMark = readableStrategy.highWaterMark; - const writableType = transformer.writableType; - if (writableType !== undefined) { - throw new RangeError( - `Expected transformer writableType to be undefined, received "${ - String(writableType) - }"`, - ); - } - const writableSizeAlgorithm = makeSizeAlgorithmFromSizeFunction( - writableSizeFunction, - ); - if (writableHighWaterMark === undefined) { - writableHighWaterMark = 1; - } - writableHighWaterMark = validateAndNormalizeHighWaterMark( - writableHighWaterMark, - ); - const readableType = transformer.readableType; - if (readableType !== undefined) { - throw new RangeError( - `Expected transformer readableType to be undefined, received "${ - String(readableType) - }"`, - ); - } - const readableSizeAlgorithm = makeSizeAlgorithmFromSizeFunction( - readableSizeFunction, - ); - if (readableHighWaterMark === undefined) { - readableHighWaterMark = 1; - } - readableHighWaterMark = validateAndNormalizeHighWaterMark( - readableHighWaterMark, - ); - const startPromise = getDeferred<void>(); - initializeTransformStream( - this, - startPromise.promise, - writableHighWaterMark, - writableSizeAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ); - // the brand check expects this, and the brand check occurs in the following - // but the property hasn't been defined. - Object.defineProperty(this, sym.transformStreamController, { - value: undefined, - writable: true, - configurable: true, - }); - setUpTransformStreamDefaultControllerFromTransformer(this, transformer); - const startResult: void | PromiseLike<void> = invokeOrNoop( - transformer, - "start", - this[sym.transformStreamController], - ); - startPromise.resolve(startResult); - } - - get readable(): ReadableStream<O> { - if (!isTransformStream(this)) { - throw new TypeError("Invalid TransformStream."); - } - return this[sym.readable]; - } - - get writable(): WritableStream<I> { - if (!isTransformStream(this)) { - throw new TypeError("Invalid TransformStream."); - } - return this[sym.writable]; - } - - [customInspect](): string { - return `${this.constructor.name} {\n readable: ${ - inspect(this.readable) - }\n writable: ${inspect(this.writable)}\n}`; - } -} - -setFunctionName(TransformStreamImpl, "TransformStream"); diff --git a/cli/js/web/streams/transform_stream_default_controller.ts b/cli/js/web/streams/transform_stream_default_controller.ts deleted file mode 100644 index 54f1e9f2d..000000000 --- a/cli/js/web/streams/transform_stream_default_controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - FlushAlgorithm, - isTransformStreamDefaultController, - readableStreamDefaultControllerGetDesiredSize, - TransformAlgorithm, - transformStreamDefaultControllerEnqueue, - transformStreamDefaultControllerError, - transformStreamDefaultControllerTerminate, -} from "./internals.ts"; -import type { ReadableStreamDefaultControllerImpl } from "./readable_stream_default_controller.ts"; -import * as sym from "./symbols.ts"; -import type { TransformStreamImpl } from "./transform_stream.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class TransformStreamDefaultControllerImpl<I = any, O = any> - implements TransformStreamDefaultController<O> { - [sym.controlledTransformStream]: TransformStreamImpl<I, O>; - [sym.flushAlgorithm]: FlushAlgorithm; - [sym.transformAlgorithm]: TransformAlgorithm<I>; - - private constructor() { - throw new TypeError( - "TransformStreamDefaultController's constructor cannot be called.", - ); - } - - get desiredSize(): number | null { - if (!isTransformStreamDefaultController(this)) { - throw new TypeError("Invalid TransformStreamDefaultController."); - } - const readableController = this[sym.controlledTransformStream][ - sym.readable - ][sym.readableStreamController]; - return readableStreamDefaultControllerGetDesiredSize( - readableController as ReadableStreamDefaultControllerImpl<O>, - ); - } - - enqueue(chunk: O): void { - if (!isTransformStreamDefaultController(this)) { - throw new TypeError("Invalid TransformStreamDefaultController."); - } - transformStreamDefaultControllerEnqueue(this, chunk); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error(reason?: any): void { - if (!isTransformStreamDefaultController(this)) { - throw new TypeError("Invalid TransformStreamDefaultController."); - } - transformStreamDefaultControllerError(this, reason); - } - - terminate(): void { - if (!isTransformStreamDefaultController(this)) { - throw new TypeError("Invalid TransformStreamDefaultController."); - } - transformStreamDefaultControllerTerminate(this); - } - - [customInspect](): string { - return `${this.constructor.name} { desiredSize: ${ - String(this.desiredSize) - } }`; - } -} - -setFunctionName( - TransformStreamDefaultControllerImpl, - "TransformStreamDefaultController", -); diff --git a/cli/js/web/streams/writable_stream.ts b/cli/js/web/streams/writable_stream.ts deleted file mode 100644 index 94bedb941..000000000 --- a/cli/js/web/streams/writable_stream.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - AbortRequest, - acquireWritableStreamDefaultWriter, - Deferred, - initializeWritableStream, - isWritableStream, - isWritableStreamLocked, - makeSizeAlgorithmFromSizeFunction, - setUpWritableStreamDefaultControllerFromUnderlyingSink, - writableStreamAbort, - writableStreamClose, - writableStreamCloseQueuedOrInFlight, - validateAndNormalizeHighWaterMark, -} from "./internals.ts"; -import * as sym from "./symbols.ts"; -import type { WritableStreamDefaultControllerImpl } from "./writable_stream_default_controller.ts"; -import type { WritableStreamDefaultWriterImpl } from "./writable_stream_default_writer.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class WritableStreamImpl<W = any> implements WritableStream<W> { - [sym.backpressure]: boolean; - [sym.closeRequest]?: Deferred<void>; - [sym.inFlightWriteRequest]?: Required<Deferred<void>>; - [sym.inFlightCloseRequest]?: Deferred<void>; - [sym.pendingAbortRequest]?: AbortRequest; - [sym.state]: "writable" | "closed" | "erroring" | "errored"; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [sym.storedError]?: any; - [sym.writableStreamController]?: WritableStreamDefaultControllerImpl<W>; - [sym.writer]?: WritableStreamDefaultWriterImpl<W>; - [sym.writeRequests]: Array<Required<Deferred<void>>>; - - constructor( - underlyingSink: UnderlyingSink = {}, - strategy: QueuingStrategy = {}, - ) { - initializeWritableStream(this); - const size = strategy.size; - let highWaterMark = strategy.highWaterMark ?? 1; - const { type } = underlyingSink; - if (type !== undefined) { - throw new RangeError(`Sink type of "${String(type)}" not supported.`); - } - const sizeAlgorithm = makeSizeAlgorithmFromSizeFunction(size); - highWaterMark = validateAndNormalizeHighWaterMark(highWaterMark); - setUpWritableStreamDefaultControllerFromUnderlyingSink( - this, - underlyingSink, - highWaterMark, - sizeAlgorithm, - ); - } - - get locked(): boolean { - if (!isWritableStream(this)) { - throw new TypeError("Invalid WritableStream."); - } - return isWritableStreamLocked(this); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - abort(reason: any): Promise<void> { - if (!isWritableStream(this)) { - return Promise.reject(new TypeError("Invalid WritableStream.")); - } - if (isWritableStreamLocked(this)) { - return Promise.reject( - new TypeError("Cannot abort a locked WritableStream."), - ); - } - return writableStreamAbort(this, reason); - } - - close(): Promise<void> { - if (!isWritableStream(this)) { - return Promise.reject(new TypeError("Invalid WritableStream.")); - } - if (isWritableStreamLocked(this)) { - return Promise.reject( - new TypeError("Cannot abort a locked WritableStream."), - ); - } - if (writableStreamCloseQueuedOrInFlight(this)) { - return Promise.reject( - new TypeError("Cannot close an already closing WritableStream."), - ); - } - return writableStreamClose(this); - } - - getWriter(): WritableStreamDefaultWriter<W> { - if (!isWritableStream(this)) { - throw new TypeError("Invalid WritableStream."); - } - return acquireWritableStreamDefaultWriter(this); - } - - [customInspect](): string { - return `${this.constructor.name} { locked: ${String(this.locked)} }`; - } -} - -setFunctionName(WritableStreamImpl, "WritableStream"); diff --git a/cli/js/web/streams/writable_stream_default_controller.ts b/cli/js/web/streams/writable_stream_default_controller.ts deleted file mode 100644 index 960060b8f..000000000 --- a/cli/js/web/streams/writable_stream_default_controller.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - AbortAlgorithm, - CloseAlgorithm, - isWritableStreamDefaultController, - Pair, - resetQueue, - SizeAlgorithm, - WriteAlgorithm, - writableStreamDefaultControllerClearAlgorithms, - writableStreamDefaultControllerError, -} from "./internals.ts"; -import * as sym from "./symbols.ts"; -import type { WritableStreamImpl } from "./writable_stream.ts"; -import { customInspect } from "../console.ts"; -import { setFunctionName } from "../util.ts"; - -export class WritableStreamDefaultControllerImpl<W> - implements WritableStreamDefaultController { - [sym.abortAlgorithm]: AbortAlgorithm; - [sym.closeAlgorithm]: CloseAlgorithm; - [sym.controlledWritableStream]: WritableStreamImpl; - [sym.queue]: Array<Pair<{ chunk: W } | "close">>; - [sym.queueTotalSize]: number; - [sym.started]: boolean; - [sym.strategyHWM]: number; - [sym.strategySizeAlgorithm]: SizeAlgorithm<W>; - [sym.writeAlgorithm]: WriteAlgorithm<W>; - - private constructor() { - throw new TypeError( - "WritableStreamDefaultController's constructor cannot be called.", - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error(e: any): void { - if (!isWritableStreamDefaultController(this)) { - throw new TypeError("Invalid WritableStreamDefaultController."); - } - const state = this[sym.controlledWritableStream][sym.state]; - if (state !== "writable") { - return; - } - writableStreamDefaultControllerError(this, e); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [sym.abortSteps](reason: any): PromiseLike<void> { - const result = this[sym.abortAlgorithm](reason); - writableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - [sym.errorSteps](): void { - resetQueue(this); - } - - [customInspect](): string { - return `${this.constructor.name} { }`; - } -} - -setFunctionName( - WritableStreamDefaultControllerImpl, - "WritableStreamDefaultController", -); diff --git a/cli/js/web/streams/writable_stream_default_writer.ts b/cli/js/web/streams/writable_stream_default_writer.ts deleted file mode 100644 index 34e664e95..000000000 --- a/cli/js/web/streams/writable_stream_default_writer.ts +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { - Deferred, - getDeferred, - isWritableStream, - isWritableStreamDefaultWriter, - isWritableStreamLocked, - setPromiseIsHandledToTrue, - writableStreamCloseQueuedOrInFlight, - writableStreamDefaultWriterAbort, - writableStreamDefaultWriterClose, - writableStreamDefaultWriterGetDesiredSize, - writableStreamDefaultWriterRelease, - writableStreamDefaultWriterWrite, -} from "./internals.ts"; -import * as sym from "./symbols.ts"; -import type { WritableStreamImpl } from "./writable_stream.ts"; -import { customInspect } from "../console.ts"; -import { assert } from "../../util.ts"; -import { setFunctionName } from "../util.ts"; - -export class WritableStreamDefaultWriterImpl<W> - implements WritableStreamDefaultWriter<W> { - [sym.closedPromise]: Deferred<void>; - [sym.ownerWritableStream]: WritableStreamImpl<W>; - [sym.readyPromise]: Deferred<void>; - - constructor(stream: WritableStreamImpl<W>) { - if (!isWritableStream(stream)) { - throw new TypeError("Invalid stream."); - } - if (isWritableStreamLocked(stream)) { - throw new TypeError("Cannot create a writer for a locked stream."); - } - this[sym.ownerWritableStream] = stream; - stream[sym.writer] = this; - const state = stream[sym.state]; - if (state === "writable") { - if ( - !writableStreamCloseQueuedOrInFlight(stream) && - stream[sym.backpressure] - ) { - this[sym.readyPromise] = getDeferred(); - } else { - this[sym.readyPromise] = { promise: Promise.resolve() }; - } - this[sym.closedPromise] = getDeferred(); - } else if (state === "erroring") { - this[sym.readyPromise] = { - promise: Promise.reject(stream[sym.storedError]), - }; - setPromiseIsHandledToTrue(this[sym.readyPromise].promise); - this[sym.closedPromise] = getDeferred(); - } else if (state === "closed") { - this[sym.readyPromise] = { promise: Promise.resolve() }; - this[sym.closedPromise] = { promise: Promise.resolve() }; - } else { - assert(state === "errored"); - const storedError = stream[sym.storedError]; - this[sym.readyPromise] = { promise: Promise.reject(storedError) }; - setPromiseIsHandledToTrue(this[sym.readyPromise].promise); - this[sym.closedPromise] = { promise: Promise.reject(storedError) }; - setPromiseIsHandledToTrue(this[sym.closedPromise].promise); - } - } - - get closed(): Promise<void> { - if (!isWritableStreamDefaultWriter(this)) { - return Promise.reject( - new TypeError("Invalid WritableStreamDefaultWriter."), - ); - } - return this[sym.closedPromise].promise; - } - - get desiredSize(): number | null { - if (!isWritableStreamDefaultWriter(this)) { - throw new TypeError("Invalid WritableStreamDefaultWriter."); - } - if (!this[sym.ownerWritableStream]) { - throw new TypeError("WritableStreamDefaultWriter has no owner."); - } - return writableStreamDefaultWriterGetDesiredSize(this); - } - - get ready(): Promise<void> { - if (!isWritableStreamDefaultWriter(this)) { - return Promise.reject( - new TypeError("Invalid WritableStreamDefaultWriter."), - ); - } - return this[sym.readyPromise].promise; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - abort(reason: any): Promise<void> { - if (!isWritableStreamDefaultWriter(this)) { - return Promise.reject( - new TypeError("Invalid WritableStreamDefaultWriter."), - ); - } - if (!this[sym.ownerWritableStream]) { - Promise.reject( - new TypeError("WritableStreamDefaultWriter has no owner."), - ); - } - return writableStreamDefaultWriterAbort(this, reason); - } - - close(): Promise<void> { - if (!isWritableStreamDefaultWriter(this)) { - return Promise.reject( - new TypeError("Invalid WritableStreamDefaultWriter."), - ); - } - const stream = this[sym.ownerWritableStream]; - if (!stream) { - Promise.reject( - new TypeError("WritableStreamDefaultWriter has no owner."), - ); - } - if (writableStreamCloseQueuedOrInFlight(stream)) { - Promise.reject( - new TypeError("Stream is in an invalid state to be closed."), - ); - } - return writableStreamDefaultWriterClose(this); - } - - releaseLock(): void { - if (!isWritableStreamDefaultWriter(this)) { - throw new TypeError("Invalid WritableStreamDefaultWriter."); - } - const stream = this[sym.ownerWritableStream]; - if (!stream) { - return; - } - assert(stream[sym.writer]); - writableStreamDefaultWriterRelease(this); - } - - write(chunk: W): Promise<void> { - if (!isWritableStreamDefaultWriter(this)) { - return Promise.reject( - new TypeError("Invalid WritableStreamDefaultWriter."), - ); - } - if (!this[sym.ownerWritableStream]) { - Promise.reject( - new TypeError("WritableStreamDefaultWriter has no owner."), - ); - } - return writableStreamDefaultWriterWrite(this, chunk); - } - - [customInspect](): string { - return `${this.constructor.name} { closed: Promise, desiredSize: ${ - String(this.desiredSize) - }, ready: Promise }`; - } -} - -setFunctionName(WritableStreamDefaultWriterImpl, "WritableStreamDefaultWriter"); diff --git a/cli/js/web/text_encoding.ts b/cli/js/web/text_encoding.ts deleted file mode 100644 index 97848cb77..000000000 --- a/cli/js/web/text_encoding.ts +++ /dev/null @@ -1,581 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// The following code is based off of text-encoding at: -// https://github.com/inexorabletash/text-encoding -// -// Anyone is free to copy, modify, publish, use, compile, sell, or -// distribute this software, either in source code form or as a compiled -// binary, for any purpose, commercial or non-commercial, and by any -// means. -// -// In jurisdictions that recognize copyright laws, the author or authors -// of this software dedicate any and all copyright interest in the -// software to the public domain. We make this dedication for the benefit -// of the public at large and to the detriment of our heirs and -// successors. We intend this dedication to be an overt act of -// relinquishment in perpetuity of all present and future rights to this -// software under copyright law. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -import { DOMExceptionImpl as DOMException } from "./dom_exception.ts"; -import * as base64 from "./base64.ts"; -import { decodeUtf8 } from "./decode_utf8.ts"; -import { core } from "../core.ts"; - -const CONTINUE = null; -const END_OF_STREAM = -1; -const FINISHED = -1; - -function decoderError(fatal: boolean): number | never { - if (fatal) { - throw new TypeError("Decoder error."); - } - return 0xfffd; // default code point -} - -function inRange(a: number, min: number, max: number): boolean { - return min <= a && a <= max; -} - -function isASCIIByte(a: number): boolean { - return inRange(a, 0x00, 0x7f); -} - -function stringToCodePoints(input: string): number[] { - const u: number[] = []; - for (const c of input) { - u.push(c.codePointAt(0)!); - } - return u; -} - -class UTF8Encoder implements Encoder { - handler(codePoint: number): "finished" | number[] { - if (codePoint === END_OF_STREAM) { - return "finished"; - } - - if (inRange(codePoint, 0x00, 0x7f)) { - return [codePoint]; - } - - let count: number; - let offset: number; - if (inRange(codePoint, 0x0080, 0x07ff)) { - count = 1; - offset = 0xc0; - } else if (inRange(codePoint, 0x0800, 0xffff)) { - count = 2; - offset = 0xe0; - } else if (inRange(codePoint, 0x10000, 0x10ffff)) { - count = 3; - offset = 0xf0; - } else { - throw TypeError(`Code point out of range: \\x${codePoint.toString(16)}`); - } - - const bytes = [(codePoint >> (6 * count)) + offset]; - - while (count > 0) { - const temp = codePoint >> (6 * (count - 1)); - bytes.push(0x80 | (temp & 0x3f)); - count--; - } - - return bytes; - } -} - -export function atob(s: string): string { - s = String(s); - s = s.replace(/[\t\n\f\r ]/g, ""); - - if (s.length % 4 === 0) { - s = s.replace(/==?$/, ""); - } - - const rem = s.length % 4; - if (rem === 1 || /[^+/0-9A-Za-z]/.test(s)) { - throw new DOMException( - "The string to be decoded is not correctly encoded", - "DataDecodeError", - ); - } - - // base64-js requires length exactly times of 4 - if (rem > 0) { - s = s.padEnd(s.length + (4 - rem), "="); - } - - const byteArray: Uint8Array = base64.toByteArray(s); - let result = ""; - for (let i = 0; i < byteArray.length; i++) { - result += String.fromCharCode(byteArray[i]); - } - return result; -} - -export function btoa(s: string): string { - const byteArray = []; - for (let i = 0; i < s.length; i++) { - const charCode = s[i].charCodeAt(0); - if (charCode > 0xff) { - throw new TypeError( - "The string to be encoded contains characters " + - "outside of the Latin1 range.", - ); - } - byteArray.push(charCode); - } - const result = base64.fromByteArray(Uint8Array.from(byteArray)); - return result; -} - -interface DecoderOptions { - fatal?: boolean; - ignoreBOM?: boolean; -} - -interface Decoder { - handler(stream: Stream, byte: number): number | null; -} - -interface Encoder { - handler(codePoint: number): "finished" | number[]; -} - -class SingleByteDecoder implements Decoder { - readonly #index: number[]; - readonly #fatal: boolean; - - constructor( - index: number[], - { ignoreBOM = false, fatal = false }: DecoderOptions = {}, - ) { - if (ignoreBOM) { - throw new TypeError("Ignoring the BOM is available only with utf-8."); - } - this.#fatal = fatal; - this.#index = index; - } - handler(_stream: Stream, byte: number): number { - if (byte === END_OF_STREAM) { - return FINISHED; - } - if (isASCIIByte(byte)) { - return byte; - } - const codePoint = this.#index[byte - 0x80]; - - if (codePoint == null) { - return decoderError(this.#fatal); - } - - return codePoint; - } -} - -// The encodingMap is a hash of labels that are indexed by the conical -// encoding. -const encodingMap: { [key: string]: string[] } = { - "windows-1252": [ - "ansi_x3.4-1968", - "ascii", - "cp1252", - "cp819", - "csisolatin1", - "ibm819", - "iso-8859-1", - "iso-ir-100", - "iso8859-1", - "iso88591", - "iso_8859-1", - "iso_8859-1:1987", - "l1", - "latin1", - "us-ascii", - "windows-1252", - "x-cp1252", - ], - "utf-8": ["unicode-1-1-utf-8", "utf-8", "utf8"], -}; -// We convert these into a Map where every label resolves to its canonical -// encoding type. -const encodings = new Map<string, string>(); -for (const key of Object.keys(encodingMap)) { - const labels = encodingMap[key]; - for (const label of labels) { - encodings.set(label, key); - } -} - -// A map of functions that return new instances of a decoder indexed by the -// encoding type. -const decoders = new Map<string, (options: DecoderOptions) => Decoder>(); - -// Single byte decoders are an array of code point lookups -const encodingIndexes = new Map<string, number[]>(); -// deno-fmt-ignore -encodingIndexes.set("windows-1252", [ - 8364, - 129, - 8218, - 402, - 8222, - 8230, - 8224, - 8225, - 710, - 8240, - 352, - 8249, - 338, - 141, - 381, - 143, - 144, - 8216, - 8217, - 8220, - 8221, - 8226, - 8211, - 8212, - 732, - 8482, - 353, - 8250, - 339, - 157, - 382, - 376, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 215, - 216, - 217, - 218, - 219, - 220, - 221, - 222, - 223, - 224, - 225, - 226, - 227, - 228, - 229, - 230, - 231, - 232, - 233, - 234, - 235, - 236, - 237, - 238, - 239, - 240, - 241, - 242, - 243, - 244, - 245, - 246, - 247, - 248, - 249, - 250, - 251, - 252, - 253, - 254, - 255, -]); -for (const [key, index] of encodingIndexes) { - decoders.set( - key, - (options: DecoderOptions): SingleByteDecoder => { - return new SingleByteDecoder(index, options); - }, - ); -} - -function codePointsToString(codePoints: number[]): string { - let s = ""; - for (const cp of codePoints) { - s += String.fromCodePoint(cp); - } - return s; -} - -class Stream { - #tokens: number[]; - constructor(tokens: number[] | Uint8Array) { - this.#tokens = [...tokens]; - this.#tokens.reverse(); - } - - endOfStream(): boolean { - return !this.#tokens.length; - } - - read(): number { - return !this.#tokens.length ? END_OF_STREAM : this.#tokens.pop()!; - } - - prepend(token: number | number[]): void { - if (Array.isArray(token)) { - while (token.length) { - this.#tokens.push(token.pop()!); - } - } else { - this.#tokens.push(token); - } - } - - push(token: number | number[]): void { - if (Array.isArray(token)) { - while (token.length) { - this.#tokens.unshift(token.shift()!); - } - } else { - this.#tokens.unshift(token); - } - } -} - -export interface TextDecodeOptions { - stream?: false; -} - -export interface TextDecoderOptions { - fatal?: boolean; - ignoreBOM?: boolean; -} - -type EitherArrayBuffer = SharedArrayBuffer | ArrayBuffer; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isEitherArrayBuffer(x: any): x is EitherArrayBuffer { - return x instanceof SharedArrayBuffer || x instanceof ArrayBuffer; -} - -export class TextDecoder { - readonly #encoding: string; - - get encoding(): string { - return this.#encoding; - } - readonly fatal: boolean = false; - readonly ignoreBOM: boolean = false; - - constructor(label = "utf-8", options: TextDecoderOptions = { fatal: false }) { - if (options.ignoreBOM) { - this.ignoreBOM = true; - } - if (options.fatal) { - this.fatal = true; - } - label = String(label).trim().toLowerCase(); - const encoding = encodings.get(label); - if (!encoding) { - throw new RangeError( - `The encoding label provided ('${label}') is invalid.`, - ); - } - if (!decoders.has(encoding) && encoding !== "utf-8") { - throw new TypeError(`Internal decoder ('${encoding}') not found.`); - } - this.#encoding = encoding; - } - - decode( - input?: BufferSource, - options: TextDecodeOptions = { stream: false }, - ): string { - if (options.stream) { - throw new TypeError("Stream not supported."); - } - - let bytes: Uint8Array; - if (input instanceof Uint8Array) { - bytes = input; - } else if (isEitherArrayBuffer(input)) { - bytes = new Uint8Array(input); - } else if ( - typeof input === "object" && - "buffer" in input && - isEitherArrayBuffer(input.buffer) - ) { - bytes = new Uint8Array(input.buffer, input.byteOffset, input.byteLength); - } else { - bytes = new Uint8Array(0); - } - - // For simple utf-8 decoding "Deno.core.decode" can be used for performance - if ( - this.#encoding === "utf-8" && - this.fatal === false && - this.ignoreBOM === false - ) { - return core.decode(bytes); - } - - // For performance reasons we utilise a highly optimised decoder instead of - // the general decoder. - if (this.#encoding === "utf-8") { - return decodeUtf8(bytes, this.fatal, this.ignoreBOM); - } - - const decoder = decoders.get(this.#encoding)!({ - fatal: this.fatal, - ignoreBOM: this.ignoreBOM, - }); - const inputStream = new Stream(bytes); - const output: number[] = []; - - while (true) { - const result = decoder.handler(inputStream, inputStream.read()); - if (result === FINISHED) { - break; - } - - if (result !== CONTINUE) { - output.push(result); - } - } - - if (output.length > 0 && output[0] === 0xfeff) { - output.shift(); - } - - return codePointsToString(output); - } - - get [Symbol.toStringTag](): string { - return "TextDecoder"; - } -} - -interface TextEncoderEncodeIntoResult { - read: number; - written: number; -} - -export class TextEncoder { - readonly encoding = "utf-8"; - encode(input = ""): Uint8Array { - // Deno.core.encode() provides very efficient utf-8 encoding - if (this.encoding === "utf-8") { - return core.encode(input); - } - - const encoder = new UTF8Encoder(); - const inputStream = new Stream(stringToCodePoints(input)); - const output: number[] = []; - - while (true) { - const result = encoder.handler(inputStream.read()); - if (result === "finished") { - break; - } - output.push(...result); - } - - return new Uint8Array(output); - } - encodeInto(input: string, dest: Uint8Array): TextEncoderEncodeIntoResult { - const encoder = new UTF8Encoder(); - const inputStream = new Stream(stringToCodePoints(input)); - - let written = 0; - let read = 0; - while (true) { - const result = encoder.handler(inputStream.read()); - if (result === "finished") { - break; - } - if (dest.length - written >= result.length) { - read++; - dest.set(result, written); - written += result.length; - if (result.length > 3) { - // increment read a second time if greater than U+FFFF - read++; - } - } else { - break; - } - } - - return { - read, - written, - }; - } - get [Symbol.toStringTag](): string { - return "TextEncoder"; - } -} diff --git a/cli/js/web/timers.ts b/cli/js/web/timers.ts deleted file mode 100644 index 71aef5f85..000000000 --- a/cli/js/web/timers.ts +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { assert } from "../util.ts"; -import { startGlobalTimer, stopGlobalTimer } from "../ops/timers.ts"; -import { RBTree } from "../rbtree.ts"; - -const { console } = globalThis; -const OriginalDate = Date; - -interface Timer { - id: number; - callback: () => void; - delay: number; - due: number; - repeat: boolean; - scheduled: boolean; -} - -// Timeout values > TIMEOUT_MAX are set to 1. -const TIMEOUT_MAX = 2 ** 31 - 1; - -let globalTimeoutDue: number | null = null; - -let nextTimerId = 1; -const idMap = new Map<number, Timer>(); -type DueNode = { due: number; timers: Timer[] }; -const dueTree = new RBTree<DueNode>((a, b) => a.due - b.due); - -function clearGlobalTimeout(): void { - globalTimeoutDue = null; - stopGlobalTimer(); -} - -let pendingEvents = 0; -const pendingFireTimers: Timer[] = []; - -/** Process and run a single ready timer macrotask. - * This function should be registered through Deno.core.setMacrotaskCallback. - * Returns true when all ready macrotasks have been processed, false if more - * ready ones are available. The Isolate future would rely on the return value - * to repeatedly invoke this function until depletion. Multiple invocations - * of this function one at a time ensures newly ready microtasks are processed - * before next macrotask timer callback is invoked. */ -export function handleTimerMacrotask(): boolean { - if (pendingFireTimers.length > 0) { - fire(pendingFireTimers.shift()!); - return pendingFireTimers.length === 0; - } - return true; -} - -async function setGlobalTimeout(due: number, now: number): Promise<void> { - // Since JS and Rust don't use the same clock, pass the time to rust as a - // relative time value. On the Rust side we'll turn that into an absolute - // value again. - const timeout = due - now; - assert(timeout >= 0); - // Send message to the backend. - globalTimeoutDue = due; - pendingEvents++; - // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout` - // is synchronous. That means that timer is cancelled, but this promise is still pending - // until next turn of event loop. This leads to "leaking of async ops" in tests; - // because `clearTimeout/clearInterval` might be the last statement in test function - // `opSanitizer` will immediately complain that there is pending op going on, unless - // some timeout/defer is put in place to allow promise resolution. - // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but - // I'm not if that's possible. - await startGlobalTimer(timeout); - pendingEvents--; - // eslint-disable-next-line @typescript-eslint/no-use-before-define - prepareReadyTimers(); -} - -function prepareReadyTimers(): void { - const now = OriginalDate.now(); - // Bail out if we're not expecting the global timer to fire. - if (globalTimeoutDue === null || pendingEvents > 0) { - return; - } - // After firing the timers that are due now, this will hold the first timer - // list that hasn't fired yet. - let nextDueNode: DueNode | null; - while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) { - dueTree.remove(nextDueNode); - // Fire all the timers in the list. - for (const timer of nextDueNode.timers) { - // With the list dropped, the timer is no longer scheduled. - timer.scheduled = false; - // Place the callback to pending timers to fire. - pendingFireTimers.push(timer); - } - } - setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now); -} - -function setOrClearGlobalTimeout(due: number | null, now: number): void { - if (due == null) { - clearGlobalTimeout(); - } else { - setGlobalTimeout(due, now); - } -} - -function schedule(timer: Timer, now: number): void { - assert(!timer.scheduled); - assert(now <= timer.due); - // Find or create the list of timers that will fire at point-in-time `due`. - const maybeNewDueNode = { due: timer.due, timers: [] }; - let dueNode = dueTree.find(maybeNewDueNode); - if (dueNode === null) { - dueTree.insert(maybeNewDueNode); - dueNode = maybeNewDueNode; - } - // Append the newly scheduled timer to the list and mark it as scheduled. - dueNode!.timers.push(timer); - timer.scheduled = true; - // If the new timer is scheduled to fire before any timer that existed before, - // update the global timeout to reflect this. - if (globalTimeoutDue === null || globalTimeoutDue > timer.due) { - setOrClearGlobalTimeout(timer.due, now); - } -} - -function unschedule(timer: Timer): void { - // Check if our timer is pending scheduling or pending firing. - // If either is true, they are not in tree, and their idMap entry - // will be deleted soon. Remove it from queue. - let index = -1; - if ((index = pendingFireTimers.indexOf(timer)) >= 0) { - pendingFireTimers.splice(index); - return; - } - // If timer is not in the 2 pending queues and is unscheduled, - // it is not in the tree. - if (!timer.scheduled) { - return; - } - const searchKey = { due: timer.due, timers: [] }; - // Find the list of timers that will fire at point-in-time `due`. - const list = dueTree.find(searchKey)!.timers; - if (list.length === 1) { - // Time timer is the only one in the list. Remove the entire list. - assert(list[0] === timer); - dueTree.remove(searchKey); - // If the unscheduled timer was 'next up', find when the next timer that - // still exists is due, and update the global alarm accordingly. - if (timer.due === globalTimeoutDue) { - const nextDueNode: DueNode | null = dueTree.min(); - setOrClearGlobalTimeout( - nextDueNode && nextDueNode.due, - OriginalDate.now(), - ); - } - } else { - // Multiple timers that are due at the same point in time. - // Remove this timer from the list. - const index = list.indexOf(timer); - assert(index > -1); - list.splice(index, 1); - } -} - -function fire(timer: Timer): void { - // If the timer isn't found in the ID map, that means it has been cancelled - // between the timer firing and the promise callback (this function). - if (!idMap.has(timer.id)) { - return; - } - // Reschedule the timer if it is a repeating one, otherwise drop it. - if (!timer.repeat) { - // One-shot timer: remove the timer from this id-to-timer map. - idMap.delete(timer.id); - } else { - // Interval timer: compute when timer was supposed to fire next. - // However make sure to never schedule the next interval in the past. - const now = OriginalDate.now(); - timer.due = Math.max(now, timer.due + timer.delay); - schedule(timer, now); - } - // Call the user callback. Intermediate assignment is to avoid leaking `this` - // to it, while also keeping the stack trace neat when it shows up in there. - const callback = timer.callback; - callback(); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Args = any[]; - -function checkThis(thisArg: unknown): void { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } -} - -function checkBigInt(n: unknown): void { - if (typeof n === "bigint") { - throw new TypeError("Cannot convert a BigInt value to a number"); - } -} - -function setTimer( - cb: (...args: Args) => void, - delay: number, - args: Args, - repeat: boolean, -): number { - // Bind `args` to the callback and bind `this` to globalThis(global). - const callback: () => void = cb.bind(globalThis, ...args); - // In the browser, the delay value must be coercible to an integer between 0 - // and INT32_MAX. Any other value will cause the timer to fire immediately. - // We emulate this behavior. - const now = OriginalDate.now(); - if (delay > TIMEOUT_MAX) { - console.warn( - `${delay} does not fit into` + - " a 32-bit signed integer." + - "\nTimeout duration was set to 1.", - ); - delay = 1; - } - delay = Math.max(0, delay | 0); - - // Create a new, unscheduled timer object. - const timer = { - id: nextTimerId++, - callback, - args, - delay, - due: now + delay, - repeat, - scheduled: false, - }; - // Register the timer's existence in the id-to-timer map. - idMap.set(timer.id, timer); - // Schedule the timer in the due table. - schedule(timer, now); - return timer.id; -} - -export function setTimeout( - this: unknown, - cb: (...args: Args) => void, - delay = 0, - ...args: Args -): number { - checkBigInt(delay); - checkThis(this); - return setTimer(cb, delay, args, false); -} - -export function setInterval( - this: unknown, - cb: (...args: Args) => void, - delay = 0, - ...args: Args -): number { - checkBigInt(delay); - checkThis(this); - return setTimer(cb, delay, args, true); -} - -function clearTimer(id: number): void { - id = Number(id); - const timer = idMap.get(id); - if (timer === undefined) { - // Timer doesn't exist any more or never existed. This is not an error. - return; - } - // Unschedule the timer if it is currently scheduled, and forget about it. - unschedule(timer); - idMap.delete(timer.id); -} - -export function clearTimeout(id = 0): void { - checkBigInt(id); - if (id === 0) { - return; - } - clearTimer(id); -} - -export function clearInterval(id = 0): void { - checkBigInt(id); - if (id === 0) { - return; - } - clearTimer(id); -} diff --git a/cli/js/web/url.ts b/cli/js/web/url.ts deleted file mode 100644 index fabef3329..000000000 --- a/cli/js/web/url.ts +++ /dev/null @@ -1,627 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { build } from "../build.ts"; -import { getRandomValues } from "../ops/get_random_values.ts"; -import { domainToAscii } from "../ops/idna.ts"; -import { customInspect } from "./console.ts"; -import { TextEncoder } from "./text_encoding.ts"; -import { urls } from "./url_search_params.ts"; - -interface URLParts { - protocol: string; - slashes: string; - username: string; - password: string; - hostname: string; - port: string; - path: string; - query: string; - hash: string; -} - -const searchParamsMethods: Array<keyof URLSearchParams> = [ - "append", - "delete", - "set", -]; - -const specialSchemes = ["ftp", "file", "http", "https", "ws", "wss"]; - -// https://url.spec.whatwg.org/#special-scheme -const schemePorts: Record<string, string> = { - ftp: "21", - file: "", - http: "80", - https: "443", - ws: "80", - wss: "443", -}; -const MAX_PORT = 2 ** 16 - 1; - -// Remove the part of the string that matches the pattern and return the -// remainder (RHS) as well as the first captured group of the matched substring -// (LHS). e.g. -// takePattern("https://deno.land:80", /^([a-z]+):[/]{2}/) -// = ["http", "deno.land:80"] -// takePattern("deno.land:80", /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/) -// = ["deno.land", "80"] -function takePattern(string: string, pattern: RegExp): [string, string] { - let capture = ""; - const rest = string.replace(pattern, (_, capture_) => { - capture = capture_; - return ""; - }); - return [capture, rest]; -} - -function parse(url: string, isBase = true): URLParts | undefined { - const parts: Partial<URLParts> = {}; - let restUrl; - [parts.protocol, restUrl] = takePattern(url.trim(), /^([a-z]+):/); - if (isBase && parts.protocol == "") { - return undefined; - } - const isSpecial = specialSchemes.includes(parts.protocol); - if (parts.protocol == "file") { - parts.slashes = "//"; - parts.username = ""; - parts.password = ""; - [parts.hostname, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/); - parts.port = ""; - if (build.os == "windows" && parts.hostname == "") { - // UNC paths. e.g. "\\\\localhost\\foo\\bar" on Windows should be - // representable as `new URL("file:////localhost/foo/bar")` which is - // equivalent to: `new URL("file://localhost/foo/bar")`. - [parts.hostname, restUrl] = takePattern(restUrl, /^[/\\]{2,}([^/\\?#]*)/); - } - } else { - let restAuthority; - if (isSpecial) { - parts.slashes = "//"; - [restAuthority, restUrl] = takePattern(restUrl, /^[/\\]{2,}([^/\\?#]*)/); - } else { - parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : ""; - [restAuthority, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/); - } - let restAuthentication; - [restAuthentication, restAuthority] = takePattern(restAuthority, /^(.*)@/); - [parts.username, restAuthentication] = takePattern( - restAuthentication, - /^([^:]*)/, - ); - parts.username = encodeUserinfo(parts.username); - [parts.password] = takePattern(restAuthentication, /^:(.*)/); - parts.password = encodeUserinfo(parts.password); - [parts.hostname, restAuthority] = takePattern( - restAuthority, - /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/, - ); - [parts.port] = takePattern(restAuthority, /^:(.*)/); - if (!isValidPort(parts.port)) { - return undefined; - } - if (parts.hostname == "" && isSpecial && isBase) { - return undefined; - } - } - try { - parts.hostname = encodeHostname(parts.hostname, isSpecial); - } catch { - return undefined; - } - [parts.path, restUrl] = takePattern(restUrl, /^([^?#]*)/); - parts.path = encodePathname(parts.path.replace(/\\/g, "/")); - [parts.query, restUrl] = takePattern(restUrl, /^(\?[^#]*)/); - parts.query = encodeSearch(parts.query); - [parts.hash] = takePattern(restUrl, /^(#.*)/); - parts.hash = encodeHash(parts.hash); - return parts as URLParts; -} - -// Based on https://github.com/kelektiv/node-uuid -// TODO(kevinkassimo): Use deno_std version once possible. -function generateUUID(): string { - return "00000000-0000-4000-8000-000000000000".replace(/[0]/g, (): string => - // random integer from 0 to 15 as a hex digit. - (getRandomValues(new Uint8Array(1))[0] % 16).toString(16)); -} - -// Keep it outside of URL to avoid any attempts of access. -export const blobURLMap = new Map<string, Blob>(); - -function isAbsolutePath(path: string): boolean { - return path.startsWith("/"); -} - -// Resolves `.`s and `..`s where possible. -// Preserves repeating and trailing `/`s by design. -// On Windows, drive letter paths will be given a leading slash, and also a -// trailing slash if there are no other components e.g. "C:" -> "/C:/". -function normalizePath(path: string, isFilePath = false): string { - if (build.os == "windows" && isFilePath) { - path = path.replace(/^\/*([A-Za-z]:)(\/|$)/, "/$1/"); - } - const isAbsolute = isAbsolutePath(path); - path = path.replace(/^\//, ""); - const pathSegments = path.split("/"); - - const newPathSegments: string[] = []; - for (let i = 0; i < pathSegments.length; i++) { - const previous = newPathSegments[newPathSegments.length - 1]; - if ( - pathSegments[i] == ".." && - previous != ".." && - (previous != undefined || isAbsolute) - ) { - newPathSegments.pop(); - } else if (pathSegments[i] != ".") { - newPathSegments.push(pathSegments[i]); - } - } - - let newPath = newPathSegments.join("/"); - if (!isAbsolute) { - if (newPathSegments.length == 0) { - newPath = "."; - } - } else { - newPath = `/${newPath}`; - } - return newPath; -} - -// Standard URL basing logic, applied to paths. -function resolvePathFromBase( - path: string, - basePath: string, - isFilePath = false, -): string { - let normalizedPath = normalizePath(path, isFilePath); - let normalizedBasePath = normalizePath(basePath, isFilePath); - - let driveLetterPrefix = ""; - if (build.os == "windows" && isFilePath) { - let driveLetter: string; - let baseDriveLetter: string; - [driveLetter, normalizedPath] = takePattern( - normalizedPath, - /^(\/[A-Za-z]:)(?=\/)/, - ); - [baseDriveLetter, normalizedBasePath] = takePattern( - normalizedBasePath, - /^(\/[A-Za-z]:)(?=\/)/, - ); - driveLetterPrefix = driveLetter || baseDriveLetter; - } - - if (isAbsolutePath(normalizedPath)) { - return `${driveLetterPrefix}${normalizedPath}`; - } - if (!isAbsolutePath(normalizedBasePath)) { - throw new TypeError("Base path must be absolute."); - } - - // Special case. - if (path == "") { - return `${driveLetterPrefix}${normalizedBasePath}`; - } - - // Remove everything after the last `/` in `normalizedBasePath`. - const prefix = normalizedBasePath.replace(/[^\/]*$/, ""); - // If `normalizedPath` ends with `.` or `..`, add a trailing slash. - const suffix = normalizedPath.replace(/(?<=(^|\/)(\.|\.\.))$/, "/"); - - return `${driveLetterPrefix}${normalizePath(prefix + suffix)}`; -} - -function isValidPort(value: string): boolean { - // https://url.spec.whatwg.org/#port-state - if (value === "") return true; - - const port = Number(value); - return Number.isInteger(port) && port >= 0 && port <= MAX_PORT; -} - -/** @internal */ -export const parts = new WeakMap<URL, URLParts>(); - -export class URLImpl implements URL { - #searchParams!: URLSearchParams; - - [customInspect](): string { - const keys = [ - "href", - "origin", - "protocol", - "username", - "password", - "host", - "hostname", - "port", - "pathname", - "hash", - "search", - ]; - const objectString = keys - .map((key: string) => `${key}: "${this[key as keyof this] || ""}"`) - .join(", "); - return `URL { ${objectString} }`; - } - - #updateSearchParams = (): void => { - const searchParams = new URLSearchParams(this.search); - - for (const methodName of searchParamsMethods) { - /* eslint-disable @typescript-eslint/no-explicit-any */ - const method: (...args: any[]) => any = searchParams[methodName]; - searchParams[methodName] = (...args: unknown[]): any => { - method.apply(searchParams, args); - this.search = searchParams.toString(); - }; - /* eslint-enable */ - } - this.#searchParams = searchParams; - - urls.set(searchParams, this); - }; - - get hash(): string { - return parts.get(this)!.hash; - } - - set hash(value: string) { - value = unescape(String(value)); - if (!value) { - parts.get(this)!.hash = ""; - } else { - if (value.charAt(0) !== "#") { - value = `#${value}`; - } - // hashes can contain % and # unescaped - parts.get(this)!.hash = encodeHash(value); - } - } - - get host(): string { - return `${this.hostname}${this.port ? `:${this.port}` : ""}`; - } - - set host(value: string) { - value = String(value); - const url = new URL(`http://${value}`); - parts.get(this)!.hostname = url.hostname; - parts.get(this)!.port = url.port; - } - - get hostname(): string { - return parts.get(this)!.hostname; - } - - set hostname(value: string) { - value = String(value); - try { - const isSpecial = specialSchemes.includes(parts.get(this)!.protocol); - parts.get(this)!.hostname = encodeHostname(value, isSpecial); - } catch {} - } - - get href(): string { - const authentication = this.username || this.password - ? `${this.username}${this.password ? ":" + this.password : ""}@` - : ""; - const host = this.host; - const slashes = host ? "//" : parts.get(this)!.slashes; - let pathname = this.pathname; - if (pathname.charAt(0) != "/" && pathname != "" && host != "") { - pathname = `/${pathname}`; - } - return `${this.protocol}${slashes}${authentication}${host}${pathname}${this.search}${this.hash}`; - } - - set href(value: string) { - value = String(value); - if (value !== this.href) { - const url = new URL(value); - parts.set(this, { ...parts.get(url)! }); - this.#updateSearchParams(); - } - } - - get origin(): string { - if (this.host) { - return `${this.protocol}//${this.host}`; - } - return "null"; - } - - get password(): string { - return parts.get(this)!.password; - } - - set password(value: string) { - value = String(value); - parts.get(this)!.password = encodeUserinfo(value); - } - - get pathname(): string { - let path = parts.get(this)!.path; - if (specialSchemes.includes(parts.get(this)!.protocol)) { - if (path.charAt(0) != "/") { - path = `/${path}`; - } - } - return path; - } - - set pathname(value: string) { - parts.get(this)!.path = encodePathname(String(value)); - } - - get port(): string { - const port = parts.get(this)!.port; - if (schemePorts[parts.get(this)!.protocol] === port) { - return ""; - } - - return port; - } - - set port(value: string) { - if (!isValidPort(value)) { - return; - } - parts.get(this)!.port = value.toString(); - } - - get protocol(): string { - return `${parts.get(this)!.protocol}:`; - } - - set protocol(value: string) { - value = String(value); - if (value) { - if (value.charAt(value.length - 1) === ":") { - value = value.slice(0, -1); - } - parts.get(this)!.protocol = encodeURIComponent(value); - } - } - - get search(): string { - return parts.get(this)!.query; - } - - set search(value: string) { - value = String(value); - const query = value == "" || value.charAt(0) == "?" ? value : `?${value}`; - parts.get(this)!.query = encodeSearch(query); - this.#updateSearchParams(); - } - - get username(): string { - return parts.get(this)!.username; - } - - set username(value: string) { - value = String(value); - parts.get(this)!.username = encodeUserinfo(value); - } - - get searchParams(): URLSearchParams { - return this.#searchParams; - } - - constructor(url: string | URL, base?: string | URL) { - let baseParts: URLParts | undefined; - if (base) { - baseParts = typeof base === "string" ? parse(base) : parts.get(base); - if (baseParts === undefined) { - throw new TypeError("Invalid base URL."); - } - } - - const urlParts = typeof url === "string" - ? parse(url, !baseParts) - : parts.get(url); - if (urlParts == undefined) { - throw new TypeError("Invalid URL."); - } - - if (urlParts.protocol) { - urlParts.path = normalizePath(urlParts.path, urlParts.protocol == "file"); - parts.set(this, urlParts); - } else if (baseParts) { - parts.set(this, { - protocol: baseParts.protocol, - slashes: baseParts.slashes, - username: baseParts.username, - password: baseParts.password, - hostname: baseParts.hostname, - port: baseParts.port, - path: resolvePathFromBase( - urlParts.path, - baseParts.path || "/", - baseParts.protocol == "file", - ), - query: urlParts.query, - hash: urlParts.hash, - }); - } else { - throw new TypeError("Invalid URL."); - } - - this.#updateSearchParams(); - } - - toString(): string { - return this.href; - } - - toJSON(): string { - return this.href; - } - - // TODO(kevinkassimo): implement MediaSource version in the future. - static createObjectURL(b: Blob): string { - const origin = "http://deno-opaque-origin"; - const key = `blob:${origin}/${generateUUID()}`; - blobURLMap.set(key, b); - return key; - } - - static revokeObjectURL(url: string): void { - let urlObject; - try { - urlObject = new URL(url); - } catch { - throw new TypeError("Provided URL string is not valid"); - } - if (urlObject.protocol !== "blob:") { - return; - } - // Origin match check seems irrelevant for now, unless we implement - // persisten storage for per globalThis.location.origin at some point. - blobURLMap.delete(url); - } -} - -function parseIpv4Number(s: string): number { - if (s.match(/^(0[Xx])[0-9A-Za-z]+$/)) { - return Number(s); - } - if (s.match(/^[0-9]+$/)) { - return Number(s.startsWith("0") ? `0o${s}` : s); - } - return NaN; -} - -function parseIpv4(s: string): string { - const parts = s.split("."); - if (parts[parts.length - 1] == "" && parts.length > 1) { - parts.pop(); - } - if (parts.includes("") || parts.length > 4) { - return s; - } - const numbers = parts.map(parseIpv4Number); - if (numbers.includes(NaN)) { - return s; - } - const last = numbers.pop()!; - if (last >= 256 ** (4 - numbers.length) || numbers.find((n) => n >= 256)) { - throw new TypeError("Invalid hostname."); - } - const ipv4 = numbers.reduce((sum, n, i) => sum + n * 256 ** (3 - i), last); - const ipv4Hex = ipv4.toString(16).padStart(8, "0"); - const ipv4HexParts = ipv4Hex.match(/(..)(..)(..)(..)$/)!.slice(1); - return ipv4HexParts.map((s) => String(Number(`0x${s}`))).join("."); -} - -function charInC0ControlSet(c: string): boolean { - return (c >= "\u0000" && c <= "\u001F") || c > "\u007E"; -} - -function charInSearchSet(c: string): boolean { - // deno-fmt-ignore - return charInC0ControlSet(c) || ["\u0020", "\u0022", "\u0023", "\u0027", "\u003C", "\u003E"].includes(c) || c > "\u007E"; -} - -function charInFragmentSet(c: string): boolean { - // deno-fmt-ignore - return charInC0ControlSet(c) || ["\u0020", "\u0022", "\u003C", "\u003E", "\u0060"].includes(c); -} - -function charInPathSet(c: string): boolean { - // deno-fmt-ignore - return charInFragmentSet(c) || ["\u0023", "\u003F", "\u007B", "\u007D"].includes(c); -} - -function charInUserinfoSet(c: string): boolean { - // "\u0027" ("'") seemingly isn't in the spec, but matches Chrome and Firefox. - // deno-fmt-ignore - return charInPathSet(c) || ["\u0027", "\u002F", "\u003A", "\u003B", "\u003D", "\u0040", "\u005B", "\u005C", "\u005D", "\u005E", "\u007C"].includes(c); -} - -function charIsForbiddenInHost(c: string): boolean { - // deno-fmt-ignore - return ["\u0000", "\u0009", "\u000A", "\u000D", "\u0020", "\u0023", "\u0025", "\u002F", "\u003A", "\u003C", "\u003E", "\u003F", "\u0040", "\u005B", "\u005C", "\u005D", "\u005E"].includes(c); -} - -const encoder = new TextEncoder(); - -function encodeChar(c: string): string { - return [...encoder.encode(c)] - .map((n) => `%${n.toString(16)}`) - .join("") - .toUpperCase(); -} - -function encodeUserinfo(s: string): string { - return [...s].map((c) => (charInUserinfoSet(c) ? encodeChar(c) : c)).join(""); -} - -function encodeHostname(s: string, isSpecial = true): string { - // IPv6 parsing. - if (s.startsWith("[") && s.endsWith("]")) { - if (!s.match(/^\[[0-9A-Fa-f.:]{2,}\]$/)) { - throw new TypeError("Invalid hostname."); - } - // IPv6 address compress - return s.toLowerCase().replace(/\b:?(?:0+:?){2,}/, "::"); - } - - let result = s; - - if (!isSpecial) { - // Check against forbidden host code points except for "%". - for (const c of result) { - if (charIsForbiddenInHost(c) && c != "\u0025") { - throw new TypeError("Invalid hostname."); - } - } - - // Percent-encode C0 control set. - result = [...result] - .map((c) => (charInC0ControlSet(c) ? encodeChar(c) : c)) - .join(""); - - return result; - } - - // Percent-decode. - if (result.match(/%(?![0-9A-Fa-f]{2})/) != null) { - throw new TypeError("Invalid hostname."); - } - result = result.replace( - /%(.{2})/g, - (_, hex) => String.fromCodePoint(Number(`0x${hex}`)), - ); - - // IDNA domain to ASCII. - result = domainToAscii(result); - - // Check against forbidden host code points. - for (const c of result) { - if (charIsForbiddenInHost(c)) { - throw new TypeError("Invalid hostname."); - } - } - - // IPv4 parsing. - if (isSpecial) { - result = parseIpv4(result); - } - - return result; -} - -function encodePathname(s: string): string { - return [...s].map((c) => (charInPathSet(c) ? encodeChar(c) : c)).join(""); -} - -function encodeSearch(s: string): string { - return [...s].map((c) => (charInSearchSet(c) ? encodeChar(c) : c)).join(""); -} - -function encodeHash(s: string): string { - return [...s].map((c) => (charInFragmentSet(c) ? encodeChar(c) : c)).join(""); -} diff --git a/cli/js/web/url_search_params.ts b/cli/js/web/url_search_params.ts deleted file mode 100644 index f3e247522..000000000 --- a/cli/js/web/url_search_params.ts +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { parts } from "./url.ts"; -import { isIterable, requiredArguments } from "./util.ts"; - -/** @internal */ -export const urls = new WeakMap<URLSearchParams, URL | null>(); - -export class URLSearchParamsImpl implements URLSearchParams { - readonly #params: Array<[string, string]> = []; - - constructor(init: string | string[][] | Record<string, string> = "") { - if (typeof init === "string") { - this.#handleStringInitialization(init); - return; - } - - if (Array.isArray(init) || isIterable(init)) { - this.#handleArrayInitialization(init); - return; - } - - if (Object(init) !== init) { - return; - } - - if (init instanceof URLSearchParamsImpl) { - this.#params = [...init.#params]; - return; - } - - // Overload: record<USVString, USVString> - for (const key of Object.keys(init)) { - this.#append(key, init[key]); - } - - urls.set(this, null); - } - - #handleStringInitialization = (init: string): void => { - // Overload: USVString - // If init is a string and starts with U+003F (?), - // remove the first code point from init. - if (init.charCodeAt(0) === 0x003f) { - init = init.slice(1); - } - - for (const pair of init.split("&")) { - // Empty params are ignored - if (pair.length === 0) { - continue; - } - const position = pair.indexOf("="); - const name = pair.slice(0, position === -1 ? pair.length : position); - const value = pair.slice(name.length + 1); - this.#append(decodeURIComponent(name), decodeURIComponent(value)); - } - }; - - #handleArrayInitialization = ( - init: string[][] | Iterable<[string, string]>, - ): void => { - // Overload: sequence<sequence<USVString>> - for (const tuple of init) { - // If pair does not contain exactly two items, then throw a TypeError. - if (tuple.length !== 2) { - throw new TypeError( - "URLSearchParams.constructor tuple array argument must only contain pair elements", - ); - } - this.#append(tuple[0], tuple[1]); - } - }; - - #updateSteps = (): void => { - const url = urls.get(this); - if (url == null) { - return; - } - parts.get(url)!.query = this.toString(); - }; - - #append = (name: string, value: string): void => { - this.#params.push([String(name), String(value)]); - }; - - append(name: string, value: string): void { - requiredArguments("URLSearchParams.append", arguments.length, 2); - this.#append(name, value); - this.#updateSteps(); - } - - delete(name: string): void { - requiredArguments("URLSearchParams.delete", arguments.length, 1); - name = String(name); - let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { - this.#params.splice(i, 1); - } else { - i++; - } - } - this.#updateSteps(); - } - - getAll(name: string): string[] { - requiredArguments("URLSearchParams.getAll", arguments.length, 1); - name = String(name); - const values = []; - for (const entry of this.#params) { - if (entry[0] === name) { - values.push(entry[1]); - } - } - - return values; - } - - get(name: string): string | null { - requiredArguments("URLSearchParams.get", arguments.length, 1); - name = String(name); - for (const entry of this.#params) { - if (entry[0] === name) { - return entry[1]; - } - } - - return null; - } - - has(name: string): boolean { - requiredArguments("URLSearchParams.has", arguments.length, 1); - name = String(name); - return this.#params.some((entry) => entry[0] === name); - } - - set(name: string, value: string): void { - requiredArguments("URLSearchParams.set", arguments.length, 2); - - // 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. - name = String(name); - value = String(value); - let found = false; - let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { - if (!found) { - this.#params[i][1] = value; - found = true; - i++; - } else { - this.#params.splice(i, 1); - } - } else { - i++; - } - } - - // Otherwise, append a new name-value pair whose name is name - // and value is value, to list. - if (!found) { - this.#append(name, value); - } - - this.#updateSteps(); - } - - sort(): void { - this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)); - this.#updateSteps(); - } - - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - thisArg?: any, - ): void { - requiredArguments("URLSearchParams.forEach", arguments.length, 1); - - if (typeof thisArg !== "undefined") { - callbackfn = callbackfn.bind(thisArg); - } - - for (const [key, value] of this.#params) { - callbackfn(value, key, this); - } - } - - *keys(): IterableIterator<string> { - for (const [key] of this.#params) { - yield key; - } - } - - *values(): IterableIterator<string> { - for (const [, value] of this.#params) { - yield value; - } - } - - *entries(): IterableIterator<[string, string]> { - yield* this.#params; - } - - *[Symbol.iterator](): IterableIterator<[string, string]> { - yield* this.#params; - } - - toString(): string { - return this.#params - .map( - (tuple) => - `${encodeURIComponent(tuple[0])}=${encodeURIComponent(tuple[1])}`, - ) - .join("&"); - } -} diff --git a/cli/js/web/util.ts b/cli/js/web/util.ts deleted file mode 100644 index 3165c37a7..000000000 --- a/cli/js/web/util.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { DOMExceptionImpl as DOMException } from "./dom_exception.ts"; - -export type TypedArray = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | Float32Array - | Float64Array; - -// @internal -export function isTypedArray(x: unknown): x is TypedArray { - return ArrayBuffer.isView(x) && !(x instanceof DataView); -} - -// @internal -export function isInvalidDate(x: Date): boolean { - return isNaN(x.getTime()); -} - -// @internal -export function requiredArguments( - name: string, - length: number, - required: number, -): void { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } -} - -// @internal -export function immutableDefine( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - o: any, - p: string | number | symbol, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any, -): void { - Object.defineProperty(o, p, { - value, - configurable: false, - writable: false, - }); -} - -// @internal -export function hasOwnProperty(obj: unknown, v: PropertyKey): boolean { - if (obj == null) { - return false; - } - return Object.prototype.hasOwnProperty.call(obj, v); -} - -/** Returns whether o is iterable. - * - * @internal */ -export function isIterable<T, P extends keyof T, K extends T[P]>( - o: T, -): o is T & Iterable<[P, K]> { - // checks for null and undefined - if (o == null) { - return false; - } - return ( - typeof ((o as unknown) as Iterable<[P, K]>)[Symbol.iterator] === "function" - ); -} - -const objectCloneMemo = new WeakMap(); - -function cloneArrayBuffer( - srcBuffer: ArrayBufferLike, - srcByteOffset: number, - srcLength: number, - cloneConstructor: ArrayBufferConstructor | SharedArrayBufferConstructor, -): InstanceType<typeof cloneConstructor> { - // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway - return srcBuffer.slice( - srcByteOffset, - srcByteOffset + srcLength, - ) as InstanceType<typeof cloneConstructor>; -} - -/** Clone a value in a similar way to structured cloning. It is similar to a - * StructureDeserialize(StructuredSerialize(...)). */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function cloneValue(value: any): any { - switch (typeof value) { - case "number": - case "string": - case "boolean": - case "undefined": - case "bigint": - return value; - case "object": { - if (objectCloneMemo.has(value)) { - return objectCloneMemo.get(value); - } - if (value === null) { - return value; - } - if (value instanceof Date) { - return new Date(value.valueOf()); - } - if (value instanceof RegExp) { - return new RegExp(value); - } - if (value instanceof SharedArrayBuffer) { - return value; - } - if (value instanceof ArrayBuffer) { - const cloned = cloneArrayBuffer( - value, - 0, - value.byteLength, - ArrayBuffer, - ); - objectCloneMemo.set(value, cloned); - return cloned; - } - if (ArrayBuffer.isView(value)) { - const clonedBuffer = cloneValue(value.buffer) as ArrayBufferLike; - // Use DataViewConstructor type purely for type-checking, can be a - // DataView or TypedArray. They use the same constructor signature, - // only DataView has a length in bytes and TypedArrays use a length in - // terms of elements, so we adjust for that. - let length: number; - if (value instanceof DataView) { - length = value.byteLength; - } else { - length = (value as Uint8Array).length; - } - return new (value.constructor as DataViewConstructor)( - clonedBuffer, - value.byteOffset, - length, - ); - } - if (value instanceof Map) { - const clonedMap = new Map(); - objectCloneMemo.set(value, clonedMap); - value.forEach((v, k) => clonedMap.set(k, cloneValue(v))); - return clonedMap; - } - if (value instanceof Set) { - const clonedSet = new Map(); - objectCloneMemo.set(value, clonedSet); - value.forEach((v, k) => clonedSet.set(k, cloneValue(v))); - return clonedSet; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const clonedObj = {} as Record<string, any>; - objectCloneMemo.set(value, clonedObj); - const sourceKeys = Object.getOwnPropertyNames(value); - for (const key of sourceKeys) { - clonedObj[key] = cloneValue(value[key]); - } - return clonedObj; - } - case "symbol": - case "function": - default: - throw new DOMException("Uncloneable value in stream", "DataCloneError"); - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -interface GenericConstructor<T = any> { - prototype: T; -} - -/** A helper function which ensures accessors are enumerable, as they normally - * are not. */ -export function defineEnumerableProps( - Ctor: GenericConstructor, - props: string[], -): void { - for (const prop of props) { - Reflect.defineProperty(Ctor.prototype, prop, { enumerable: true }); - } -} - -// @internal -export function getHeaderValueParams(value: string): Map<string, string> { - const params = new Map(); - // Forced to do so for some Map constructor param mismatch - value - .split(";") - .slice(1) - .map((s): string[] => s.trim().split("=")) - .filter((arr): boolean => arr.length > 1) - .map(([k, v]): [string, string] => [k, v.replace(/^"([^"]*)"$/, "$1")]) - .forEach(([k, v]): Map<string, string> => params.set(k, v)); - return params; -} - -// @internal -export function hasHeaderValueOf(s: string, value: string): boolean { - return new RegExp(`^${value}[\t\s]*;?`).test(s); -} - -/** An internal function which provides a function name for some generated - * functions, so stack traces are a bit more readable. - * - * @internal */ -export function setFunctionName(fn: Function, value: string): void { - Object.defineProperty(fn, "name", { value, configurable: true }); -} diff --git a/cli/js/web/workers.ts b/cli/js/web/workers.ts deleted file mode 100644 index 5fd63477a..000000000 --- a/cli/js/web/workers.ts +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { - createWorker, - hostTerminateWorker, - hostPostMessage, - hostGetMessage, -} from "../ops/worker_host.ts"; -import { log } from "../util.ts"; -import { TextDecoder, TextEncoder } from "./text_encoding.ts"; -/* -import { blobURLMap } from "./web/url.ts"; -*/ -import { ErrorEventImpl as ErrorEvent } from "./error_event.ts"; -import { EventImpl as Event } from "./event.ts"; -import { EventTargetImpl as EventTarget } from "./event_target.ts"; - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - -export interface MessageEventInit extends EventInit { - data?: any; - origin?: string; - lastEventId?: string; -} - -export class MessageEvent extends Event { - readonly data: any; - readonly origin: string; - readonly lastEventId: string; - - constructor(type: string, eventInitDict?: MessageEventInit) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - }); - - this.data = eventInitDict?.data ?? null; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } -} - -function encodeMessage(data: any): Uint8Array { - const dataJson = JSON.stringify(data); - return encoder.encode(dataJson); -} - -function decodeMessage(dataIntArray: Uint8Array): any { - const dataJson = decoder.decode(dataIntArray); - return JSON.parse(dataJson); -} - -interface WorkerHostError { - message: string; - fileName?: string; - lineNumber?: number; - columnNumber?: number; -} - -interface WorkerHostMessage { - type: "terminalError" | "error" | "msg"; - data?: any; - error?: WorkerHostError; -} - -export interface Worker { - onerror?: (e: ErrorEvent) => void; - onmessage?: (e: MessageEvent) => void; - onmessageerror?: (e: MessageEvent) => void; - postMessage(data: any): void; - terminate(): void; -} - -export interface WorkerOptions { - type?: "classic" | "module"; - name?: string; - deno?: boolean; -} - -export class WorkerImpl extends EventTarget implements Worker { - readonly #id: number; - readonly #name: string; - #terminated = false; - - public onerror?: (e: ErrorEvent) => void; - public onmessage?: (e: MessageEvent) => void; - public onmessageerror?: (e: MessageEvent) => void; - - constructor(specifier: string, options?: WorkerOptions) { - super(); - const { type = "classic", name = "unknown" } = options ?? {}; - - if (type !== "module") { - throw new Error( - 'Not yet implemented: only "module" type workers are supported', - ); - } - - this.#name = name; - const hasSourceCode = false; - const sourceCode = decoder.decode(new Uint8Array()); - - /* TODO(bartlomieju): - // Handle blob URL. - if (specifier.startsWith("blob:")) { - hasSourceCode = true; - const b = blobURLMap.get(specifier); - if (!b) { - throw new Error("No Blob associated with the given URL is found"); - } - const blobBytes = blobBytesWeakMap.get(b!); - if (!blobBytes) { - throw new Error("Invalid Blob"); - } - sourceCode = blobBytes!; - } - */ - - const useDenoNamespace = options ? !!options.deno : false; - - const { id } = createWorker( - specifier, - hasSourceCode, - sourceCode, - useDenoNamespace, - options?.name, - ); - this.#id = id; - this.#poll(); - } - - #handleMessage = (msgData: any): void => { - let data; - try { - data = decodeMessage(new Uint8Array(msgData)); - } catch (e) { - const msgErrorEvent = new MessageEvent("messageerror", { - cancelable: false, - data, - }); - if (this.onmessageerror) { - this.onmessageerror(msgErrorEvent); - } - return; - } - - const msgEvent = new MessageEvent("message", { - cancelable: false, - data, - }); - - if (this.onmessage) { - this.onmessage(msgEvent); - } - - this.dispatchEvent(msgEvent); - }; - - #handleError = (e: WorkerHostError): boolean => { - const event = new ErrorEvent("error", { - cancelable: true, - message: e.message, - lineno: e.lineNumber ? e.lineNumber + 1 : undefined, - colno: e.columnNumber ? e.columnNumber + 1 : undefined, - filename: e.fileName, - error: null, - }); - - let handled = false; - if (this.onerror) { - this.onerror(event); - } - - this.dispatchEvent(event); - if (event.defaultPrevented) { - handled = true; - } - - return handled; - }; - - #poll = async (): Promise<void> => { - while (!this.#terminated) { - const event = (await hostGetMessage(this.#id)) as WorkerHostMessage; - - // If terminate was called then we ignore all messages - if (this.#terminated) { - return; - } - - const type = event.type; - - if (type === "terminalError") { - this.#terminated = true; - if (!this.#handleError(event.error!)) { - throw Error(event.error!.message); - } - continue; - } - - if (type === "msg") { - this.#handleMessage(event.data); - continue; - } - - if (type === "error") { - if (!this.#handleError(event.error!)) { - throw Error(event.error!.message); - } - continue; - } - - if (type === "close") { - log(`Host got "close" message from worker: ${this.#name}`); - this.#terminated = true; - return; - } - - throw new Error(`Unknown worker event: "${type}"`); - } - }; - - postMessage(message: any, transferOrOptions?: any): void { - if (transferOrOptions) { - throw new Error( - "Not yet implemented: `transfer` and `options` are not supported.", - ); - } - - if (this.#terminated) { - return; - } - - hostPostMessage(this.#id, encodeMessage(message)); - } - - terminate(): void { - if (!this.#terminated) { - this.#terminated = true; - hostTerminateWorker(this.#id); - } - } -} |