1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 16:49:18 -05:00

refactor: add deno_file op crate (#10019)

Also enables WPT for FileReader.
This commit is contained in:
Luca Casonato 2021-04-06 12:55:05 +02:00 committed by GitHub
parent ff5d072702
commit 00e63306cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 470 additions and 321 deletions

8
Cargo.lock generated
View file

@ -616,6 +616,13 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "deno_file"
version = "0.1.0"
dependencies = [
"deno_core",
]
[[package]] [[package]]
name = "deno_lint" name = "deno_lint"
version = "0.2.19" version = "0.2.19"
@ -643,6 +650,7 @@ dependencies = [
"deno_core", "deno_core",
"deno_crypto", "deno_crypto",
"deno_fetch", "deno_fetch",
"deno_file",
"deno_url", "deno_url",
"deno_web", "deno_web",
"deno_webgpu", "deno_webgpu",

View file

@ -11,6 +11,7 @@ use deno_core::RuntimeOptions;
use deno_runtime::deno_console; use deno_runtime::deno_console;
use deno_runtime::deno_crypto; use deno_runtime::deno_crypto;
use deno_runtime::deno_fetch; use deno_runtime::deno_fetch;
use deno_runtime::deno_file;
use deno_runtime::deno_url; use deno_runtime::deno_url;
use deno_runtime::deno_web; use deno_runtime::deno_web;
use deno_runtime::deno_webgpu; use deno_runtime::deno_webgpu;
@ -66,6 +67,7 @@ fn create_compiler_snapshot(
op_crate_libs.insert("deno.console", deno_console::get_declaration()); op_crate_libs.insert("deno.console", deno_console::get_declaration());
op_crate_libs.insert("deno.url", deno_url::get_declaration()); op_crate_libs.insert("deno.url", deno_url::get_declaration());
op_crate_libs.insert("deno.web", deno_web::get_declaration()); op_crate_libs.insert("deno.web", deno_web::get_declaration());
op_crate_libs.insert("deno.file", deno_file::get_declaration());
op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration()); op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration());
op_crate_libs.insert("deno.webgpu", deno_webgpu::get_declaration()); op_crate_libs.insert("deno.webgpu", deno_webgpu::get_declaration());
op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration()); op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
@ -270,6 +272,10 @@ fn main() {
"cargo:rustc-env=DENO_WEB_LIB_PATH={}", "cargo:rustc-env=DENO_WEB_LIB_PATH={}",
deno_web::get_declaration().display() deno_web::get_declaration().display()
); );
println!(
"cargo:rustc-env=DENO_FILE_LIB_PATH={}",
deno_file::get_declaration().display()
);
println!( println!(
"cargo:rustc-env=DENO_FETCH_LIB_PATH={}", "cargo:rustc-env=DENO_FETCH_LIB_PATH={}",
deno_fetch::get_declaration().display() deno_fetch::get_declaration().display()

View file

@ -6,6 +6,7 @@
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
/// <reference lib="deno.console" /> /// <reference lib="deno.console" />
/// <reference lib="deno.file" />
/// <reference lib="deno.url" /> /// <reference lib="deno.url" />
/// <reference lib="deno.web" /> /// <reference lib="deno.web" />
/// <reference lib="deno.fetch" /> /// <reference lib="deno.fetch" />

View file

@ -275,11 +275,12 @@ fn print_cache_info(
pub fn get_types(unstable: bool) -> String { pub fn get_types(unstable: bool) -> String {
let mut types = format!( let mut types = format!(
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
crate::tsc::DENO_NS_LIB, crate::tsc::DENO_NS_LIB,
crate::tsc::DENO_CONSOLE_LIB, crate::tsc::DENO_CONSOLE_LIB,
crate::tsc::DENO_URL_LIB, crate::tsc::DENO_URL_LIB,
crate::tsc::DENO_WEB_LIB, crate::tsc::DENO_WEB_LIB,
crate::tsc::DENO_FILE_LIB,
crate::tsc::DENO_FETCH_LIB, crate::tsc::DENO_FETCH_LIB,
crate::tsc::DENO_WEBGPU_LIB, crate::tsc::DENO_WEBGPU_LIB,
crate::tsc::DENO_WEBSOCKET_LIB, crate::tsc::DENO_WEBSOCKET_LIB,

View file

@ -32,6 +32,7 @@ pub static DENO_NS_LIB: &str = include_str!("dts/lib.deno.ns.d.ts");
pub static DENO_CONSOLE_LIB: &str = include_str!(env!("DENO_CONSOLE_LIB_PATH")); pub static DENO_CONSOLE_LIB: &str = include_str!(env!("DENO_CONSOLE_LIB_PATH"));
pub static DENO_URL_LIB: &str = include_str!(env!("DENO_URL_LIB_PATH")); pub static DENO_URL_LIB: &str = include_str!(env!("DENO_URL_LIB_PATH"));
pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH")); pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH"));
pub static DENO_FILE_LIB: &str = include_str!(env!("DENO_FILE_LIB_PATH"));
pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH")); pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH"));
pub static DENO_WEBGPU_LIB: &str = include_str!(env!("DENO_WEBGPU_LIB_PATH")); pub static DENO_WEBGPU_LIB: &str = include_str!(env!("DENO_WEBGPU_LIB_PATH"));
pub static DENO_WEBSOCKET_LIB: &str = pub static DENO_WEBSOCKET_LIB: &str =

View file

@ -19,16 +19,6 @@ declare namespace globalThis {
Headers: typeof Headers; Headers: typeof Headers;
}; };
declare var file: {
Blob: typeof Blob & {
[globalThis.__bootstrap.file._byteSequence]: Uint8Array;
};
_byteSequence: unique symbol;
File: typeof File & {
[globalThis.__bootstrap.file._byteSequence]: Uint8Array;
};
};
declare var streams: { declare var streams: {
ReadableStream: typeof ReadableStream; ReadableStream: typeof ReadableStream;
isReadableStreamDisturbed(stream: ReadableStream): boolean; isReadableStreamDisturbed(stream: ReadableStream): boolean;

View file

@ -287,42 +287,6 @@ interface TransformStreamDefaultControllerTransformCallback<I, O> {
): void | PromiseLike<void>; ): void | PromiseLike<void>;
} }
type BlobPart = BufferSource | Blob | string;
interface BlobPropertyBag {
type?: string;
endings?: "transparent" | "native";
}
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
declare class Blob {
constructor(blobParts?: BlobPart[], options?: BlobPropertyBag);
readonly size: number;
readonly type: string;
arrayBuffer(): Promise<ArrayBuffer>;
slice(start?: number, end?: number, contentType?: string): Blob;
stream(): ReadableStream;
text(): Promise<string>;
}
interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}
/** Provides information about files and allows JavaScript in a web page to
* access their content. */
declare class File extends Blob {
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag,
);
readonly lastModified: number;
readonly name: string;
}
type FormDataEntryValue = File | string; type FormDataEntryValue = File | string;
/** Provides a way to easily construct a set of key/value pairs representing /** Provides a way to easily construct a set of key/value pairs representing

View file

@ -68,10 +68,6 @@ pub fn init(isolate: &mut JsRuntime) {
"deno:op_crates/fetch/20_headers.js", "deno:op_crates/fetch/20_headers.js",
include_str!("20_headers.js"), include_str!("20_headers.js"),
), ),
(
"deno:op_crates/fetch/21_file.js",
include_str!("21_file.js"),
),
( (
"deno:op_crates/fetch/26_fetch.js", "deno:op_crates/fetch/26_fetch.js",
include_str!("26_fetch.js"), include_str!("26_fetch.js"),

View file

@ -6,7 +6,7 @@
/// <reference path="../web/internal.d.ts" /> /// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" /> /// <reference path="../web/lib.deno_web.d.ts" />
/// <reference path="./internal.d.ts" /> /// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_file.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
"use strict"; "use strict";

View file

@ -0,0 +1,334 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
// @ts-check
/// <reference no-default-lib="true" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../webidl/internal.d.ts" />
/// <reference path="../web/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" />
/// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_file.d.ts" />
/// <reference lib="esnext" />
"use strict";
((window) => {
const webidl = window.__bootstrap.webidl;
const base64 = window.__bootstrap.base64;
const state = Symbol("[[state]]");
const result = Symbol("[[result]]");
const error = Symbol("[[error]]");
const aborted = Symbol("[[aborted]]");
class FileReader extends EventTarget {
/** @type {"empty" | "loading" | "done"} */
[state] = "empty";
/** @type {null | string | ArrayBuffer} */
[result] = null;
/** @type {null | DOMException} */
[error] = null;
[aborted] = false;
/**
* @param {Blob} blob
* @param {{kind: "ArrayBuffer" | "Text" | "DataUrl", encoding?: string}} readtype
*/
#readOperation = async (blob, readtype) => {
// 1. If frs state is "loading", throw an InvalidStateError DOMException.
if (this[state] === "loading") {
throw new DOMException(
"Invalid FileReader state.",
"InvalidStateError",
);
}
// 2. Set frs state to "loading".
this[state] = "loading";
// 3. Set frs result to null.
this[result] = null;
// 4. Set frs error to null.
this[error] = null;
// 5. Let stream be the result of calling get stream on blob.
const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream();
// 6. Let reader be the result of getting a reader from stream.
const reader = stream.getReader();
// 7. Let bytes be an empty byte sequence.
/** @type {Uint8Array[]} */
const chunks = [];
// 8. Let chunkPromise be the result of reading a chunk from stream with reader.
let chunkPromise = reader.read();
// 9. Let isFirstChunk be true.
let isFirstChunk = true;
// 10 in parallel while true
while (!this[aborted]) {
// 1. Wait for chunkPromise to be fulfilled or rejected.
try {
const chunk = await chunkPromise;
if (this[aborted]) return;
// 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr.
if (isFirstChunk) {
queueMicrotask(() => {
// fire a progress event for loadstart
const ev = new ProgressEvent("loadstart", {});
this.dispatchEvent(ev);
});
}
// 3. Set isFirstChunk to false.
isFirstChunk = false;
// 4. If chunkPromise is fulfilled with an object whose done property is false
// and whose value property is a Uint8Array object, run these steps:
if (!chunk.done && chunk.value instanceof Uint8Array) {
chunks.push(chunk.value);
// TODO(bartlomieju): (only) If roughly 50ms have passed since last progress
{
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
const ev = new ProgressEvent("progress", {
loaded: size,
});
this.dispatchEvent(ev);
}
chunkPromise = reader.read();
} // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm:
else if (chunk.done === true) {
queueMicrotask(() => {
// 1. Set frs state to "done".
this[state] = "done";
// 2. Let result be the result of package data given bytes, type, blobs type, and encodingName.
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
const bytes = new Uint8Array(size);
let offs = 0;
for (const chunk of chunks) {
bytes.set(chunk, offs);
offs += chunk.byteLength;
}
switch (readtype.kind) {
case "ArrayBuffer": {
this[result] = bytes.buffer;
break;
}
case "Text": {
const decoder = new TextDecoder(readtype.encoding);
this[result] = decoder.decode(bytes.buffer);
break;
}
case "DataUrl": {
this[result] = "data:application/octet-stream;base64," +
base64.fromByteArray(bytes);
break;
}
}
// 4.2 Fire a progress event called load at the fr.
{
const ev = new ProgressEvent("load", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev);
}
// 5. If frs state is not "loading", fire a progress event called loadend at the fr.
//Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {
lengthComputable: true,
loaded: size,
total: size,
});
this.dispatchEvent(ev);
}
});
break;
}
} catch (err) {
if (this[aborted]) return;
// chunkPromise rejected
this[state] = "done";
this[error] = err;
{
const ev = new ProgressEvent("error", {});
this.dispatchEvent(ev);
}
//If frs state is not "loading", fire a progress event called loadend at fr.
//Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {});
this.dispatchEvent(ev);
}
break;
}
}
};
static EMPTY = 0;
static LOADING = 1;
static DONE = 2;
constructor() {
super();
this[webidl.brand] = webidl.brand;
}
/** @returns {number} */
get readyState() {
webidl.assertBranded(this, FileReader);
switch (this[state]) {
case "empty":
return FileReader.EMPTY;
case "loading":
return FileReader.LOADING;
case "done":
return FileReader.DONE;
default:
throw new TypeError("Invalid state");
}
}
get result() {
webidl.assertBranded(this, FileReader);
return this[result];
}
get error() {
webidl.assertBranded(this, FileReader);
return this[error];
}
abort() {
webidl.assertBranded(this, FileReader);
// If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm.
if (
this[state] === "empty" ||
this[state] === "done"
) {
this[result] = null;
return;
}
// If context object's state is "loading" set context object's state to "done" and set context object's result to null.
if (this[state] === "loading") {
this[state] = "done";
this[result] = null;
}
// If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue.
// Terminate the algorithm for the read method being processed.
this[aborted] = true;
// Fire a progress event called abort at the context object.
const ev = new ProgressEvent("abort", {});
this.dispatchEvent(ev);
// If context object's state is not "loading", fire a progress event called loadend at the context object.
if (this[state] !== "loading") {
const ev = new ProgressEvent("loadend", {});
this.dispatchEvent(ev);
}
}
/** @param {Blob} blob */
readAsArrayBuffer(blob) {
webidl.assertBranded(this, FileReader);
const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
this.#readOperation(blob, { kind: "ArrayBuffer" });
}
/** @param {Blob} blob */
readAsBinaryString(blob) {
webidl.assertBranded(this, FileReader);
const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "ArrayBuffer" });
}
/** @param {Blob} blob */
readAsDataURL(blob) {
webidl.assertBranded(this, FileReader);
const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "DataUrl" });
}
/**
* @param {Blob} blob
* @param {string} [encoding]
*/
readAsText(blob, encoding) {
webidl.assertBranded(this, FileReader);
const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'";
webidl.requiredArguments(arguments.length, 1, { prefix });
if (encoding !== undefined) {
encoding = webidl.converters["DOMString"](encoding, {
prefix,
context: "Argument 2",
});
}
// alias for readAsArrayBuffer
this.#readOperation(blob, { kind: "Text", encoding });
}
}
const handlerSymbol = Symbol("eventHandlers");
function makeWrappedHandler(handler) {
function wrappedHandler(...args) {
if (typeof wrappedHandler.handler !== "function") {
return;
}
return wrappedHandler.handler.call(this, ...args);
}
wrappedHandler.handler = handler;
return wrappedHandler;
}
// TODO(benjamingr) reuse when we can reuse code between web crates
function defineEventHandler(emitter, name) {
// HTML specification section 8.1.5.1
Object.defineProperty(emitter, `on${name}`, {
get() {
return this[handlerSymbol]?.get(name)?.handler;
},
set(value) {
if (!this[handlerSymbol]) {
this[handlerSymbol] = new Map();
}
let handlerWrapper = this[handlerSymbol]?.get(name);
if (handlerWrapper) {
handlerWrapper.handler = value;
} else {
handlerWrapper = makeWrappedHandler(value);
this.addEventListener(name, handlerWrapper);
}
this[handlerSymbol].set(name, handlerWrapper);
},
configurable: true,
enumerable: true,
});
}
defineEventHandler(FileReader.prototype, "error");
defineEventHandler(FileReader.prototype, "loadstart");
defineEventHandler(FileReader.prototype, "load");
defineEventHandler(FileReader.prototype, "loadend");
defineEventHandler(FileReader.prototype, "progress");
defineEventHandler(FileReader.prototype, "abort");
window.__bootstrap.fileReader = {
FileReader,
};
})(this);

