diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2021-08-11 12:27:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-11 12:27:05 +0200 |
commit | a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch) | |
tree | 90671b004537e20f9493fd3277ffd21d30b39a0e /ext/web/08_text_encoding.js | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (diff) |
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/web/08_text_encoding.js')
-rw-r--r-- | ext/web/08_text_encoding.js | 420 |
1 files changed, 420 insertions, 0 deletions
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 +/// <reference path="../../core/lib.deno_core.d.ts" /> +/// <reference path="../../core/internal.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" /> + +"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<BufferSource, string>} */ + #transform; + + /** + * + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoderStream'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + this.#decoder = new TextDecoder(label, options); + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextDecoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.BufferSource(chunk, { + allowShared: true, + }); + const decoded = this.#decoder.decode(chunk, { stream: true }); + if (decoded) { + controller.enqueue(decoded); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + 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<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 [SymbolToStringTag]() { + return "TextDecoderStream"; + } + } + + webidl.configurePrototype(TextDecoderStream); + + 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 = 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<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 [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); |