From a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 11 Aug 2021 12:27:05 +0200 Subject: Rename extensions/ directory to ext/ (#11643) --- ext/web/00_infra.js | 264 +++ ext/web/01_dom_exception.js | 171 ++ ext/web/01_mimesniff.js | 211 ++ ext/web/02_event.js | 1294 +++++++++++ ext/web/02_structured_clone.js | 85 + ext/web/03_abort_signal.js | 123 ++ ext/web/04_global_interfaces.js | 79 + ext/web/05_base64.js | 73 + ext/web/06_streams.js | 4473 +++++++++++++++++++++++++++++++++++++++ ext/web/06_streams_types.d.ts | 55 + ext/web/08_text_encoding.js | 420 ++++ ext/web/09_file.js | 569 +++++ ext/web/10_filereader.js | 461 ++++ ext/web/11_blob_url.js | 59 + ext/web/12_location.js | 409 ++++ ext/web/13_message_port.js | 286 +++ ext/web/Cargo.toml | 26 + ext/web/README.md | 6 + ext/web/blob.rs | 265 +++ ext/web/internal.d.ts | 98 + ext/web/lib.deno_web.d.ts | 752 +++++++ ext/web/lib.rs | 390 ++++ ext/web/message_port.rs | 217 ++ 23 files changed, 10786 insertions(+) create mode 100644 ext/web/00_infra.js create mode 100644 ext/web/01_dom_exception.js create mode 100644 ext/web/01_mimesniff.js create mode 100644 ext/web/02_event.js create mode 100644 ext/web/02_structured_clone.js create mode 100644 ext/web/03_abort_signal.js create mode 100644 ext/web/04_global_interfaces.js create mode 100644 ext/web/05_base64.js create mode 100644 ext/web/06_streams.js create mode 100644 ext/web/06_streams_types.d.ts create mode 100644 ext/web/08_text_encoding.js create mode 100644 ext/web/09_file.js create mode 100644 ext/web/10_filereader.js create mode 100644 ext/web/11_blob_url.js create mode 100644 ext/web/12_location.js create mode 100644 ext/web/13_message_port.js create mode 100644 ext/web/Cargo.toml create mode 100644 ext/web/README.md create mode 100644 ext/web/blob.rs create mode 100644 ext/web/internal.d.ts create mode 100644 ext/web/lib.deno_web.d.ts create mode 100644 ext/web/lib.rs create mode 100644 ext/web/message_port.rs (limited to 'ext/web') diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js new file mode 100644 index 000000000..7c065bcd3 --- /dev/null +++ b/ext/web/00_infra.js @@ -0,0 +1,264 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = Deno.core; + const { + RegExp, + ArrayPrototypeMap, + StringPrototypeCharCodeAt, + NumberPrototypeToString, + StringPrototypePadStart, + TypeError, + ArrayPrototypeJoin, + StringPrototypeCharAt, + StringPrototypeSlice, + String, + StringPrototypeReplace, + StringPrototypeToUpperCase, + StringPrototypeToLowerCase, + StringPrototypeSubstring, + } = window.__bootstrap.primordials; + + const ASCII_DIGIT = ["\u0030-\u0039"]; + const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; + const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; + const ASCII_ALPHA = [...ASCII_UPPER_ALPHA, ...ASCII_LOWER_ALPHA]; + const ASCII_ALPHANUMERIC = [...ASCII_DIGIT, ...ASCII_ALPHA]; + + const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; + const HTTP_WHITESPACE = ["\u000A", "\u000D", ...HTTP_TAB_OR_SPACE]; + + const HTTP_TOKEN_CODE_POINT = [ + "\u0021", + "\u0023", + "\u0024", + "\u0025", + "\u0026", + "\u0027", + "\u002A", + "\u002B", + "\u002D", + "\u002E", + "\u005E", + "\u005F", + "\u0060", + "\u007C", + "\u007E", + ...ASCII_ALPHANUMERIC, + ]; + const HTTP_TOKEN_CODE_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, + ); + const HTTP_QUOTED_STRING_TOKEN_POINT = [ + "\u0009", + "\u0020-\u007E", + "\u0080-\u00FF", + ]; + const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, + ); + const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); + const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( + `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, + "g", + ); + const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( + `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, + "g", + ); + const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); + const HTTP_WHITESPACE_PREFIX_RE = new RegExp( + `^[${HTTP_WHITESPACE_MATCHER}]+`, + "g", + ); + const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( + `[${HTTP_WHITESPACE_MATCHER}]+$`, + "g", + ); + + /** + * Turn a string of chars into a regex safe matcher. + * @param {string[]} chars + * @returns {string} + */ + function regexMatcher(chars) { + const matchers = ArrayPrototypeMap(chars, (char) => { + if (char.length === 1) { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", + ); + return `\\u${a}`; + } else if (char.length === 3 && char[1] === "-") { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", + ); + const b = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16), + 4, + "0", + ); + return `\\u${a}-\\u${b}`; + } else { + throw TypeError("unreachable"); + } + }); + return ArrayPrototypeJoin(matchers, ""); + } + + /** + * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + * @param {string} input + * @param {number} position + * @param {(char: string) => boolean} condition + * @returns {{result: string, position: number}} + */ + function collectSequenceOfCodepoints(input, position, condition) { + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && condition(c); + c = StringPrototypeCharAt(input, ++position) + ); + return { result: StringPrototypeSlice(input, start, position), position }; + } + + /** + * @param {string} s + * @returns {string} + */ + function byteUpperCase(s) { + return StringPrototypeReplace( + String(s), + /[a-z]/g, + function byteUpperCaseReplace(c) { + return StringPrototypeToUpperCase(c); + }, + ); + } + + /** + * @param {string} s + * @returns {string} + */ + function byteLowerCase(s) { + return StringPrototypeReplace( + String(s), + /[A-Z]/g, + function byteUpperCaseReplace(c) { + return StringPrototypeToLowerCase(c); + }, + ); + } + + /** + * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string + * @param {string} input + * @param {number} position + * @param {boolean} extractValue + * @returns {{result: string, position: number}} + */ + function collectHttpQuotedString(input, position, extractValue) { + // 1. + const positionStart = position; + // 2. + let value = ""; + // 3. + if (input[position] !== "\u0022") throw new TypeError('must be "'); + // 4. + position++; + // 5. + while (true) { + // 5.1. + const res = collectSequenceOfCodepoints( + input, + position, + (c) => c !== "\u0022" && c !== "\u005C", + ); + value += res.result; + position = res.position; + // 5.2. + if (position >= input.length) break; + // 5.3. + const quoteOrBackslash = input[position]; + // 5.4. + position++; + // 5.5. + if (quoteOrBackslash === "\u005C") { + // 5.5.1. + if (position >= input.length) { + value += "\u005C"; + break; + } + // 5.5.2. + value += input[position]; + // 5.5.3. + position++; + } else { // 5.6. + // 5.6.1 + if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); + // 5.6.2 + break; + } + } + // 6. + if (extractValue) return { result: value, position }; + // 7. + return { + result: StringPrototypeSubstring(input, positionStart, position + 1), + position, + }; + } + + /** + * @param {Uint8Array} data + * @returns {string} + */ + function forgivingBase64Encode(data) { + return core.opSync("op_base64_encode", data); + } + + /** + * @param {string} data + * @returns {Uint8Array} + */ + function forgivingBase64Decode(data) { + return core.opSync("op_base64_decode", data); + } + + window.__bootstrap.infra = { + collectSequenceOfCodepoints, + ASCII_DIGIT, + ASCII_UPPER_ALPHA, + ASCII_LOWER_ALPHA, + ASCII_ALPHA, + ASCII_ALPHANUMERIC, + HTTP_TAB_OR_SPACE, + HTTP_WHITESPACE, + HTTP_TOKEN_CODE_POINT, + HTTP_TOKEN_CODE_POINT_RE, + HTTP_QUOTED_STRING_TOKEN_POINT, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TAB_OR_SPACE_PREFIX_RE, + HTTP_TAB_OR_SPACE_SUFFIX_RE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, + regexMatcher, + byteUpperCase, + byteLowerCase, + collectHttpQuotedString, + forgivingBase64Encode, + forgivingBase64Decode, + }; +})(globalThis); diff --git a/ext/web/01_dom_exception.js b/ext/web/01_dom_exception.js new file mode 100644 index 000000000..c6f60ae2f --- /dev/null +++ b/ext/web/01_dom_exception.js @@ -0,0 +1,171 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// + +"use strict"; + +((window) => { + const { + ErrorPrototype, + ObjectDefineProperty, + ObjectEntries, + ObjectSetPrototypeOf, + SymbolFor, + SymbolToStringTag, + } = window.__bootstrap.primordials; + const webidl = window.__bootstrap.webidl; + const consoleInternal = window.__bootstrap.console; + + // Defined in WebIDL 4.3. + // https://heycam.github.io/webidl/#idl-DOMException + const INDEX_SIZE_ERR = 1; + const DOMSTRING_SIZE_ERR = 2; + const HIERARCHY_REQUEST_ERR = 3; + const WRONG_DOCUMENT_ERR = 4; + const INVALID_CHARACTER_ERR = 5; + const NO_DATA_ALLOWED_ERR = 6; + const NO_MODIFICATION_ALLOWED_ERR = 7; + const NOT_FOUND_ERR = 8; + const NOT_SUPPORTED_ERR = 9; + const INUSE_ATTRIBUTE_ERR = 10; + const INVALID_STATE_ERR = 11; + const SYNTAX_ERR = 12; + const INVALID_MODIFICATION_ERR = 13; + const NAMESPACE_ERR = 14; + const INVALID_ACCESS_ERR = 15; + const VALIDATION_ERR = 16; + const TYPE_MISMATCH_ERR = 17; + const SECURITY_ERR = 18; + const NETWORK_ERR = 19; + const ABORT_ERR = 20; + const URL_MISMATCH_ERR = 21; + const QUOTA_EXCEEDED_ERR = 22; + const TIMEOUT_ERR = 23; + const INVALID_NODE_TYPE_ERR = 24; + const DATA_CLONE_ERR = 25; + + // Defined in WebIDL 2.8.1. + // https://heycam.github.io/webidl/#dfn-error-names-table + /** @type {Record} */ + const nameToCodeMapping = { + IndexSizeError: INDEX_SIZE_ERR, + HierarchyRequestError: HIERARCHY_REQUEST_ERR, + WrongDocumentError: WRONG_DOCUMENT_ERR, + InvalidCharacterError: INVALID_CHARACTER_ERR, + NoModificationAllowedError: NO_MODIFICATION_ALLOWED_ERR, + NotFoundError: NOT_FOUND_ERR, + NotSupportedError: NOT_SUPPORTED_ERR, + InUseAttributeError: INUSE_ATTRIBUTE_ERR, + InvalidStateError: INVALID_STATE_ERR, + SyntaxError: SYNTAX_ERR, + InvalidModificationError: INVALID_MODIFICATION_ERR, + NamespaceError: NAMESPACE_ERR, + InvalidAccessError: INVALID_ACCESS_ERR, + TypeMismatchError: TYPE_MISMATCH_ERR, + SecurityError: SECURITY_ERR, + NetworkError: NETWORK_ERR, + AbortError: ABORT_ERR, + URLMismatchError: URL_MISMATCH_ERR, + QuotaExceededError: QUOTA_EXCEEDED_ERR, + TimeoutError: TIMEOUT_ERR, + InvalidNodeTypeError: INVALID_NODE_TYPE_ERR, + DataCloneError: DATA_CLONE_ERR, + }; + + // Defined in WebIDL 4.3. + // https://heycam.github.io/webidl/#idl-DOMException + class DOMException { + #message = ""; + #name = ""; + #code = 0; + + constructor(message = "", name = "Error") { + this.#message = webidl.converters.DOMString(message, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 1", + }); + this.#name = webidl.converters.DOMString(name, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 2", + }); + this.#code = nameToCodeMapping[this.#name] ?? 0; + } + + get message() { + return this.#message; + } + + get name() { + return this.#name; + } + + get code() { + return this.#code; + } + + get [SymbolToStringTag]() { + return "DOMException"; + } + + [SymbolFor("Deno.customInspect")](inspect) { + if (this instanceof DOMException) { + return `DOMException: ${this.#message}`; + } else { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: false, + keys: [ + "message", + "name", + "code", + ], + })); + } + } + } + + ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); + + webidl.configurePrototype(DOMException); + + for ( + const [key, value] of ObjectEntries({ + INDEX_SIZE_ERR, + DOMSTRING_SIZE_ERR, + HIERARCHY_REQUEST_ERR, + WRONG_DOCUMENT_ERR, + INVALID_CHARACTER_ERR, + NO_DATA_ALLOWED_ERR, + NO_MODIFICATION_ALLOWED_ERR, + NOT_FOUND_ERR, + NOT_SUPPORTED_ERR, + INUSE_ATTRIBUTE_ERR, + INVALID_STATE_ERR, + SYNTAX_ERR, + INVALID_MODIFICATION_ERR, + NAMESPACE_ERR, + INVALID_ACCESS_ERR, + VALIDATION_ERR, + TYPE_MISMATCH_ERR, + SECURITY_ERR, + NETWORK_ERR, + ABORT_ERR, + URL_MISMATCH_ERR, + QUOTA_EXCEEDED_ERR, + TIMEOUT_ERR, + INVALID_NODE_TYPE_ERR, + DATA_CLONE_ERR, + }) + ) { + const desc = { value, enumerable: true }; + ObjectDefineProperty(DOMException, key, desc); + ObjectDefineProperty(DOMException.prototype, key, desc); + } + + window.__bootstrap.domException = { DOMException }; +})(this); diff --git a/ext/web/01_mimesniff.js b/ext/web/01_mimesniff.js new file mode 100644 index 000000000..360d1ffe4 --- /dev/null +++ b/ext/web/01_mimesniff.js @@ -0,0 +1,211 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const { + ArrayPrototypeIncludes, + Map, + MapPrototypeHas, + MapPrototypeSet, + RegExpPrototypeTest, + StringPrototypeReplaceAll, + StringPrototypeToLowerCase, + } = window.__bootstrap.primordials; + const { + collectSequenceOfCodepoints, + HTTP_WHITESPACE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TOKEN_CODE_POINT_RE, + collectHttpQuotedString, + } = window.__bootstrap.infra; + + /** + * @typedef MimeType + * @property {string} type + * @property {string} subtype + * @property {Map} parameters + */ + + /** + * @param {string} input + * @returns {MimeType | null} + */ + function parseMimeType(input) { + // 1. + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); + + // 2. + let position = 0; + const endOfInput = input.length; + + // 3. + const res1 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u002F", + ); + const type = res1.result; + position = res1.position; + + // 4. + if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { + return null; + } + + // 5. + if (position >= endOfInput) return null; + + // 6. + position++; + + // 7. + const res2 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u003B", + ); + let subtype = res2.result; + position = res2.position; + + // 8. + subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); + + // 9. + if ( + subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) + ) { + return null; + } + + // 10. + const mimeType = { + type: StringPrototypeToLowerCase(type), + subtype: StringPrototypeToLowerCase(subtype), + /** @type {Map} */ + parameters: new Map(), + }; + + // 11. + while (position < endOfInput) { + // 11.1. + position++; + + // 11.2. + const res1 = collectSequenceOfCodepoints( + input, + position, + (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), + ); + position = res1.position; + + // 11.3. + const res2 = collectSequenceOfCodepoints( + input, + position, + (c) => c !== "\u003B" && c !== "\u003D", + ); + let parameterName = res2.result; + position = res2.position; + + // 11.4. + parameterName = StringPrototypeToLowerCase(parameterName); + + // 11.5. + if (position < endOfInput) { + if (input[position] == "\u003B") continue; + position++; + } + + // 11.6. + if (position >= endOfInput) break; + + // 11.7. + let parameterValue = null; + + // 11.8. + if (input[position] === "\u0022") { + // 11.8.1. + const res = collectHttpQuotedString(input, position, true); + parameterValue = res.result; + position = res.position; + + // 11.8.2. + position++; + } else { // 11.9. + // 11.9.1. + const res = collectSequenceOfCodepoints( + input, + position, + (c) => c !== "\u003B", + ); + parameterValue = res.result; + position = res.position; + + // 11.9.2. + parameterValue = StringPrototypeReplaceAll( + parameterValue, + HTTP_WHITESPACE_SUFFIX_RE, + "", + ); + + // 11.9.3. + if (parameterValue === "") continue; + } + + // 11.10. + if ( + parameterName !== "" && + RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && + RegExpPrototypeTest( + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + parameterValue, + ) && + !MapPrototypeHas(mimeType.parameters, parameterName) + ) { + MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); + } + } + + // 12. + return mimeType; + } + + /** + * @param {MimeType} mimeType + * @returns {string} + */ + function essence(mimeType) { + return `${mimeType.type}/${mimeType.subtype}`; + } + + /** + * @param {MimeType} mimeType + * @returns {string} + */ + function serializeMimeType(mimeType) { + let serialization = essence(mimeType); + for (const param of mimeType.parameters) { + serialization += `;${param[0]}=`; + let value = param[1]; + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) { + value = StringPrototypeReplaceAll(value, "\\", "\\\\"); + value = StringPrototypeReplaceAll(value, '"', '\\"'); + value = `"${value}"`; + } + serialization += value; + } + return serialization; + } + + window.__bootstrap.mimesniff = { parseMimeType, essence, serializeMimeType }; +})(this); diff --git a/ext/web/02_event.js b/ext/web/02_event.js new file mode 100644 index 000000000..4cca20e00 --- /dev/null +++ b/ext/web/02_event.js @@ -0,0 +1,1294 @@ +// Copyright 2018-2021 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. +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { DOMException } = window.__bootstrap.domException; + const consoleInternal = window.__bootstrap.console; + const { + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + Boolean, + DateNow, + Error, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectCreate, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ReflectDefineProperty, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, + } = window.__bootstrap.primordials; + + // accessors for non runtime visible data + + function getDispatched(event) { + return Boolean(event[_dispatched]); + } + + function getPath(event) { + return event[_path] ?? []; + } + + function getStopImmediatePropagation(event) { + return Boolean(event[_stopImmediatePropagationFlag]); + } + + function setCurrentTarget( + event, + value, + ) { + event[_attributes].currentTarget = value; + } + + function setIsTrusted(event, value) { + event[_isTrusted] = value; + } + + function setDispatched(event, value) { + event[_dispatched] = value; + } + + function setEventPhase(event, value) { + event[_attributes].eventPhase = value; + } + + function setInPassiveListener(event, value) { + event[_inPassiveListener] = value; + } + + function setPath(event, value) { + event[_path] = value; + } + + function setRelatedTarget( + event, + value, + ) { + event[_attributes].relatedTarget = value; + } + + function setTarget(event, value) { + event[_attributes].target = value; + } + + function setStopImmediatePropagation( + event, + value, + ) { + event[_stopImmediatePropagationFlag] = value; + } + + // Type guards that widen the event type + + function hasRelatedTarget( + event, + ) { + return "relatedTarget" in event; + } + + const isTrusted = ObjectGetOwnPropertyDescriptor({ + get isTrusted() { + return this[_isTrusted]; + }, + }, "isTrusted").get; + + const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ + key: "bubbles", + defaultValue: false, + converter: webidl.converters.boolean, + }, { + key: "cancelable", + defaultValue: false, + converter: webidl.converters.boolean, + }, { + key: "composed", + defaultValue: false, + converter: webidl.converters.boolean, + }]); + + const _attributes = Symbol("[[attributes]]"); + const _canceledFlag = Symbol("[[canceledFlag]]"); + const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); + const _stopImmediatePropagationFlag = Symbol( + "[[stopImmediatePropagationFlag]]", + ); + const _inPassiveListener = Symbol("[[inPassiveListener]]"); + const _dispatched = Symbol("[[dispatched]]"); + const _isTrusted = Symbol("[[isTrusted]]"); + const _path = Symbol("[[path]]"); + + class Event { + [_attributes] = {}; + [_canceledFlag] = false; + [_stopPropagationFlag] = false; + [_stopImmediatePropagationFlag] = false; + [_inPassiveListener] = false; + [_dispatched] = false; + [_isTrusted] = false; + [_path] = []; + + constructor(type, eventInitDict = {}) { + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'Event'", + }); + type = webidl.converters.DOMString(type, { + prefix: "Failed to construct 'Event'", + context: "Argument 1", + }); + const eventInit = eventInitConverter(eventInitDict, { + prefix: "Failed to construct 'Event'", + context: "Argument 2", + }); + this[_attributes] = { + type, + ...eventInit, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + ReflectDefineProperty(this, "isTrusted", { + enumerable: true, + get: isTrusted, + }); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof Event, + keys: EVENT_PROPS, + })); + } + + get type() { + return this[_attributes].type; + } + + get target() { + return this[_attributes].target; + } + + get srcElement() { + return null; + } + + set srcElement(_) { + // this member is deprecated + } + + get currentTarget() { + return this[_attributes].currentTarget; + } + + composedPath() { + const path = this[_path]; + if (path.length === 0) { + return []; + } + + if (!this.currentTarget) { + throw new Error("assertion error"); + } + const composedPath = [ + { + 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) { + ArrayPrototypeUnshift(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } + + if (slotInClosedTree) { + currentHiddenLevel--; + + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; + } + } + } + + currentHiddenLevel = currentTargetHiddenSubtreeLevel; + maxHiddenLevel = currentTargetHiddenSubtreeLevel; + + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; + + if (slotInClosedTree) { + currentHiddenLevel++; + } + + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypePush(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } + + if (rootOfClosedTree) { + currentHiddenLevel--; + + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; + } + } + } + return ArrayPrototypeMap(composedPath, (p) => p.item); + } + + get NONE() { + return Event.NONE; + } + + get CAPTURING_PHASE() { + return Event.CAPTURING_PHASE; + } + + get AT_TARGET() { + return Event.AT_TARGET; + } + + get BUBBLING_PHASE() { + return Event.BUBBLING_PHASE; + } + + static get NONE() { + return 0; + } + + static get CAPTURING_PHASE() { + return 1; + } + + static get AT_TARGET() { + return 2; + } + + static get BUBBLING_PHASE() { + return 3; + } + + get eventPhase() { + return this[_attributes].eventPhase; + } + + stopPropagation() { + this[_stopPropagationFlag] = true; + } + + get cancelBubble() { + return this[_stopPropagationFlag]; + } + + set cancelBubble(value) { + this[_stopPropagationFlag] = webidl.converters.boolean(value); + } + + stopImmediatePropagation() { + this[_stopPropagationFlag] = true; + this[_stopImmediatePropagationFlag] = true; + } + + get bubbles() { + return this[_attributes].bubbles; + } + + get cancelable() { + return this[_attributes].cancelable; + } + + get returnValue() { + return !this[_canceledFlag]; + } + + set returnValue(value) { + if (!webidl.converters.boolean(value)) { + this[_canceledFlag] = true; + } + } + + preventDefault() { + if (this[_attributes].cancelable && !this[_inPassiveListener]) { + this[_canceledFlag] = true; + } + } + + get defaultPrevented() { + return this[_canceledFlag]; + } + + get composed() { + return this[_attributes].composed; + } + + get initialized() { + return true; + } + + get timeStamp() { + return this[_attributes].timeStamp; + } + } + + function defineEnumerableProps( + Ctor, + props, + ) { + for (const prop of props) { + ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); + } + } + + const EVENT_PROPS = [ + "bubbles", + "cancelable", + "composed", + "currentTarget", + "defaultPrevented", + "eventPhase", + "srcElement", + "target", + "returnValue", + "timeStamp", + "type", + ]; + + defineEnumerableProps(Event, EVENT_PROPS); + + // This is currently the only node type we are using, so instead of implementing + // the whole of the Node interface at the moment, this just gives us the one + // value to power the standards based logic + const DOCUMENT_FRAGMENT_NODE = 11; + + // DOM Logic Helper functions and type guards + + /** Get the parent node, for event targets that have a parent. + * + * Ref: https://dom.spec.whatwg.org/#get-the-parent */ + function getParent(eventTarget) { + return isNode(eventTarget) ? eventTarget.parentNode : null; + } + + function getRoot(eventTarget) { + return isNode(eventTarget) + ? eventTarget.getRootNode({ composed: true }) + : null; + } + + function isNode( + eventTarget, + ) { + return Boolean(eventTarget && "nodeType" in eventTarget); + } + + // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor + function isShadowInclusiveAncestor( + ancestor, + node, + ) { + while (isNode(node)) { + if (node === ancestor) { + return true; + } + + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); + } + } + + return false; + } + + function isShadowRoot(nodeImpl) { + return Boolean( + nodeImpl && + isNode(nodeImpl) && + nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && + getHost(nodeImpl) != null, + ); + } + + function isSlotable( + nodeImpl, + ) { + return Boolean(isNode(nodeImpl) && "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, + target, + targetOverride, + relatedTarget, + touchTargets, + slotInClosedTree, + ) { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && + getMode(target) === "closed"; + + ArrayPrototypePush(getPath(eventImpl), { + item: target, + itemInShadowTree, + target: targetOverride, + relatedTarget, + touchTargetList: touchTargets, + rootOfClosedTree, + slotInClosedTree, + }); + } + + function dispatch( + targetImpl, + eventImpl, + targetOverride, + ) { + let clearTargets = false; + let activationTarget = null; + + setDispatched(eventImpl, true); + + targetOverride = targetOverride ?? targetImpl; + const eventRelatedTarget = hasRelatedTarget(eventImpl) + ? eventImpl.relatedTarget + : null; + let relatedTarget = retarget(eventRelatedTarget, targetImpl); + + if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { + const touchTargets = []; + + appendToEventPath( + eventImpl, + targetImpl, + targetOverride, + relatedTarget, + 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(bartlomieju): invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } + + return !eventImpl.defaultPrevented; + } + + /** Inner invoking of the event listeners where the resolved listeners are + * called. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ + function innerInvokeEventListeners( + eventImpl, + targetListeners, + ) { + let found = false; + + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { + return found; + } + + // Copy event listeners before iterating since the list can be modified during the iteration. + const handlers = ArrayPrototypeSlice(targetListeners[type]); + + for (let i = 0; i < handlers.length; i++) { + const listener = handlers[i]; + + let capture, once, passive; + if (typeof listener.options === "boolean") { + capture = listener.options; + once = false; + passive = false; + } else { + capture = listener.options.capture; + once = listener.options.once; + passive = listener.options.passive; + } + + // Check if the event listener has been removed since the listeners has been cloned. + if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { + continue; + } + + found = true; + + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } + + if (once) { + ArrayPrototypeSplice( + targetListeners[type], + ArrayPrototypeIndexOf(targetListeners[type], listener), + 1, + ); + } + + if (passive) { + setInPassiveListener(eventImpl, true); + } + + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); + } + } else { + FunctionPrototypeCall( + listener.callback, + eventImpl.currentTarget, + eventImpl, + ); + } + + setInPassiveListener(eventImpl, false); + + if (getStopImmediatePropagation(eventImpl)) { + return found; + } + } + + return found; + } + + /** Invokes the listeners on a given event path with the supplied event. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ + function invokeEventListeners(tuple, eventImpl) { + const path = getPath(eventImpl); + const tupleIndex = ArrayPrototypeIndexOf(path, tuple); + for (let i = tupleIndex; i >= 0; i--) { + const t = path[i]; + if (t.target) { + setTarget(eventImpl, t.target); + break; + } + } + + setRelatedTarget(eventImpl, tuple.relatedTarget); + + if (eventImpl.cancelBubble) { + return; + } + + setCurrentTarget(eventImpl, tuple.item); + + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); + } + + function normalizeAddEventHandlerOptions( + options, + ) { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + once: false, + passive: false, + }; + } else { + return options; + } + } + + function normalizeEventHandlerOptions( + options, + ) { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + }; + } else { + return options; + } + } + + /** Retarget the target following the spec logic. + * + * Ref: https://dom.spec.whatwg.org/#retarget */ + function retarget(a, b) { + while (true) { + if (!isNode(a)) { + return a; + } + + const aRoot = a.getRootNode(); + + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } + + a = getHost(aRoot); + } + } + } + + // Accessors for non-public data + + const eventTargetData = new WeakMap(); + + function setEventTargetData(value) { + WeakMapPrototypeSet(eventTargetData, value, getDefaultTargetData()); + } + + function getAssignedSlot(target) { + return Boolean(WeakMapPrototypeGet(eventTargetData, target)?.assignedSlot); + } + + function getHasActivationBehavior(target) { + return Boolean( + WeakMapPrototypeGet(eventTargetData, target)?.hasActivationBehavior, + ); + } + + function getHost(target) { + return WeakMapPrototypeGet(eventTargetData, target)?.host ?? null; + } + + function getListeners(target) { + return WeakMapPrototypeGet(eventTargetData, target)?.listeners ?? {}; + } + + function getMode(target) { + return WeakMapPrototypeGet(eventTargetData, target)?.mode ?? null; + } + + function getDefaultTargetData() { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: ObjectCreate(null), + mode: "", + }; + } + + class EventTarget { + constructor() { + WeakMapPrototypeSet(eventTargetData, this, getDefaultTargetData()); + } + + addEventListener( + type, + callback, + options, + ) { + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'addEventListener' on 'EventTarget'", + }); + if (callback === null) { + return; + } + + options = normalizeAddEventHandlerOptions(options); + const { listeners } = WeakMapPrototypeGet( + eventTargetData, + 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; + } + } + if (options?.signal) { + const signal = options?.signal; + if (signal.aborted) { + // If signal is not null and its aborted flag is set, then return. + return; + } else { + // If listener’s signal is not null, then add the following abort + // abort steps to it: Remove an event listener. + signal.addEventListener("abort", () => { + this.removeEventListener(type, callback, options); + }); + } + } else if (options?.signal === null) { + throw new TypeError("signal must be non-null"); + } + + ArrayPrototypePush(listeners[type], { callback, options }); + } + + removeEventListener( + type, + callback, + options, + ) { + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", + }); + + const listeners = + WeakMapPrototypeGet(eventTargetData, this ?? globalThis).listeners; + if (callback !== null && type in listeners) { + listeners[type] = ArrayPrototypeFilter( + listeners[type], + (listener) => listener.callback !== callback, + ); + } else if (callback === null || !listeners[type]) { + return; + } + + options = normalizeEventHandlerOptions(options); + + for (let i = 0; i < listeners[type].length; ++i) { + const listener = listeners[type][i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + ArrayPrototypeSplice(listeners[type], i, 1); + break; + } + } + } + + dispatchEvent(event) { + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", + }); + const self = this ?? globalThis; + + const listeners = WeakMapPrototypeGet(eventTargetData, self).listeners; + if (!(event.type in listeners)) { + setTarget(event, this); + return true; + } + + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); + } + + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); + } + + return dispatch(self, event); + } + + get [SymbolToStringTag]() { + return "EventTarget"; + } + + getParent(_event) { + return null; + } + } + + defineEnumerableProps(EventTarget, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", + ]); + + class ErrorEvent extends Event { + #message = ""; + #filename = ""; + #lineno = ""; + #colno = ""; + #error = ""; + + get message() { + return this.#message; + } + get filename() { + return this.#filename; + } + get lineno() { + return this.#lineno; + } + get colno() { + return this.#colno; + } + get error() { + return this.#error; + } + + constructor( + type, + { + bubbles, + cancelable, + composed, + message = "", + filename = "", + lineno = 0, + colno = 0, + error = null, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); + + this.#message = message; + this.#filename = filename; + this.#lineno = lineno; + this.#colno = colno; + this.#error = error; + } + + get [SymbolToStringTag]() { + return "ErrorEvent"; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof ErrorEvent, + keys: [ + ...EVENT_PROPS, + "message", + "filename", + "lineno", + "colno", + "error", + ], + })); + } + } + + defineEnumerableProps(ErrorEvent, [ + "message", + "filename", + "lineno", + "colno", + "error", + ]); + + class CloseEvent extends Event { + #wasClean = ""; + #code = ""; + #reason = ""; + + get wasClean() { + return this.#wasClean; + } + get code() { + return this.#code; + } + get reason() { + return this.#reason; + } + + constructor(type, { + bubbles, + cancelable, + composed, + wasClean = false, + code = 0, + reason = "", + } = {}) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); + + this.#wasClean = wasClean; + this.#code = code; + this.#reason = reason; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof CloseEvent, + keys: [ + ...EVENT_PROPS, + "wasClean", + "code", + "reason", + ], + })); + } + } + + class MessageEvent extends Event { + get source() { + return null; + } + + constructor(type, eventInitDict) { + super(type, { + bubbles: eventInitDict?.bubbles ?? false, + cancelable: eventInitDict?.cancelable ?? false, + composed: eventInitDict?.composed ?? false, + }); + + this.data = eventInitDict?.data ?? null; + this.ports = eventInitDict?.ports ?? []; + this.origin = eventInitDict?.origin ?? ""; + this.lastEventId = eventInitDict?.lastEventId ?? ""; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof MessageEvent, + keys: [ + ...EVENT_PROPS, + "data", + "origin", + "lastEventId", + ], + })); + } + } + + class CustomEvent extends Event { + #detail = null; + + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'CustomEvent'", + }); + const { detail } = eventInitDict; + this.#detail = detail; + } + + get detail() { + return this.#detail; + } + + get [SymbolToStringTag]() { + return "CustomEvent"; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof CustomEvent, + keys: [ + ...EVENT_PROPS, + "detail", + ], + })); + } + } + + ReflectDefineProperty(CustomEvent.prototype, "detail", { + enumerable: true, + }); + + // ProgressEvent could also be used in other DOM progress event emits. + // Current use is for FileReader. + class ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + + this.lengthComputable = eventInitDict?.lengthComputable ?? false; + this.loaded = eventInitDict?.loaded ?? 0; + this.total = eventInitDict?.total ?? 0; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof ProgressEvent, + keys: [ + ...EVENT_PROPS, + "lengthComputable", + "loaded", + "total", + ], + })); + } + } + + const _eventHandlers = Symbol("eventHandlers"); + + function makeWrappedHandler(handler) { + function wrappedHandler(...args) { + if (typeof wrappedHandler.handler !== "function") { + return; + } + return FunctionPrototypeCall(wrappedHandler.handler, this, ...args); + } + wrappedHandler.handler = handler; + return wrappedHandler; + } + + // TODO(benjamingr) reuse this here and websocket where possible + function defineEventHandler(emitter, name, init) { + // HTML specification section 8.1.5.1 + ObjectDefineProperty(emitter, `on${name}`, { + get() { + const map = this[_eventHandlers]; + + if (!map) return undefined; + return MapPrototypeGet(map, name)?.handler; + }, + set(value) { + if (!this[_eventHandlers]) { + this[_eventHandlers] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else { + handlerWrapper = makeWrappedHandler(value); + this.addEventListener(name, handlerWrapper); + init?.(this); + } + MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); + }, + configurable: true, + enumerable: true, + }); + } + + window.Event = Event; + window.EventTarget = EventTarget; + window.ErrorEvent = ErrorEvent; + window.CloseEvent = CloseEvent; + window.MessageEvent = MessageEvent; + window.CustomEvent = CustomEvent; + window.ProgressEvent = ProgressEvent; + window.dispatchEvent = EventTarget.prototype.dispatchEvent; + window.addEventListener = EventTarget.prototype.addEventListener; + window.removeEventListener = EventTarget.prototype.removeEventListener; + window.__bootstrap.eventTarget = { + EventTarget, + setEventTargetData, + }; + window.__bootstrap.event = { + setIsTrusted, + setTarget, + defineEventHandler, + }; +})(this); diff --git a/ext/web/02_structured_clone.js b/ext/web/02_structured_clone.js new file mode 100644 index 000000000..4845c6508 --- /dev/null +++ b/ext/web/02_structured_clone.js @@ -0,0 +1,85 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = window.Deno.core; + const { DOMException } = window.__bootstrap.domException; + const { + ArrayBuffer, + ArrayBufferIsView, + DataView, + TypedArrayPrototypeSlice, + TypeError, + WeakMap, + WeakMapPrototypeSet, + } = window.__bootstrap.primordials; + + const objectCloneMemo = new WeakMap(); + + function cloneArrayBuffer( + srcBuffer, + srcByteOffset, + srcLength, + _cloneConstructor, + ) { + // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway + return TypedArrayPrototypeSlice( + srcBuffer, + srcByteOffset, + srcByteOffset + srcLength, + ); + } + + /** Clone a value in a similar way to structured cloning. It is similar to a +* StructureDeserialize(StructuredSerialize(...)). */ + function structuredClone(value) { + // Performance optimization for buffers, otherwise + // `serialize/deserialize` will allocate new buffer. + if (value instanceof ArrayBuffer) { + const cloned = cloneArrayBuffer( + value, + 0, + value.byteLength, + ArrayBuffer, + ); + WeakMapPrototypeSet(objectCloneMemo, value, cloned); + return cloned; + } + if (ArrayBufferIsView(value)) { + const clonedBuffer = structuredClone(value.buffer); + // 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; + if (value instanceof DataView) { + length = value.byteLength; + } else { + length = value.length; + } + return new (value.constructor)( + clonedBuffer, + value.byteOffset, + length, + ); + } + + try { + return core.deserialize(core.serialize(value)); + } catch (e) { + if (e instanceof TypeError) { + throw new DOMException("Uncloneable value", "DataCloneError"); + } + throw e; + } + } + + window.__bootstrap.structuredClone = structuredClone; +})(globalThis); diff --git a/ext/web/03_abort_signal.js b/ext/web/03_abort_signal.js new file mode 100644 index 000000000..d67bfef26 --- /dev/null +++ b/ext/web/03_abort_signal.js @@ -0,0 +1,123 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +// @ts-check +/// + +((window) => { + const webidl = window.__bootstrap.webidl; + const { setIsTrusted, defineEventHandler } = window.__bootstrap.event; + const { + Boolean, + Set, + SetPrototypeAdd, + SetPrototypeClear, + SetPrototypeDelete, + Symbol, + SymbolToStringTag, + TypeError, + } = window.__bootstrap.primordials; + + const add = Symbol("add"); + const signalAbort = Symbol("signalAbort"); + const remove = Symbol("remove"); + + const illegalConstructorKey = Symbol("illegalConstructorKey"); + + class AbortSignal extends EventTarget { + #aborted = false; + #abortAlgorithms = new Set(); + + static abort() { + const signal = new AbortSignal(illegalConstructorKey); + signal[signalAbort](); + return signal; + } + + [add](algorithm) { + SetPrototypeAdd(this.#abortAlgorithms, algorithm); + } + + [signalAbort]() { + if (this.#aborted) { + return; + } + this.#aborted = true; + for (const algorithm of this.#abortAlgorithms) { + algorithm(); + } + SetPrototypeClear(this.#abortAlgorithms); + const event = new Event("abort"); + setIsTrusted(event, true); + this.dispatchEvent(event); + } + + [remove](algorithm) { + SetPrototypeDelete(this.#abortAlgorithms, algorithm); + } + + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + this[webidl.brand] = webidl.brand; + } + + get aborted() { + return Boolean(this.#aborted); + } + + get [SymbolToStringTag]() { + return "AbortSignal"; + } + } + defineEventHandler(AbortSignal.prototype, "abort"); + + webidl.configurePrototype(AbortSignal); + + class AbortController { + #signal = new AbortSignal(illegalConstructorKey); + + get signal() { + return this.#signal; + } + + abort() { + this.#signal[signalAbort](); + } + + get [SymbolToStringTag]() { + return "AbortController"; + } + } + + webidl.configurePrototype(AbortController); + + webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( + "AbortSignal", + AbortSignal, + ); + + function newSignal() { + return new AbortSignal(illegalConstructorKey); + } + + function follow(followingSignal, parentSignal) { + if (parentSignal.aborted) { + followingSignal[signalAbort](); + } else { + parentSignal[add](() => followingSignal[signalAbort]()); + } + } + + window.AbortSignal = AbortSignal; + window.AbortController = AbortController; + window.__bootstrap.abortSignal = { + add, + signalAbort, + remove, + follow, + newSignal, + }; +})(this); diff --git a/ext/web/04_global_interfaces.js b/ext/web/04_global_interfaces.js new file mode 100644 index 000000000..8117bface --- /dev/null +++ b/ext/web/04_global_interfaces.js @@ -0,0 +1,79 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +// @ts-check +/// + +((window) => { + const { EventTarget } = window; + const { + Symbol, + SymbolToStringTag, + TypeError, + } = window.__bootstrap.primordials; + + const illegalConstructorKey = Symbol("illegalConstructorKey"); + + class Window extends EventTarget { + constructor(key = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + } + + get [SymbolToStringTag]() { + return "Window"; + } + } + + class WorkerGlobalScope extends EventTarget { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + } + + get [SymbolToStringTag]() { + return "WorkerGlobalScope"; + } + } + + class DedicatedWorkerGlobalScope extends WorkerGlobalScope { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + } + + get [SymbolToStringTag]() { + return "DedicatedWorkerGlobalScope"; + } + } + + window.__bootstrap.globalInterfaces = { + DedicatedWorkerGlobalScope, + Window, + WorkerGlobalScope, + dedicatedWorkerGlobalScopeConstructorDescriptor: { + configurable: true, + enumerable: false, + value: DedicatedWorkerGlobalScope, + writable: true, + }, + windowConstructorDescriptor: { + configurable: true, + enumerable: false, + value: Window, + writable: true, + }, + workerGlobalScopeConstructorDescriptor: { + configurable: true, + enumerable: false, + value: WorkerGlobalScope, + writable: true, + }, + }; +})(this); diff --git a/ext/web/05_base64.js b/ext/web/05_base64.js new file mode 100644 index 000000000..9c9c23b0f --- /dev/null +++ b/ext/web/05_base64.js @@ -0,0 +1,73 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { + forgivingBase64Encode, + forgivingBase64Decode, + } = window.__bootstrap.infra; + const { DOMException } = window.__bootstrap.domException; + const { + ArrayPrototypeMap, + StringPrototypeCharCodeAt, + ArrayPrototypeJoin, + StringFromCharCode, + TypedArrayFrom, + Uint8Array, + } = window.__bootstrap.primordials; + + /** + * @param {string} data + * @returns {string} + */ + function atob(data) { + data = webidl.converters.DOMString(data, { + prefix: "Failed to execute 'atob'", + context: "Argument 1", + }); + + const uint8Array = forgivingBase64Decode(data); + const result = ArrayPrototypeMap( + [...uint8Array], + (byte) => StringFromCharCode(byte), + ); + return ArrayPrototypeJoin(result, ""); + } + + /** + * @param {string} data + * @returns {string} + */ + function btoa(data) { + const prefix = "Failed to execute 'btoa'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + data = webidl.converters.DOMString(data, { + prefix, + context: "Argument 1", + }); + const byteArray = ArrayPrototypeMap([...data], (char) => { + const charCode = StringPrototypeCharCodeAt(char, 0); + if (charCode > 0xff) { + throw new DOMException( + "The string to be encoded contains characters outside of the Latin1 range.", + "InvalidCharacterError", + ); + } + return charCode; + }); + return forgivingBase64Encode(TypedArrayFrom(Uint8Array, byteArray)); + } + + window.__bootstrap.base64 = { + atob, + btoa, + }; +})(globalThis); diff --git a/ext/web/06_streams.js b/ext/web/06_streams.js new file mode 100644 index 000000000..c4bfad0c8 --- /dev/null +++ b/ext/web/06_streams.js @@ -0,0 +1,4473 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + // TODO(lucacasonato): get AbortSignal from __bootstrap. + const { + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + Error, + NumberIsInteger, + NumberIsNaN, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectSetPrototypeOf, + Promise, + PromiseAll, + PromisePrototypeThen, + PromiseReject, + queueMicrotask, + RangeError, + Symbol, + SymbolAsyncIterator, + SymbolFor, + SymbolToStringTag, + TypeError, + Uint8Array, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeHas, + WeakMapPrototypeSet, + } = globalThis.__bootstrap.primordials; + const consoleInternal = window.__bootstrap.console; + const { DOMException } = window.__bootstrap.domException; + + class AssertionError extends Error { + constructor(msg) { + super(msg); + this.name = "AssertionError"; + } + } + + /** + * @param {unknown} cond + * @param {string=} msg + * @returns {asserts cond} + */ + function assert(cond, msg = "Assertion failed.") { + if (!cond) { + throw new AssertionError(msg); + } + } + + /** @template T */ + class Deferred { + /** @type {Promise} */ + #promise; + /** @type {(reject?: any) => void} */ + #reject; + /** @type {(value: T | PromiseLike) => void} */ + #resolve; + /** @type {"pending" | "fulfilled"} */ + #state = "pending"; + + constructor() { + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); + } + + /** @returns {Promise} */ + get promise() { + return this.#promise; + } + + /** @returns {"pending" | "fulfilled"} */ + get state() { + return this.#state; + } + + /** @param {any=} reason */ + reject(reason) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; + } + this.#state = "fulfilled"; + this.#reject(reason); + } + + /** @param {T | PromiseLike} value */ + resolve(value) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; + } + this.#state = "fulfilled"; + this.#resolve(value); + } + } + + /** + * @template T + * @param {T | PromiseLike} value + * @returns {Promise} + */ + function resolvePromiseWith(value) { + return new Promise((resolve) => resolve(value)); + } + + /** @param {any} e */ + function rethrowAssertionErrorRejection(e) { + if (e && e instanceof AssertionError) { + queueMicrotask(() => { + console.error(`Internal Error: ${e.stack}`); + }); + } + } + + /** @param {Promise} promise */ + function setPromiseIsHandledToTrue(promise) { + PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); + } + + /** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} fulfillmentHandler + * @param {(reason: any) => TResult2 | PromiseLike=} rejectionHandler + * @returns {Promise} + */ + function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { + return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); + } + + /** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onFulfilled + * @returns {void} + */ + function uponFulfillment(promise, onFulfilled) { + uponPromise(promise, onFulfilled); + } + + /** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onRejected + * @returns {void} + */ + function uponRejection(promise, onRejected) { + uponPromise(promise, undefined, onRejected); + } + + /** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} onFulfilled + * @param {(reason: any) => TResult2 | PromiseLike=} onRejected + * @returns {void} + */ + function uponPromise(promise, onFulfilled, onRejected) { + PromisePrototypeThen( + PromisePrototypeThen(promise, onFulfilled, onRejected), + undefined, + rethrowAssertionErrorRejection, + ); + } + + const isFakeDetached = Symbol("<>"); + + /** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ + function isDetachedBuffer(O) { + return isFakeDetached in O; + } + + /** + * @param {ArrayBufferLike} O + * @returns {ArrayBufferLike} + */ + function transferArrayBuffer(O) { + assert(!isDetachedBuffer(O)); + const transferredIshVersion = O.slice(0); + ObjectDefineProperty(O, "byteLength", { + get() { + return 0; + }, + }); + O[isFakeDetached] = true; + return transferredIshVersion; + } + + const _abortAlgorithm = Symbol("[[abortAlgorithm]]"); + const _abortSteps = Symbol("[[AbortSteps]]"); + const _autoAllocateChunkSize = Symbol("[[autoAllocateChunkSize]]"); + const _backpressure = Symbol("[[backpressure]]"); + const _backpressureChangePromise = Symbol("[[backpressureChangePromise]]"); + const _byobRequest = Symbol("[[byobRequest]]"); + const _cancelAlgorithm = Symbol("[[cancelAlgorithm]]"); + const _cancelSteps = Symbol("[[CancelSteps]]"); + const _close = Symbol("close sentinel"); + const _closeAlgorithm = Symbol("[[closeAlgorithm]]"); + const _closedPromise = Symbol("[[closedPromise]]"); + const _closeRequest = Symbol("[[closeRequest]]"); + const _closeRequested = Symbol("[[closeRequested]]"); + const _controller = Symbol("[[controller]]"); + const _detached = Symbol("[[Detached]]"); + const _disturbed = Symbol("[[disturbed]]"); + const _errorSteps = Symbol("[[ErrorSteps]]"); + const _flushAlgorithm = Symbol("[[flushAlgorithm]]"); + const _globalObject = Symbol("[[globalObject]]"); + const _highWaterMark = Symbol("[[highWaterMark]]"); + const _inFlightCloseRequest = Symbol("[[inFlightCloseRequest]]"); + const _inFlightWriteRequest = Symbol("[[inFlightWriteRequest]]"); + const _pendingAbortRequest = Symbol("[pendingAbortRequest]"); + const _preventCancel = Symbol("[[preventCancel]]"); + const _pullAgain = Symbol("[[pullAgain]]"); + const _pullAlgorithm = Symbol("[[pullAlgorithm]]"); + const _pulling = Symbol("[[pulling]]"); + const _pullSteps = Symbol("[[PullSteps]]"); + const _queue = Symbol("[[queue]]"); + const _queueTotalSize = Symbol("[[queueTotalSize]]"); + const _readable = Symbol("[[readable]]"); + const _reader = Symbol("[[reader]]"); + const _readRequests = Symbol("[[readRequests]]"); + const _readyPromise = Symbol("[[readyPromise]]"); + const _started = Symbol("[[started]]"); + const _state = Symbol("[[state]]"); + const _storedError = Symbol("[[storedError]]"); + const _strategyHWM = Symbol("[[strategyHWM]]"); + const _strategySizeAlgorithm = Symbol("[[strategySizeAlgorithm]]"); + const _stream = Symbol("[[stream]]"); + const _transformAlgorithm = Symbol("[[transformAlgorithm]]"); + const _writable = Symbol("[[writable]]"); + const _writeAlgorithm = Symbol("[[writeAlgorithm]]"); + const _writer = Symbol("[[writer]]"); + const _writeRequests = Symbol("[[writeRequests]]"); + + /** + * @template R + * @param {ReadableStream} stream + * @returns {ReadableStreamDefaultReader} + */ + function acquireReadableStreamDefaultReader(stream) { + return new ReadableStreamDefaultReader(stream); + } + + /** + * @template W + * @param {WritableStream} stream + * @returns {WritableStreamDefaultWriter} + */ + function acquireWritableStreamDefaultWriter(stream) { + return new WritableStreamDefaultWriter(stream); + } + + /** + * @template R + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number=} highWaterMark + * @param {((chunk: R) => number)=} sizeAlgorithm + * @returns {ReadableStream} + */ + function createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark = 1, + sizeAlgorithm = () => 1, + ) { + assert(isNonNegativeNumber(highWaterMark)); + /** @type {ReadableStream} */ + const stream = webidl.createBranded(ReadableStream); + initializeReadableStream(stream); + const controller = webidl.createBranded(ReadableStreamDefaultController); + setUpReadableStreamDefaultController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, + ); + return stream; + } + + /** + * @template W + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + * @returns {WritableStream} + */ + function createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, + ) { + assert(isNonNegativeNumber(highWaterMark)); + const stream = webidl.createBranded(WritableStream); + initializeWritableStream(stream); + const controller = webidl.createBranded(WritableStreamDefaultController); + setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, + ); + return stream; + } + + /** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T} + */ + function dequeueValue(container) { + assert(_queue in container && _queueTotalSize in container); + assert(container[_queue].length); + const valueWithSize = ArrayPrototypeShift(container[_queue]); + container[_queueTotalSize] -= valueWithSize.size; + if (container[_queueTotalSize] < 0) { + container[_queueTotalSize] = 0; + } + return valueWithSize.value; + } + + /** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @param {T} value + * @param {number} size + * @returns {void} + */ + function enqueueValueWithSize(container, value, size) { + assert(_queue in container && _queueTotalSize in container); + if (isNonNegativeNumber(size) === false) { + throw RangeError("chunk size isn't a positive number"); + } + if (size === Infinity) { + throw RangeError("chunk size is invalid"); + } + ArrayPrototypePush(container[_queue], { value, size }); + container[_queueTotalSize] += size; + } + + /** + * @param {QueuingStrategy} strategy + * @param {number} defaultHWM + */ + function extractHighWaterMark(strategy, defaultHWM) { + if (strategy.highWaterMark === undefined) { + return defaultHWM; + } + const highWaterMark = strategy.highWaterMark; + if (NumberIsNaN(highWaterMark) || highWaterMark < 0) { + throw RangeError( + `Expected highWaterMark to be a positive number or Infinity, got "${highWaterMark}".`, + ); + } + return highWaterMark; + } + + /** + * @template T + * @param {QueuingStrategy} strategy + * @return {(chunk: T) => number} + */ + function extractSizeAlgorithm(strategy) { + if (strategy.size === undefined) { + return () => 1; + } + return (chunk) => + webidl.invokeCallbackFunction( + strategy.size, + [chunk], + undefined, + webidl.converters["unrestricted double"], + { prefix: "Failed to call `sizeAlgorithm`" }, + ); + } + + /** + * @param {ReadableStream} stream + * @returns {void} + */ + function initializeReadableStream(stream) { + stream[_state] = "readable"; + stream[_reader] = stream[_storedError] = undefined; + stream[_disturbed] = false; + } + + /** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Deferred} startPromise + * @param {number} writableHighWaterMark + * @param {(chunk: I) => number} writableSizeAlgorithm + * @param {number} readableHighWaterMark + * @param {(chunk: O) => number} readableSizeAlgorithm + */ + function initializeTransformStream( + stream, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ) { + function startAlgorithm() { + return startPromise.promise; + } + + function writeAlgorithm(chunk) { + return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); + } + + function abortAlgorithm(reason) { + return transformStreamDefaultSinkAbortAlgorithm(stream, reason); + } + + function closeAlgorithm() { + return transformStreamDefaultSinkCloseAlgorithm(stream); + } + + stream[_writable] = createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + writableHighWaterMark, + writableSizeAlgorithm, + ); + + function pullAlgorithm() { + return transformStreamDefaultSourcePullAlgorithm(stream); + } + + function cancelAlgorithm(reason) { + transformStreamErrorWritableAndUnblockWrite(stream, reason); + return resolvePromiseWith(undefined); + } + + stream[_readable] = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ); + + stream[_backpressure] = stream[_backpressureChangePromise] = undefined; + transformStreamSetBackpressure(stream, true); + stream[_controller] = undefined; + } + + /** @param {WritableStream} stream */ + function initializeWritableStream(stream) { + stream[_state] = "writable"; + stream[_storedError] = stream[_writer] = stream[_controller] = + stream[_inFlightWriteRequest] = stream[_closeRequest] = + stream[_inFlightCloseRequest] = stream[_pendingAbortRequest] = + undefined; + stream[_writeRequests] = []; + stream[_backpressure] = false; + } + + /** + * @param {unknown} v + * @returns {v is number} + */ + function isNonNegativeNumber(v) { + if (typeof v !== "number") { + return false; + } + if (NumberIsNaN(v)) { + return false; + } + if (v < 0) { + return false; + } + return true; + } + + /** + * @param {unknown} value + * @returns {value is ReadableStream} + */ + function isReadableStream(value) { + return !(typeof value !== "object" || value === null || + !(_controller in value)); + } + + /** + * @param {ReadableStream} stream + * @returns {boolean} + */ + function isReadableStreamLocked(stream) { + if (stream[_reader] === undefined) { + return false; + } + return true; + } + + /** + * @param {unknown} value + * @returns {value is ReadableStreamDefaultReader} + */ + function isReadableStreamDefaultReader(value) { + return !(typeof value !== "object" || value === null || + !(_readRequests in value)); + } + + /** + * @param {ReadableStream} stream + * @returns {boolean} + */ + function isReadableStreamDisturbed(stream) { + assert(isReadableStream(stream)); + return stream[_disturbed]; + } + + /** + * @param {unknown} value + * @returns {value is WritableStream} + */ + function isWritableStream(value) { + return !(typeof value !== "object" || value === null || + !(_controller in value)); + } + + /** + * @param {WritableStream} stream + * @returns {boolean} + */ + function isWritableStreamLocked(stream) { + if (stream[_writer] === undefined) { + return false; + } + return true; + } + + /** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T | _close} + */ + function peekQueueValue(container) { + assert(_queue in container && _queueTotalSize in container); + assert(container[_queue].length); + const valueWithSize = container[_queue][0]; + return valueWithSize.value; + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ + function readableByteStreamControllerCallPullIfNeeded(controller) { + const shouldPull = readableByteStreamControllerShouldCallPull(controller); + if (!shouldPull) { + return; + } + if (controller[_pulling]) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + /** @type {Promise} */ + const pullPromise = controller[_pullAlgorithm](controller); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + pullPromise, + () => { + controller[_pulling] = false; + if (controller[_pullAgain]) { + controller[_pullAgain] = false; + readableByteStreamControllerCallPullIfNeeded(controller); + } + }, + (e) => { + readableByteStreamControllerError(controller, e); + }, + ), + ); + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ + function readableByteStreamControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; + } + + /** + * @param {ReadableByteStreamController} controller + * @param {any} e + */ + function readableByteStreamControllerError(controller, e) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + // 3. Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). + resetQueue(controller); + readableByteStreamControllerClearAlgorithms(controller); + readableStreamError(stream, e); + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ + function readableByteStreamControllerClose(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (controller[_closeRequested] || stream[_state] !== "readable") { + return; + } + if (controller[_queueTotalSize] > 0) { + controller[_closeRequested] = true; + return; + } + // 3.13.6.4 If controller.[[pendingPullIntos]] is not empty, (BYOB Support) + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(stream); + } + + /** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} chunk + */ + function readableByteStreamControllerEnqueue(controller, chunk) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + controller[_closeRequested] || + controller[_stream][_state] !== "readable" + ) { + return; + } + + const { buffer, byteOffset, byteLength } = chunk; + const transferredBuffer = transferArrayBuffer(buffer); + if (readableStreamHasDefaultReader(stream)) { + if (readableStreamGetNumReadRequests(stream) === 0) { + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); + } else { + assert(controller[_queue].length === 0); + const transferredView = new Uint8Array( + transferredBuffer, + byteOffset, + byteLength, + ); + readableStreamFulfillReadRequest(stream, transferredView, false); + } + // 8 Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true, + } else { + assert(isReadableStreamLocked(stream) === false); + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); + } + readableByteStreamControllerCallPullIfNeeded(controller); + } + + /** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferLike} buffer + * @param {number} byteOffset + * @param {number} byteLength + * @returns {void} + */ + function readableByteStreamControllerEnqueueChunkToQueue( + controller, + buffer, + byteOffset, + byteLength, + ) { + ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); + controller[_queueTotalSize] += byteLength; + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {number | null} + */ + function readableByteStreamControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; + } + if (state === "closed") { + return 0; + } + return controller[_strategyHWM] - controller[_queueTotalSize]; + } + + /** + * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container + * @returns {void} + */ + function resetQueue(container) { + container[_queue] = []; + container[_queueTotalSize] = 0; + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ + function readableByteStreamControllerHandleQueueDrain(controller) { + assert(controller[_stream][_state] === "readable"); + if ( + controller[_queueTotalSize] === 0 && controller[_closeRequested] + ) { + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(controller[_stream]); + } else { + readableByteStreamControllerCallPullIfNeeded(controller); + } + } + + /** + * @param {ReadableByteStreamController} controller + * @returns {boolean} + */ + function readableByteStreamControllerShouldCallPull(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + stream[_state] !== "readable" || + controller[_closeRequested] || + !controller[_started] + ) { + return false; + } + if ( + readableStreamHasDefaultReader(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; + } + // 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; + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {ReadRequest} readRequest + * @returns {void} + */ + function readableStreamAddReadRequest(stream, readRequest) { + assert(isReadableStreamDefaultReader(stream[_reader])); + assert(stream[_state] === "readable"); + ArrayPrototypePush(stream[_reader][_readRequests], readRequest); + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {any=} reason + * @returns {Promise} + */ + function readableStreamCancel(stream, reason) { + stream[_disturbed] = true; + if (stream[_state] === "closed") { + return resolvePromiseWith(undefined); + } + if (stream[_state] === "errored") { + return PromiseReject(stream[_storedError]); + } + readableStreamClose(stream); + /** @type {Promise} */ + const sourceCancelPromise = stream[_controller][_cancelSteps](reason); + return PromisePrototypeThen(sourceCancelPromise, () => undefined); + } + + /** + * @template R + * @param {ReadableStream} stream + * @returns {void} + */ + function readableStreamClose(stream) { + assert(stream[_state] === "readable"); + stream[_state] = "closed"; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (!reader) { + return; + } + if (isReadableStreamDefaultReader(reader)) { + /** @type {Array>} */ + const readRequests = reader[_readRequests]; + for (const readRequest of readRequests) { + readRequest.closeSteps(); + } + reader[_readRequests] = []; + } + // This promise can be double resolved. + // See: https://github.com/whatwg/streams/issues/1100 + reader[_closedPromise].resolve(undefined); + } + + /** @param {ReadableStreamDefaultController} controller */ + function readableStreamDefaultControllerCallPullIfNeeded(controller) { + const shouldPull = readableStreamDefaultcontrollerShouldCallPull( + controller, + ); + if (shouldPull === false) { + return; + } + if (controller[_pulling] === true) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + const pullPromise = controller[_pullAlgorithm](controller); + uponFulfillment(pullPromise, () => { + controller[_pulling] = false; + if (controller[_pullAgain] === true) { + controller[_pullAgain] = false; + readableStreamDefaultControllerCallPullIfNeeded(controller); + } + }); + uponRejection(pullPromise, (e) => { + readableStreamDefaultControllerError(controller, e); + }); + } + + /** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ + function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { + const state = controller[_stream][_state]; + if (controller[_closeRequested] === false && state === "readable") { + return true; + } else { + return false; + } + } + + /** @param {ReadableStreamDefaultController} controller */ + function readableStreamDefaultControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; + } + + /** @param {ReadableStreamDefaultController} controller */ + function readableStreamDefaultControllerClose(controller) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; + } + const stream = controller[_stream]; + controller[_closeRequested] = true; + if (controller[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamClose(stream); + } + } + + /** + * @template R + * @param {ReadableStreamDefaultController} controller + * @param {R} chunk + * @returns {void} + */ + function readableStreamDefaultControllerEnqueue(controller, chunk) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; + } + const stream = controller[_stream]; + if ( + isReadableStreamLocked(stream) === true && + readableStreamGetNumReadRequests(stream) > 0 + ) { + readableStreamFulfillReadRequest(stream, chunk, false); + } else { + let chunkSize; + try { + chunkSize = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; + } + + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; + } + } + readableStreamDefaultControllerCallPullIfNeeded(controller); + } + + /** + * @param {ReadableStreamDefaultController} controller + * @param {any} e + */ + function readableStreamDefaultControllerError(controller, e) { + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + resetQueue(controller); + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamError(stream, e); + } + + /** + * @param {ReadableStreamDefaultController} controller + * @returns {number | null} + */ + function readableStreamDefaultControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; + } + if (state === "closed") { + return 0; + } + return controller[_strategyHWM] - controller[_queueTotalSize]; + } + + /** @param {ReadableStreamDefaultController} controller */ + function readableStreamDefaultcontrollerHasBackpressure(controller) { + if (readableStreamDefaultcontrollerShouldCallPull(controller) === true) { + return false; + } else { + return true; + } + } + + /** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ + function readableStreamDefaultcontrollerShouldCallPull(controller) { + const stream = controller[_stream]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return false; + } + if (controller[_started] === false) { + return false; + } + if ( + isReadableStreamLocked(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; + } + const desiredSize = readableStreamDefaultControllerGetDesiredSize( + controller, + ); + assert(desiredSize !== null); + if (desiredSize > 0) { + return true; + } + return false; + } + + /** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadRequest} readRequest + * @returns {void} + */ + function readableStreamDefaultReaderRead(reader, readRequest) { + const stream = reader[_stream]; + assert(stream); + stream[_disturbed] = true; + if (stream[_state] === "closed") { + readRequest.closeSteps(); + } else if (stream[_state] === "errored") { + readRequest.errorSteps(stream[_storedError]); + } else { + assert(stream[_state] === "readable"); + stream[_controller][_pullSteps](readRequest); + } + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {any} e + */ + function readableStreamError(stream, e) { + assert(stream[_state] === "readable"); + stream[_state] = "errored"; + stream[_storedError] = e; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (reader === undefined) { + return; + } + /** @type {Deferred} */ + const closedPromise = reader[_closedPromise]; + closedPromise.reject(e); + setPromiseIsHandledToTrue(closedPromise.promise); + if (isReadableStreamDefaultReader(reader)) { + /** @type {Array>} */ + const readRequests = reader[_readRequests]; + for (const readRequest of readRequests) { + readRequest.errorSteps(e); + } + reader[_readRequests] = []; + } + // 3.5.6.8 Otherwise, support BYOB Reader + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {R} chunk + * @param {boolean} done + */ + function readableStreamFulfillReadRequest(stream, chunk, done) { + assert(readableStreamHasDefaultReader(stream) === true); + /** @type {ReadableStreamDefaultReader} */ + const reader = stream[_reader]; + assert(reader[_readRequests].length); + /** @type {ReadRequest} */ + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + if (done) { + readRequest.closeSteps(); + } else { + readRequest.chunkSteps(chunk); + } + } + + /** + * @param {ReadableStream} stream + * @return {number} + */ + function readableStreamGetNumReadRequests(stream) { + assert(readableStreamHasDefaultReader(stream) === true); + return stream[_reader][_readRequests].length; + } + + /** + * @param {ReadableStream} stream + * @returns {boolean} + */ + function readableStreamHasDefaultReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { + return false; + } + if (isReadableStreamDefaultReader(reader)) { + return true; + } + return false; + } + + /** + * @template T + * @param {ReadableStream} source + * @param {WritableStream} dest + * @param {boolean} preventClose + * @param {boolean} preventAbort + * @param {boolean} preventCancel + * @param {AbortSignal=} signal + * @returns {Promise} + */ + function readableStreamPipeTo( + source, + dest, + preventClose, + preventAbort, + preventCancel, + signal, + ) { + assert(isReadableStream(source)); + assert(isWritableStream(dest)); + assert( + typeof preventClose === "boolean" && typeof preventAbort === "boolean" && + typeof preventCancel === "boolean", + ); + assert(signal === undefined || signal instanceof AbortSignal); + assert(!isReadableStreamLocked(source)); + assert(!isWritableStreamLocked(dest)); + const reader = acquireReadableStreamDefaultReader(source); + const writer = acquireWritableStreamDefaultWriter(dest); + source[_disturbed] = true; + let shuttingDown = false; + let currentWrite = resolvePromiseWith(undefined); + /** @type {Deferred} */ + const promise = new Deferred(); + /** @type {() => void} */ + let abortAlgorithm; + if (signal) { + abortAlgorithm = () => { + const error = new DOMException("Aborted", "AbortError"); + /** @type {Array<() => Promise>} */ + const actions = []; + if (preventAbort === false) { + ArrayPrototypePush(actions, () => { + if (dest[_state] === "writable") { + return writableStreamAbort(dest, error); + } else { + return resolvePromiseWith(undefined); + } + }); + } + if (preventCancel === false) { + ArrayPrototypePush(actions, () => { + if (source[_state] === "readable") { + return readableStreamCancel(source, error); + } else { + return resolvePromiseWith(undefined); + } + }); + } + shutdownWithAction( + () => PromiseAll(ArrayPrototypeMap(actions, (action) => action())), + true, + error, + ); + }; + + if (signal.aborted) { + abortAlgorithm(); + return promise.promise; + } + // TODO(lucacasonato): use the internal API to listen for abort. + signal.addEventListener("abort", abortAlgorithm); + } + + function pipeLoop() { + return new Promise((resolveLoop, rejectLoop) => { + /** @param {boolean} done */ + function next(done) { + if (done) { + resolveLoop(); + } else { + uponPromise(pipeStep(), next, rejectLoop); + } + } + next(false); + }); + } + + /** @returns {Promise} */ + function pipeStep() { + if (shuttingDown === true) { + return resolvePromiseWith(true); + } + + return transformPromiseWith(writer[_readyPromise].promise, () => { + return new Promise((resolveRead, rejectRead) => { + readableStreamDefaultReaderRead( + reader, + { + chunkSteps(chunk) { + currentWrite = transformPromiseWith( + writableStreamDefaultWriterWrite(writer, chunk), + undefined, + () => {}, + ); + resolveRead(false); + }, + closeSteps() { + resolveRead(true); + }, + errorSteps: rejectRead, + }, + ); + }); + }); + } + + isOrBecomesErrored( + source, + reader[_closedPromise].promise, + (storedError) => { + if (preventAbort === false) { + shutdownWithAction( + () => writableStreamAbort(dest, storedError), + true, + storedError, + ); + } else { + shutdown(true, storedError); + } + }, + ); + + isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, storedError), + true, + storedError, + ); + } else { + shutdown(true, storedError); + } + }); + + isOrBecomesClosed(source, reader[_closedPromise].promise, () => { + if (preventClose === false) { + shutdownWithAction(() => + writableStreamDefaultWriterCloseWithErrorPropagation(writer) + ); + } else { + shutdown(); + } + }); + + if ( + writableStreamCloseQueuedOrInFlight(dest) === true || + dest[_state] === "closed" + ) { + const destClosed = new TypeError( + "The destination writable stream closed before all the data could be piped to it.", + ); + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, destClosed), + true, + destClosed, + ); + } else { + shutdown(true, destClosed); + } + } + + setPromiseIsHandledToTrue(pipeLoop()); + + return promise.promise; + + /** @returns {Promise} */ + function waitForWritesToFinish() { + const oldCurrentWrite = currentWrite; + return transformPromiseWith( + currentWrite, + () => + oldCurrentWrite !== currentWrite + ? waitForWritesToFinish() + : undefined, + ); + } + + /** + * @param {ReadableStream | WritableStream} stream + * @param {Promise} promise + * @param {(e: any) => void} action + */ + function isOrBecomesErrored(stream, promise, action) { + if (stream[_state] === "errored") { + action(stream[_storedError]); + } else { + uponRejection(promise, action); + } + } + + /** + * @param {ReadableStream} stream + * @param {Promise} promise + * @param {() => void} action + */ + function isOrBecomesClosed(stream, promise, action) { + if (stream[_state] === "closed") { + action(); + } else { + uponFulfillment(promise, action); + } + } + + /** + * @param {() => Promise} action + * @param {boolean=} originalIsError + * @param {any=} originalError + */ + function shutdownWithAction(action, originalIsError, originalError) { + function doTheRest() { + uponPromise( + action(), + () => finalize(originalIsError, originalError), + (newError) => finalize(true, newError), + ); + } + + if (shuttingDown === true) { + return; + } + shuttingDown = true; + + if ( + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false + ) { + uponFulfillment(waitForWritesToFinish(), doTheRest); + } else { + doTheRest(); + } + } + + /** + * @param {boolean=} isError + * @param {any=} error + */ + function shutdown(isError, error) { + if (shuttingDown) { + return; + } + shuttingDown = true; + if ( + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false + ) { + uponFulfillment( + waitForWritesToFinish(), + () => finalize(isError, error), + ); + } else { + finalize(isError, error); + } + } + + /** + * @param {boolean=} isError + * @param {any=} error + */ + function finalize(isError, error) { + writableStreamDefaultWriterRelease(writer); + readableStreamReaderGenericRelease(reader); + + if (signal !== undefined) { + // TODO(lucacasonato): use the internal API to remove the listener. + signal.removeEventListener("abort", abortAlgorithm); + } + if (isError) { + promise.reject(error); + } else { + promise.resolve(undefined); + } + } + } + + /** + * @param {ReadableStreamGenericReader} reader + * @param {any} reason + * @returns {Promise} + */ + function readableStreamReaderGenericCancel(reader, reason) { + const stream = reader[_stream]; + assert(stream !== undefined); + return readableStreamCancel(stream, reason); + } + + /** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadableStream} stream + */ + function readableStreamReaderGenericInitialize(reader, stream) { + reader[_stream] = stream; + stream[_reader] = reader; + if (stream[_state] === "readable") { + reader[_closedPromise] = new Deferred(); + } else if (stream[_state] === "closed") { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].resolve(undefined); + } else { + assert(stream[_state] === "errored"); + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(reader[_closedPromise].promise); + } + } + + /** + * @template R + * @param {ReadableStreamGenericReader} reader + */ + function readableStreamReaderGenericRelease(reader) { + assert(reader[_stream] !== undefined); + assert(reader[_stream][_reader] === reader); + if (reader[_stream][_state] === "readable") { + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } else { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } + setPromiseIsHandledToTrue(reader[_closedPromise].promise); + reader[_stream][_reader] = undefined; + reader[_stream] = undefined; + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream, ReadableStream]} + */ + function readableStreamTee(stream, cloneForBranch2) { + assert(isReadableStream(stream)); + assert(typeof cloneForBranch2 === "boolean"); + const reader = acquireReadableStreamDefaultReader(stream); + let reading = false; + let canceled1 = false; + let canceled2 = false; + /** @type {any} */ + let reason1; + /** @type {any} */ + let reason2; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch1; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch2; + + /** @type {Deferred} */ + const cancelPromise = new Deferred(); + + function pullAlgorithm() { + if (reading === true) { + return resolvePromiseWith(undefined); + } + reading = true; + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(value) { + queueMicrotask(() => { + reading = false; + const value1 = value; + const value2 = value; + + // TODO(lucacasonato): respect clonedForBranch2. + + if (canceled1 === false) { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ (branch1[ + _controller + ]), + value1, + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ (branch2[ + _controller + ]), + value2, + ); + } + }); + }, + closeSteps() { + reading = false; + if (canceled1 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ (branch1[ + _controller + ]), + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ (branch2[ + _controller + ]), + ); + } + cancelPromise.resolve(undefined); + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + return resolvePromiseWith(undefined); + } + + /** + * @param {any} reason + * @returns {Promise} + */ + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; + } + + function startAlgorithm() {} + + branch1 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel1Algorithm, + ); + branch2 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel2Algorithm, + ); + + uponRejection(reader[_closedPromise].promise, (r) => { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ (branch1[ + _controller + ]), + r, + ); + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ (branch2[ + _controller + ]), + r, + ); + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }); + + return [branch1, branch2]; + } + + /** + * @param {ReadableStream} stream + * @param {ReadableByteStreamController} controller + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {number | undefined} autoAllocateChunkSize + */ + function setUpReadableByteStreamController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + autoAllocateChunkSize, + ) { + assert(stream[_controller] === undefined); + if (autoAllocateChunkSize !== undefined) { + assert(NumberIsInteger(autoAllocateChunkSize)); + assert(autoAllocateChunkSize >= 0); + } + controller[_stream] = stream; + controller[_pullAgain] = controller[_pulling] = false; + controller[_byobRequest] = undefined; + resetQueue(controller); + controller[_closeRequested] = controller[_started] = false; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + controller[_autoAllocateChunkSize] = autoAllocateChunkSize; + // 12. Set controller.[[pendingPullIntos]] to a new empty list. + stream[_controller] = controller; + const startResult = startAlgorithm(); + const startPromise = resolvePromiseWith(startResult); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + startPromise, + () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableByteStreamControllerCallPullIfNeeded(controller); + }, + (r) => { + readableByteStreamControllerError(controller, r); + }, + ), + ); + } + + /** + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + */ + function setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, + ) { + const controller = webidl.createBranded(ReadableByteStreamController); + /** @type {() => void} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason: any) => Promise} */ + let cancelAlgorithm = (_reason) => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableByteStreamController'", + }, + ); + } + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); + } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); + } + // 3.13.27.6 Let autoAllocateChunkSize be ? GetV(underlyingByteSource, "autoAllocateChunkSize"). + /** @type {undefined} */ + const autoAllocateChunkSize = undefined; + setUpReadableByteStreamController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + autoAllocateChunkSize, + ); + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {ReadableStreamDefaultController} controller + * @param {(controller: ReadableStreamDefaultController) => void | Promise} startAlgorithm + * @param {(controller: ReadableStreamDefaultController) => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ + function setUpReadableStreamDefaultController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, + ) { + assert(stream[_controller] === undefined); + controller[_stream] = stream; + resetQueue(controller); + controller[_started] = controller[_closeRequested] = + controller[_pullAgain] = controller[_pulling] = false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + stream[_controller] = controller; + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableStreamDefaultControllerCallPullIfNeeded(controller); + }, (r) => { + readableStreamDefaultControllerError(controller, r); + }); + } + + /** + * @template R + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ + function setUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, + ) { + const controller = webidl.createBranded(ReadableStreamDefaultController); + /** @type {() => Promise} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let cancelAlgorithm = () => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableStreamDefaultController'", + }, + ); + } + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpReadableStreamDefaultController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, + ); + } + + /** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadableStream} stream + */ + function setUpReadableStreamDefaultReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); + } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readRequests] = []; + } + + /** + * @template O + * @param {TransformStream} stream + * @param {TransformStreamDefaultController} controller + * @param {(chunk: O, controller: TransformStreamDefaultController) => Promise} transformAlgorithm + * @param {(controller: TransformStreamDefaultController) => Promise} flushAlgorithm + */ + function setUpTransformStreamDefaultController( + stream, + controller, + transformAlgorithm, + flushAlgorithm, + ) { + assert(stream instanceof TransformStream); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + controller[_transformAlgorithm] = transformAlgorithm; + controller[_flushAlgorithm] = flushAlgorithm; + } + + /** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Transformer} transformer + * @param {Transformer} transformerDict + */ + function setUpTransformStreamDefaultControllerFromTransformer( + stream, + transformer, + transformerDict, + ) { + /** @type {TransformStreamDefaultController} */ + const controller = webidl.createBranded(TransformStreamDefaultController); + /** @type {(chunk: O, controller: TransformStreamDefaultController) => Promise} */ + let transformAlgorithm = (chunk) => { + try { + transformStreamDefaultControllerEnqueue(controller, chunk); + } catch (e) { + return PromiseReject(e); + } + return resolvePromiseWith(undefined); + }; + /** @type {(controller: TransformStreamDefaultController) => Promise} */ + let flushAlgorithm = () => resolvePromiseWith(undefined); + if (transformerDict.transform !== undefined) { + transformAlgorithm = (chunk, controller) => + webidl.invokeCallbackFunction( + transformerDict.transform, + [chunk, controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (transformerDict.flush !== undefined) { + flushAlgorithm = (controller) => + webidl.invokeCallbackFunction( + transformerDict.flush, + [controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpTransformStreamDefaultController( + stream, + controller, + transformAlgorithm, + flushAlgorithm, + ); + } + + /** + * @template W + * @param {WritableStream} stream + * @param {WritableStreamDefaultController} controller + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W, controller: WritableStreamDefaultController) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason?: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ + function setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, + ) { + assert(isWritableStream(stream)); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + resetQueue(controller); + controller[_started] = false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_writeAlgorithm] = writeAlgorithm; + controller[_closeAlgorithm] = closeAlgorithm; + controller[_abortAlgorithm] = abortAlgorithm; + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (r) => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDealWithRejection(stream, r); + }); + } + + /** + * @template W + * @param {WritableStream} stream + * @param {UnderlyingSink} underlyingSink + * @param {UnderlyingSink} underlyingSinkDict + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ + function setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, + ) { + const controller = webidl.createBranded(WritableStreamDefaultController); + /** @type {(controller: WritableStreamDefaultController) => any} */ + let startAlgorithm = () => undefined; + /** @type {(chunk: W, controller: WritableStreamDefaultController) => Promise} */ + let writeAlgorithm = () => resolvePromiseWith(undefined); + let closeAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let abortAlgorithm = () => resolvePromiseWith(undefined); + + if (underlyingSinkDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.start, + [controller], + underlyingSink, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'WritableStreamDefaultController'", + }, + ); + } + if (underlyingSinkDict.write !== undefined) { + writeAlgorithm = (chunk) => + webidl.invokeCallbackFunction( + underlyingSinkDict.write, + [chunk, controller], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSinkDict.close !== undefined) { + closeAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.close, + [], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + if (underlyingSinkDict.abort !== undefined) { + abortAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSinkDict.abort, + [reason], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); + } + setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, + ); + } + + /** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {WritableStream} stream + */ + function setUpWritableStreamDefaultWriter(writer, stream) { + if (isWritableStreamLocked(stream) === true) { + throw new TypeError("The stream is already locked."); + } + writer[_stream] = stream; + stream[_writer] = writer; + const state = stream[_state]; + if (state === "writable") { + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + stream[_backpressure] === true + ) { + writer[_readyPromise] = new Deferred(); + } else { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].resolve(undefined); + } + writer[_closedPromise] = new Deferred(); + } else if (state === "erroring") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + } else if (state === "closed") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].resolve(undefined); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].resolve(undefined); + } else { + assert(state === "errored"); + const storedError = stream[_storedError]; + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(storedError); + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(storedError); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); + } + } + + /** @param {TransformStreamDefaultController} controller */ + function transformStreamDefaultControllerClearAlgorithms(controller) { + controller[_transformAlgorithm] = undefined; + controller[_flushAlgorithm] = undefined; + } + + /** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {O} chunk + */ + function transformStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue( + /** @type {ReadableStreamDefaultController} */ (readableController), + ) === false + ) { + throw new TypeError("Readable stream is unavailable."); + } + try { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ (readableController), + chunk, + ); + } catch (e) { + transformStreamErrorWritableAndUnblockWrite(stream, e); + throw stream[_readable][_storedError]; + } + const backpressure = readableStreamDefaultcontrollerHasBackpressure( + /** @type {ReadableStreamDefaultController} */ (readableController), + ); + if (backpressure !== stream[_backpressure]) { + assert(backpressure === true); + transformStreamSetBackpressure(stream, true); + } + } + + /** + * @param {TransformStreamDefaultController} controller + * @param {any=} e + */ + function transformStreamDefaultControllerError(controller, e) { + transformStreamError(controller[_stream], e); + } + + /** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {any} chunk + * @returns {Promise} + */ + function transformStreamDefaultControllerPerformTransform(controller, chunk) { + const transformPromise = controller[_transformAlgorithm](chunk, controller); + return transformPromiseWith(transformPromise, undefined, (r) => { + transformStreamError(controller[_stream], r); + throw r; + }); + } + + /** @param {TransformStreamDefaultController} controller */ + function transformStreamDefaultControllerTerminate(controller) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ (readableController), + ); + const error = new TypeError("The stream has been terminated."); + transformStreamErrorWritableAndUnblockWrite(stream, error); + } + + /** + * @param {TransformStream} stream + * @param {any=} reason + * @returns {Promise} + */ + function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { + transformStreamError(stream, reason); + return resolvePromiseWith(undefined); + } + + /** + * @template I + * @template O + * @param {TransformStream} stream + * @returns {Promise} + */ + function transformStreamDefaultSinkCloseAlgorithm(stream) { + const readable = stream[_readable]; + const controller = stream[_controller]; + const flushPromise = controller[_flushAlgorithm](controller); + transformStreamDefaultControllerClearAlgorithms(controller); + return transformPromiseWith(flushPromise, () => { + if (readable[_state] === "errored") { + throw readable[_storedError]; + } + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ (readable[_controller]), + ); + }, (r) => { + transformStreamError(stream, r); + throw readable[_storedError]; + }); + } + + /** + * @template I + * @template O + * @param {TransformStream} stream + * @param {I} chunk + * @returns {Promise} + */ + function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { + assert(stream[_writable][_state] === "writable"); + const controller = stream[_controller]; + if (stream[_backpressure] === true) { + const backpressureChangePromise = stream[_backpressureChangePromise]; + assert(backpressureChangePromise !== undefined); + return transformPromiseWith(backpressureChangePromise.promise, () => { + const writable = stream[_writable]; + const state = writable[_state]; + if (state === "erroring") { + throw writable[_storedError]; + } + assert(state === "writable"); + return transformStreamDefaultControllerPerformTransform( + controller, + chunk, + ); + }); + } + return transformStreamDefaultControllerPerformTransform(controller, chunk); + } + + /** + * @param {TransformStream} stream + * @returns {Promise} + */ + function transformStreamDefaultSourcePullAlgorithm(stream) { + assert(stream[_backpressure] === true); + assert(stream[_backpressureChangePromise] !== undefined); + transformStreamSetBackpressure(stream, false); + return stream[_backpressureChangePromise].promise; + } + + /** + * @param {TransformStream} stream + * @param {any=} e + */ + function transformStreamError(stream, e) { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ (stream[_readable][ + _controller + ]), + e, + ); + transformStreamErrorWritableAndUnblockWrite(stream, e); + } + + /** + * @param {TransformStream} stream + * @param {any=} e + */ + function transformStreamErrorWritableAndUnblockWrite(stream, e) { + transformStreamDefaultControllerClearAlgorithms(stream[_controller]); + writableStreamDefaultControllerErrorIfNeeded( + stream[_writable][_controller], + e, + ); + if (stream[_backpressure] === true) { + transformStreamSetBackpressure(stream, false); + } + } + + /** + * @param {TransformStream} stream + * @param {boolean} backpressure + */ + function transformStreamSetBackpressure(stream, backpressure) { + assert(stream[_backpressure] !== backpressure); + if (stream[_backpressureChangePromise] !== undefined) { + stream[_backpressureChangePromise].resolve(undefined); + } + stream[_backpressureChangePromise] = new Deferred(); + stream[_backpressure] = backpressure; + } + + /** + * @param {WritableStream} stream + * @param {any=} reason + * @returns {Promise} + */ + function writableStreamAbort(stream, reason) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); + } + if (stream[_pendingAbortRequest] !== undefined) { + return stream[_pendingAbortRequest].deferred.promise; + } + assert(state === "writable" || state === "erroring"); + let wasAlreadyErroring = false; + if (state === "erroring") { + wasAlreadyErroring = true; + reason = undefined; + } + /** Deferred */ + const deferred = new Deferred(); + stream[_pendingAbortRequest] = { + deferred, + reason, + wasAlreadyErroring, + }; + if (wasAlreadyErroring === false) { + writableStreamStartErroring(stream, reason); + } + return deferred.promise; + } + + /** + * @param {WritableStream} stream + * @returns {Promise} + */ + function writableStreamAddWriteRequest(stream) { + assert(isWritableStreamLocked(stream) === true); + assert(stream[_state] === "writable"); + /** @type {Deferred} */ + const deferred = new Deferred(); + ArrayPrototypePush(stream[_writeRequests], deferred); + return deferred.promise; + } + + /** + * @param {WritableStream} stream + * @returns {Promise} + */ + function writableStreamClose(stream) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return PromiseReject( + new TypeError("Writable stream is closed or errored."), + ); + } + assert(state === "writable" || state === "erroring"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + /** @type {Deferred} */ + const deferred = new Deferred(); + stream[_closeRequest] = deferred; + const writer = stream[_writer]; + if ( + writer !== undefined && stream[_backpressure] === true && + state === "writable" + ) { + writer[_readyPromise].resolve(undefined); + } + writableStreamDefaultControllerClose(stream[_controller]); + return deferred.promise; + } + + /** + * @param {WritableStream} stream + * @returns {boolean} + */ + function writableStreamCloseQueuedOrInFlight(stream) { + if ( + stream[_closeRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; + } + return true; + } + + /** + * @param {WritableStream} stream + * @param {any=} error + */ + function writableStreamDealWithRejection(stream, error) { + const state = stream[_state]; + if (state === "writable") { + writableStreamStartErroring(stream, error); + return; + } + assert(state === "erroring"); + writableStreamFinishErroring(stream); + } + + /** + * @template W + * @param {WritableStreamDefaultController} controller + */ + function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { + const stream = controller[_stream]; + if (controller[_started] === false) { + return; + } + if (stream[_inFlightWriteRequest] !== undefined) { + return; + } + const state = stream[_state]; + assert(state !== "closed" && state !== "errored"); + if (state === "erroring") { + writableStreamFinishErroring(stream); + return; + } + if (controller[_queue].length === 0) { + return; + } + const value = peekQueueValue(controller); + if (value === _close) { + writableStreamDefaultControllerProcessClose(controller); + } else { + writableStreamDefaultControllerProcessWrite(controller, value); + } + } + + function writableStreamDefaultControllerClearAlgorithms(controller) { + controller[_writeAlgorithm] = undefined; + controller[_closeAlgorithm] = undefined; + controller[_abortAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; + } + + /** @param {WritableStreamDefaultController} controller */ + function writableStreamDefaultControllerClose(controller) { + enqueueValueWithSize(controller, _close, 0); + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + } + + /** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ + function writableStreamDefaultControllerError(controller, error) { + const stream = controller[_stream]; + assert(stream[_state] === "writable"); + writableStreamDefaultControllerClearAlgorithms(controller); + writableStreamStartErroring(stream, error); + } + + /** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ + function writableStreamDefaultControllerErrorIfNeeded(controller, error) { + if (controller[_stream][_state] === "writable") { + writableStreamDefaultControllerError(controller, error); + } + } + + /** + * @param {WritableStreamDefaultController} controller + * @returns {boolean} + */ + function writableStreamDefaultControllerGetBackpressure(controller) { + const desiredSize = writableStreamDefaultControllerGetDesiredSize( + controller, + ); + return desiredSize <= 0; + } + + /** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @returns {number} + */ + function writableStreamDefaultControllerGetChunkSize(controller, chunk) { + let value; + try { + value = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return 1; + } + return value; + } + + /** + * @param {WritableStreamDefaultController} controller + * @returns {number} + */ + function writableStreamDefaultControllerGetDesiredSize(controller) { + return controller[_strategyHWM] - controller[_queueTotalSize]; + } + + /** @param {WritableStreamDefaultController} controller */ + function writableStreamDefaultControllerProcessClose(controller) { + const stream = controller[_stream]; + writableStreamMarkCloseRequestInFlight(stream); + dequeueValue(controller); + assert(controller[_queue].length === 0); + const sinkClosePromise = controller[_closeAlgorithm](); + writableStreamDefaultControllerClearAlgorithms(controller); + uponPromise(sinkClosePromise, () => { + writableStreamFinishInFlightClose(stream); + }, (reason) => { + writableStreamFinishInFlightCloseWithError(stream, reason); + }); + } + + /** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + */ + function writableStreamDefaultControllerProcessWrite(controller, chunk) { + const stream = controller[_stream]; + writableStreamMarkFirstWriteRequestInFlight(stream); + const sinkWritePromise = controller[_writeAlgorithm](chunk, controller); + uponPromise(sinkWritePromise, () => { + writableStreamFinishInFlightWrite(stream); + const state = stream[_state]; + assert(state === "writable" || state === "erroring"); + dequeueValue(controller); + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + state === "writable" + ) { + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); + } + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (reason) => { + if (stream[_state] === "writable") { + writableStreamDefaultControllerClearAlgorithms(controller); + } + writableStreamFinishInFlightWriteWithError(stream, reason); + }); + } + + /** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @param {number} chunkSize + */ + function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return; + } + const stream = controller[_stream]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + stream[_state] === "writable" + ) { + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); + } + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} reason + * @returns {Promise} + */ + function writableStreamDefaultWriterAbort(writer, reason) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamAbort(stream, reason); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ + function writableStreamDefaultWriterClose(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamClose(stream); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ + function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + const state = stream[_state]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return resolvePromiseWith(undefined); + } + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable" || state === "erroring"); + return writableStreamDefaultWriterClose(writer); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ + function writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + error, + ) { + if (writer[_closedPromise].state === "pending") { + writer[_closedPromise].reject(error); + } else { + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_closedPromise].promise); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ + function writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + error, + ) { + if (writer[_readyPromise].state === "pending") { + writer[_readyPromise].reject(error); + } else { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + } + + /** + * @param {WritableStreamDefaultWriter} writer + * @returns {number | null} + */ + function writableStreamDefaultWriterGetDesiredSize(writer) { + const stream = writer[_stream]; + const state = stream[_state]; + if (state === "errored" || state === "erroring") { + return null; + } + if (state === "closed") { + return 0; + } + return writableStreamDefaultControllerGetDesiredSize(stream[_controller]); + } + + /** @param {WritableStreamDefaultWriter} writer */ + function writableStreamDefaultWriterRelease(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + assert(stream[_writer] === writer); + const releasedError = new TypeError( + "The writer has already been released.", + ); + writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + releasedError, + ); + writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + releasedError, + ); + stream[_writer] = undefined; + writer[_stream] = undefined; + } + + /** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {W} chunk + * @returns {Promise} + */ + function writableStreamDefaultWriterWrite(writer, chunk) { + const stream = writer[_stream]; + assert(stream !== undefined); + const controller = stream[_controller]; + const chunkSize = writableStreamDefaultControllerGetChunkSize( + controller, + chunk, + ); + if (stream !== writer[_stream]) { + return PromiseReject(new TypeError("Writer's stream is unexpected.")); + } + const state = stream[_state]; + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return PromiseReject( + new TypeError("The stream is closing or is closed."), + ); + } + if (state === "erroring") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable"); + const promise = writableStreamAddWriteRequest(stream); + writableStreamDefaultControllerWrite(controller, chunk, chunkSize); + return promise; + } + + /** @param {WritableStream} stream */ + function writableStreamFinishErroring(stream) { + assert(stream[_state] === "erroring"); + assert(writableStreamHasOperationMarkedInFlight(stream) === false); + stream[_state] = "errored"; + stream[_controller][_errorSteps](); + const storedError = stream[_storedError]; + for (const writeRequest of stream[_writeRequests]) { + writeRequest.reject(storedError); + } + stream[_writeRequests] = []; + if (stream[_pendingAbortRequest] === undefined) { + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const abortRequest = stream[_pendingAbortRequest]; + stream[_pendingAbortRequest] = undefined; + if (abortRequest.wasAlreadyErroring === true) { + abortRequest.deferred.reject(storedError); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const promise = stream[_controller][_abortSteps](abortRequest.reason); + uponPromise(promise, () => { + abortRequest.deferred.resolve(undefined); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }, (reason) => { + abortRequest.deferred.reject(reason); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }); + } + + /** @param {WritableStream} stream */ + function writableStreamFinishInFlightClose(stream) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].resolve(undefined); + stream[_inFlightCloseRequest] = undefined; + const state = stream[_state]; + assert(state === "writable" || state === "erroring"); + if (state === "erroring") { + stream[_storedError] = undefined; + if (stream[_pendingAbortRequest] !== undefined) { + stream[_pendingAbortRequest].deferred.resolve(undefined); + stream[_pendingAbortRequest] = undefined; + } + } + stream[_state] = "closed"; + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].resolve(undefined); + } + assert(stream[_pendingAbortRequest] === undefined); + assert(stream[_storedError] === undefined); + } + + /** + * @param {WritableStream} stream + * @param {any=} error + */ + function writableStreamFinishInFlightCloseWithError(stream, error) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].reject(error); + stream[_inFlightCloseRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + if (stream[_pendingAbortRequest] !== undefined) { + stream[_pendingAbortRequest].deferred.reject(error); + stream[_pendingAbortRequest] = undefined; + } + writableStreamDealWithRejection(stream, error); + } + + /** @param {WritableStream} stream */ + function writableStreamFinishInFlightWrite(stream) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].resolve(undefined); + stream[_inFlightWriteRequest] = undefined; + } + + /** + * @param {WritableStream} stream + * @param {any=} error + */ + function writableStreamFinishInFlightWriteWithError(stream, error) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].reject(error); + stream[_inFlightWriteRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + writableStreamDealWithRejection(stream, error); + } + + /** + * @param {WritableStream} stream + * @returns {boolean} + */ + function writableStreamHasOperationMarkedInFlight(stream) { + if ( + stream[_inFlightWriteRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; + } + return true; + } + + /** @param {WritableStream} stream */ + function writableStreamMarkCloseRequestInFlight(stream) { + assert(stream[_inFlightCloseRequest] === undefined); + assert(stream[_closeRequest] !== undefined); + stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest] = undefined; + } + + /** + * @template W + * @param {WritableStream} stream + * */ + function writableStreamMarkFirstWriteRequestInFlight(stream) { + assert(stream[_inFlightWriteRequest] === undefined); + assert(stream[_writeRequests].length); + const writeRequest = stream[_writeRequests].shift(); + stream[_inFlightWriteRequest] = writeRequest; + } + + /** @param {WritableStream} stream */ + function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { + assert(stream[_state] === "errored"); + if (stream[_closeRequest] !== undefined) { + assert(stream[_inFlightCloseRequest] === undefined); + stream[_closeRequest].reject(stream[_storedError]); + stream[_closeRequest] = undefined; + } + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); + } + } + + /** + * @param {WritableStream} stream + * @param {any=} reason + */ + function writableStreamStartErroring(stream, reason) { + assert(stream[_storedError] === undefined); + assert(stream[_state] === "writable"); + const controller = stream[_controller]; + assert(controller !== undefined); + stream[_state] = "erroring"; + stream[_storedError] = reason; + const writer = stream[_writer]; + if (writer !== undefined) { + writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + } + if ( + writableStreamHasOperationMarkedInFlight(stream) === false && + controller[_started] === true + ) { + writableStreamFinishErroring(stream); + } + } + + /** + * @param {WritableStream} stream + * @param {boolean} backpressure + */ + function writableStreamUpdateBackpressure(stream, backpressure) { + assert(stream[_state] === "writable"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + const writer = stream[_writer]; + if (writer !== undefined && backpressure !== stream[_backpressure]) { + if (backpressure === true) { + writer[_readyPromise] = new Deferred(); + } else { + assert(backpressure === false); + writer[_readyPromise].resolve(undefined); + } + } + stream[_backpressure] = backpressure; + } + + /** + * @template T + * @param {T} value + * @param {boolean} done + * @returns {IteratorResult} + */ + function createIteratorResult(value, done) { + const result = ObjectCreate(null); + ObjectDefineProperties(result, { + value: { value, writable: true, enumerable: true, configurable: true }, + done: { + value: done, + writable: true, + enumerable: true, + configurable: true, + }, + }); + return result; + } + + /** @type {AsyncIterator} */ + const asyncIteratorPrototype = ObjectGetPrototypeOf( + ObjectGetPrototypeOf(async function* () {}).prototype, + ); + + /** @type {AsyncIterator} */ + const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ + /** @returns {Promise>} */ + next() { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + if (reader[_stream] === undefined) { + return PromiseReject( + new TypeError( + "Cannot get the next iteration result once the reader has been released.", + ), + ); + } + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve(createIteratorResult(chunk, false)); + }, + closeSteps() { + readableStreamReaderGenericRelease(reader); + promise.resolve(createIteratorResult(undefined, true)); + }, + errorSteps(e) { + readableStreamReaderGenericRelease(reader); + promise.reject(e); + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + return promise.promise; + }, + /** + * @param {unknown} arg + * @returns {Promise>} + */ + async return(arg) { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + if (reader[_stream] === undefined) { + return createIteratorResult(undefined, true); + } + assert(reader[_readRequests].length === 0); + if (this[_preventCancel] === false) { + const result = readableStreamReaderGenericCancel(reader, arg); + readableStreamReaderGenericRelease(reader); + await result; + return createIteratorResult(arg, true); + } + readableStreamReaderGenericRelease(reader); + return createIteratorResult(undefined, true); + }, + }, asyncIteratorPrototype); + + class ByteLengthQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'ByteLengthQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = window; + this[_highWaterMark] = init.highWaterMark; + } + + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, ByteLengthQueuingStrategy); + return this[_highWaterMark]; + } + + /** @returns {(chunk: ArrayBufferView) => number} */ + get size() { + webidl.assertBranded(this, ByteLengthQueuingStrategy); + initializeByteLengthSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof ByteLengthQueuingStrategy, + keys: [ + "highWaterMark", + "size", + ], + })); + } + + get [SymbolToStringTag]() { + return "ByteLengthQueuingStrategy"; + } + } + + webidl.configurePrototype(ByteLengthQueuingStrategy); + + /** @type {WeakMap number>} */ + const byteSizeFunctionWeakMap = new WeakMap(); + + function initializeByteLengthSizeFunction(globalObject) { + if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { + return; + } + const size = (chunk) => chunk.byteLength; + WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); + } + + class CountQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'CountQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = window; + this[_highWaterMark] = init.highWaterMark; + } + + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, CountQueuingStrategy); + return this[_highWaterMark]; + } + + /** @returns {(chunk: any) => 1} */ + get size() { + webidl.assertBranded(this, CountQueuingStrategy); + initializeCountSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof CountQueuingStrategy, + keys: [ + "highWaterMark", + "size", + ], + })); + } + + get [SymbolToStringTag]() { + return "CountQueuingStrategy"; + } + } + + webidl.configurePrototype(CountQueuingStrategy); + + /** @type {WeakMap 1>} */ + const countSizeFunctionWeakMap = new WeakMap(); + + /** @param {typeof globalThis} globalObject */ + function initializeCountSizeFunction(globalObject) { + if (WeakMapPrototypeHas(countSizeFunctionWeakMap, globalObject)) { + return; + } + const size = () => 1; + WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size); + } + + /** @template R */ + class ReadableStream { + /** @type {ReadableStreamDefaultController | ReadableByteStreamController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {boolean} */ + [_disturbed]; + /** @type {ReadableStreamDefaultReader | undefined} */ + [_reader]; + /** @type {"readable" | "closed" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + + /** + * @param {UnderlyingSource=} underlyingSource + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSource = undefined, strategy = {}) { + const prefix = "Failed to construct 'ReadableStream'"; + if (underlyingSource !== undefined) { + underlyingSource = webidl.converters.object(underlyingSource, { + prefix, + context: "Argument 1", + }); + } + strategy = webidl.converters.QueuingStrategy(strategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (underlyingSource === undefined) { + underlyingSource = null; + } + const underlyingSourceDict = webidl.converters.UnderlyingSource( + underlyingSource, + { prefix, context: "underlyingSource" }, + ); + initializeReadableStream(this); + if (underlyingSourceDict.type === "bytes") { + if (strategy.size !== undefined) { + throw new RangeError( + `${prefix}: When underlying source is "bytes", strategy.size must be undefined.`, + ); + } + const highWaterMark = extractHighWaterMark(strategy, 0); + setUpReadableByteStreamControllerFromUnderlyingSource( + // @ts-ignore cannot easily assert this is ReadableStream + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + ); + } else { + assert(!("type" in underlyingSourceDict)); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpReadableStreamDefaultControllerFromUnderlyingSource( + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, + ); + } + } + + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, ReadableStream); + return isReadableStreamLocked(this); + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStream); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); + } + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("Cannot cancel a locked ReadableStream."), + ); + } + return readableStreamCancel(this, reason); + } + + /** + * @deprecated TODO(@kitsonk): Remove in Deno 1.8 + * @param {ReadableStreamIteratorOptions=} options + * @returns {AsyncIterableIterator} + */ + getIterator(options = {}) { + return this[SymbolAsyncIterator](options); + } + + /** + * @param {ReadableStreamGetReaderOptions=} options + * @returns {ReadableStreamDefaultReader} + */ + getReader(options = {}) { + webidl.assertBranded(this, ReadableStream); + const prefix = "Failed to execute 'getReader' on 'ReadableStream'"; + options = webidl.converters.ReadableStreamGetReaderOptions(options, { + prefix, + context: "Argument 1", + }); + const { mode } = options; + if (mode === undefined) { + return acquireReadableStreamDefaultReader(this); + } + // 3. Return ? AcquireReadableStreamBYOBReader(this). + throw new RangeError(`${prefix}: Unsupported mode '${mode}'`); + } + + /** + * @template T + * @param {{ readable: ReadableStream, writable: WritableStream }} transform + * @param {PipeOptions=} options + * @returns {ReadableStream} + */ + pipeThrough(transform, options = {}) { + webidl.assertBranded(this, ReadableStream); + const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + transform = webidl.converters.ReadableWritablePair(transform, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.StreamPipeOptions(options, { + prefix, + context: "Argument 2", + }); + const { readable, writable } = transform; + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + throw new TypeError("ReadableStream is already locked."); + } + if (isWritableStreamLocked(writable)) { + throw new TypeError("Target WritableStream is already locked."); + } + const promise = readableStreamPipeTo( + this, + writable, + preventClose, + preventAbort, + preventCancel, + signal, + ); + setPromiseIsHandledToTrue(promise); + return readable; + } + + /** + * @param {WritableStream} destination + * @param {PipeOptions=} options + * @returns {Promise} + */ + pipeTo(destination, options = {}) { + try { + webidl.assertBranded(this, ReadableStream); + const prefix = "Failed to execute 'pipeTo' on 'ReadableStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + destination = webidl.converters.WritableStream(destination, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.StreamPipeOptions(options, { + prefix, + context: "Argument 2", + }); + } catch (err) { + return PromiseReject(err); + } + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("ReadableStream is already locked."), + ); + } + if (isWritableStreamLocked(destination)) { + return PromiseReject( + new TypeError("destination WritableStream is already locked."), + ); + } + return readableStreamPipeTo( + this, + destination, + preventClose, + preventAbort, + preventCancel, + signal, + ); + } + + /** @returns {[ReadableStream, ReadableStream]} */ + tee() { + webidl.assertBranded(this, ReadableStream); + return readableStreamTee(this, false); + } + + // TODO(lucacasonato): should be moved to webidl crate + /** + * @param {ReadableStreamIteratorOptions=} options + * @returns {AsyncIterableIterator} + */ + values(options = {}) { + webidl.assertBranded(this, ReadableStream); + const prefix = "Failed to execute 'values' on 'ReadableStream'"; + options = webidl.converters.ReadableStreamIteratorOptions(options, { + prefix, + context: "Argument 1", + }); + /** @type {AsyncIterableIterator} */ + const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); + const reader = acquireReadableStreamDefaultReader(this); + iterator[_reader] = reader; + iterator[_preventCancel] = options.preventCancel; + return iterator; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + } + + get [SymbolToStringTag]() { + return "ReadableStream"; + } + } + + // TODO(lucacasonato): should be moved to webidl crate + ReadableStream.prototype[SymbolAsyncIterator] = + ReadableStream.prototype.values; + ObjectDefineProperty(ReadableStream.prototype, SymbolAsyncIterator, { + writable: true, + enumerable: false, + configurable: true, + }); + + webidl.configurePrototype(ReadableStream); + + function errorReadableStream(stream, e) { + readableStreamDefaultControllerError(stream[_controller], e); + } + + /** @template R */ + class ReadableStreamDefaultReader { + /** @type {Deferred} */ + [_closedPromise]; + /** @type {ReadableStream | undefined} */ + [_stream]; + /** @type {ReadRequest[]} */ + [_readRequests]; + + /** @param {ReadableStream} stream */ + constructor(stream) { + const prefix = "Failed to construct 'ReadableStreamDefaultReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.ReadableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpReadableStreamDefaultReader(this, stream); + } + + /** @returns {Promise>} */ + read() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReader); + } catch (err) { + return PromiseReject(err); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve({ value: chunk, done: false }); + }, + closeSteps() { + promise.resolve({ value: undefined, done: true }); + }, + errorSteps(e) { + promise.reject(e); + }, + }; + readableStreamDefaultReaderRead(this, readRequest); + return promise.promise; + } + + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamDefaultReader); + if (this[_stream] === undefined) { + return; + } + if (this[_readRequests].length) { + throw new TypeError( + "There are pending read requests, so the reader cannot be release.", + ); + } + readableStreamReaderGenericRelease(this); + } + + get closed() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReader); + } catch (err) { + return PromiseReject(err); + } + return this[_closedPromise].promise; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamDefaultReader); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); + } + + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + return readableStreamReaderGenericCancel(this, reason); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + } + + get [SymbolToStringTag]() { + return "ReadableStreamDefaultReader"; + } + } + + webidl.configurePrototype(ReadableStreamDefaultReader); + + class ReadableByteStreamController { + /** @type {number | undefined} */ + [_autoAllocateChunkSize]; + /** @type {null} */ + [_byobRequest]; + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {ReadableByteStreamQueueEntry[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + get byobRequest() { + webidl.assertBranded(this, ReadableByteStreamController); + return undefined; + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableByteStreamController); + return readableByteStreamControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableByteStreamController); + if (this[_closeRequested] === true) { + throw new TypeError("Closed already requested."); + } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "ReadableByteStreamController's stream is not in a readable state.", + ); + } + readableByteStreamControllerClose(this); + } + + /** + * @param {ArrayBufferView} chunk + * @returns {void} + */ + enqueue(chunk) { + webidl.assertBranded(this, ReadableByteStreamController); + const prefix = + "Failed to execute 'enqueue' on 'ReadableByteStreamController'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + const arg1 = "Argument 1"; + chunk = webidl.converters.ArrayBufferView(chunk, { + prefix, + context: arg1, + }); + if (chunk.byteLength === 0) { + throw webidl.makeException(TypeError, "length must be non-zero", { + prefix, + context: arg1, + }); + } + if (chunk.buffer.byteLength === 0) { + throw webidl.makeException( + TypeError, + "buffer length must be non-zero", + { prefix, context: arg1 }, + ); + } + if (this[_closeRequested] === true) { + throw new TypeError( + "Cannot enqueue chunk after a close has been requested.", + ); + } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "Cannot enqueue chunk when underlying stream is not readable.", + ); + } + return readableByteStreamControllerEnqueue(this, chunk); + } + + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableByteStreamController); + if (e !== undefined) { + e = webidl.converters.any(e); + } + readableByteStreamControllerError(this, e); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof ReadableByteStreamController, + keys: ["desiredSize"], + })); + } + + get [SymbolToStringTag]() { + return "ReadableByteStreamController"; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + // 4.7.4. CancelStep 1. If this.[[pendingPullIntos]] is not empty, + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableByteStreamControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + /** @type {ReadableStream} */ + const stream = this[_stream]; + assert(readableStreamHasDefaultReader(stream)); + if (this[_queueTotalSize] > 0) { + assert(readableStreamGetNumReadRequests(stream) === 0); + const entry = ArrayPrototypeShift(this[_queue]); + this[_queueTotalSize] -= entry.byteLength; + readableByteStreamControllerHandleQueueDrain(this); + const view = new Uint8Array( + entry.buffer, + entry.byteOffset, + entry.byteLength, + ); + readRequest.chunkSteps(view); + return; + } + // 4. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. + // 5. If autoAllocateChunkSize is not undefined, + readableStreamAddReadRequest(stream, readRequest); + readableByteStreamControllerCallPullIfNeeded(this); + } + } + + webidl.configurePrototype(ReadableByteStreamController); + + /** @template R */ + class ReadableStreamDefaultController { + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {Array>} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: R) => number} */ + [_strategySizeAlgorithm]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableStreamDefaultController); + return readableStreamDefaultControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableStreamDefaultController); + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); + } + readableStreamDefaultControllerClose(this); + } + + /** + * @param {R} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultController); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); + } + readableStreamDefaultControllerEnqueue(this, chunk); + } + + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultController); + if (e !== undefined) { + e = webidl.converters.any(e); + } + readableStreamDefaultControllerError(this, e); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof ReadableStreamDefaultController, + keys: ["desiredSize"], + })); + } + + get [SymbolToStringTag]() { + return "ReadableStreamDefaultController"; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + const stream = this[_stream]; + if (this[_queue].length) { + const chunk = dequeueValue(this); + if (this[_closeRequested] && this[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(this); + readableStreamClose(stream); + } else { + readableStreamDefaultControllerCallPullIfNeeded(this); + } + readRequest.chunkSteps(chunk); + } else { + readableStreamAddReadRequest(stream, readRequest); + readableStreamDefaultControllerCallPullIfNeeded(this); + } + } + } + + webidl.configurePrototype(ReadableStreamDefaultController); + + /** + * @template I + * @template O + */ + class TransformStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred} */ + [_backpressureChangePromise]; + /** @type {TransformStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {ReadableStream} */ + [_readable]; + /** @type {WritableStream} */ + [_writable]; + + /** + * + * @param {Transformer} transformer + * @param {QueuingStrategy} writableStrategy + * @param {QueuingStrategy} readableStrategy + */ + constructor( + transformer = undefined, + writableStrategy = {}, + readableStrategy = {}, + ) { + const prefix = "Failed to construct 'TransformStream'"; + if (transformer !== undefined) { + transformer = webidl.converters.object(transformer, { + prefix, + context: "Argument 1", + }); + } + writableStrategy = webidl.converters.QueuingStrategy(writableStrategy, { + prefix, + context: "Argument 2", + }); + readableStrategy = webidl.converters.QueuingStrategy(readableStrategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (transformer === undefined) { + transformer = null; + } + const transformerDict = webidl.converters.Transformer(transformer, { + prefix, + context: "transformer", + }); + if (transformerDict.readableType !== undefined) { + throw new RangeError( + `${prefix}: readableType transformers not supported.`, + ); + } + if (transformerDict.writableType !== undefined) { + throw new RangeError( + `${prefix}: writableType transformers not supported.`, + ); + } + const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); + const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); + const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); + const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); + /** @type {Deferred} */ + const startPromise = new Deferred(); + initializeTransformStream( + this, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ); + setUpTransformStreamDefaultControllerFromTransformer( + this, + transformer, + transformerDict, + ); + if (transformerDict.start) { + startPromise.resolve( + webidl.invokeCallbackFunction( + transformerDict.start, + [this[_controller]], + transformer, + webidl.converters.any, + { + prefix: + "Failed to call 'start' on 'TransformStreamDefaultController'", + }, + ), + ); + } else { + startPromise.resolve(undefined); + } + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TransformStream); + return this[_readable]; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TransformStream); + return this[_writable]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ readable: this.readable, writable: this.writable }) + }`; + } + + get [SymbolToStringTag]() { + return "TransformStream"; + } + } + + webidl.configurePrototype(TransformStream); + + /** @template O */ + class TransformStreamDefaultController { + /** @type {(controller: this) => Promise} */ + [_flushAlgorithm]; + /** @type {TransformStream} */ + [_stream]; + /** @type {(chunk: O, controller: this) => Promise} */ + [_transformAlgorithm]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, TransformStreamDefaultController); + const readableController = this[_stream][_readable][_controller]; + return readableStreamDefaultControllerGetDesiredSize( + /** @type {ReadableStreamDefaultController} */ (readableController), + ); + } + + /** + * @param {O} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + transformStreamDefaultControllerEnqueue(this, chunk); + } + + /** + * @param {any=} reason + * @returns {void} + */ + error(reason = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + transformStreamDefaultControllerError(this, reason); + } + + /** @returns {void} */ + terminate() { + webidl.assertBranded(this, TransformStreamDefaultController); + transformStreamDefaultControllerTerminate(this); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof TransformStreamDefaultController, + keys: ["desiredSize"], + })); + } + + get [SymbolToStringTag]() { + return "TransformStreamDefaultController"; + } + } + + webidl.configurePrototype(TransformStreamDefaultController); + + /** @template W */ + class WritableStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred | undefined} */ + [_closeRequest]; + /** @type {WritableStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {Deferred | undefined} */ + [_inFlightWriteRequest]; + /** @type {Deferred | undefined} */ + [_inFlightCloseRequest]; + /** @type {PendingAbortRequest | undefined} */ + [_pendingAbortRequest]; + /** @type {"writable" | "closed" | "erroring" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {WritableStreamDefaultWriter} */ + [_writer]; + /** @type {Deferred[]} */ + [_writeRequests]; + + /** + * @param {UnderlyingSink=} underlyingSink + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSink = undefined, strategy = {}) { + const prefix = "Failed to construct 'WritableStream'"; + if (underlyingSink !== undefined) { + underlyingSink = webidl.converters.object(underlyingSink, { + prefix, + context: "Argument 1", + }); + } + strategy = webidl.converters.QueuingStrategy(strategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (underlyingSink === undefined) { + underlyingSink = null; + } + const underlyingSinkDict = webidl.converters.UnderlyingSink( + underlyingSink, + { prefix, context: "underlyingSink" }, + ); + if (underlyingSinkDict.type != null) { + throw new RangeError( + `${prefix}: WritableStream does not support 'type' in the underlying sink.`, + ); + } + initializeWritableStream(this); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpWritableStreamDefaultControllerFromUnderlyingSink( + this, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, + ); + } + + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, WritableStream); + return isWritableStreamLocked(this); + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStream); + } catch (err) { + return PromiseReject(err); + } + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be aborted.", + ), + ); + } + return writableStreamAbort(this, reason); + } + + /** @returns {Promise} */ + close() { + try { + webidl.assertBranded(this, WritableStream); + } catch (err) { + return PromiseReject(err); + } + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be closed.", + ), + ); + } + if (writableStreamCloseQueuedOrInFlight(this) === true) { + return PromiseReject( + new TypeError("The writable stream is already closing."), + ); + } + return writableStreamClose(this); + } + + /** @returns {WritableStreamDefaultWriter} */ + getWriter() { + webidl.assertBranded(this, WritableStream); + return acquireWritableStreamDefaultWriter(this); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + } + + get [SymbolToStringTag]() { + return "WritableStream"; + } + } + + webidl.configurePrototype(WritableStream); + + /** @template W */ + class WritableStreamDefaultWriter { + /** @type {Deferred} */ + [_closedPromise]; + + /** @type {Deferred} */ + [_readyPromise]; + + /** @type {WritableStream} */ + [_stream]; + + /** + * @param {WritableStream} stream + */ + constructor(stream) { + const prefix = "Failed to construct 'WritableStreamDefaultWriter'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.WritableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpWritableStreamDefaultWriter(this, stream); + } + + /** @returns {Promise} */ + get closed() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriter); + } catch (err) { + return PromiseReject(err); + } + return this[_closedPromise].promise; + } + + /** @returns {number} */ + get desiredSize() { + webidl.assertBranded(this, WritableStreamDefaultWriter); + if (this[_stream] === undefined) { + throw new TypeError( + "A writable stream is not associated with the writer.", + ); + } + return writableStreamDefaultWriterGetDesiredSize(this); + } + + /** @returns {Promise} */ + get ready() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriter); + } catch (err) { + return PromiseReject(err); + } + return this[_readyPromise].promise; + } + + /** + * @param {any} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriter); + } catch (err) { + return PromiseReject(err); + } + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); + } + return writableStreamDefaultWriterAbort(this, reason); + } + + /** @returns {Promise} */ + close() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriter); + } catch (err) { + return PromiseReject(err); + } + const stream = this[_stream]; + if (stream === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); + } + if (writableStreamCloseQueuedOrInFlight(stream) === true) { + return PromiseReject( + new TypeError("The associated stream is already closing."), + ); + } + return writableStreamDefaultWriterClose(this); + } + + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, WritableStreamDefaultWriter); + const stream = this[_stream]; + if (stream === undefined) { + return; + } + assert(stream[_writer] !== undefined); + writableStreamDefaultWriterRelease(this); + } + + /** + * @param {W} chunk + * @returns {Promise} + */ + write(chunk = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriter); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + } catch (err) { + return PromiseReject(err); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associate with the writer."), + ); + } + return writableStreamDefaultWriterWrite(this, chunk); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof WritableStreamDefaultWriter, + keys: [ + "closed", + "desiredSize", + "ready", + ], + })); + } + + get [SymbolToStringTag]() { + return "WritableStreamDefaultWriter"; + } + } + + webidl.configurePrototype(WritableStreamDefaultWriter); + + /** @template W */ + class WritableStreamDefaultController { + /** @type {(reason?: any) => Promise} */ + [_abortAlgorithm]; + /** @type {() => Promise} */ + [_closeAlgorithm]; + /** @type {ValueWithSize[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: W) => number} */ + [_strategySizeAlgorithm]; + /** @type {WritableStream} */ + [_stream]; + /** @type {(chunk: W, controller: this) => Promise} */ + [_writeAlgorithm]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, WritableStreamDefaultController); + if (e !== undefined) { + e = webidl.converters.any(e); + } + const state = this[_stream][_state]; + if (state !== "writable") { + return; + } + writableStreamDefaultControllerError(this, e); + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof WritableStreamDefaultController, + keys: [], + })); + } + + get [SymbolToStringTag]() { + return "WritableStreamDefaultController"; + } + + /** + * @param {any=} reason + * @returns {Promise} + */ + [_abortSteps](reason) { + const result = this[_abortAlgorithm](reason); + writableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + [_errorSteps]() { + resetQueue(this); + } + } + + webidl.configurePrototype(WritableStreamDefaultController); + + /** + * @param {ReadableStream} stream + */ + function createProxy(stream) { + return stream.pipeThrough(new TransformStream()); + } + + webidl.converters.ReadableStream = webidl + .createInterfaceConverter("ReadableStream", ReadableStream); + webidl.converters.WritableStream = webidl + .createInterfaceConverter("WritableStream", WritableStream); + + webidl.converters.ReadableStreamType = webidl.createEnumConverter( + "ReadableStreamType", + ["bytes"], + ); + + webidl.converters.UnderlyingSource = webidl + .createDictionaryConverter("UnderlyingSource", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "pull", + converter: webidl.converters.Function, + }, + { + key: "cancel", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.ReadableStreamType, + }, + { + key: "autoAllocateChunkSize", + converter: (V, opts) => + webidl.converters["unsigned long long"](V, { + ...opts, + enforceRange: true, + }), + }, + ]); + webidl.converters.UnderlyingSink = webidl + .createDictionaryConverter("UnderlyingSink", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "write", + converter: webidl.converters.Function, + }, + { + key: "close", + converter: webidl.converters.Function, + }, + { + key: "abort", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.any, + }, + ]); + webidl.converters.Transformer = webidl + .createDictionaryConverter("Transformer", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "transform", + converter: webidl.converters.Function, + }, + { + key: "flush", + converter: webidl.converters.Function, + }, + { + key: "readableType", + converter: webidl.converters.any, + }, + { + key: "writableType", + converter: webidl.converters.any, + }, + ]); + webidl.converters.QueuingStrategy = webidl + .createDictionaryConverter("QueuingStrategy", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + }, + { + key: "size", + converter: webidl.converters.Function, + }, + ]); + webidl.converters.QueuingStrategyInit = webidl + .createDictionaryConverter("QueuingStrategyInit", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + required: true, + }, + ]); + + webidl.converters.ReadableStreamIteratorOptions = webidl + .createDictionaryConverter("ReadableStreamIteratorOptions", [ + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + ]); + + webidl.converters.ReadableStreamReaderMode = webidl + .createEnumConverter("ReadableStreamReaderMode", ["byob"]); + webidl.converters.ReadableStreamGetReaderOptions = webidl + .createDictionaryConverter("ReadableStreamGetReaderOptions", [{ + key: "mode", + converter: webidl.converters.ReadableStreamReaderMode, + }]); + + webidl.converters.ReadableWritablePair = webidl + .createDictionaryConverter("ReadableWritablePair", [ + { + key: "readable", + converter: webidl.converters.ReadableStream, + required: true, + }, + { + key: "writable", + converter: webidl.converters.WritableStream, + required: true, + }, + ]); + webidl.converters.StreamPipeOptions = webidl + .createDictionaryConverter("StreamPipeOptions", [ + { + key: "preventClose", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventAbort", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { key: "signal", converter: webidl.converters.AbortSignal }, + ]); + + window.__bootstrap.streams = { + // Non-Public + isReadableStreamDisturbed, + errorReadableStream, + createProxy, + writableStreamClose, + Deferred, + // Exposed in global runtime scope + ByteLengthQueuingStrategy, + CountQueuingStrategy, + ReadableStream, + ReadableStreamDefaultReader, + TransformStream, + WritableStream, + WritableStreamDefaultWriter, + WritableStreamDefaultController, + ReadableByteStreamController, + ReadableStreamDefaultController, + TransformStreamDefaultController, + }; +})(this); diff --git a/ext/web/06_streams_types.d.ts b/ext/web/06_streams_types.d.ts new file mode 100644 index 000000000..61621c003 --- /dev/null +++ b/ext/web/06_streams_types.d.ts @@ -0,0 +1,55 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// ** Internal Interfaces ** + +interface PendingAbortRequest { + deferred: Deferred; + // deno-lint-ignore no-explicit-any + reason: any; + wasAlreadyErroring: boolean; +} + +// deno-lint-ignore no-explicit-any +interface ReadRequest { + chunkSteps: (chunk: R) => void; + closeSteps: () => void; + // deno-lint-ignore no-explicit-any + errorSteps: (error: any) => void; +} + +interface ReadableByteStreamQueueEntry { + buffer: ArrayBufferLike; + byteOffset: number; + byteLength: number; +} + +interface ReadableStreamGetReaderOptions { + mode?: "byob"; +} + +interface ReadableStreamIteratorOptions { + preventCancel?: boolean; +} + +interface ValueWithSize { + value: T; + size: number; +} + +interface VoidFunction { + (): void; +} + +interface ReadableStreamGenericReader { + readonly closed: Promise; + // deno-lint-ignore no-explicit-any + cancel(reason?: any): Promise; +} + +// ** Ambient Definitions and Interfaces not provided by fetch ** + +declare function queueMicrotask(callback: VoidFunction): void; + +declare namespace Deno { + function inspect(value: unknown, options?: Record): string; +} diff --git a/ext/web/08_text_encoding.js b/ext/web/08_text_encoding.js new file mode 100644 index 000000000..9be4aa753 --- /dev/null +++ b/ext/web/08_text_encoding.js @@ -0,0 +1,420 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = Deno.core; + const webidl = window.__bootstrap.webidl; + const { + ArrayBufferIsView, + PromiseReject, + PromiseResolve, + StringPrototypeCharCodeAt, + StringPrototypeSlice, + SymbolToStringTag, + TypedArrayPrototypeSubarray, + TypedArrayPrototypeSlice, + Uint8Array, + } = window.__bootstrap.primordials; + + class TextDecoder { + /** @type {string} */ + #encoding; + /** @type {boolean} */ + #fatal; + /** @type {boolean} */ + #ignoreBOM; + + /** @type {number | null} */ + #rid = null; + + /** + * + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoder'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + const encoding = core.opSync("op_encoding_normalize_label", label); + this.#encoding = encoding; + this.#fatal = options.fatal; + this.#ignoreBOM = options.ignoreBOM; + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoder); + return this.#encoding; + } + + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoder); + return this.#fatal; + } + + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoder); + return this.#ignoreBOM; + } + + /** + * @param {BufferSource} [input] + * @param {TextDecodeOptions} options + */ + decode(input = new Uint8Array(), options = {}) { + webidl.assertBranded(this, TextDecoder); + const prefix = "Failed to execute 'decode' on 'TextDecoder'"; + if (input !== undefined) { + input = webidl.converters.BufferSource(input, { + prefix, + context: "Argument 1", + allowShared: true, + }); + } + options = webidl.converters.TextDecodeOptions(options, { + prefix, + context: "Argument 2", + }); + + // TODO(lucacasonato): add fast path for non-streaming decoder & decode + + if (this.#rid === null) { + this.#rid = core.opSync("op_encoding_new_decoder", { + label: this.#encoding, + fatal: this.#fatal, + ignoreBom: this.#ignoreBOM, + }); + } + + try { + if (ArrayBufferIsView(input)) { + input = new Uint8Array( + input.buffer, + input.byteOffset, + input.byteLength, + ); + } else { + input = new Uint8Array(input); + } + return core.opSync("op_encoding_decode", new Uint8Array(input), { + rid: this.#rid, + stream: options.stream, + }); + } finally { + if (!options.stream) { + core.close(this.#rid); + this.#rid = null; + } + } + } + + get [SymbolToStringTag]() { + return "TextDecoder"; + } + } + + webidl.configurePrototype(TextDecoder); + + class TextEncoder { + constructor() { + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoder); + return "utf-8"; + } + + /** + * @param {string} input + * @returns {Uint8Array} + */ + encode(input = "") { + webidl.assertBranded(this, TextEncoder); + const prefix = "Failed to execute 'encode' on 'TextEncoder'"; + // The WebIDL type of `input` is `USVString`, but `core.encode` already + // converts lone surrogates to the replacement character. + input = webidl.converters.DOMString(input, { + prefix, + context: "Argument 1", + }); + return core.encode(input); + } + + /** + * @param {string} source + * @param {Uint8Array} destination + * @returns {TextEncoderEncodeIntoResult} + */ + encodeInto(source, destination) { + webidl.assertBranded(this, TextEncoder); + const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'"; + // The WebIDL type of `source` is `USVString`, but the ops bindings + // already convert lone surrogates to the replacement character. + source = webidl.converters.DOMString(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.Uint8Array(destination, { + prefix, + context: "Argument 2", + allowShared: true, + }); + return core.opSync("op_encoding_encode_into", source, destination); + } + + get [SymbolToStringTag]() { + return "TextEncoder"; + } + } + + webidl.configurePrototype(TextEncoder); + + class TextDecoderStream { + /** @type {TextDecoder} */ + #decoder; + /** @type {TransformStream} */ + #transform; + + /** + * + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoderStream'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + this.#decoder = new TextDecoder(label, options); + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextDecoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.BufferSource(chunk, { + allowShared: true, + }); + const decoded = this.#decoder.decode(chunk, { stream: true }); + if (decoded) { + controller.enqueue(decoded); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderStream); + return this.#decoder.encoding; + } + + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderStream); + return this.#decoder.fatal; + } + + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderStream); + return this.#decoder.ignoreBOM; + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextDecoderStream); + return this.#transform.readable; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextDecoderStream); + return this.#transform.writable; + } + + get [SymbolToStringTag]() { + return "TextDecoderStream"; + } + } + + webidl.configurePrototype(TextDecoderStream); + + class TextEncoderStream { + /** @type {string | null} */ + #pendingHighSurrogate = null; + /** @type {TransformStream} */ + #transform; + + constructor() { + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextEncoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.DOMString(chunk); + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; + } + const lastCodeUnit = StringPrototypeCharCodeAt( + chunk, + chunk.length - 1, + ); + if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { + this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1); + chunk = StringPrototypeSlice(chunk, 0, -1); + } else { + this.#pendingHighSurrogate = null; + } + if (chunk) { + controller.enqueue(core.encode(chunk)); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + if (this.#pendingHighSurrogate !== null) { + controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderStream); + return "utf-8"; + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextEncoderStream); + return this.#transform.readable; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextEncoderStream); + return this.#transform.writable; + } + + get [SymbolToStringTag]() { + return "TextEncoderStream"; + } + } + + webidl.configurePrototype(TextEncoderStream); + + webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( + "TextDecoderOptions", + [ + { + key: "fatal", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "ignoreBOM", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], + ); + webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( + "TextDecodeOptions", + [ + { + key: "stream", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], + ); + + /** + * @param {Uint8Array} bytes + */ + function decode(bytes, encoding) { + const BOMEncoding = BOMSniff(bytes); + let start = 0; + if (BOMEncoding !== null) { + encoding = BOMEncoding; + if (BOMEncoding === "UTF-8") start = 3; + else start = 2; + } + return new TextDecoder(encoding).decode( + TypedArrayPrototypeSlice(bytes, start), + ); + } + + /** + * @param {Uint8Array} bytes + */ + function BOMSniff(bytes) { + const BOM = TypedArrayPrototypeSubarray(bytes, 0, 3); + if (BOM[0] === 0xEF && BOM[1] === 0xBB && BOM[2] === 0xBF) { + return "UTF-8"; + } + if (BOM[0] === 0xFE && BOM[1] === 0xFF) return "UTF-16BE"; + if (BOM[0] === 0xFF && BOM[1] === 0xFE) return "UTF-16LE"; + return null; + } + + window.__bootstrap.encoding = { + TextEncoder, + TextDecoder, + TextEncoderStream, + TextDecoderStream, + decode, + }; +})(this); diff --git a/ext/web/09_file.js b/ext/web/09_file.js new file mode 100644 index 000000000..516e80adf --- /dev/null +++ b/ext/web/09_file.js @@ -0,0 +1,569 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// +/// +"use strict"; + +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + const { + ArrayBuffer, + ArrayBufferPrototypeSlice, + ArrayBufferIsView, + ArrayPrototypePush, + Date, + DatePrototypeGetTime, + MathMax, + MathMin, + RegExpPrototypeTest, + StringPrototypeCharAt, + StringPrototypeToLowerCase, + StringPrototypeSlice, + Symbol, + SymbolFor, + TypedArrayPrototypeSet, + SymbolToStringTag, + TypeError, + Uint8Array, + } = window.__bootstrap.primordials; + const consoleInternal = window.__bootstrap.console; + + // TODO(lucacasonato): this needs to not be hardcoded and instead depend on + // host os. + const isWindows = false; + + /** + * @param {string} input + * @param {number} position + * @returns {{result: string, position: number}} + */ + function collectCodepointsNotCRLF(input, position) { + // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && !(c === "\r" || c === "\n"); + c = StringPrototypeCharAt(input, ++position) + ); + return { result: StringPrototypeSlice(input, start, position), position }; + } + + /** + * @param {string} s + * @returns {string} + */ + function convertLineEndingsToNative(s) { + const nativeLineEnding = isWindows ? "\r\n" : "\n"; + + let { result, position } = collectCodepointsNotCRLF(s, 0); + + while (position < s.length) { + const codePoint = StringPrototypeCharAt(s, position); + if (codePoint === "\r") { + result += nativeLineEnding; + position++; + if ( + position < s.length && StringPrototypeCharAt(s, position) === "\n" + ) { + position++; + } + } else if (codePoint === "\n") { + position++; + result += nativeLineEnding; + } + const { result: token, position: newPosition } = collectCodepointsNotCRLF( + s, + position, + ); + position = newPosition; + result += token; + } + + return result; + } + + /** @param {(BlobReference | Blob)[]} parts */ + async function* toIterator(parts) { + for (const part of parts) { + yield* part.stream(); + } + } + + /** @typedef {BufferSource | Blob | string} BlobPart */ + + /** + * @param {BlobPart[]} parts + * @param {string} endings + * @returns {{ parts: (BlobReference|Blob)[], size: number }} + */ + function processBlobParts(parts, endings) { + /** @type {(BlobReference|Blob)[]} */ + const processedParts = []; + let size = 0; + for (const element of parts) { + if (element instanceof ArrayBuffer) { + const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0)); + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + size += element.byteLength; + } else if (ArrayBufferIsView(element)) { + const chunk = new Uint8Array( + element.buffer, + element.byteOffset, + element.byteLength, + ); + size += element.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else if (element instanceof Blob) { + ArrayPrototypePush(processedParts, element); + size += element.size; + } else if (typeof element === "string") { + const chunk = core.encode( + endings == "native" ? convertLineEndingsToNative(element) : element, + ); + size += chunk.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else { + throw new TypeError("Unreachable code (invalid element type)"); + } + } + return { parts: processedParts, size }; + } + + /** + * @param {string} str + * @returns {string} + */ + function normalizeType(str) { + let normalizedType = str; + if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { + normalizedType = ""; + } + return StringPrototypeToLowerCase(normalizedType); + } + + /** + * Get all Parts as a flat array containing all references + * @param {Blob} blob + * @param {string[]} bag + * @returns {string[]} + */ + function getParts(blob, bag = []) { + for (const part of blob[_parts]) { + if (part instanceof Blob) { + getParts(part, bag); + } else { + ArrayPrototypePush(bag, part._id); + } + } + return bag; + } + + const _size = Symbol("Size"); + const _parts = Symbol("Parts"); + + class Blob { + #type = ""; + [_size] = 0; + [_parts]; + + /** + * @param {BlobPart[]} blobParts + * @param {BlobPropertyBag} options + */ + constructor(blobParts = [], options = {}) { + const prefix = "Failed to construct 'Blob'"; + blobParts = webidl.converters["sequence"](blobParts, { + context: "Argument 1", + prefix, + }); + options = webidl.converters["BlobPropertyBag"](options, { + context: "Argument 2", + prefix, + }); + + this[webidl.brand] = webidl.brand; + + const { parts, size } = processBlobParts( + blobParts, + options.endings, + ); + + this[_parts] = parts; + this[_size] = size; + this.#type = normalizeType(options.type); + } + + /** @returns {number} */ + get size() { + webidl.assertBranded(this, Blob); + return this[_size]; + } + + /** @returns {string} */ + get type() { + webidl.assertBranded(this, Blob); + return this.#type; + } + + /** + * @param {number} [start] + * @param {number} [end] + * @param {string} [contentType] + * @returns {Blob} + */ + slice(start, end, contentType) { + webidl.assertBranded(this, Blob); + const prefix = "Failed to execute 'slice' on 'Blob'"; + if (start !== undefined) { + start = webidl.converters["long long"](start, { + clamp: true, + context: "Argument 1", + prefix, + }); + } + if (end !== undefined) { + end = webidl.converters["long long"](end, { + clamp: true, + context: "Argument 2", + prefix, + }); + } + if (contentType !== undefined) { + contentType = webidl.converters["DOMString"](contentType, { + context: "Argument 3", + prefix, + }); + } + + // deno-lint-ignore no-this-alias + const O = this; + /** @type {number} */ + let relativeStart; + if (start === undefined) { + relativeStart = 0; + } else { + if (start < 0) { + relativeStart = MathMax(O.size + start, 0); + } else { + relativeStart = MathMin(start, O.size); + } + } + /** @type {number} */ + let relativeEnd; + if (end === undefined) { + relativeEnd = O.size; + } else { + if (end < 0) { + relativeEnd = MathMax(O.size + end, 0); + } else { + relativeEnd = MathMin(end, O.size); + } + } + + const span = MathMax(relativeEnd - relativeStart, 0); + const blobParts = []; + let added = 0; + + for (const part of this[_parts]) { + // don't add the overflow to new blobParts + if (added >= span) { + // Could maybe be possible to remove variable `added` + // and only use relativeEnd? + break; + } + const size = part.size; + if (relativeStart && size <= relativeStart) { + // Skip the beginning and change the relative + // start & end position as we skip the unwanted parts + relativeStart -= size; + relativeEnd -= size; + } else { + const chunk = part.slice( + relativeStart, + MathMin(part.size, relativeEnd), + ); + added += chunk.size; + relativeEnd -= part.size; + ArrayPrototypePush(blobParts, chunk); + relativeStart = 0; // All next sequential parts should start at 0 + } + } + + /** @type {string} */ + let relativeContentType; + if (contentType === undefined) { + relativeContentType = ""; + } else { + relativeContentType = normalizeType(contentType); + } + + const blob = new Blob([], { type: relativeContentType }); + blob[_parts] = blobParts; + blob[_size] = span; + return blob; + } + + /** + * @returns {ReadableStream} + */ + stream() { + webidl.assertBranded(this, Blob); + const partIterator = toIterator(this[_parts]); + const stream = new ReadableStream({ + type: "bytes", + /** @param {ReadableByteStreamController} controller */ + async pull(controller) { + while (true) { + const { value, done } = await partIterator.next(); + if (done) return controller.close(); + if (value.byteLength > 0) { + return controller.enqueue(value); + } + } + }, + }); + return stream; + } + + /** + * @returns {Promise} + */ + async text() { + webidl.assertBranded(this, Blob); + const buffer = await this.arrayBuffer(); + return core.decode(new Uint8Array(buffer)); + } + + /** + * @returns {Promise} + */ + async arrayBuffer() { + webidl.assertBranded(this, Blob); + const stream = this.stream(); + const bytes = new Uint8Array(this.size); + let offset = 0; + for await (const chunk of stream) { + TypedArrayPrototypeSet(bytes, chunk, offset); + offset += chunk.byteLength; + } + return bytes.buffer; + } + + get [SymbolToStringTag]() { + return "Blob"; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(consoleInternal.createFilteredInspectProxy({ + object: this, + evaluate: this instanceof Blob, + keys: [ + "size", + "type", + ], + })); + } + } + + webidl.configurePrototype(Blob); + + webidl.converters["Blob"] = webidl.createInterfaceConverter("Blob", Blob); + webidl.converters["BlobPart"] = (V, opts) => { + // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) + if (typeof V == "object") { + if (V instanceof Blob) { + return webidl.converters["Blob"](V, opts); + } + if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { + return webidl.converters["ArrayBuffer"](V, opts); + } + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); + } + } + return webidl.converters["USVString"](V, opts); + }; + webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters["BlobPart"], + ); + webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ + "transparent", + "native", + ]); + const blobPropertyBagDictionary = [ + { + key: "type", + converter: webidl.converters["DOMString"], + defaultValue: "", + }, + { + key: "endings", + converter: webidl.converters["EndingType"], + defaultValue: "transparent", + }, + ]; + webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( + "BlobPropertyBag", + blobPropertyBagDictionary, + ); + + const _Name = Symbol("[[Name]]"); + const _LastModified = Symbol("[[LastModified]]"); + + class File extends Blob { + /** @type {string} */ + [_Name]; + /** @type {number} */ + [_LastModified]; + + /** + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} options + */ + constructor(fileBits, fileName, options = {}) { + const prefix = "Failed to construct 'File'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + fileBits = webidl.converters["sequence"](fileBits, { + context: "Argument 1", + prefix, + }); + fileName = webidl.converters["USVString"](fileName, { + context: "Argument 2", + prefix, + }); + options = webidl.converters["FilePropertyBag"](options, { + context: "Argument 3", + prefix, + }); + + super(fileBits, options); + + /** @type {string} */ + this[_Name] = fileName; + if (options.lastModified === undefined) { + /** @type {number} */ + this[_LastModified] = DatePrototypeGetTime(new Date()); + } else { + /** @type {number} */ + this[_LastModified] = options.lastModified; + } + } + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, File); + return this[_Name]; + } + + /** @returns {number} */ + get lastModified() { + webidl.assertBranded(this, File); + return this[_LastModified]; + } + + get [SymbolToStringTag]() { + return "File"; + } + } + + webidl.configurePrototype(File); + + webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( + "FilePropertyBag", + blobPropertyBagDictionary, + [ + { + key: "lastModified", + converter: webidl.converters["long long"], + }, + ], + ); + + // A finalization registry to deallocate a blob part when its JS reference is + // garbage collected. + const registry = new FinalizationRegistry((uuid) => { + core.opSync("op_blob_remove_part", uuid); + }); + + // TODO(lucacasonato): get a better stream from Rust in BlobReference#stream + + /** + * An opaque reference to a blob part in Rust. This could be backed by a file, + * in memory storage, or something else. + */ + class BlobReference { + /** + * Don't use directly. Use `BlobReference.fromUint8Array`. + * @param {string} id + * @param {number} size + */ + constructor(id, size) { + this._id = id; + this.size = size; + registry.register(this, id); + } + + /** + * Create a new blob part from a Uint8Array. + * + * @param {Uint8Array} data + * @returns {BlobReference} + */ + static fromUint8Array(data) { + const id = core.opSync("op_blob_create_part", data); + return new BlobReference(id, data.byteLength); + } + + /** + * Create a new BlobReference by slicing this BlobReference. This is a copy + * free operation - the sliced reference will still reference the original + * underlying bytes. + * + * @param {number} start + * @param {number} end + * @returns {BlobReference} + */ + slice(start, end) { + const size = end - start; + const id = core.opSync("op_blob_slice_part", this._id, { + start, + len: size, + }); + return new BlobReference(id, size); + } + + /** + * Read the entire contents of the reference blob. + * @returns {AsyncGenerator} + */ + async *stream() { + yield core.opAsync("op_blob_read_part", this._id); + + // let position = 0; + // const end = this.size; + // while (position !== end) { + // const size = MathMin(end - position, 65536); + // const chunk = this.slice(position, position + size); + // position += chunk.size; + // yield core.opAsync("op_blob_read_part", chunk._id); + // } + } + } + + window.__bootstrap.file = { + getParts, + Blob, + File, + }; +})(this); diff --git a/ext/web/10_filereader.js b/ext/web/10_filereader.js new file mode 100644 index 000000000..13fe6af2d --- /dev/null +++ b/ext/web/10_filereader.js @@ -0,0 +1,461 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// +/// + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { forgivingBase64Encode } = window.__bootstrap.infra; + const { decode, TextDecoder } = window.__bootstrap.encoding; + const { parseMimeType } = window.__bootstrap.mimesniff; + const { DOMException } = window.__bootstrap.domException; + const { + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeReduce, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectDefineProperty, + queueMicrotask, + StringFromCodePoint, + Symbol, + SymbolToStringTag, + TypedArrayPrototypeSet, + TypeError, + Uint8Array, + } = window.__bootstrap.primordials; + + const state = Symbol("[[state]]"); + const result = Symbol("[[result]]"); + const error = Symbol("[[error]]"); + const aborted = Symbol("[[aborted]]"); + + class FileReader extends EventTarget { + get [SymbolToStringTag]() { + return "FileReader"; + } + + /** @type {"empty" | "loading" | "done"} */ + [state] = "empty"; + /** @type {null | string | ArrayBuffer} */ + [result] = null; + /** @type {null | DOMException} */ + [error] = null; + /** @type {null | {aborted: boolean}} */ + [aborted] = null; + + /** + * @param {Blob} blob + * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype + */ + #readOperation(blob, readtype) { + // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. + if (this[state] === "loading") { + throw new DOMException( + "Invalid FileReader state.", + "InvalidStateError", + ); + } + // 2. Set fr’s state to "loading". + this[state] = "loading"; + // 3. Set fr’s result to null. + this[result] = null; + // 4. Set fr’s error to null. + this[error] = null; + + // We set this[aborted] to a new object, and keep track of it in a + // separate variable, so if a new read operation starts while there are + // remaining tasks from a previous aborted operation, the new operation + // will run while the tasks from the previous one are still aborted. + const abortedState = this[aborted] = { aborted: false }; + + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream*/ = blob.stream(); + + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader(); + + // 7. Let bytes be an empty byte sequence. + /** @type {Uint8Array[]} */ + const chunks = []; + + // 8. Let chunkPromise be the result of reading a chunk from stream with reader. + let chunkPromise = reader.read(); + + // 9. Let isFirstChunk be true. + let isFirstChunk = true; + + // 10 in parallel while true + (async () => { + while (!abortedState.aborted) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const chunk = await chunkPromise; + if (abortedState.aborted) return; + + // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. + if (isFirstChunk) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + // fire a progress event for loadstart + const ev = new ProgressEvent("loadstart", {}); + this.dispatchEvent(ev); + }); + } + // 3. Set isFirstChunk to false. + isFirstChunk = false; + + // 4. If chunkPromise is fulfilled with an object whose done property is false + // and whose value property is a Uint8Array object, run these steps: + if (!chunk.done && chunk.value instanceof Uint8Array) { + ArrayPrototypePush(chunks, chunk.value); + + // TODO(bartlomieju): (only) If roughly 50ms have passed since last progress + { + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const ev = new ProgressEvent("progress", { + loaded: size, + }); + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + this.dispatchEvent(ev); + }); + } + + chunkPromise = reader.read(); + } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: + else if (chunk.done === true) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + // 1. Set fr’s state to "done". + this[state] = "done"; + // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const bytes = new Uint8Array(size); + let offs = 0; + for (const chunk of chunks) { + TypedArrayPrototypeSet(bytes, chunk, offs); + offs += chunk.byteLength; + } + switch (readtype.kind) { + case "ArrayBuffer": { + this[result] = bytes.buffer; + break; + } + case "BinaryString": + this[result] = ArrayPrototypeJoin( + ArrayPrototypeMap( + [...new Uint8Array(bytes.buffer)], + (v) => StringFromCodePoint(v), + ), + "", + ); + break; + case "Text": { + let decoder = undefined; + if (readtype.encoding) { + try { + decoder = new TextDecoder(readtype.encoding); + } catch { + // don't care about the error + } + } + if (decoder === undefined) { + const mimeType = parseMimeType(blob.type); + if (mimeType) { + const charset = MapPrototypeGet( + mimeType.parameters, + "charset", + ); + if (charset) { + try { + decoder = new TextDecoder(charset); + } catch { + // don't care about the error + } + } + } + } + if (decoder === undefined) { + decoder = new TextDecoder(); + } + this[result] = decode(bytes, decoder.encoding); + break; + } + case "DataUrl": { + const mediaType = blob.type || "application/octet-stream"; + this[result] = `data:${mediaType};base64,${ + forgivingBase64Encode(bytes) + }`; + break; + } + } + // 4.2 Fire a progress event called load at the fr. + { + const ev = new ProgressEvent("load", { + lengthComputable: true, + loaded: size, + total: size, + }); + this.dispatchEvent(ev); + } + + // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. + //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", { + lengthComputable: true, + loaded: size, + total: size, + }); + this.dispatchEvent(ev); + } + }); + break; + } + } catch (err) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + + // chunkPromise rejected + this[state] = "done"; + this[error] = err; + + { + const ev = new ProgressEvent("error", {}); + this.dispatchEvent(ev); + } + + //If fr’s state is not "loading", fire a progress event called loadend at fr. + //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); + } + }); + break; + } + } + })(); + } + + constructor() { + super(); + this[webidl.brand] = webidl.brand; + } + + /** @returns {number} */ + get readyState() { + webidl.assertBranded(this, FileReader); + switch (this[state]) { + case "empty": + return FileReader.EMPTY; + case "loading": + return FileReader.LOADING; + case "done": + return FileReader.DONE; + default: + throw new TypeError("Invalid state"); + } + } + + get result() { + webidl.assertBranded(this, FileReader); + return this[result]; + } + + get error() { + webidl.assertBranded(this, FileReader); + return this[error]; + } + + abort() { + webidl.assertBranded(this, FileReader); + // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. + if ( + this[state] === "empty" || + this[state] === "done" + ) { + this[result] = null; + return; + } + // If context object's state is "loading" set context object's state to "done" and set context object's result to null. + if (this[state] === "loading") { + this[state] = "done"; + this[result] = null; + } + // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. + // Terminate the algorithm for the read method being processed. + if (this[aborted] !== null) { + this[aborted].aborted = true; + } + + // Fire a progress event called abort at the context object. + const ev = new ProgressEvent("abort", {}); + this.dispatchEvent(ev); + + // If context object's state is not "loading", fire a progress event called loadend at the context object. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); + } + } + + /** @param {Blob} blob */ + readAsArrayBuffer(blob) { + webidl.assertBranded(this, FileReader); + const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + this.#readOperation(blob, { kind: "ArrayBuffer" }); + } + + /** @param {Blob} blob */ + readAsBinaryString(blob) { + webidl.assertBranded(this, FileReader); + const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "BinaryString" }); + } + + /** @param {Blob} blob */ + readAsDataURL(blob) { + webidl.assertBranded(this, FileReader); + const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "DataUrl" }); + } + + /** + * @param {Blob} blob + * @param {string} [encoding] + */ + readAsText(blob, encoding) { + webidl.assertBranded(this, FileReader); + const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + if (encoding !== undefined) { + encoding = webidl.converters["DOMString"](encoding, { + prefix, + context: "Argument 2", + }); + } + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "Text", encoding }); + } + } + + webidl.configurePrototype(FileReader); + + ObjectDefineProperty(FileReader, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, + }); + ObjectDefineProperty(FileReader, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, + }); + ObjectDefineProperty(FileReader, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, + }); + ObjectDefineProperty(FileReader.prototype, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, + }); + ObjectDefineProperty(FileReader.prototype, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, + }); + ObjectDefineProperty(FileReader.prototype, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, + }); + + const handlerSymbol = Symbol("eventHandlers"); + + function makeWrappedHandler(handler) { + function wrappedHandler(...args) { + if (typeof wrappedHandler.handler !== "function") { + return; + } + return FunctionPrototypeCall(wrappedHandler.handler, this, ...args); + } + wrappedHandler.handler = handler; + return wrappedHandler; + } + // TODO(benjamingr) reuse when we can reuse code between web crates + function defineEventHandler(emitter, name) { + // HTML specification section 8.1.5.1 + ObjectDefineProperty(emitter, `on${name}`, { + get() { + const maybeMap = this[handlerSymbol]; + if (!maybeMap) return null; + + return MapPrototypeGet(maybeMap, name)?.handler ?? null; + }, + set(value) { + if (!this[handlerSymbol]) { + this[handlerSymbol] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else { + handlerWrapper = makeWrappedHandler(value); + this.addEventListener(name, handlerWrapper); + } + MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); + }, + configurable: true, + enumerable: true, + }); + } + defineEventHandler(FileReader.prototype, "error"); + defineEventHandler(FileReader.prototype, "loadstart"); + defineEventHandler(FileReader.prototype, "load"); + defineEventHandler(FileReader.prototype, "loadend"); + defineEventHandler(FileReader.prototype, "progress"); + defineEventHandler(FileReader.prototype, "abort"); + + window.__bootstrap.fileReader = { + FileReader, + }; +})(this); diff --git a/ext/web/11_blob_url.js b/ext/web/11_blob_url.js new file mode 100644 index 000000000..fa0ea041c --- /dev/null +++ b/ext/web/11_blob_url.js @@ -0,0 +1,59 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// +/// +/// +"use strict"; + +((window) => { + const core = Deno.core; + const webidl = window.__bootstrap.webidl; + const { getParts } = window.__bootstrap.file; + const { URL } = window.__bootstrap.url; + + /** + * @param {Blob} blob + * @returns {string} + */ + function createObjectURL(blob) { + const prefix = "Failed to execute 'createObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + blob = webidl.converters["Blob"](blob, { + context: "Argument 1", + prefix, + }); + + const url = core.opSync( + "op_blob_create_object_url", + blob.type, + getParts(blob), + ); + + return url; + } + + /** + * @param {string} url + * @returns {void} + */ + function revokeObjectURL(url) { + const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters["DOMString"](url, { + context: "Argument 1", + prefix, + }); + + core.opSync("op_blob_revoke_object_url", url); + } + + URL.createObjectURL = createObjectURL; + URL.revokeObjectURL = revokeObjectURL; +})(globalThis); diff --git a/ext/web/12_location.js b/ext/web/12_location.js new file mode 100644 index 000000000..40dd545fe --- /dev/null +++ b/ext/web/12_location.js @@ -0,0 +1,409 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +/// + +((window) => { + const { URL } = window.__bootstrap.url; + const { DOMException } = window.__bootstrap.domException; + const { + Error, + ObjectDefineProperties, + ReferenceError, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, + } = window.__bootstrap.primordials; + + const locationConstructorKey = Symbol("locationConstuctorKey"); + + // The differences between the definitions of `Location` and `WorkerLocation` + // are because of the `LegacyUnforgeable` attribute only specified upon + // `Location`'s properties. See: + // - https://html.spec.whatwg.org/multipage/history.html#the-location-interface + // - https://heycam.github.io/webidl/#LegacyUnforgeable + class Location { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + url.username = ""; + url.password = ""; + ObjectDefineProperties(this, { + hash: { + get() { + return url.hash; + }, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + host: { + get() { + return url.host; + }, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + hostname: { + get() { + return url.hostname; + }, + set() { + throw new DOMException( + `Cannot set "location.hostname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + href: { + get() { + return url.href; + }, + set() { + throw new DOMException( + `Cannot set "location.href".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + origin: { + get() { + return url.origin; + }, + enumerable: true, + }, + pathname: { + get() { + return url.pathname; + }, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + port: { + get() { + return url.port; + }, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + protocol: { + get() { + return url.protocol; + }, + set() { + throw new DOMException( + `Cannot set "location.protocol".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + search: { + get() { + return url.search; + }, + set() { + throw new DOMException( + `Cannot set "location.search".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + ancestorOrigins: { + get() { + // TODO(nayeemrmn): Replace with a `DOMStringList` instance. + return { + length: 0, + item: () => null, + contains: () => false, + }; + }, + enumerable: true, + }, + assign: { + value: function assign() { + throw new DOMException( + `Cannot call "location.assign()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + reload: { + value: function reload() { + throw new DOMException( + `Cannot call "location.reload()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + replace: { + value: function replace() { + throw new DOMException( + `Cannot call "location.replace()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + toString: { + value: function toString() { + return url.href; + }, + enumerable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + }, + }, + }); + } + } + + ObjectDefineProperties(Location.prototype, { + [SymbolToStringTag]: { + value: "Location", + configurable: true, + }, + }); + + const workerLocationUrls = new WeakMap(); + + class WorkerLocation { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + url.username = ""; + url.password = ""; + WeakMapPrototypeSet(workerLocationUrls, this, url); + } + } + + ObjectDefineProperties(WorkerLocation.prototype, { + hash: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hash; + }, + configurable: true, + enumerable: true, + }, + host: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.host; + }, + configurable: true, + enumerable: true, + }, + hostname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hostname; + }, + configurable: true, + enumerable: true, + }, + href: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + }, + origin: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.origin; + }, + configurable: true, + enumerable: true, + }, + pathname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.pathname; + }, + configurable: true, + enumerable: true, + }, + port: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.port; + }, + configurable: true, + enumerable: true, + }, + protocol: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.protocol; + }, + configurable: true, + enumerable: true, + }, + search: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.search; + }, + configurable: true, + enumerable: true, + }, + toString: { + value: function toString() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; + }, + configurable: true, + enumerable: true, + writable: true, + }, + [SymbolToStringTag]: { + value: "WorkerLocation", + configurable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + }, + }, + }); + + let location = null; + let workerLocation = null; + + function setLocationHref(href) { + location = new Location(href, locationConstructorKey); + workerLocation = new WorkerLocation(href, locationConstructorKey); + } + + window.__bootstrap.location = { + locationConstructorDescriptor: { + value: Location, + configurable: true, + writable: true, + }, + workerLocationConstructorDescriptor: { + value: WorkerLocation, + configurable: true, + writable: true, + }, + locationDescriptor: { + get() { + if (location == null) { + throw new ReferenceError( + `Access to "location", run again with --location .`, + ); + } + return location; + }, + set() { + throw new DOMException(`Cannot set "location".`, "NotSupportedError"); + }, + enumerable: true, + }, + workerLocationDescriptor: { + get() { + if (workerLocation == null) { + throw new Error( + `Assertion: "globalThis.location" must be defined in a worker.`, + ); + } + return workerLocation; + }, + configurable: true, + enumerable: true, + }, + setLocationHref, + getLocationHref() { + return location?.href; + }, + }; +})(this); diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js new file mode 100644 index 000000000..d5014fdb9 --- /dev/null +++ b/ext/web/13_message_port.js @@ -0,0 +1,286 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + const { setEventTargetData } = window.__bootstrap.eventTarget; + const { defineEventHandler } = window.__bootstrap.event; + const { DOMException } = window.__bootstrap.domException; + const { + ObjectSetPrototypeOf, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, + } = window.__bootstrap.primordials; + + class MessageChannel { + /** @type {MessagePort} */ + #port1; + /** @type {MessagePort} */ + #port2; + + constructor() { + this[webidl.brand] = webidl.brand; + const [port1Id, port2Id] = opCreateEntangledMessagePort(); + const port1 = createMessagePort(port1Id); + const port2 = createMessagePort(port2Id); + this.#port1 = port1; + this.#port2 = port2; + } + + get port1() { + webidl.assertBranded(this, MessageChannel); + return this.#port1; + } + + get port2() { + webidl.assertBranded(this, MessageChannel); + return this.#port2; + } + + [SymbolFor("Deno.inspect")](inspect) { + return `MessageChannel ${ + inspect({ port1: this.port1, port2: this.port2 }) + }`; + } + + get [SymbolToStringTag]() { + return "MessageChannel"; + } + } + + webidl.configurePrototype(MessageChannel); + + const _id = Symbol("id"); + const _enabled = Symbol("enabled"); + + /** + * @param {number} id + * @returns {MessagePort} + */ + function createMessagePort(id) { + const port = core.createHostObject(); + ObjectSetPrototypeOf(port, MessagePort.prototype); + port[webidl.brand] = webidl.brand; + setEventTargetData(port); + port[_id] = id; + return port; + } + + class MessagePort extends EventTarget { + /** @type {number | null} */ + [_id] = null; + /** @type {boolean} */ + [_enabled] = false; + + constructor() { + super(); + webidl.illegalConstructor(); + } + + /** + * @param {any} message + * @param {object[] | StructuredSerializeOptions} transferOrOptions + */ + postMessage(message, transferOrOptions = {}) { + webidl.assertBranded(this, MessagePort); + const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[Symbol.iterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); + } + const { transfer } = options; + if (transfer.includes(this)) { + throw new DOMException("Can not tranfer self", "DataCloneError"); + } + const data = serializeJsMessageData(message, transfer); + if (this[_id] === null) return; + core.opSync("op_message_port_post_message", this[_id], data); + } + + start() { + webidl.assertBranded(this, MessagePort); + if (this[_enabled]) return; + (async () => { + this[_enabled] = true; + while (true) { + if (this[_id] === null) break; + const data = await core.opAsync( + "op_message_port_recv_message", + this[_id], + ); + if (data === null) break; + let message, transfer; + try { + const v = deserializeJsMessageData(data); + message = v[0]; + transfer = v[1]; + } catch (err) { + const event = new MessageEvent("messageerror", { data: err }); + this.dispatchEvent(event); + return; + } + const event = new MessageEvent("message", { + data: message, + ports: transfer, + }); + this.dispatchEvent(event); + } + this[_enabled] = false; + })(); + } + + close() { + webidl.assertBranded(this, MessagePort); + if (this[_id] !== null) { + core.close(this[_id]); + this[_id] = null; + } + } + + get [SymbolToStringTag]() { + return "MessagePort"; + } + } + + defineEventHandler(MessagePort.prototype, "message", function (self) { + self.start(); + }); + defineEventHandler(MessagePort.prototype, "messageerror"); + + webidl.configurePrototype(MessagePort); + + /** + * @returns {[number, number]} + */ + function opCreateEntangledMessagePort() { + return core.opSync("op_message_port_create_entangled"); + } + + /** + * @param {globalThis.__bootstrap.messagePort.MessageData} messageData + * @returns {[any, object[]]} + */ + function deserializeJsMessageData(messageData) { + /** @type {object[]} */ + const transferables = []; + + for (const transferable of messageData.transferables) { + switch (transferable.kind) { + case "messagePort": { + const port = createMessagePort(transferable.data); + transferables.push(port); + break; + } + default: + throw new TypeError("Unreachable"); + } + } + + const data = core.deserialize(messageData.data, { + hostObjects: transferables, + }); + + return [data, transferables]; + } + + /** + * @param {any} data + * @param {object[]} tranferables + * @returns {globalThis.__bootstrap.messagePort.MessageData} + */ + function serializeJsMessageData(data, tranferables) { + let serializedData; + try { + serializedData = core.serialize(data, { hostObjects: tranferables }); + } catch (err) { + throw new DOMException(err.message, "DataCloneError"); + } + + /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */ + const serializedTransferables = []; + + for (const transferable of tranferables) { + if (transferable instanceof MessagePort) { + webidl.assertBranded(transferable, MessagePort); + const id = transferable[_id]; + if (id === null) { + throw new DOMException( + "Can not transfer disentangled message port", + "DataCloneError", + ); + } + transferable[_id] = null; + serializedTransferables.push({ kind: "messagePort", data: id }); + } else { + throw new DOMException("Value not transferable", "DataCloneError"); + } + } + + return { + data: serializedData, + transferables: serializedTransferables, + }; + } + + webidl.converters.StructuredSerializeOptions = webidl + .createDictionaryConverter( + "StructuredSerializeOptions", + [ + { + key: "transfer", + converter: webidl.converters["sequence"], + get defaultValue() { + return []; + }, + }, + ], + ); + + function structuredClone(value, options) { + const prefix = "Failed to execute 'structuredClone'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + options = webidl.converters.StructuredSerializeOptions(options, { + prefix, + context: "Argument 2", + }); + const messageData = serializeJsMessageData(value, options.transfer); + const [data] = deserializeJsMessageData(messageData); + return data; + } + + window.__bootstrap.messagePort = { + MessageChannel, + MessagePort, + deserializeJsMessageData, + serializeJsMessageData, + structuredClone, + }; +})(globalThis); diff --git a/ext/web/Cargo.toml b/ext/web/Cargo.toml new file mode 100644 index 000000000..6160ef89f --- /dev/null +++ b/ext/web/Cargo.toml @@ -0,0 +1,26 @@ +# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_web" +version = "0.45.0" +authors = ["the Deno authors"] +edition = "2018" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "Collection of Web APIs" + +[lib] +path = "lib.rs" + +[dependencies] +async-trait = "0.1.50" +base64 = "0.13.0" +deno_core = { version = "0.96.0", path = "../../core" } +encoding_rs = "0.8.28" +serde = "1.0" +tokio = { version = "1.8.1", features = ["full"] } +uuid = { version = "0.8.2", features = ["v4", "serde"] } + +[dev-dependencies] +futures = "0.3.15" diff --git a/ext/web/README.md b/ext/web/README.md new file mode 100644 index 000000000..d847ae52e --- /dev/null +++ b/ext/web/README.md @@ -0,0 +1,6 @@ +# deno web + +Op crate that implements Event, TextEncoder, TextDecoder and File API +(https://w3c.github.io/FileAPI). + +Testing for text encoding is done via WPT in cli/. diff --git a/ext/web/blob.rs b/ext/web/blob.rs new file mode 100644 index 000000000..0f27553c7 --- /dev/null +++ b/ext/web/blob.rs @@ -0,0 +1,265 @@ +use async_trait::async_trait; +use deno_core::error::type_error; +use deno_core::parking_lot::Mutex; +use deno_core::url::Url; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::Debug; +use std::rc::Rc; +use std::sync::Arc; + +use deno_core::error::AnyError; +use uuid::Uuid; + +use crate::Location; + +pub type PartMap = HashMap>>; + +#[derive(Clone, Default, Debug)] +pub struct BlobStore { + parts: Arc>, + object_urls: Arc>>>, +} + +impl BlobStore { + pub fn insert_part(&self, part: Box) -> Uuid { + let id = Uuid::new_v4(); + let mut parts = self.parts.lock(); + parts.insert(id, Arc::new(part)); + id + } + + pub fn get_part( + &self, + id: &Uuid, + ) -> Option>> { + let parts = self.parts.lock(); + let part = parts.get(id); + part.cloned() + } + + pub fn remove_part( + &self, + id: &Uuid, + ) -> Option>> { + let mut parts = self.parts.lock(); + parts.remove(id) + } + + pub fn get_object_url( + &self, + mut url: Url, + ) -> Result>, AnyError> { + let blob_store = self.object_urls.lock(); + url.set_fragment(None); + Ok(blob_store.get(&url).cloned()) + } + + pub fn insert_object_url( + &self, + blob: Blob, + maybe_location: Option, + ) -> Url { + let origin = if let Some(location) = maybe_location { + location.origin().ascii_serialization() + } else { + "null".to_string() + }; + let id = Uuid::new_v4(); + let url = Url::parse(&format!("blob:{}/{}", origin, id)).unwrap(); + + let mut blob_store = self.object_urls.lock(); + blob_store.insert(url.clone(), Arc::new(blob)); + + url + } + + pub fn remove_object_url(&self, url: &Url) { + let mut blob_store = self.object_urls.lock(); + blob_store.remove(url); + } +} + +#[derive(Debug)] +pub struct Blob { + pub media_type: String, + + pub parts: Vec>>, +} + +impl Blob { + // TODO(lucacsonato): this should be a stream! + pub async fn read_all(&self) -> Result, AnyError> { + let size = self.size(); + let mut bytes = Vec::with_capacity(size); + + for part in &self.parts { + let chunk = part.read().await?; + bytes.extend_from_slice(chunk); + } + + assert_eq!(bytes.len(), size); + + Ok(bytes) + } + + fn size(&self) -> usize { + let mut total = 0; + for part in &self.parts { + total += part.size() + } + total + } +} + +#[async_trait] +pub trait BlobPart: Debug { + // TODO(lucacsonato): this should be a stream! + async fn read(&self) -> Result<&[u8], AnyError>; + fn size(&self) -> usize; +} + +#[derive(Debug)] +pub struct InMemoryBlobPart(Vec); + +impl From> for InMemoryBlobPart { + fn from(vec: Vec) -> Self { + Self(vec) + } +} + +#[async_trait] +impl BlobPart for InMemoryBlobPart { + async fn read(&self) -> Result<&[u8], AnyError> { + Ok(&self.0) + } + + fn size(&self) -> usize { + self.0.len() + } +} + +#[derive(Debug)] +pub struct SlicedBlobPart { + part: Arc>, + start: usize, + len: usize, +} + +#[async_trait] +impl BlobPart for SlicedBlobPart { + async fn read(&self) -> Result<&[u8], AnyError> { + let original = self.part.read().await?; + Ok(&original[self.start..self.start + self.len]) + } + + fn size(&self) -> usize { + self.len + } +} + +pub fn op_blob_create_part( + state: &mut deno_core::OpState, + data: ZeroCopyBuf, + _: (), +) -> Result { + let blob_store = state.borrow::(); + let part = InMemoryBlobPart(data.to_vec()); + let id = blob_store.insert_part(Box::new(part)); + Ok(id) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SliceOptions { + start: usize, + len: usize, +} + +pub fn op_blob_slice_part( + state: &mut deno_core::OpState, + id: Uuid, + options: SliceOptions, +) -> Result { + let blob_store = state.borrow::(); + let part = blob_store + .get_part(&id) + .ok_or_else(|| type_error("Blob part not found"))?; + + let SliceOptions { start, len } = options; + + let size = part.size(); + if start + len > size { + return Err(type_error( + "start + len can not be larger than blob part size", + )); + } + + let sliced_part = SlicedBlobPart { part, start, len }; + let id = blob_store.insert_part(Box::new(sliced_part)); + + Ok(id) +} + +pub async fn op_blob_read_part( + state: Rc>, + id: Uuid, + _: (), +) -> Result { + let part = { + let state = state.borrow(); + let blob_store = state.borrow::(); + blob_store.get_part(&id) + } + .ok_or_else(|| type_error("Blob part not found"))?; + let buf = part.read().await?; + Ok(ZeroCopyBuf::from(buf.to_vec())) +} + +pub fn op_blob_remove_part( + state: &mut deno_core::OpState, + id: Uuid, + _: (), +) -> Result<(), AnyError> { + let blob_store = state.borrow::(); + blob_store.remove_part(&id); + Ok(()) +} + +pub fn op_blob_create_object_url( + state: &mut deno_core::OpState, + media_type: String, + part_ids: Vec, +) -> Result { + let mut parts = Vec::with_capacity(part_ids.len()); + let blob_store = state.borrow::(); + for part_id in part_ids { + let part = blob_store + .get_part(&part_id) + .ok_or_else(|| type_error("Blob part not found"))?; + parts.push(part); + } + + let blob = Blob { media_type, parts }; + + let maybe_location = state.try_borrow::(); + let blob_store = state.borrow::(); + + let url = blob_store + .insert_object_url(blob, maybe_location.map(|location| location.0.clone())); + + Ok(url.to_string()) +} + +pub fn op_blob_revoke_object_url( + state: &mut deno_core::OpState, + url: String, + _: (), +) -> Result<(), AnyError> { + let url = Url::parse(&url)?; + let blob_store = state.borrow::(); + blob_store.remove_object_url(&url); + Ok(()) +} diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts new file mode 100644 index 000000000..3a2a0c1be --- /dev/null +++ b/ext/web/internal.d.ts @@ -0,0 +1,98 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +/// +/// + +declare namespace globalThis { + declare namespace __bootstrap { + declare var infra: { + collectSequenceOfCodepoints( + input: string, + position: number, + condition: (char: string) => boolean, + ): { + result: string; + position: number; + }; + ASCII_DIGIT: string[]; + ASCII_UPPER_ALPHA: string[]; + ASCII_LOWER_ALPHA: string[]; + ASCII_ALPHA: string[]; + ASCII_ALPHANUMERIC: string[]; + HTTP_TAB_OR_SPACE: string[]; + HTTP_WHITESPACE: string[]; + HTTP_TOKEN_CODE_POINT: string[]; + HTTP_TOKEN_CODE_POINT_RE: RegExp; + HTTP_QUOTED_STRING_TOKEN_POINT: string[]; + HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; + HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; + HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; + HTTP_WHITESPACE_PREFIX_RE: RegExp; + HTTP_WHITESPACE_SUFFIX_RE: RegExp; + regexMatcher(chars: string[]): string; + byteUpperCase(s: string): string; + byteLowerCase(s: string): string; + collectHttpQuotedString( + input: string, + position: number, + extractValue: boolean, + ): { + result: string; + position: number; + }; + forgivingBase64Encode(data: Uint8Array): string; + forgivingBase64Decode(data: string): Uint8Array; + }; + + declare var domException: { + DOMException: typeof DOMException; + }; + + declare namespace mimesniff { + declare interface MimeType { + type: string; + subtype: string; + parameters: Map; + } + declare function parseMimeType(input: string): MimeType | null; + declare function essence(mimeType: MimeType): string; + declare function serializeMimeType(mimeType: MimeType): string; + } + + declare var eventTarget: { + EventTarget: typeof EventTarget; + }; + + declare var location: { + getLocationHref(): string | undefined; + }; + + declare var base64: { + atob(data: string): string; + btoa(data: string): string; + }; + + declare var file: { + getParts(blob: Blob): string[]; + Blob: typeof Blob; + File: typeof File; + }; + + declare var streams: { + ReadableStream: typeof ReadableStream; + isReadableStreamDisturbed(stream: ReadableStream): boolean; + createProxy(stream: ReadableStream): ReadableStream; + }; + + declare namespace messagePort { + declare type Transferable = { + kind: "messagePort"; + data: number; + }; + declare interface MessageData { + data: Uint8Array; + transferables: Transferable[]; + } + } + } +} diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts new file mode 100644 index 000000000..3f110353f --- /dev/null +++ b/ext/web/lib.deno_web.d.ts @@ -0,0 +1,752 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any + +/// +/// + +declare class DOMException extends Error { + constructor(message?: string, name?: string); + readonly name: string; + readonly message: string; + readonly code: number; +} + +interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} + +/** An event which takes place in the DOM. */ +declare class Event { + constructor(type: string, eventInitDict?: EventInit); + /** Returns true or false depending on how event was initialized. True if + * event goes through its target's ancestors in reverse tree order, and + * false otherwise. */ + readonly bubbles: boolean; + cancelBubble: boolean; + /** Returns true or false depending on how event was initialized. Its return + * value does not always carry meaning, but true can indicate that part of the + * operation during which event was dispatched, can be canceled by invoking + * the preventDefault() method. */ + readonly cancelable: boolean; + /** Returns true or false depending on how event was initialized. True if + * event invokes listeners past a ShadowRoot node that is the root of its + * target, and false otherwise. */ + readonly composed: boolean; + /** Returns the object whose event listener's callback is currently being + * invoked. */ + readonly currentTarget: EventTarget | null; + /** Returns true if preventDefault() was invoked successfully to indicate + * cancellation, and false otherwise. */ + readonly defaultPrevented: boolean; + /** Returns the event's phase, which is one of NONE, CAPTURING_PHASE, + * AT_TARGET, and BUBBLING_PHASE. */ + readonly eventPhase: number; + /** Returns true if event was dispatched by the user agent, and false + * otherwise. */ + readonly isTrusted: boolean; + /** Returns the object to which event is dispatched (its target). */ + readonly target: EventTarget | null; + /** Returns the event's timestamp as the number of milliseconds measured + * relative to the time origin. */ + readonly timeStamp: number; + /** Returns the type of event, e.g. "click", "hashchange", or "submit". */ + readonly type: string; + /** Returns the invocation target objects of event's path (objects on which + * listeners will be invoked), except for any nodes in shadow trees of which + * the shadow root's mode is "closed" that are not reachable from event's + * currentTarget. */ + composedPath(): EventTarget[]; + /** If invoked when the cancelable attribute value is true, and while + * executing a listener for the event with passive set to false, signals to + * the operation that caused event to be dispatched that it needs to be + * canceled. */ + preventDefault(): void; + /** Invoking this method prevents event from reaching any registered event + * listeners after the current one finishes running and, when dispatched in a + * tree, also prevents event from reaching any other objects. */ + stopImmediatePropagation(): void; + /** When dispatched in a tree, invoking this method prevents event from + * reaching any objects other than the current object. */ + stopPropagation(): void; + readonly AT_TARGET: number; + readonly BUBBLING_PHASE: number; + readonly CAPTURING_PHASE: number; + readonly NONE: number; + static readonly AT_TARGET: number; + static readonly BUBBLING_PHASE: number; + static readonly CAPTURING_PHASE: number; + static readonly NONE: number; +} + +/** + * EventTarget is a DOM interface implemented by objects that can receive events + * and may have listeners for them. + */ +declare class EventTarget { + /** Appends an event listener for events whose type attribute value is type. + * The callback argument sets the callback that will be invoked when the event + * is dispatched. + * + * The options argument sets listener-specific options. For compatibility this + * can be a boolean, in which case the method behaves exactly as if the value + * was specified as options's capture. + * + * When set to true, options's capture prevents callback from being invoked + * when the event's eventPhase attribute value is BUBBLING_PHASE. When false + * (or not present), callback will not be invoked when event's eventPhase + * attribute value is CAPTURING_PHASE. Either way, callback will be invoked if + * event's eventPhase attribute value is AT_TARGET. + * + * When set to true, options's passive indicates that the callback will not + * cancel the event by invoking preventDefault(). This is used to enable + * performance optimizations described in § 2.8 Observing event listeners. + * + * When set to true, options's once indicates that the callback will only be + * invoked once after which the event listener will be removed. + * + * The event listener is appended to target's event listener list and is not + * appended if it has the same type, callback, and capture. */ + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions, + ): void; + /** Dispatches a synthetic event event to target and returns true if either + * event's cancelable attribute value is false or its preventDefault() method + * was not invoked, and false otherwise. */ + dispatchEvent(event: Event): boolean; + /** Removes the event listener in target's event listener list with the same + * type, callback, and options. */ + removeEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean, + ): void; +} + +interface EventListener { + (evt: Event): void | Promise; +} + +interface EventListenerObject { + handleEvent(evt: Event): void | Promise; +} + +declare type EventListenerOrEventListenerObject = + | EventListener + | EventListenerObject; + +interface AddEventListenerOptions extends EventListenerOptions { + once?: boolean; + passive?: boolean; +} + +interface EventListenerOptions { + capture?: boolean; +} + +interface ProgressEventInit extends EventInit { + lengthComputable?: boolean; + loaded?: number; + total?: number; +} + +/** Events measuring progress of an underlying process, like an HTTP request + * (for an XMLHttpRequest, or the loading of the underlying resource of an + * ,