17
op_crates/file/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
[package]
name = "deno_file"
version = "0.1.0"
edition = "2018"
description = "File API implementation for Deno"
authors = ["the Deno authors"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/denoland/deno"
[lib]
path = "lib.rs"
[dependencies]
deno_core = { version = "0.83.0", path = "../../core" }

5
op_crates/file/README.md Normal file
View file

@ -0,0 +1,5 @@
# deno_file
This crate implements the File API.
Spec: https://w3c.github.io/FileAPI

18
op_crates/file/internal.d.ts vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
declare namespace globalThis {
declare namespace __bootstrap {
declare var file: {
Blob: typeof Blob & {
[globalThis.__bootstrap.file._byteSequence]: Uint8Array;
};
_byteSequence: unique symbol;
File: typeof File & {
[globalThis.__bootstrap.file._byteSequence]: Uint8Array;
};
};
}
}

40
op_crates/file/lib.deno_file.d.ts vendored Normal file
View file

@ -0,0 +1,40 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
type BlobPart = BufferSource | Blob | string;
interface BlobPropertyBag {
type?: string;
endings?: "transparent" | "native";
}
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
declare class Blob {
constructor(blobParts?: BlobPart[], options?: BlobPropertyBag);
readonly size: number;
readonly type: string;
arrayBuffer(): Promise<ArrayBuffer>;
slice(start?: number, end?: number, contentType?: string): Blob;
stream(): ReadableStream<Uint8Array>;
text(): Promise<string>;
}
interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}
/** Provides information about files and allows JavaScript in a web page to
* access their content. */
declare class File extends Blob {
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag,
);
readonly lastModified: number;
readonly name: string;
}

