mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 00:59:24 -05:00
refactor(web): use encoding_rs for text encoding (#10844)
This commit removes all JS based text encoding / text decoding. Instead encoding now happens in Rust via encoding_rs (already in tree). This implementation retains stream support, but adds the last missing encodings. We are incredibly close to 100% WPT on text encoding now. This should reduce our baseline heap by quite a bit.
This commit is contained in:
parent
bb0c90cadb
commit
c73ef5fa14
27 changed files with 641 additions and 4705 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -783,8 +783,11 @@ dependencies = [
|
|||
name = "deno_web"
|
||||
version = "0.38.1"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"deno_core",
|
||||
"encoding_rs",
|
||||
"futures",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
|
||||
at blob:null/[WILDCARD]:1:0
|
||||
error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
|
||||
at blob:null/[WILDCARD]:1:0
|
||||
error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
|
||||
at data:application/javascript;base64,[WILDCARD]:1:0
|
||||
error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
|
||||
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0
|
||||
error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -3,4 +3,4 @@ await import("https://example.com/some/file.ts");
|
|||
^
|
||||
at async http://localhost:4545/cli/tests/workers/dynamic_remote.ts:2:1
|
||||
[WILDCARD]error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
|
||||
at http://localhost:4545/cli/tests/workers/static_remote.ts:2:0
|
||||
error: Uncaught (in promise) Error: Unhandled error event reached main worker.
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:245:23)
|
||||
at Worker.#poll (deno:runtime/js/11_workers.js:243:23)
|
||||
|
|
|
@ -36,6 +36,10 @@ pub fn type_error(message: impl Into<Cow<'static, str>>) -> AnyError {
|
|||
custom_error("TypeError", message)
|
||||
}
|
||||
|
||||
pub fn range_error(message: impl Into<Cow<'static, str>>) -> AnyError {
|
||||
custom_error("RangeError", message)
|
||||
}
|
||||
|
||||
pub fn invalid_hostname(hostname: &str) -> AnyError {
|
||||
type_error(format!("Invalid hostname: '{}'", hostname))
|
||||
}
|
||||
|
|
11
core/lib.deno_core.d.ts
vendored
11
core/lib.deno_core.d.ts
vendored
|
@ -10,15 +10,15 @@ declare namespace Deno {
|
|||
/** Call an op in Rust, and synchronously receive the result. */
|
||||
function opSync(
|
||||
opName: string,
|
||||
args?: any,
|
||||
zeroCopy?: Uint8Array,
|
||||
a?: any,
|
||||
b?: any,
|
||||
): any;
|
||||
|
||||
/** Call an op in Rust, and asynchronously receive the result. */
|
||||
function opAsync(
|
||||
opName: string,
|
||||
args?: any,
|
||||
zeroCopy?: Uint8Array,
|
||||
a?: any,
|
||||
b?: any,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
|
@ -38,5 +38,8 @@ declare namespace Deno {
|
|||
|
||||
/** Get heap stats for current isolate/worker */
|
||||
function heapStats(): Record<string, number>;
|
||||
|
||||
/** Encode a string to its Uint8Array representation. */
|
||||
function encode(input: string): Uint8Array;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
/// <reference lib="esnext" />
|
||||
"use strict";
|
||||
|
||||
((_window) => {
|
||||
((window) => {
|
||||
const core = window.Deno.core;
|
||||
const webidl = globalThis.__bootstrap.webidl;
|
||||
const { Blob, File, _byteSequence } = globalThis.__bootstrap.file;
|
||||
|
||||
|
@ -240,8 +241,6 @@
|
|||
|
||||
webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
class MultipartBuilder {
|
||||
/**
|
||||
* @param {FormData} formData
|
||||
|
@ -270,7 +269,7 @@
|
|||
} else this.#writeField(name, value);
|
||||
}
|
||||
|
||||
this.chunks.push(encoder.encode(`\r\n--${this.boundary}--`));
|
||||
this.chunks.push(core.encode(`\r\n--${this.boundary}--`));
|
||||
|
||||
let totalLength = 0;
|
||||
for (const chunk of this.chunks) {
|
||||
|
@ -309,7 +308,7 @@
|
|||
}
|
||||
buf += `\r\n`;
|
||||
|
||||
this.chunks.push(encoder.encode(buf));
|
||||
this.chunks.push(core.encode(buf));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,7 +355,7 @@
|
|||
*/
|
||||
#writeField(field, value) {
|
||||
this.#writeFieldHeaders(field);
|
||||
this.chunks.push(encoder.encode(this.#normalizeNewlines(value)));
|
||||
this.chunks.push(core.encode(this.#normalizeNewlines(value)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -428,7 +427,6 @@
|
|||
|
||||
const LF = "\n".codePointAt(0);
|
||||
const CR = "\r".codePointAt(0);
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
class MultipartParser {
|
||||
/**
|
||||
|
@ -442,7 +440,7 @@
|
|||
|
||||
this.boundary = `--${boundary}`;
|
||||
this.body = body;
|
||||
this.boundaryChars = encoder.encode(this.boundary);
|
||||
this.boundaryChars = core.encode(this.boundary);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -539,7 +537,7 @@
|
|||
});
|
||||
formData.append(name, blob, filename);
|
||||
} else {
|
||||
formData.append(name, decoder.decode(content));
|
||||
formData.append(name, core.decode(content));
|
||||
}
|
||||
}
|
||||
} else if (state === 5 && isNewLine) {
|
||||
|
|
|
@ -223,8 +223,6 @@
|
|||
return Object.defineProperties(prototype.prototype, mixin);
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
/**
|
||||
* https://fetch.spec.whatwg.org/#concept-body-package-data
|
||||
* @param {Uint8Array} bytes
|
||||
|
@ -263,14 +261,12 @@
|
|||
throw new TypeError("Missing content type");
|
||||
}
|
||||
case "JSON":
|
||||
return JSON.parse(decoder.decode(bytes));
|
||||
return JSON.parse(core.decode(bytes));
|
||||
case "text":
|
||||
return decoder.decode(bytes);
|
||||
return core.decode(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @param {BodyInit} object
|
||||
* @returns {{body: InnerBody, contentType: string | null}}
|
||||
|
@ -305,10 +301,10 @@
|
|||
length = res.body.byteLength;
|
||||
contentType = res.contentType;
|
||||
} else if (object instanceof URLSearchParams) {
|
||||
source = encoder.encode(object.toString());
|
||||
source = core.encode(object.toString());
|
||||
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||
} else if (typeof object === "string") {
|
||||
source = encoder.encode(object);
|
||||
source = core.encode(object);
|
||||
contentType = "text/plain;charset=UTF-8";
|
||||
} else if (object instanceof ReadableStream) {
|
||||
stream = object;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const core = window.Deno.core;
|
||||
const webidl = window.__bootstrap.webidl;
|
||||
|
||||
// TODO(lucacasonato): this needs to not be hardcoded and instead depend on
|
||||
|
@ -85,9 +86,6 @@
|
|||
return finalBytes;
|
||||
}
|
||||
|
||||
const utf8Encoder = new TextEncoder();
|
||||
const utf8Decoder = new TextDecoder();
|
||||
|
||||
/** @typedef {BufferSource | Blob | string} BlobPart */
|
||||
|
||||
/**
|
||||
|
@ -116,7 +114,7 @@
|
|||
if (endings == "native") {
|
||||
s = convertLineEndingsToNative(s);
|
||||
}
|
||||
bytesArrays.push(utf8Encoder.encode(s));
|
||||
bytesArrays.push(core.encode(s));
|
||||
} else {
|
||||
throw new TypeError("Unreachable code (invalild element type)");
|
||||
}
|
||||
|
@ -276,7 +274,7 @@
|
|||
async text() {
|
||||
webidl.assertBranded(this, Blob);
|
||||
const buffer = await this.arrayBuffer();
|
||||
return utf8Decoder.decode(buffer);
|
||||
return core.decode(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
((window) => {
|
||||
const webidl = window.__bootstrap.webidl;
|
||||
const { decode } = window.__bootstrap.encoding;
|
||||
const { forgivingBase64Encode } = window.__bootstrap.infra;
|
||||
const { decode, TextDecoder } = window.__bootstrap.encoding;
|
||||
const { parseMimeType } = window.__bootstrap.mimesniff;
|
||||
const base64 = window.__bootstrap.base64;
|
||||
|
||||
const state = Symbol("[[state]]");
|
||||
const result = Symbol("[[result]]");
|
||||
|
@ -168,7 +168,7 @@
|
|||
case "DataUrl": {
|
||||
const mediaType = blob.type || "application/octet-stream";
|
||||
this[result] = `data:${mediaType};base64,${
|
||||
base64.fromByteArray(bytes)
|
||||
forgivingBase64Encode(bytes)
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const core = Deno.core;
|
||||
|
||||
const ASCII_DIGIT = ["\u0030-\u0039"];
|
||||
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
|
||||
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
|
||||
|
@ -178,6 +180,22 @@
|
|||
return { result: input.substring(positionStart, position + 1), position };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function forgivingBase64Encode(data) {
|
||||
return core.opSync("op_base64_encode", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} data
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function forgivingBase64Decode(data) {
|
||||
return core.opSync("op_base64_decode", data);
|
||||
}
|
||||
|
||||
window.__bootstrap.infra = {
|
||||
collectSequenceOfCodepoints,
|
||||
ASCII_DIGIT,
|
||||
|
@ -199,5 +217,7 @@
|
|||
byteUpperCase,
|
||||
byteLowerCase,
|
||||
collectHttpQuotedString,
|
||||
forgivingBase64Encode,
|
||||
forgivingBase64Decode,
|
||||
};
|
||||
})(globalThis);
|
||||
|
|
62
extensions/web/05_base64.js
Normal file
62
extensions/web/05_base64.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// @ts-check
|
||||
/// <reference path="../webidl/internal.d.ts" />
|
||||
/// <reference path="../web/internal.d.ts" />
|
||||
/// <reference lib="esnext" />
|
||||
|
||||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const webidl = window.__bootstrap.webidl;
|
||||
const {
|
||||
forgivingBase64Encode,
|
||||
forgivingBase64Decode,
|
||||
} = window.__bootstrap.infra;
|
||||
|
||||
/**
|
||||
* @param {string} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function atob(data) {
|
||||
data = webidl.converters.DOMString(data, {
|
||||
prefix: "Failed to execute 'atob'",
|
||||
context: "Argument 1",
|
||||
});
|
||||
|
||||
const uint8Array = forgivingBase64Decode(data);
|
||||
const result = [...uint8Array]
|
||||
.map((byte) => String.fromCharCode(byte))
|
||||
.join("");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function btoa(data) {
|
||||
const prefix = "Failed to execute 'btoa'";
|
||||
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||
data = webidl.converters.DOMString(data, {
|
||||
prefix,
|
||||
context: "Argument 1",
|
||||
});
|
||||
const byteArray = [...data].map((char) => {
|
||||
const charCode = char.charCodeAt(0);
|
||||
if (charCode > 0xff) {
|
||||
throw new DOMException(
|
||||
"The string to be encoded contains characters outside of the Latin1 range.",
|
||||
"InvalidCharacterError",
|
||||
);
|
||||
}
|
||||
return charCode;
|
||||
});
|
||||
return forgivingBase64Encode(Uint8Array.from(byteArray));
|
||||
}
|
||||
|
||||
window.__bootstrap.base64 = {
|
||||
atob,
|
||||
btoa,
|
||||
};
|
||||
})(globalThis);
|
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,10 @@ repository = "https://github.com/denoland/deno"
|
|||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
deno_core = { version = "0.88.1", path = "../../core" }
|
||||
encoding_rs = "0.8.28"
|
||||
serde = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
futures = "0.3.15"
|
||||
|
|
7
extensions/web/internal.d.ts
vendored
7
extensions/web/internal.d.ts
vendored
|
@ -43,6 +43,8 @@ declare namespace globalThis {
|
|||
result: string;
|
||||
position: number;
|
||||
};
|
||||
forgivingBase64Encode(data: Uint8Array): string;
|
||||
forgivingBase64Decode(data: string): Uint8Array;
|
||||
};
|
||||
|
||||
declare namespace mimesniff {
|
||||
|
@ -65,9 +67,8 @@ declare namespace globalThis {
|
|||
};
|
||||
|
||||
declare var base64: {
|
||||
byteLength(b64: string): number;
|
||||
toByteArray(b64: string): Uint8Array;
|
||||
fromByteArray(uint8: Uint8Array): string;
|
||||
atob(data: string): string;
|
||||
btoa(data: string): string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
30
extensions/web/lib.deno_web.d.ts
vendored
30
extensions/web/lib.deno_web.d.ts
vendored
|
@ -177,20 +177,32 @@ declare function atob(s: string): string;
|
|||
*/
|
||||
declare function btoa(s: string): string;
|
||||
|
||||
declare interface TextDecoderOptions {
|
||||
fatal?: boolean;
|
||||
ignoreBOM?: boolean;
|
||||
}
|
||||
|
||||
declare interface TextDecodeOptions {
|
||||
stream?: boolean;
|
||||
}
|
||||
|
||||
declare class TextDecoder {
|
||||
constructor(label?: string, options?: TextDecoderOptions);
|
||||
|
||||
/** 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?: { fatal?: boolean; ignoreBOM?: boolean },
|
||||
);
|
||||
/** Returns the result of running encoding's decoder. */
|
||||
decode(input?: BufferSource, options?: { stream?: boolean }): string;
|
||||
readonly [Symbol.toStringTag]: string;
|
||||
|
||||
decode(input?: BufferSource, options?: TextDecodeOptions): string;
|
||||
}
|
||||
|
||||
declare interface TextEncoderEncodeIntoResult {
|
||||
read: number;
|
||||
written: number;
|
||||
}
|
||||
|
||||
declare class TextEncoder {
|
||||
|
@ -198,11 +210,7 @@ declare class TextEncoder {
|
|||
readonly encoding = "utf-8";
|
||||
/** Returns the result of running UTF-8's encoder. */
|
||||
encode(input?: string): Uint8Array;
|
||||
encodeInto(
|
||||
input: string,
|
||||
dest: Uint8Array,
|
||||
): { read: number; written: number };
|
||||
readonly [Symbol.toStringTag]: string;
|
||||
encodeInto(input: string, dest: Uint8Array): TextEncoderEncodeIntoResult;
|
||||
}
|
||||
|
||||
/** A controller object that allows you to abort one or more DOM requests as and
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::error::bad_resource_id;
|
||||
use deno_core::error::range_error;
|
||||
use deno_core::error::type_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::include_js_files;
|
||||
use deno_core::op_sync;
|
||||
use deno_core::Extension;
|
||||
use deno_core::OpState;
|
||||
use deno_core::Resource;
|
||||
use deno_core::ResourceId;
|
||||
use deno_core::ZeroCopyBuf;
|
||||
use encoding_rs::CoderResult;
|
||||
use encoding_rs::Decoder;
|
||||
use encoding_rs::DecoderResult;
|
||||
use encoding_rs::Encoding;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::usize;
|
||||
|
||||
/// Load and execute the javascript code.
|
||||
pub fn init() -> Extension {
|
||||
|
@ -17,12 +35,235 @@ pub fn init() -> Extension {
|
|||
"02_event.js",
|
||||
"03_abort_signal.js",
|
||||
"04_global_interfaces.js",
|
||||
"05_base64.js",
|
||||
"08_text_encoding.js",
|
||||
"12_location.js",
|
||||
))
|
||||
.ops(vec![
|
||||
("op_base64_decode", op_sync(op_base64_decode)),
|
||||
("op_base64_encode", op_sync(op_base64_encode)),
|
||||
(
|
||||
"op_encoding_normalize_label",
|
||||
op_sync(op_encoding_normalize_label),
|
||||
),
|
||||
("op_encoding_new_decoder", op_sync(op_encoding_new_decoder)),
|
||||
("op_encoding_decode", op_sync(op_encoding_decode)),
|
||||
("op_encoding_encode_into", op_sync(op_encoding_encode_into)),
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
fn op_base64_decode(
|
||||
_state: &mut OpState,
|
||||
input: String,
|
||||
_: (),
|
||||
) -> Result<ZeroCopyBuf, AnyError> {
|
||||
let mut input: &str = &input.replace(|c| char::is_ascii_whitespace(&c), "");
|
||||
// "If the length of input divides by 4 leaving no remainder, then:
|
||||
// if input ends with one or two U+003D EQUALS SIGN (=) characters,
|
||||
// remove them from input."
|
||||
if input.len() % 4 == 0 {
|
||||
if input.ends_with("==") {
|
||||
input = &input[..input.len() - 2]
|
||||
} else if input.ends_with('=') {
|
||||
input = &input[..input.len() - 1]
|
||||
}
|
||||
}
|
||||
|
||||
// "If the length of input divides by 4 leaving a remainder of 1,
|
||||
// throw an InvalidCharacterError exception and abort these steps."
|
||||
if input.len() % 4 == 1 {
|
||||
return Err(
|
||||
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
|
||||
);
|
||||
}
|
||||
|
||||
if input
|
||||
.chars()
|
||||
.any(|c| c != '+' && c != '/' && !c.is_alphanumeric())
|
||||
{
|
||||
return Err(
|
||||
DomExceptionInvalidCharacterError::new(
|
||||
"Failed to decode base64: invalid character",
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
||||
.decode_allow_trailing_bits(true);
|
||||
let out = base64::decode_config(&input, cfg).map_err(|err| {
|
||||
DomExceptionInvalidCharacterError::new(&format!(
|
||||
"Failed to decode base64: {:?}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
Ok(ZeroCopyBuf::from(out))
|
||||
}
|
||||
|
||||
fn op_base64_encode(
|
||||
_state: &mut OpState,
|
||||
s: ZeroCopyBuf,
|
||||
_: (),
|
||||
) -> Result<String, AnyError> {
|
||||
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
||||
.decode_allow_trailing_bits(true);
|
||||
let out = base64::encode_config(&s, cfg);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct DecoderOptions {
|
||||
label: String,
|
||||
ignore_bom: bool,
|
||||
fatal: bool,
|
||||
}
|
||||
|
||||
fn op_encoding_normalize_label(
|
||||
_state: &mut OpState,
|
||||
label: String,
|
||||
_: (),
|
||||
) -> Result<String, AnyError> {
|
||||
let encoding = Encoding::for_label_no_replacement(label.as_bytes())
|
||||
.ok_or_else(|| {
|
||||
range_error(format!(
|
||||
"The encoding label provided ('{}') is invalid.",
|
||||
label
|
||||
))
|
||||
})?;
|
||||
Ok(encoding.name().to_lowercase())
|
||||
}
|
||||
|
||||
fn op_encoding_new_decoder(
|
||||
state: &mut OpState,
|
||||
options: DecoderOptions,
|
||||
_: (),
|
||||
) -> Result<ResourceId, AnyError> {
|
||||
let DecoderOptions {
|
||||
label,
|
||||
fatal,
|
||||
ignore_bom,
|
||||
} = options;
|
||||
|
||||
let encoding = Encoding::for_label(label.as_bytes()).ok_or_else(|| {
|
||||
range_error(format!(
|
||||
"The encoding label provided ('{}') is invalid.",
|
||||
label
|
||||
))
|
||||
})?;
|
||||
|
||||
let decoder = if ignore_bom {
|
||||
encoding.new_decoder_without_bom_handling()
|
||||
} else {
|
||||
encoding.new_decoder_with_bom_removal()
|
||||
};
|
||||
|
||||
let rid = state.resource_table.add(TextDecoderResource {
|
||||
decoder: RefCell::new(decoder),
|
||||
fatal,
|
||||
});
|
||||
|
||||
Ok(rid)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct DecodeOptions {
|
||||
rid: ResourceId,
|
||||
stream: bool,
|
||||
}
|
||||
|
||||
fn op_encoding_decode(
|
||||
state: &mut OpState,
|
||||
data: ZeroCopyBuf,
|
||||
options: DecodeOptions,
|
||||
) -> Result<String, AnyError> {
|
||||
let DecodeOptions { rid, stream } = options;
|
||||
|
||||
let resource = state
|
||||
.resource_table
|
||||
.get::<TextDecoderResource>(rid)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
|
||||
let mut decoder = resource.decoder.borrow_mut();
|
||||
let fatal = resource.fatal;
|
||||
|
||||
let max_buffer_length = if fatal {
|
||||
decoder
|
||||
.max_utf8_buffer_length_without_replacement(data.len())
|
||||
.ok_or_else(|| range_error("Value too large to decode."))?
|
||||
} else {
|
||||
decoder
|
||||
.max_utf8_buffer_length(data.len())
|
||||
.ok_or_else(|| range_error("Value too large to decode."))?
|
||||
};
|
||||
|
||||
let mut output = String::with_capacity(max_buffer_length);
|
||||
|
||||
if fatal {
|
||||
let (result, _) =
|
||||
decoder.decode_to_string_without_replacement(&data, &mut output, !stream);
|
||||
match result {
|
||||
DecoderResult::InputEmpty => Ok(output),
|
||||
DecoderResult::OutputFull => {
|
||||
Err(range_error("Provided buffer too small."))
|
||||
}
|
||||
DecoderResult::Malformed(_, _) => {
|
||||
Err(type_error("The encoded data is not valid."))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (result, _, _) = decoder.decode_to_string(&data, &mut output, !stream);
|
||||
match result {
|
||||
CoderResult::InputEmpty => Ok(output),
|
||||
CoderResult::OutputFull => Err(range_error("Provided buffer too small.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TextDecoderResource {
|
||||
decoder: RefCell<Decoder>,
|
||||
fatal: bool,
|
||||
}
|
||||
|
||||
impl Resource for TextDecoderResource {
|
||||
fn name(&self) -> Cow<str> {
|
||||
"textDecoder".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EncodeIntoResult {
|
||||
read: usize,
|
||||
written: usize,
|
||||
}
|
||||
|
||||
fn op_encoding_encode_into(
|
||||
_state: &mut OpState,
|
||||
input: String,
|
||||
mut buffer: ZeroCopyBuf,
|
||||
) -> Result<EncodeIntoResult, AnyError> {
|
||||
let dst: &mut [u8] = &mut buffer;
|
||||
let mut read = 0;
|
||||
let mut written = 0;
|
||||
for char in input.chars() {
|
||||
let len = char.len_utf8();
|
||||
if dst.len() < written + len {
|
||||
break;
|
||||
}
|
||||
char.encode_utf8(&mut dst[written..]);
|
||||
written += len;
|
||||
if char > '\u{FFFF}' {
|
||||
read += 2
|
||||
} else {
|
||||
read += 1
|
||||
};
|
||||
}
|
||||
Ok(EncodeIntoResult { read, written })
|
||||
}
|
||||
|
||||
pub fn get_declaration() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_web.d.ts")
|
||||
}
|
||||
|
@ -40,17 +281,39 @@ impl DomExceptionQuotaExceededError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DomExceptionInvalidCharacterError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl DomExceptionInvalidCharacterError {
|
||||
pub fn new(msg: &str) -> Self {
|
||||
DomExceptionInvalidCharacterError {
|
||||
msg: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DomExceptionQuotaExceededError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(&self.msg)
|
||||
}
|
||||
}
|
||||
impl fmt::Display for DomExceptionInvalidCharacterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(&self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for DomExceptionQuotaExceededError {}
|
||||
|
||||
pub fn get_quota_exceeded_error_class_name(
|
||||
e: &AnyError,
|
||||
) -> Option<&'static str> {
|
||||
impl std::error::Error for DomExceptionInvalidCharacterError {}
|
||||
|
||||
pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
|
||||
e.downcast_ref::<DomExceptionQuotaExceededError>()
|
||||
.map(|_| "DOMExceptionQuotaExceededError")
|
||||
.or_else(|| {
|
||||
e.downcast_ref::<DomExceptionInvalidCharacterError>()
|
||||
.map(|_| "DOMExceptionInvalidCharacterError")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -241,8 +241,7 @@
|
|||
sendTypedArray(new DataView(data));
|
||||
} else {
|
||||
const string = String(data);
|
||||
const encoder = new TextEncoder();
|
||||
const d = encoder.encode(string);
|
||||
const d = core.encode(string);
|
||||
this.#bufferedAmount += d.size;
|
||||
core.opAsync("op_ws_send", {
|
||||
rid: this.#rid,
|
||||
|
@ -262,8 +261,7 @@
|
|||
);
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
if (reason && encoder.encode(reason).byteLength > 123) {
|
||||
if (reason && core.encode(reason).byteLength > 123) {
|
||||
throw new DOMException(
|
||||
"The close reason may not be longer than 123 bytes.",
|
||||
"SyntaxError",
|
||||
|
|
|
@ -157,7 +157,7 @@ fn get_nix_error_class(error: &nix::Error) -> &'static str {
|
|||
pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
|
||||
deno_core::error::get_custom_error_class(e)
|
||||
.or_else(|| deno_webgpu::error::get_error_class_name(e))
|
||||
.or_else(|| deno_web::get_quota_exceeded_error_class_name(e))
|
||||
.or_else(|| deno_web::get_error_class_name(e))
|
||||
.or_else(|| deno_webstorage::get_not_supported_error_class_name(e))
|
||||
.or_else(|| {
|
||||
e.downcast_ref::<dlopen::Error>()
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
return core.opAsync("op_host_get_message", id);
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
/**
|
||||
* @param {string} permission
|
||||
* @return {boolean}
|
||||
|
@ -166,7 +164,7 @@
|
|||
|
||||
this.#name = name;
|
||||
const hasSourceCode = false;
|
||||
const sourceCode = decoder.decode(new Uint8Array());
|
||||
const sourceCode = core.decode(new Uint8Array());
|
||||
|
||||
if (
|
||||
specifier.startsWith("./") || specifier.startsWith("../") ||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const core = window.Deno.core;
|
||||
const { open, openSync } = window.__bootstrap.files;
|
||||
const { readAll, readAllSync } = window.__bootstrap.io;
|
||||
|
||||
|
@ -29,8 +30,7 @@
|
|||
const file = openSync(path);
|
||||
try {
|
||||
const contents = readAllSync(file);
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(contents);
|
||||
return core.decode(contents);
|
||||
} finally {
|
||||
file.close();
|
||||
}
|
||||
|
@ -40,8 +40,7 @@
|
|||
const file = await open(path);
|
||||
try {
|
||||
const contents = await readAll(file);
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(contents);
|
||||
return core.decode(contents);
|
||||
} finally {
|
||||
file.close();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
const { isatty } = window.__bootstrap.tty;
|
||||
const LF = "\n".charCodeAt(0);
|
||||
const CR = "\r".charCodeAt(0);
|
||||
const decoder = new TextDecoder();
|
||||
const core = window.Deno.core;
|
||||
|
||||
function alert(message = "Alert") {
|
||||
|
@ -70,7 +69,7 @@
|
|||
}
|
||||
buf.push(c[0]);
|
||||
}
|
||||
return decoder.decode(new Uint8Array(buf));
|
||||
return core.decode(new Uint8Array(buf));
|
||||
}
|
||||
|
||||
window.__bootstrap.prompt = {
|
||||
|
|
|
@ -16,6 +16,8 @@ delete Object.prototype.__proto__;
|
|||
const errorStack = window.__bootstrap.errorStack;
|
||||
const os = window.__bootstrap.os;
|
||||
const timers = window.__bootstrap.timers;
|
||||
const base64 = window.__bootstrap.base64;
|
||||
const encoding = window.__bootstrap.encoding;
|
||||
const Console = window.__bootstrap.console.Console;
|
||||
const worker = window.__bootstrap.worker;
|
||||
const signals = window.__bootstrap.signals;
|
||||
|
@ -198,6 +200,12 @@ delete Object.prototype.__proto__;
|
|||
return new DOMException(msg, "NotSupported");
|
||||
},
|
||||
);
|
||||
core.registerErrorBuilder(
|
||||
"DOMExceptionInvalidCharacterError",
|
||||
function DOMExceptionInvalidCharacterError(msg) {
|
||||
return new DOMException(msg, "InvalidCharacterError");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class Navigator {
|
||||
|
@ -277,8 +285,8 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
Request: util.nonEnumerable(fetch.Request),
|
||||
Response: util.nonEnumerable(fetch.Response),
|
||||
TextDecoder: util.nonEnumerable(TextDecoder),
|
||||
TextEncoder: util.nonEnumerable(TextEncoder),
|
||||
TextDecoder: util.nonEnumerable(encoding.TextDecoder),
|
||||
TextEncoder: util.nonEnumerable(encoding.TextEncoder),
|
||||
TransformStream: util.nonEnumerable(streams.TransformStream),
|
||||
URL: util.nonEnumerable(url.URL),
|
||||
URLSearchParams: util.nonEnumerable(url.URLSearchParams),
|
||||
|
@ -295,8 +303,8 @@ delete Object.prototype.__proto__;
|
|||
TransformStreamDefaultController: util.nonEnumerable(
|
||||
streams.TransformStreamDefaultController,
|
||||
),
|
||||
atob: util.writable(atob),
|
||||
btoa: util.writable(btoa),
|
||||
atob: util.writable(base64.atob),
|
||||
btoa: util.writable(base64.btoa),
|
||||
clearInterval: util.writable(timers.clearInterval),
|
||||
clearTimeout: util.writable(timers.clearTimeout),
|
||||
console: util.writable(
|
||||
|
|
|
@ -187,18 +187,6 @@
|
|||
"encodeInto() and a detached output buffer"
|
||||
],
|
||||
"idlharness.any.html": [
|
||||
"TextDecoder interface: existence and properties of interface object",
|
||||
"TextDecoder interface: operation decode(optional BufferSource, optional TextDecodeOptions)",
|
||||
"TextDecoder interface: attribute encoding",
|
||||
"TextDecoder interface: attribute fatal",
|
||||
"TextDecoder interface: attribute ignoreBOM",
|
||||
"TextDecoder interface: new TextDecoder() must inherit property \"fatal\" with the proper type",
|
||||
"TextDecoder interface: new TextDecoder() must inherit property \"ignoreBOM\" with the proper type",
|
||||
"TextEncoder interface: existence and properties of interface object",
|
||||
"TextEncoder interface: operation encode(optional USVString)",
|
||||
"TextEncoder interface: operation encodeInto(USVString, Uint8Array)",
|
||||
"TextEncoder interface: attribute encoding",
|
||||
"TextEncoder interface: new TextEncoder() must inherit property \"encoding\" with the proper type",
|
||||
"TextDecoderStream interface: existence and properties of interface object",
|
||||
"TextDecoderStream interface object length",
|
||||
"TextDecoderStream interface object name",
|
||||
|
@ -216,7 +204,7 @@
|
|||
"TextEncoderStream interface: existence and properties of interface prototype object's @@unscopables property",
|
||||
"TextEncoderStream interface: attribute encoding"
|
||||
],
|
||||
"iso-2022-jp-decoder.any.html": false,
|
||||
"iso-2022-jp-decoder.any.html": true,
|
||||
"legacy-mb-schinese": {
|
||||
"gb18030": {
|
||||
"gb18030-decoder.any.html": true
|
||||
|
@ -237,11 +225,12 @@
|
|||
"decode-utf8.any.html": false,
|
||||
"encode-bad-chunks.any.html": false,
|
||||
"encode-utf8.any.html": false,
|
||||
"readable-writable-properties.any.html": false
|
||||
"readable-writable-properties.any.html": false,
|
||||
"realms.window.html": false
|
||||
},
|
||||
"textdecoder-arguments.any.html": true,
|
||||
"textdecoder-byte-order-marks.any.html": true,
|
||||
"textdecoder-copy.any.html": false,
|
||||
"textdecoder-copy.any.html": true,
|
||||
"textdecoder-fatal-single-byte.any.html?1-1000": true,
|
||||
"textdecoder-fatal-single-byte.any.html?1001-2000": true,
|
||||
"textdecoder-fatal-single-byte.any.html?2001-3000": true,
|
||||
|
@ -250,46 +239,23 @@
|
|||
"textdecoder-fatal-single-byte.any.html?5001-6000": true,
|
||||
"textdecoder-fatal-single-byte.any.html?6001-7000": true,
|
||||
"textdecoder-fatal-single-byte.any.html?7001-last": true,
|
||||
"textdecoder-fatal-streaming.any.html": [
|
||||
"Fatal flag, streaming cases"
|
||||
],
|
||||
"textdecoder-fatal-streaming.any.html": true,
|
||||
"textdecoder-fatal.any.html": true,
|
||||
"textdecoder-ignorebom.any.html": true,
|
||||
"textdecoder-labels.any.html": [
|
||||
"cseucpkdfmtjapanese => EUC-JP",
|
||||
"euc-jp => EUC-JP",
|
||||
"x-euc-jp => EUC-JP",
|
||||
"csiso2022jp => ISO-2022-JP",
|
||||
"iso-2022-jp => ISO-2022-JP",
|
||||
"csshiftjis => Shift_JIS",
|
||||
"ms932 => Shift_JIS",
|
||||
"ms_kanji => Shift_JIS",
|
||||
"shift-jis => Shift_JIS",
|
||||
"shift_jis => Shift_JIS",
|
||||
"sjis => Shift_JIS",
|
||||
"windows-31j => Shift_JIS",
|
||||
"x-sjis => Shift_JIS",
|
||||
"cseuckr => EUC-KR",
|
||||
"csksc56011987 => EUC-KR",
|
||||
"euc-kr => EUC-KR",
|
||||
"iso-ir-149 => EUC-KR",
|
||||
"korean => EUC-KR",
|
||||
"ks_c_5601-1987 => EUC-KR",
|
||||
"ks_c_5601-1989 => EUC-KR",
|
||||
"ksc5601 => EUC-KR",
|
||||
"ksc_5601 => EUC-KR",
|
||||
"windows-949 => EUC-KR",
|
||||
"x-user-defined => x-user-defined"
|
||||
"unicode11utf8 => UTF-8",
|
||||
"unicode20utf8 => UTF-8",
|
||||
"x-unicode20utf8 => UTF-8",
|
||||
"unicodefffe => UTF-16BE",
|
||||
"csunicode => UTF-16LE",
|
||||
"iso-10646-ucs-2 => UTF-16LE",
|
||||
"ucs-2 => UTF-16LE",
|
||||
"unicode => UTF-16LE",
|
||||
"unicodefeff => UTF-16LE"
|
||||
],
|
||||
"textdecoder-streaming.any.html": true,
|
||||
"textdecoder-utf16-surrogates.any.html": true,
|
||||
"textencoder-constructor-non-utf.any.html": [
|
||||
"Encoding argument supported for decode: EUC-JP",
|
||||
"Encoding argument supported for decode: ISO-2022-JP",
|
||||
"Encoding argument supported for decode: Shift_JIS",
|
||||
"Encoding argument supported for decode: EUC-KR",
|
||||
"Encoding argument supported for decode: x-user-defined"
|
||||
],
|
||||
"textencoder-constructor-non-utf.any.html": true,
|
||||
"textencoder-utf16-surrogates.any.html": true,
|
||||
"unsupported-encodings.any.html": false
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue