diff options
author | Andreu Botella <abb@randomunok.com> | 2021-06-06 03:23:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-06 03:23:16 +0200 |
commit | 62bf4031576ab833a8057c6b7037e6476d13bf65 (patch) | |
tree | 14f8b37fa022544634f8b6bbb125d5c535a2833e /extensions | |
parent | eb3a20292f01fb621bd0027a9fb2827131e34de8 (diff) |
feat(web): Implement TextDecoderStream and TextEncoderStream (#10842)
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/web/08_text_encoding.js | 194 | ||||
-rw-r--r-- | extensions/web/lib.deno_web.d.ts | 25 |
2 files changed, 217 insertions, 2 deletions
diff --git a/extensions/web/08_text_encoding.js b/extensions/web/08_text_encoding.js index 7b2c97497..be66e4981 100644 --- a/extensions/web/08_text_encoding.js +++ b/extensions/web/08_text_encoding.js @@ -3,6 +3,7 @@ // @ts-check /// <reference path="../../core/lib.deno_core.d.ts" /> /// <reference path="../webidl/internal.d.ts" /> +/// <reference path="../fetch/lib.deno_fetch.d.ts" /> /// <reference path="../web/internal.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference lib="esnext" /> @@ -203,6 +204,197 @@ configurable: true, }); + class TextDecoderStream { + /** @type {TextDecoder} */ + #decoder; + /** @type {TransformStream<BufferSource, string>} */ + #transform; + + /** + * + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoderStream'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + this.#decoder = new TextDecoder(label, options); + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextDecoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.BufferSource(chunk, { + allowShared: true, + }); + const decoded = this.#decoder.decode(chunk, { stream: true }); + if (decoded) { + controller.enqueue(decoded); + } + return Promise.resolve(); + } catch (err) { + return Promise.reject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); + } + return Promise.resolve(); + } catch (err) { + return Promise.reject(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<string>} */ + get readable() { + webidl.assertBranded(this, TextDecoderStream); + return this.#transform.readable; + } + + /** @returns {WritableStream<BufferSource>} */ + get writable() { + webidl.assertBranded(this, TextDecoderStream); + return this.#transform.writable; + } + + get [Symbol.toStringTag]() { + return "TextDecoderStream"; + } + } + + Object.defineProperty(TextDecoderStream.prototype, "encoding", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextDecoderStream.prototype, "fatal", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextDecoderStream.prototype, "ignoreBOM", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextDecoderStream.prototype, "readable", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextDecoderStream.prototype, "writable", { + enumerable: true, + configurable: true, + }); + + class TextEncoderStream { + /** @type {string | null} */ + #pendingHighSurrogate = null; + /** @type {TransformStream<string, Uint8Array>} */ + #transform; + + constructor() { + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextEncoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.DOMString(chunk); + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; + } + const lastCodeUnit = chunk.charCodeAt(chunk.length - 1); + if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { + this.#pendingHighSurrogate = chunk.slice(-1); + chunk = chunk.slice(0, -1); + } else { + this.#pendingHighSurrogate = null; + } + if (chunk) { + controller.enqueue(core.encode(chunk)); + } + return Promise.resolve(); + } catch (err) { + return Promise.reject(err); + } + }, + flush: (controller) => { + try { + if (this.#pendingHighSurrogate !== null) { + controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); + } + return Promise.resolve(); + } catch (err) { + return Promise.reject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderStream); + return "utf-8"; + } + + /** @returns {ReadableStream<Uint8Array>} */ + get readable() { + webidl.assertBranded(this, TextEncoderStream); + return this.#transform.readable; + } + + /** @returns {WritableStream<string>} */ + get writable() { + webidl.assertBranded(this, TextEncoderStream); + return this.#transform.writable; + } + + get [Symbol.toStringTag]() { + return "TextEncoderStream"; + } + } + + Object.defineProperty(TextEncoderStream.prototype, "encoding", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextEncoderStream.prototype, "readable", { + enumerable: true, + configurable: true, + }); + Object.defineProperty(TextEncoderStream.prototype, "writable", { + enumerable: true, + configurable: true, + }); + webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( "TextDecoderOptions", [ @@ -259,6 +451,8 @@ window.__bootstrap.encoding = { TextEncoder, TextDecoder, + TextEncoderStream, + TextDecoderStream, decode, }; })(this); diff --git a/extensions/web/lib.deno_web.d.ts b/extensions/web/lib.deno_web.d.ts index e91534567..0c3673351 100644 --- a/extensions/web/lib.deno_web.d.ts +++ b/extensions/web/lib.deno_web.d.ts @@ -195,8 +195,8 @@ declare class TextDecoder { readonly fatal: boolean; /** Returns `true` if ignore BOM flag is set, and `false` otherwise. */ readonly ignoreBOM = false; - /** Returns the result of running encoding's decoder. */ + /** Returns the result of running encoding's decoder. */ decode(input?: BufferSource, options?: TextDecodeOptions): string; } @@ -207,12 +207,33 @@ declare interface TextEncoderEncodeIntoResult { declare class TextEncoder { /** Returns "utf-8". */ - readonly encoding = "utf-8"; + readonly encoding: "utf-8"; /** Returns the result of running UTF-8's encoder. */ encode(input?: string): Uint8Array; encodeInto(input: string, dest: Uint8Array): TextEncoderEncodeIntoResult; } +declare class TextDecoderStream { + /** Returns encoding's name, lowercased. */ + readonly encoding: string; + /** Returns `true` if error mode is "fatal", and `false` otherwise. */ + readonly fatal: boolean; + /** Returns `true` if ignore BOM flag is set, and `false` otherwise. */ + readonly ignoreBOM = false; + constructor(label?: string, options?: TextDecoderOptions); + readonly readable: ReadableStream<string>; + readonly writable: WritableStream<BufferSource>; + readonly [Symbol.toStringTag]: string; +} + +declare class TextEncoderStream { + /** Returns "utf-8". */ + readonly encoding: "utf-8"; + readonly readable: ReadableStream<Uint8Array>; + readonly writable: WritableStream<string>; + readonly [Symbol.toStringTag]: string; +} + /** A controller object that allows you to abort one or more DOM requests as and * when desired. */ declare class AbortController { |