22
op_crates/file/lib.rs Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::JsRuntime;
use std::path::PathBuf;
/// Load and execute the javascript code.
pub fn init(isolate: &mut JsRuntime) {
let files = vec![
("deno:op_crates/file/01_file.js", include_str!("01_file.js")),
(
"deno:op_crates/file/02_filereader.js",
include_str!("02_filereader.js"),
),
];
for (url, source_code) in files {
isolate.execute(url, source_code).unwrap();
}
}
pub fn get_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_file.d.ts")
}

View file

@ -1,261 +0,0 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const base64 = window.__bootstrap.base64;
async function readOperation(fr, blob, readtype) {
// Implementation from https://w3c.github.io/FileAPI/ notes
// And body of deno blob.ts readBytes
fr.aborting = false;
// 1. If frs state is "loading", throw an InvalidStateError DOMException.
if (fr.readyState === FileReader.LOADING) {
throw new DOMException(
"Invalid FileReader state.",
"InvalidStateError",
);
}
// 2. Set frs state to "loading".
fr.readyState = FileReader.LOADING;
// 3. Set frs result to null.
fr.result = null;
// 4. Set frs error to null.
fr.error = null;
// 5. Let stream be the result of calling get stream on blob.
const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream();
// 6. Let reader be the result of getting a reader from stream.
const reader = stream.getReader();
// 7. Let bytes be an empty byte sequence.
//let bytes = new Uint8Array();
const chunks /*: Uint8Array[]*/ = [];
// 8. Let chunkPromise be the result of reading a chunk from stream with reader.
let chunkPromise = reader.read();
// 9. Let isFirstChunk be true.
let isFirstChunk = true;
// 10 in parallel while true
while (!fr.aborting) {
// 1. Wait for chunkPromise to be fulfilled or rejected.
try {
const chunk = await chunkPromise;
// 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr.
if (isFirstChunk) {
queueMicrotask(() => {
// fire a progress event for loadstart
const ev = new ProgressEvent("loadstart", {});
fr.dispatchEvent(ev);
});
}
// 3. Set isFirstChunk to false.
isFirstChunk = false;
// 4. If chunkPromise is fulfilled with an object whose done property is false
// and whose value property is a Uint8Array object, run these steps:
if (!chunk.done && chunk.value instanceof Uint8Array) {
chunks.push(chunk.value);
// TODO(bartlomieju): (only) If roughly 50ms have passed since last progress
{
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
const ev = new ProgressEvent("progress", {
loaded: size,
});
fr.dispatchEvent(ev);
}
chunkPromise = reader.read();
} // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm:
else if (chunk.done === true) {
queueMicrotask(() => {
if (fr.aborting) {
return;
}
// 1. Set frs state to "done".
fr.readyState = FileReader.DONE;
// 2. Let result be the result of package data given bytes, type, blobs type, and encodingName.
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
const bytes = new Uint8Array(size);
let offs = 0;
for (const chunk of chunks) {
bytes.set(chunk, offs);
offs += chunk.byteLength;
}
switch (readtype.kind) {
case "ArrayBuffer": {
fr.result = bytes.buffer;
break;
}
case "Text": {
const decoder = new TextDecoder(readtype.encoding);
fr.result = decoder.decode(bytes.buffer);
break;
}
case "DataUrl": {
fr.result = "data:application/octet-stream;base64," +
base64.fromByteArray(bytes);
break;
}
}
// 4.2 Fire a progress event called load at the fr.
{
const ev = new ProgressEvent("load", {
lengthComputable: true,
loaded: size,
total: size,
});
fr.dispatchEvent(ev);
}
// 5. If frs state is not "loading", fire a progress event called loadend at the fr.
//Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired.
if (fr.readyState !== FileReader.LOADING) {
const ev = new ProgressEvent("loadend", {
lengthComputable: true,
loaded: size,
total: size,
});
fr.dispatchEvent(ev);
}
});
break;
}
} catch (err) {
if (fr.aborting) {
break;
}
// chunkPromise rejected
fr.readyState = FileReader.DONE;
fr.error = err;
{
const ev = new ProgressEvent("error", {});
fr.dispatchEvent(ev);
}
//If frs state is not "loading", fire a progress event called loadend at fr.
//Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired.
if (fr.readyState !== FileReader.LOADING) {
const ev = new ProgressEvent("loadend", {});
fr.dispatchEvent(ev);
}
break;
}
}
}
class FileReader extends EventTarget {
error = null;
readyState = FileReader.EMPTY;
result = null;
aborting = false;
constructor() {
super();
}
abort() {
// If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm.
if (
this.readyState === FileReader.EMPTY ||
this.readyState === FileReader.DONE
) {
this.result = null;
return;
}
// If context object's state is "loading" set context object's state to "done" and set context object's result to null.
if (this.readyState === FileReader.LOADING) {
this.readyState = FileReader.DONE;
this.result = null;
}
// If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue.
// Terminate the algorithm for the read method being processed.
this.aborting = true;
// Fire a progress event called abort at the context object.
const ev = new ProgressEvent("abort", {});
this.dispatchEvent(ev);
// If context object's state is not "loading", fire a progress event called loadend at the context object.
if (this.readyState !== FileReader.LOADING) {
const ev = new ProgressEvent("loadend", {});
this.dispatchEvent(ev);
}
}
readAsArrayBuffer(blob) {
readOperation(this, blob, { kind: "ArrayBuffer" });
}
readAsBinaryString(blob) {
// alias for readAsArrayBuffer
readOperation(this, blob, { kind: "ArrayBuffer" });
}
readAsDataURL(blob) {
readOperation(this, blob, { kind: "DataUrl" });
}
readAsText(blob, encoding) {
readOperation(this, blob, { kind: "Text", encoding });
}
}
FileReader.EMPTY = 0;
FileReader.LOADING = 1;
FileReader.DONE = 2;
const handlerSymbol = Symbol("eventHandlers");
function makeWrappedHandler(handler) {
function wrappedHandler(...args) {
if (typeof wrappedHandler.handler !== "function") {
return;
}
return wrappedHandler.handler.call(this, ...args);
}
wrappedHandler.handler = handler;
return wrappedHandler;
}
// TODO(benjamingr) reuse when we can reuse code between web crates
function defineEventHandler(emitter, name) {
// HTML specification section 8.1.5.1
Object.defineProperty(emitter, `on${name}`, {
get() {
return this[handlerSymbol]?.get(name)?.handler;
},
set(value) {
if (!this[handlerSymbol]) {
this[handlerSymbol] = new Map();
}
let handlerWrapper = this[handlerSymbol]?.get(name);
if (handlerWrapper) {
handlerWrapper.handler = value;
} else {
handlerWrapper = makeWrappedHandler(value);
this.addEventListener(name, handlerWrapper);
}
this[handlerSymbol].set(name, handlerWrapper);
},
configurable: true,
enumerable: true,
});
}
defineEventHandler(FileReader.prototype, "error");
defineEventHandler(FileReader.prototype, "loadstart");
defineEventHandler(FileReader.prototype, "load");
defineEventHandler(FileReader.prototype, "loadend");
defineEventHandler(FileReader.prototype, "progress");
defineEventHandler(FileReader.prototype, "abort");
window.__bootstrap.fileReader = {
FileReader,
};
})(this);

