summaryrefslogtreecommitdiff
path: root/ext/web/08_text_encoding.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/web/08_text_encoding.js')
-rw-r--r--ext/web/08_text_encoding.js420
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);