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/web/08_text_encoding.js | |
parent | eb3a20292f01fb621bd0027a9fb2827131e34de8 (diff) |
feat(web): Implement TextDecoderStream and TextEncoderStream (#10842)
Diffstat (limited to 'extensions/web/08_text_encoding.js')
-rw-r--r-- | extensions/web/08_text_encoding.js | 194 |
1 files changed, 194 insertions, 0 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); |