View file

@ -12,5 +12,11 @@ declare namespace globalThis {
declare var location: { declare var location: {
getLocationHref(): string | undefined; getLocationHref(): string | undefined;
}; };
declare var base64: {
byteLength(b64: string): number;
toByteArray(b64: string): Uint8Array;
fromByteArray(uint8: Uint8Array): string;
};
} }
} }

View file

@ -30,10 +30,6 @@ pub fn init(isolate: &mut JsRuntime) {
"deno:op_crates/web/12_location.js", "deno:op_crates/web/12_location.js",
include_str!("12_location.js"), include_str!("12_location.js"),
), ),
(
"deno:op_crates/web/21_filereader.js",
include_str!("21_filereader.js"),
),
]; ];
for (url, source_code) in files { for (url, source_code) in files {
isolate.execute(url, source_code).unwrap(); isolate.execute(url, source_code).unwrap();

View file

@ -22,6 +22,7 @@ deno_core = { path = "../core", version = "0.83.0" }
deno_console = { path = "../op_crates/console", version = "0.2.1" } deno_console = { path = "../op_crates/console", version = "0.2.1" }
deno_crypto = { path = "../op_crates/crypto", version = "0.16.1" } deno_crypto = { path = "../op_crates/crypto", version = "0.16.1" }
deno_fetch = { path = "../op_crates/fetch", version = "0.24.1" } deno_fetch = { path = "../op_crates/fetch", version = "0.24.1" }
deno_file = { path = "../op_crates/file", version = "0.1.0" }
deno_web = { path = "../op_crates/web", version = "0.32.1" } deno_web = { path = "../op_crates/web", version = "0.32.1" }
deno_url = { path = "../op_crates/url", version = "0.2.1" } deno_url = { path = "../op_crates/url", version = "0.2.1" }
deno_webidl = { path = "../op_crates/webidl", version = "0.2.1" } deno_webidl = { path = "../op_crates/webidl", version = "0.2.1" }
@ -37,6 +38,7 @@ deno_core = { path = "../core", version = "0.83.0" }
deno_console = { path = "../op_crates/console", version = "0.2.1" } deno_console = { path = "../op_crates/console", version = "0.2.1" }
deno_crypto = { path = "../op_crates/crypto", version = "0.16.1" } deno_crypto = { path = "../op_crates/crypto", version = "0.16.1" }
deno_fetch = { path = "../op_crates/fetch", version = "0.24.1" } deno_fetch = { path = "../op_crates/fetch", version = "0.24.1" }
deno_file = { path = "../op_crates/file", version = "0.1.0" }
deno_web = { path = "../op_crates/web", version = "0.32.1" } deno_web = { path = "../op_crates/web", version = "0.32.1" }
deno_url = { path = "../op_crates/url", version = "0.2.1" } deno_url = { path = "../op_crates/url", version = "0.2.1" }
deno_webidl = { path = "../op_crates/webidl", version = "0.2.1" } deno_webidl = { path = "../op_crates/webidl", version = "0.2.1" }

View file

@ -17,6 +17,7 @@ fn create_snapshot(
deno_console::init(&mut js_runtime); deno_console::init(&mut js_runtime);
deno_url::init(&mut js_runtime); deno_url::init(&mut js_runtime);
deno_web::init(&mut js_runtime); deno_web::init(&mut js_runtime);
deno_file::init(&mut js_runtime);
deno_fetch::init(&mut js_runtime); deno_fetch::init(&mut js_runtime);
deno_websocket::init(&mut js_runtime); deno_websocket::init(&mut js_runtime);
deno_crypto::init(&mut js_runtime); deno_crypto::init(&mut js_runtime);

View file

@ -5,6 +5,7 @@
pub use deno_console; pub use deno_console;
pub use deno_crypto; pub use deno_crypto;
pub use deno_fetch; pub use deno_fetch;
pub use deno_file;
pub use deno_url; pub use deno_url;
pub use deno_web; pub use deno_web;
pub use deno_webgpu; pub use deno_webgpu;

@ -1 +1 @@
Subproject commit 681d273a49e7b5228394285b0c017f1b4c0d33b0 Subproject commit f897da00871cf39366bc2f0ceec051c65bc75703

View file

@ -358,7 +358,7 @@
], ],
"patched-global.any.js": true, "patched-global.any.js": true,
"reentrant-strategies.any.js": true, "reentrant-strategies.any.js": true,
"tee.any.js": true, "tee.any.js": false,
"templated.any.js": [ "templated.any.js": [
"ReadableStream (empty) reader: canceling via the stream should fail" "ReadableStream (empty) reader: canceling via the stream should fail"
] ]
@ -784,7 +784,8 @@
}, },
"file": { "file": {
"File-constructor.any.js": true "File-constructor.any.js": true
} },
"fileReader.any.js": true
}, },
"html": { "html": {
"webappapis": { "webappapis": {