diff options
Diffstat (limited to 'ext/web/08_text_encoding.js')
-rw-r--r-- | ext/web/08_text_encoding.js | 777 |
1 files changed, 387 insertions, 390 deletions
diff --git a/ext/web/08_text_encoding.js b/ext/web/08_text_encoding.js index 8de7b949f..f3ad966d0 100644 --- a/ext/web/08_text_encoding.js +++ b/ext/web/08_text_encoding.js @@ -9,437 +9,434 @@ /// <reference path="../web/lib.deno_web.d.ts" /> /// <reference lib="esnext" /> -"use strict"; - -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - PromiseReject, - PromiseResolve, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - StringPrototypeCharCodeAt, - StringPrototypeSlice, - TypedArrayPrototypeSubarray, - Uint8Array, - ObjectPrototypeIsPrototypeOf, - ArrayBufferIsView, - Uint32Array, - } = window.__bootstrap.primordials; - - class TextDecoder { - /** @type {string} */ - #encoding; - /** @type {boolean} */ - #fatal; - /** @type {boolean} */ - #ignoreBOM; - /** @type {boolean} */ - #utf8SinglePass; - - /** @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, { +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + PromiseReject, + PromiseResolve, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + StringPrototypeCharCodeAt, + StringPrototypeSlice, + TypedArrayPrototypeSubarray, + Uint8Array, + ObjectPrototypeIsPrototypeOf, + ArrayBufferIsView, + Uint32Array, +} = primordials; + +class TextDecoder { + /** @type {string} */ + #encoding; + /** @type {boolean} */ + #fatal; + /** @type {boolean} */ + #ignoreBOM; + /** @type {boolean} */ + #utf8SinglePass; + + /** @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 = ops.op_encoding_normalize_label(label); + this.#encoding = encoding; + this.#fatal = options.fatal; + this.#ignoreBOM = options.ignoreBOM; + this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#encoding; + } + + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#fatal; + } + + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#ignoreBOM; + } + + /** + * @param {BufferSource} [input] + * @param {TextDecodeOptions} options + */ + decode(input = new Uint8Array(), options = undefined) { + webidl.assertBranded(this, TextDecoderPrototype); + 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.TextDecoderOptions(options, { + } + let stream = false; + if (options !== undefined) { + options = webidl.converters.TextDecodeOptions(options, { prefix, context: "Argument 2", }); - const encoding = ops.op_encoding_normalize_label(label); - this.#encoding = encoding; - this.#fatal = options.fatal; - this.#ignoreBOM = options.ignoreBOM; - this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; - this[webidl.brand] = webidl.brand; + stream = options.stream; } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#encoding; - } - - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#fatal; - } - - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#ignoreBOM; - } - - /** - * @param {BufferSource} [input] - * @param {TextDecodeOptions} options - */ - decode(input = new Uint8Array(), options = undefined) { - webidl.assertBranded(this, TextDecoderPrototype); - const prefix = "Failed to execute 'decode' on 'TextDecoder'"; - if (input !== undefined) { - input = webidl.converters.BufferSource(input, { - prefix, - context: "Argument 1", - allowShared: true, - }); - } - let stream = false; - if (options !== undefined) { - options = webidl.converters.TextDecodeOptions(options, { - prefix, - context: "Argument 2", - }); - stream = options.stream; + try { + // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. + // When doing so they will have to make sure that changes to input do not affect future calls to decode(). + if ( + ObjectPrototypeIsPrototypeOf( + // deno-lint-ignore prefer-primordials + SharedArrayBuffer.prototype, + input || input.buffer, + ) + ) { + // We clone the data into a non-shared ArrayBuffer so we can pass it + // to Rust. + // `input` is now a Uint8Array, and calling the TypedArray constructor + // with a TypedArray argument copies the data. + if (ArrayBufferIsView(input)) { + input = new Uint8Array( + input.buffer, + input.byteOffset, + input.byteLength, + ); + } else { + input = new Uint8Array(input); + } } - try { - // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. - // When doing so they will have to make sure that changes to input do not affect future calls to decode(). - if ( - ObjectPrototypeIsPrototypeOf( - // deno-lint-ignore prefer-primordials - SharedArrayBuffer.prototype, - input || input.buffer, - ) - ) { - // We clone the data into a non-shared ArrayBuffer so we can pass it - // to Rust. - // `input` is now a Uint8Array, and calling the TypedArray constructor - // with a TypedArray argument copies the data. - if (ArrayBufferIsView(input)) { - input = new Uint8Array( - input.buffer, - input.byteOffset, - input.byteLength, - ); - } else { - input = new Uint8Array(input); - } + // Fast path for single pass encoding. + if (!stream && this.#rid === null) { + // Fast path for utf8 single pass encoding. + if (this.#utf8SinglePass) { + return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); } - // Fast path for single pass encoding. - if (!stream && this.#rid === null) { - // Fast path for utf8 single pass encoding. - if (this.#utf8SinglePass) { - return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); - } - - return ops.op_encoding_decode_single( - input, - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } + return ops.op_encoding_decode_single( + input, + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } - if (this.#rid === null) { - this.#rid = ops.op_encoding_new_decoder( - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } - return ops.op_encoding_decode(input, this.#rid, stream); - } finally { - if (!stream && this.#rid !== null) { - core.close(this.#rid); - this.#rid = null; - } + if (this.#rid === null) { + this.#rid = ops.op_encoding_new_decoder( + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } + return ops.op_encoding_decode(input, this.#rid, stream); + } finally { + if (!stream && this.#rid !== null) { + core.close(this.#rid); + this.#rid = null; } } } +} - webidl.configurePrototype(TextDecoder); - const TextDecoderPrototype = TextDecoder.prototype; +webidl.configurePrototype(TextDecoder); +const TextDecoderPrototype = TextDecoder.prototype; - class TextEncoder { - constructor() { - this[webidl.brand] = webidl.brand; - } +class TextEncoder { + constructor() { + this[webidl.brand] = webidl.brand; + } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderPrototype); - return "utf-8"; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderPrototype); + return "utf-8"; + } - /** - * @param {string} input - * @returns {Uint8Array} - */ - encode(input = "") { - webidl.assertBranded(this, TextEncoderPrototype); - 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} input + * @returns {Uint8Array} + */ + encode(input = "") { + webidl.assertBranded(this, TextEncoderPrototype); + 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, TextEncoderPrototype); - 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, - }); - ops.op_encoding_encode_into(source, destination, encodeIntoBuf); - return { - read: encodeIntoBuf[0], - written: encodeIntoBuf[1], - }; - } + /** + * @param {string} source + * @param {Uint8Array} destination + * @returns {TextEncoderEncodeIntoResult} + */ + encodeInto(source, destination) { + webidl.assertBranded(this, TextEncoderPrototype); + 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, + }); + ops.op_encoding_encode_into(source, destination, encodeIntoBuf); + return { + read: encodeIntoBuf[0], + written: encodeIntoBuf[1], + }; } +} - const encodeIntoBuf = new Uint32Array(2); +const encodeIntoBuf = new Uint32Array(2); - webidl.configurePrototype(TextEncoder); - const TextEncoderPrototype = TextEncoder.prototype; +webidl.configurePrototype(TextEncoder); +const TextEncoderPrototype = TextEncoder.prototype; - class TextDecoderStream { - /** @type {TextDecoder} */ - #decoder; - /** @type {TransformStream<BufferSource, string>} */ - #transform; +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); + /** + * @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); } - }, - flush: (controller) => { - try { - const final = this.#decoder.decode(); - if (final) { - controller.enqueue(final); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.encoding; - } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.fatal; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.encoding; + } - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.ignoreBOM; - } + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.fatal; + } - /** @returns {ReadableStream<string>} */ - get readable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.readable; - } + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.ignoreBOM; + } - /** @returns {WritableStream<BufferSource>} */ - get writable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.writable; - } + /** @returns {ReadableStream<string>} */ + get readable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.readable; } - webidl.configurePrototype(TextDecoderStream); - const TextDecoderStreamPrototype = TextDecoderStream.prototype; - - 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 (chunk === "") { - return PromiseResolve(); - } - 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)); - } + /** @returns {WritableStream<BufferSource>} */ + get writable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextDecoderStream); +const TextDecoderStreamPrototype = TextDecoderStream.prototype; + +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 (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); + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return "utf-8"; - } - - /** @returns {ReadableStream<Uint8Array>} */ - get readable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.readable; - } - - /** @returns {WritableStream<string>} */ - get writable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.writable; - } - } - - webidl.configurePrototype(TextEncoderStream); - const TextEncoderStreamPrototype = TextEncoderStream.prototype; - - webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( - "TextDecoderOptions", - [ - { - key: "fatal", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "ignoreBOM", - converter: webidl.converters.boolean, - defaultValue: false, + 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); + } }, - ], - ); - webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( - "TextDecodeOptions", - [ - { - key: "stream", - converter: webidl.converters.boolean, - defaultValue: false, + 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; + } - /** - * @param {Uint8Array} bytes - */ - function decode(bytes, encoding) { - const BOMEncoding = BOMSniff(bytes); - if (BOMEncoding !== null) { - encoding = BOMEncoding; - const start = BOMEncoding === "UTF-8" ? 3 : 2; - bytes = TypedArrayPrototypeSubarray(bytes, start); - } - return new TextDecoder(encoding).decode(bytes); + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return "utf-8"; } - /** - * @param {Uint8Array} bytes - */ - function BOMSniff(bytes) { - if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { - return "UTF-8"; - } - if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; - if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; - return null; + /** @returns {ReadableStream<Uint8Array>} */ + get readable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.readable; } - window.__bootstrap.encoding = { - TextEncoder, - TextDecoder, - TextEncoderStream, - TextDecoderStream, - decode, - }; -})(this); + /** @returns {WritableStream<string>} */ + get writable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextEncoderStream); +const TextEncoderStreamPrototype = TextEncoderStream.prototype; + +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); + if (BOMEncoding !== null) { + encoding = BOMEncoding; + const start = BOMEncoding === "UTF-8" ? 3 : 2; + bytes = TypedArrayPrototypeSubarray(bytes, start); + } + return new TextDecoder(encoding).decode(bytes); +} + +/** + * @param {Uint8Array} bytes + */ +function BOMSniff(bytes) { + if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { + return "UTF-8"; + } + if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; + if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; + return null; +} + +export { + decode, + TextDecoder, + TextDecoderStream, + TextEncoder, + TextEncoderStream, +}; |