From 6091ea098a86cafb38aed3228a64ad96a046c817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 10 Jun 2021 15:26:10 +0200 Subject: refactor: merge deno_file crate into deno_web (#10914) This refactor makes it so there's one less crate to publish on each release. --- Cargo.lock | 13 +- Cargo.toml | 1 - cli/Cargo.toml | 1 - cli/build.rs | 5 - cli/dts/lib.deno.shared_globals.d.ts | 1 - cli/file_fetcher.rs | 4 +- cli/lsp/registries.rs | 2 +- cli/main.rs | 3 +- cli/program_state.rs | 2 +- cli/specifier_handler.rs | 2 +- cli/standalone.rs | 2 +- cli/tsc.rs | 1 - extensions/fetch/20_headers.js | 3 +- extensions/fetch/21_formdata.js | 3 +- extensions/fetch/22_body.js | 3 +- extensions/fetch/22_http_client.js | 3 +- extensions/fetch/23_request.js | 3 +- extensions/fetch/23_response.js | 3 +- extensions/fetch/Cargo.toml | 2 +- extensions/fetch/lib.rs | 2 +- extensions/file/01_file.js | 415 ---------------------------------- extensions/file/02_filereader.js | 416 ----------------------------------- extensions/file/03_blob_url.js | 63 ------ extensions/file/Cargo.toml | 18 -- extensions/file/README.md | 5 - extensions/file/internal.d.ts | 18 -- extensions/file/lib.deno_file.d.ts | 40 ---- extensions/file/lib.rs | 120 ---------- extensions/web/09_file.js | 414 ++++++++++++++++++++++++++++++++++ extensions/web/10_filereader.js | 415 ++++++++++++++++++++++++++++++++++ extensions/web/11_blob_url.js | 62 ++++++ extensions/web/Cargo.toml | 1 + extensions/web/README.md | 3 +- extensions/web/internal.d.ts | 10 + extensions/web/lib.deno_web.d.ts | 36 +++ extensions/web/lib.rs | 101 ++++++++- runtime/Cargo.toml | 2 - runtime/build.rs | 3 +- runtime/examples/hello_runtime.rs | 2 +- runtime/lib.rs | 1 - runtime/web_worker.rs | 8 +- runtime/worker.rs | 5 +- tools/cut_a_release.md | 6 +- 43 files changed, 1064 insertions(+), 1159 deletions(-) delete mode 100644 extensions/file/01_file.js delete mode 100644 extensions/file/02_filereader.js delete mode 100644 extensions/file/03_blob_url.js delete mode 100644 extensions/file/Cargo.toml delete mode 100644 extensions/file/README.md delete mode 100644 extensions/file/internal.d.ts delete mode 100644 extensions/file/lib.deno_file.d.ts delete mode 100644 extensions/file/lib.rs create mode 100644 extensions/web/09_file.js create mode 100644 extensions/web/10_filereader.js create mode 100644 extensions/web/11_blob_url.js diff --git a/Cargo.lock b/Cargo.lock index 098c2799b..022a609b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,7 +535,6 @@ dependencies = [ "deno_crypto", "deno_doc", "deno_fetch", - "deno_file", "deno_lint", "deno_runtime", "deno_timers", @@ -674,7 +673,7 @@ dependencies = [ "bytes", "data-url", "deno_core", - "deno_file", + "deno_web", "http", "reqwest", "serde", @@ -683,14 +682,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "deno_file" -version = "0.7.0" -dependencies = [ - "deno_core", - "uuid", -] - [[package]] name = "deno_lint" version = "0.6.1" @@ -722,7 +713,6 @@ dependencies = [ "deno_core", "deno_crypto", "deno_fetch", - "deno_file", "deno_timers", "deno_url", "deno_web", @@ -790,6 +780,7 @@ dependencies = [ "encoding_rs", "futures", "serde", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5f60ac660..26ea13647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "extensions/console", "extensions/crypto", "extensions/fetch", - "extensions/file", "extensions/timers", "extensions/url", "extensions/web", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 305b19c9b..64f261c7b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -25,7 +25,6 @@ deno_console = { version = "0.8.0", path = "../extensions/console" } deno_core = { version = "0.89.0", path = "../core" } deno_crypto = { version = "0.22.0", path = "../extensions/crypto" } deno_fetch = { version = "0.30.0", path = "../extensions/fetch" } -deno_file = { version = "0.7.0", path = "../extensions/file" } deno_timers = { version = "0.6.0", path = "../extensions/timers" } deno_url = { version = "0.8.0", path = "../extensions/url" } deno_web = { version = "0.39.0", path = "../extensions/web" } diff --git a/cli/build.rs b/cli/build.rs index 053d05395..c88c00376 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -59,7 +59,6 @@ fn create_compiler_snapshot( 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.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.webgpu", deno_webgpu::get_declaration()); op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration()); @@ -275,10 +274,6 @@ fn main() { "cargo:rustc-env=DENO_WEB_LIB_PATH={}", deno_web::get_declaration().display() ); - println!( - "cargo:rustc-env=DENO_FILE_LIB_PATH={}", - deno_file::get_declaration().display() - ); println!( "cargo:rustc-env=DENO_FETCH_LIB_PATH={}", deno_fetch::get_declaration().display() diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts index e35de2e77..a59a7af36 100644 --- a/cli/dts/lib.deno.shared_globals.d.ts +++ b/cli/dts/lib.deno.shared_globals.d.ts @@ -6,7 +6,6 @@ /// /// /// -/// /// /// /// diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 8403c660d..bd16cb1e1 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -19,7 +19,7 @@ use deno_core::futures; use deno_core::futures::future::FutureExt; use deno_core::ModuleSpecifier; use deno_runtime::deno_fetch::reqwest; -use deno_runtime::deno_file::BlobUrlStore; +use deno_runtime::deno_web::BlobUrlStore; use deno_runtime::permissions::Permissions; use log::debug; use log::info; @@ -579,7 +579,7 @@ mod tests { use deno_core::error::get_custom_error_class; use deno_core::resolve_url; use deno_core::resolve_url_or_path; - use deno_runtime::deno_file::Blob; + use deno_runtime::deno_web::Blob; use std::rc::Rc; use tempfile::TempDir; diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs index 29ec3258b..83b322eab 100644 --- a/cli/lsp/registries.rs +++ b/cli/lsp/registries.rs @@ -26,7 +26,7 @@ use deno_core::serde_json::json; use deno_core::url::Position; use deno_core::url::Url; use deno_core::ModuleSpecifier; -use deno_runtime::deno_file::BlobUrlStore; +use deno_runtime::deno_web::BlobUrlStore; use deno_runtime::permissions::Permissions; use log::error; use lspower::lsp; diff --git a/cli/main.rs b/cli/main.rs index 23b51219f..5827203ce 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -330,12 +330,11 @@ fn print_cache_info( pub fn get_types(unstable: bool) -> String { let mut types = format!( - "{}\n{}\n{}\n{}\n{}\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_CONSOLE_LIB, crate::tsc::DENO_URL_LIB, crate::tsc::DENO_WEB_LIB, - crate::tsc::DENO_FILE_LIB, crate::tsc::DENO_FETCH_LIB, crate::tsc::DENO_WEBGPU_LIB, crate::tsc::DENO_WEBSOCKET_LIB, diff --git a/cli/program_state.rs b/cli/program_state.rs index c3c399b69..95362165f 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -16,7 +16,7 @@ use crate::source_maps::SourceMapGetter; use crate::specifier_handler::FetchHandler; use crate::version; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; -use deno_runtime::deno_file::BlobUrlStore; +use deno_runtime::deno_web::BlobUrlStore; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::permissions::Permissions; diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs index 900b918ab..cd27e45cc 100644 --- a/cli/specifier_handler.rs +++ b/cli/specifier_handler.rs @@ -574,7 +574,7 @@ pub mod tests { use crate::file_fetcher::CacheSetting; use crate::http_cache::HttpCache; use deno_core::resolve_url_or_path; - use deno_runtime::deno_file::BlobUrlStore; + use deno_runtime::deno_web::BlobUrlStore; use tempfile::TempDir; macro_rules! map ( diff --git a/cli/standalone.rs b/cli/standalone.rs index 7216a5fd6..8cb21f710 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -23,7 +23,7 @@ use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; -use deno_runtime::deno_file::BlobUrlStore; +use deno_runtime::deno_web::BlobUrlStore; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsOptions; use deno_runtime::worker::MainWorker; diff --git a/cli/tsc.rs b/cli/tsc.rs index 9d7b2a5ff..f87682fca 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -33,7 +33,6 @@ 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_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_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_WEBGPU_LIB: &str = include_str!(env!("DENO_WEBGPU_LIB_PATH")); pub static DENO_WEBSOCKET_LIB: &str = diff --git a/extensions/fetch/20_headers.js b/extensions/fetch/20_headers.js index 5f865749a..24dcdb74a 100644 --- a/extensions/fetch/20_headers.js +++ b/extensions/fetch/20_headers.js @@ -3,8 +3,7 @@ // @ts-check /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js index beae69aca..32a4e69a7 100644 --- a/extensions/fetch/21_formdata.js +++ b/extensions/fetch/21_formdata.js @@ -3,8 +3,7 @@ // @ts-check /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/22_body.js b/extensions/fetch/22_body.js index dcf1128a7..5039ab910 100644 --- a/extensions/fetch/22_body.js +++ b/extensions/fetch/22_body.js @@ -5,8 +5,7 @@ /// /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/22_http_client.js b/extensions/fetch/22_http_client.js index 770080cc9..9900cfb58 100644 --- a/extensions/fetch/22_http_client.js +++ b/extensions/fetch/22_http_client.js @@ -4,8 +4,7 @@ /// /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/23_request.js b/extensions/fetch/23_request.js index d03023687..ad05b5cd8 100644 --- a/extensions/fetch/23_request.js +++ b/extensions/fetch/23_request.js @@ -3,8 +3,7 @@ // @ts-check /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/23_response.js b/extensions/fetch/23_response.js index b1b037187..a8fd52162 100644 --- a/extensions/fetch/23_response.js +++ b/extensions/fetch/23_response.js @@ -4,8 +4,7 @@ /// /// /// -/// -/// +/// /// /// /// diff --git a/extensions/fetch/Cargo.toml b/extensions/fetch/Cargo.toml index c01af459a..27ab2e05d 100644 --- a/extensions/fetch/Cargo.toml +++ b/extensions/fetch/Cargo.toml @@ -17,7 +17,7 @@ path = "lib.rs" bytes = "1.0.1" data-url = "0.1.0" deno_core = { version = "0.89.0", path = "../../core" } -deno_file = { version = "0.7.0", path = "../file" } +deno_web = { version = "0.39.0", path = "../web" } http = "0.2.4" reqwest = { version = "0.11.3", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } serde = { version = "1.0.125", features = ["derive"] } diff --git a/extensions/fetch/lib.rs b/extensions/fetch/lib.rs index 2fbd38b3a..5638b1f33 100644 --- a/extensions/fetch/lib.rs +++ b/extensions/fetch/lib.rs @@ -25,7 +25,7 @@ use deno_core::ResourceId; use deno_core::ZeroCopyBuf; use data_url::DataUrl; -use deno_file::BlobUrlStore; +use deno_web::BlobUrlStore; use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; diff --git a/extensions/file/01_file.js b/extensions/file/01_file.js deleted file mode 100644 index 42298c3a1..000000000 --- a/extensions/file/01_file.js +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// @ts-check -/// -/// -/// -/// -/// -/// -/// -/// -"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 - // host os. - const isWindows = false; - - /** - * @param {string} input - * @param {number} position - * @returns {{result: string, position: number}} - */ - function collectCodepointsNotCRLF(input, position) { - // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and - // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points - const start = position; - for ( - let c = input.charAt(position); - position < input.length && !(c === "\r" || c === "\n"); - c = input.charAt(++position) - ); - return { result: input.slice(start, position), position }; - } - - /** - * @param {string} s - * @returns {string} - */ - function convertLineEndingsToNative(s) { - const nativeLineEnding = isWindows ? "\r\n" : "\n"; - - let { result, position } = collectCodepointsNotCRLF(s, 0); - - while (position < s.length) { - const codePoint = s.charAt(position); - if (codePoint === "\r") { - result += nativeLineEnding; - position++; - if (position < s.length && s.charAt(position) === "\n") { - position++; - } - } else if (codePoint === "\n") { - position++; - result += nativeLineEnding; - } - const { result: token, position: newPosition } = collectCodepointsNotCRLF( - s, - position, - ); - position = newPosition; - result += token; - } - - return result; - } - - /** - * @param {...Uint8Array} bytesArrays - * @returns {Uint8Array} - */ - function concatUint8Arrays(...bytesArrays) { - let byteLength = 0; - for (const bytes of bytesArrays) { - byteLength += bytes.byteLength; - } - const finalBytes = new Uint8Array(byteLength); - let current = 0; - for (const bytes of bytesArrays) { - finalBytes.set(bytes, current); - current += bytes.byteLength; - } - return finalBytes; - } - - /** @typedef {BufferSource | Blob | string} BlobPart */ - - /** - * @param {BlobPart[]} parts - * @param {string} endings - * @returns {Uint8Array} - */ - function processBlobParts(parts, endings) { - /** @type {Uint8Array[]} */ - const bytesArrays = []; - for (const element of parts) { - if (element instanceof ArrayBuffer) { - bytesArrays.push(new Uint8Array(element.slice(0))); - } else if (ArrayBuffer.isView(element)) { - const buffer = element.buffer.slice( - element.byteOffset, - element.byteOffset + element.byteLength, - ); - bytesArrays.push(new Uint8Array(buffer)); - } else if (element instanceof Blob) { - bytesArrays.push( - new Uint8Array(element[_byteSequence].buffer.slice(0)), - ); - } else if (typeof element === "string") { - let s = element; - if (endings == "native") { - s = convertLineEndingsToNative(s); - } - bytesArrays.push(core.encode(s)); - } else { - throw new TypeError("Unreachable code (invalild element type)"); - } - } - return concatUint8Arrays(...bytesArrays); - } - - /** - * @param {string} str - * @returns {string} - */ - function normalizeType(str) { - let normalizedType = str; - if (!/^[\x20-\x7E]*$/.test(str)) { - normalizedType = ""; - } - return normalizedType.toLowerCase(); - } - - const _byteSequence = Symbol("[[ByteSequence]]"); - - class Blob { - get [Symbol.toStringTag]() { - return "Blob"; - } - - /** @type {string} */ - #type; - - /** @type {Uint8Array} */ - [_byteSequence]; - - /** - * @param {BlobPart[]} blobParts - * @param {BlobPropertyBag} options - */ - constructor(blobParts = [], options = {}) { - const prefix = "Failed to construct 'Blob'"; - blobParts = webidl.converters["sequence"](blobParts, { - context: "Argument 1", - prefix, - }); - options = webidl.converters["BlobPropertyBag"](options, { - context: "Argument 2", - prefix, - }); - - this[webidl.brand] = webidl.brand; - - /** @type {Uint8Array} */ - this[_byteSequence] = processBlobParts( - blobParts, - options.endings, - ); - this.#type = normalizeType(options.type); - } - - /** @returns {number} */ - get size() { - webidl.assertBranded(this, Blob); - return this[_byteSequence].byteLength; - } - - /** @returns {string} */ - get type() { - webidl.assertBranded(this, Blob); - return this.#type; - } - - /** - * @param {number} [start] - * @param {number} [end] - * @param {string} [contentType] - * @returns {Blob} - */ - slice(start, end, contentType) { - webidl.assertBranded(this, Blob); - const prefix = "Failed to execute 'slice' on 'Blob'"; - if (start !== undefined) { - start = webidl.converters["long long"](start, { - clamp: true, - context: "Argument 1", - prefix, - }); - } - if (end !== undefined) { - end = webidl.converters["long long"](end, { - clamp: true, - context: "Argument 2", - prefix, - }); - } - if (contentType !== undefined) { - contentType = webidl.converters["DOMString"](contentType, { - context: "Argument 3", - prefix, - }); - } - - // deno-lint-ignore no-this-alias - const O = this; - /** @type {number} */ - let relativeStart; - if (start === undefined) { - relativeStart = 0; - } else { - if (start < 0) { - relativeStart = Math.max(O.size + start, 0); - } else { - relativeStart = Math.min(start, O.size); - } - } - /** @type {number} */ - let relativeEnd; - if (end === undefined) { - relativeEnd = O.size; - } else { - if (end < 0) { - relativeEnd = Math.max(O.size + end, 0); - } else { - relativeEnd = Math.min(end, O.size); - } - } - /** @type {string} */ - let relativeContentType; - if (contentType === undefined) { - relativeContentType = ""; - } else { - relativeContentType = normalizeType(contentType); - } - return new Blob([ - O[_byteSequence].buffer.slice(relativeStart, relativeEnd), - ], { type: relativeContentType }); - } - - /** - * @returns {ReadableStream} - */ - stream() { - webidl.assertBranded(this, Blob); - const bytes = this[_byteSequence]; - const stream = new ReadableStream({ - type: "bytes", - /** @param {ReadableByteStreamController} controller */ - start(controller) { - const chunk = new Uint8Array(bytes.buffer.slice(0)); - if (chunk.byteLength > 0) controller.enqueue(chunk); - controller.close(); - }, - }); - return stream; - } - - /** - * @returns {Promise} - */ - async text() { - webidl.assertBranded(this, Blob); - const buffer = await this.arrayBuffer(); - return core.decode(new Uint8Array(buffer)); - } - - /** - * @returns {Promise} - */ - async arrayBuffer() { - webidl.assertBranded(this, Blob); - const stream = this.stream(); - let bytes = new Uint8Array(); - for await (const chunk of stream) { - bytes = concatUint8Arrays(bytes, chunk); - } - return bytes.buffer; - } - } - - webidl.configurePrototype(Blob); - - webidl.converters["Blob"] = webidl.createInterfaceConverter("Blob", Blob); - webidl.converters["BlobPart"] = (V, opts) => { - // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) - if (typeof V == "object") { - if (V instanceof Blob) { - return webidl.converters["Blob"](V, opts); - } - if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBuffer.isView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } - } - return webidl.converters["USVString"](V, opts); - }; - webidl.converters["sequence"] = webidl.createSequenceConverter( - webidl.converters["BlobPart"], - ); - webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ - "transparent", - "native", - ]); - const blobPropertyBagDictionary = [ - { - key: "type", - converter: webidl.converters["DOMString"], - defaultValue: "", - }, - { - key: "endings", - converter: webidl.converters["EndingType"], - defaultValue: "transparent", - }, - ]; - webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( - "BlobPropertyBag", - blobPropertyBagDictionary, - ); - - const _Name = Symbol("[[Name]]"); - const _LastModfied = Symbol("[[LastModified]]"); - - class File extends Blob { - get [Symbol.toStringTag]() { - return "File"; - } - - /** @type {string} */ - [_Name]; - /** @type {number} */ - [_LastModfied]; - - /** - * @param {BlobPart[]} fileBits - * @param {string} fileName - * @param {FilePropertyBag} options - */ - constructor(fileBits, fileName, options = {}) { - const prefix = "Failed to construct 'File'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - fileBits = webidl.converters["sequence"](fileBits, { - context: "Argument 1", - prefix, - }); - fileName = webidl.converters["USVString"](fileName, { - context: "Argument 2", - prefix, - }); - options = webidl.converters["FilePropertyBag"](options, { - context: "Argument 3", - prefix, - }); - - super(fileBits, options); - - /** @type {string} */ - this[_Name] = fileName; - if (options.lastModified === undefined) { - /** @type {number} */ - this[_LastModfied] = new Date().getTime(); - } else { - /** @type {number} */ - this[_LastModfied] = options.lastModified; - } - } - - /** @returns {string} */ - get name() { - webidl.assertBranded(this, File); - return this[_Name]; - } - - /** @returns {number} */ - get lastModified() { - webidl.assertBranded(this, File); - return this[_LastModfied]; - } - } - - webidl.configurePrototype(File); - - webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( - "FilePropertyBag", - blobPropertyBagDictionary, - [ - { - key: "lastModified", - converter: webidl.converters["long long"], - }, - ], - ); - - window.__bootstrap.file = { - Blob, - _byteSequence, - File, - }; -})(this); diff --git a/extensions/file/02_filereader.js b/extensions/file/02_filereader.js deleted file mode 100644 index 7cc40f7a5..000000000 --- a/extensions/file/02_filereader.js +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// @ts-check -/// -/// -/// -/// -/// -/// -/// -/// - -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { forgivingBase64Encode } = window.__bootstrap.infra; - const { decode, TextDecoder } = window.__bootstrap.encoding; - const { parseMimeType } = window.__bootstrap.mimesniff; - - const state = Symbol("[[state]]"); - const result = Symbol("[[result]]"); - const error = Symbol("[[error]]"); - const aborted = Symbol("[[aborted]]"); - - class FileReader extends EventTarget { - get [Symbol.toStringTag]() { - return "FileReader"; - } - - /** @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" | "BinaryString", encoding?: string}} readtype - */ - #readOperation(blob, readtype) { - // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. - if (this[state] === "loading") { - throw new DOMException( - "Invalid FileReader state.", - "InvalidStateError", - ); - } - // 2. Set fr’s state to "loading". - this[state] = "loading"; - // 3. Set fr’s result to null. - this[result] = null; - // 4. Set fr’s error to null. - this[error] = null; - - // 5. Let stream be the result of calling get stream on blob. - const stream /*: ReadableStream*/ = 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 - (async () => { - 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) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (this[aborted]) return; - // 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, - }); - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (this[aborted]) return; - 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) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (this[aborted]) return; - // 1. Set fr’s state to "done". - this[state] = "done"; - // 2. Let result be the result of package data given bytes, type, blob’s 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 "BinaryString": - this[result] = [...new Uint8Array(bytes.buffer)].map((v) => - String.fromCodePoint(v) - ).join(""); - break; - case "Text": { - let decoder = undefined; - if (readtype.encoding) { - try { - decoder = new TextDecoder(readtype.encoding); - } catch { - // don't care about the error - } - } - if (decoder === undefined) { - const mimeType = parseMimeType(blob.type); - if (mimeType) { - const charset = mimeType.parameters.get("charset"); - if (charset) { - try { - decoder = new TextDecoder(charset); - } catch { - // don't care about the error - } - } - } - } - if (decoder === undefined) { - decoder = new TextDecoder(); - } - this[result] = decode(bytes, decoder.encoding); - break; - } - case "DataUrl": { - const mediaType = blob.type || "application/octet-stream"; - this[result] = `data:${mediaType};base64,${ - forgivingBase64Encode(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 fr’s 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) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (this[aborted]) return; - - // chunkPromise rejected - this[state] = "done"; - this[error] = err; - - { - const ev = new ProgressEvent("error", {}); - this.dispatchEvent(ev); - } - - //If fr’s 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; - } - } - })(); - } - - 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: "BinaryString" }); - } - - /** @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 }); - } - } - - webidl.configurePrototype(FileReader); - - Object.defineProperty(FileReader, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - Object.defineProperty(FileReader, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - Object.defineProperty(FileReader, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 2, - }); - Object.defineProperty(FileReader.prototype, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - Object.defineProperty(FileReader.prototype, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - Object.defineProperty(FileReader.prototype, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 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 ?? null; - }, - 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); diff --git a/extensions/file/03_blob_url.js b/extensions/file/03_blob_url.js deleted file mode 100644 index 2b5e16b55..000000000 --- a/extensions/file/03_blob_url.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// @ts-check -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -"use strict"; - -((window) => { - const core = Deno.core; - const webidl = window.__bootstrap.webidl; - const { _byteSequence } = window.__bootstrap.file; - const { URL } = window.__bootstrap.url; - - /** - * @param {Blob} blob - * @returns {string} - */ - function createObjectURL(blob) { - const prefix = "Failed to execute 'createObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - blob = webidl.converters["Blob"](blob, { - context: "Argument 1", - prefix, - }); - - const url = core.opSync( - "op_file_create_object_url", - blob.type, - blob[_byteSequence], - ); - - return url; - } - - /** - * @param {string} url - * @returns {void} - */ - function revokeObjectURL(url) { - const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - url = webidl.converters["DOMString"](url, { - context: "Argument 1", - prefix, - }); - - core.opSync( - "op_file_revoke_object_url", - url, - ); - } - - URL.createObjectURL = createObjectURL; - URL.revokeObjectURL = revokeObjectURL; -})(globalThis); diff --git a/extensions/file/Cargo.toml b/extensions/file/Cargo.toml deleted file mode 100644 index da70cf1b8..000000000 --- a/extensions/file/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -[package] -name = "deno_file" -version = "0.7.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.89.0", path = "../../core" } -uuid = { version = "0.8.2", features = ["v4"] } diff --git a/extensions/file/README.md b/extensions/file/README.md deleted file mode 100644 index c421bf004..000000000 --- a/extensions/file/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# deno_file - -This crate implements the File API. - -Spec: https://w3c.github.io/FileAPI diff --git a/extensions/file/internal.d.ts b/extensions/file/internal.d.ts deleted file mode 100644 index dd892e736..000000000 --- a/extensions/file/internal.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -/// -/// - -declare namespace globalThis { - declare namespace __bootstrap { - declare var file: { - Blob: typeof Blob & { - [globalThis.__bootstrap.file._byteSequence]: Uint8Array; - }; - readonly _byteSequence: unique symbol; - File: typeof File & { - [globalThis.__bootstrap.file._byteSequence]: Uint8Array; - }; - }; - } -} diff --git a/extensions/file/lib.deno_file.d.ts b/extensions/file/lib.deno_file.d.ts deleted file mode 100644 index a907c3f50..000000000 --- a/extensions/file/lib.deno_file.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -/// -/// - -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; - slice(start?: number, end?: number, contentType?: string): Blob; - stream(): ReadableStream; - text(): Promise; -} - -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; -} diff --git a/extensions/file/lib.rs b/extensions/file/lib.rs deleted file mode 100644 index 1dcc02d27..000000000 --- a/extensions/file/lib.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use deno_core::error::null_opbuf; -use deno_core::error::AnyError; -use deno_core::include_js_files; -use deno_core::op_sync; -use deno_core::url::Url; -use deno_core::Extension; -use deno_core::ModuleSpecifier; -use deno_core::ZeroCopyBuf; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use std::sync::Mutex; -use uuid::Uuid; - -#[derive(Debug, Clone)] -pub struct Blob { - pub data: Vec, - pub media_type: String, -} - -pub struct Location(pub Url); - -#[derive(Debug, Default, Clone)] -pub struct BlobUrlStore(Arc>>); - -impl BlobUrlStore { - pub fn get(&self, mut url: Url) -> Result, AnyError> { - let blob_store = self.0.lock().unwrap(); - url.set_fragment(None); - Ok(blob_store.get(&url).cloned()) - } - - pub fn insert(&self, blob: Blob, maybe_location: Option) -> Url { - let origin = if let Some(location) = maybe_location { - location.origin().ascii_serialization() - } else { - "null".to_string() - }; - let id = Uuid::new_v4(); - let url = Url::parse(&format!("blob:{}/{}", origin, id)).unwrap(); - - let mut blob_store = self.0.lock().unwrap(); - blob_store.insert(url.clone(), blob); - - url - } - - pub fn remove(&self, url: &ModuleSpecifier) { - let mut blob_store = self.0.lock().unwrap(); - blob_store.remove(&url); - } -} - -pub fn op_file_create_object_url( - state: &mut deno_core::OpState, - media_type: String, - zero_copy: Option, -) -> Result { - let data = zero_copy.ok_or_else(null_opbuf)?; - let blob = Blob { - data: data.to_vec(), - media_type, - }; - - let maybe_location = state.try_borrow::(); - let blob_store = state.borrow::(); - - let url = - blob_store.insert(blob, maybe_location.map(|location| location.0.clone())); - - Ok(url.to_string()) -} - -pub fn op_file_revoke_object_url( - state: &mut deno_core::OpState, - url: String, - _: (), -) -> Result<(), AnyError> { - let url = Url::parse(&url)?; - let blob_store = state.borrow::(); - blob_store.remove(&url); - Ok(()) -} - -pub fn init( - blob_url_store: BlobUrlStore, - maybe_location: Option, -) -> Extension { - Extension::builder() - .js(include_js_files!( - prefix "deno:extensions/file", - "01_file.js", - "02_filereader.js", - "03_blob_url.js", - )) - .ops(vec![ - ( - "op_file_create_object_url", - op_sync(op_file_create_object_url), - ), - ( - "op_file_revoke_object_url", - op_sync(op_file_revoke_object_url), - ), - ]) - .state(move |state| { - state.put(blob_url_store.clone()); - if let Some(location) = maybe_location.clone() { - state.put(Location(location)); - } - Ok(()) - }) - .build() -} - -pub fn get_declaration() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_file.d.ts") -} diff --git a/extensions/web/09_file.js b/extensions/web/09_file.js new file mode 100644 index 000000000..403bbee35 --- /dev/null +++ b/extensions/web/09_file.js @@ -0,0 +1,414 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// +"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 + // host os. + const isWindows = false; + + /** + * @param {string} input + * @param {number} position + * @returns {{result: string, position: number}} + */ + function collectCodepointsNotCRLF(input, position) { + // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + const start = position; + for ( + let c = input.charAt(position); + position < input.length && !(c === "\r" || c === "\n"); + c = input.charAt(++position) + ); + return { result: input.slice(start, position), position }; + } + + /** + * @param {string} s + * @returns {string} + */ + function convertLineEndingsToNative(s) { + const nativeLineEnding = isWindows ? "\r\n" : "\n"; + + let { result, position } = collectCodepointsNotCRLF(s, 0); + + while (position < s.length) { + const codePoint = s.charAt(position); + if (codePoint === "\r") { + result += nativeLineEnding; + position++; + if (position < s.length && s.charAt(position) === "\n") { + position++; + } + } else if (codePoint === "\n") { + position++; + result += nativeLineEnding; + } + const { result: token, position: newPosition } = collectCodepointsNotCRLF( + s, + position, + ); + position = newPosition; + result += token; + } + + return result; + } + + /** + * @param {...Uint8Array} bytesArrays + * @returns {Uint8Array} + */ + function concatUint8Arrays(...bytesArrays) { + let byteLength = 0; + for (const bytes of bytesArrays) { + byteLength += bytes.byteLength; + } + const finalBytes = new Uint8Array(byteLength); + let current = 0; + for (const bytes of bytesArrays) { + finalBytes.set(bytes, current); + current += bytes.byteLength; + } + return finalBytes; + } + + /** @typedef {BufferSource | Blob | string} BlobPart */ + + /** + * @param {BlobPart[]} parts + * @param {string} endings + * @returns {Uint8Array} + */ + function processBlobParts(parts, endings) { + /** @type {Uint8Array[]} */ + const bytesArrays = []; + for (const element of parts) { + if (element instanceof ArrayBuffer) { + bytesArrays.push(new Uint8Array(element.slice(0))); + } else if (ArrayBuffer.isView(element)) { + const buffer = element.buffer.slice( + element.byteOffset, + element.byteOffset + element.byteLength, + ); + bytesArrays.push(new Uint8Array(buffer)); + } else if (element instanceof Blob) { + bytesArrays.push( + new Uint8Array(element[_byteSequence].buffer.slice(0)), + ); + } else if (typeof element === "string") { + let s = element; + if (endings == "native") { + s = convertLineEndingsToNative(s); + } + bytesArrays.push(core.encode(s)); + } else { + throw new TypeError("Unreachable code (invalild element type)"); + } + } + return concatUint8Arrays(...bytesArrays); + } + + /** + * @param {string} str + * @returns {string} + */ + function normalizeType(str) { + let normalizedType = str; + if (!/^[\x20-\x7E]*$/.test(str)) { + normalizedType = ""; + } + return normalizedType.toLowerCase(); + } + + const _byteSequence = Symbol("[[ByteSequence]]"); + + class Blob { + get [Symbol.toStringTag]() { + return "Blob"; + } + + /** @type {string} */ + #type; + + /** @type {Uint8Array} */ + [_byteSequence]; + + /** + * @param {BlobPart[]} blobParts + * @param {BlobPropertyBag} options + */ + constructor(blobParts = [], options = {}) { + const prefix = "Failed to construct 'Blob'"; + blobParts = webidl.converters["sequence"](blobParts, { + context: "Argument 1", + prefix, + }); + options = webidl.converters["BlobPropertyBag"](options, { + context: "Argument 2", + prefix, + }); + + this[webidl.brand] = webidl.brand; + + /** @type {Uint8Array} */ + this[_byteSequence] = processBlobParts( + blobParts, + options.endings, + ); + this.#type = normalizeType(options.type); + } + + /** @returns {number} */ + get size() { + webidl.assertBranded(this, Blob); + return this[_byteSequence].byteLength; + } + + /** @returns {string} */ + get type() { + webidl.assertBranded(this, Blob); + return this.#type; + } + + /** + * @param {number} [start] + * @param {number} [end] + * @param {string} [contentType] + * @returns {Blob} + */ + slice(start, end, contentType) { + webidl.assertBranded(this, Blob); + const prefix = "Failed to execute 'slice' on 'Blob'"; + if (start !== undefined) { + start = webidl.converters["long long"](start, { + clamp: true, + context: "Argument 1", + prefix, + }); + } + if (end !== undefined) { + end = webidl.converters["long long"](end, { + clamp: true, + context: "Argument 2", + prefix, + }); + } + if (contentType !== undefined) { + contentType = webidl.converters["DOMString"](contentType, { + context: "Argument 3", + prefix, + }); + } + + // deno-lint-ignore no-this-alias + const O = this; + /** @type {number} */ + let relativeStart; + if (start === undefined) { + relativeStart = 0; + } else { + if (start < 0) { + relativeStart = Math.max(O.size + start, 0); + } else { + relativeStart = Math.min(start, O.size); + } + } + /** @type {number} */ + let relativeEnd; + if (end === undefined) { + relativeEnd = O.size; + } else { + if (end < 0) { + relativeEnd = Math.max(O.size + end, 0); + } else { + relativeEnd = Math.min(end, O.size); + } + } + /** @type {string} */ + let relativeContentType; + if (contentType === undefined) { + relativeContentType = ""; + } else { + relativeContentType = normalizeType(contentType); + } + return new Blob([ + O[_byteSequence].buffer.slice(relativeStart, relativeEnd), + ], { type: relativeContentType }); + } + + /** + * @returns {ReadableStream} + */ + stream() { + webidl.assertBranded(this, Blob); + const bytes = this[_byteSequence]; + const stream = new ReadableStream({ + type: "bytes", + /** @param {ReadableByteStreamController} controller */ + start(controller) { + const chunk = new Uint8Array(bytes.buffer.slice(0)); + if (chunk.byteLength > 0) controller.enqueue(chunk); + controller.close(); + }, + }); + return stream; + } + + /** + * @returns {Promise} + */ + async text() { + webidl.assertBranded(this, Blob); + const buffer = await this.arrayBuffer(); + return core.decode(new Uint8Array(buffer)); + } + + /** + * @returns {Promise} + */ + async arrayBuffer() { + webidl.assertBranded(this, Blob); + const stream = this.stream(); + let bytes = new Uint8Array(); + for await (const chunk of stream) { + bytes = concatUint8Arrays(bytes, chunk); + } + return bytes.buffer; + } + } + + webidl.configurePrototype(Blob); + + webidl.converters["Blob"] = webidl.createInterfaceConverter("Blob", Blob); + webidl.converters["BlobPart"] = (V, opts) => { + // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) + if (typeof V == "object") { + if (V instanceof Blob) { + return webidl.converters["Blob"](V, opts); + } + if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { + return webidl.converters["ArrayBuffer"](V, opts); + } + if (ArrayBuffer.isView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); + } + } + return webidl.converters["USVString"](V, opts); + }; + webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters["BlobPart"], + ); + webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ + "transparent", + "native", + ]); + const blobPropertyBagDictionary = [ + { + key: "type", + converter: webidl.converters["DOMString"], + defaultValue: "", + }, + { + key: "endings", + converter: webidl.converters["EndingType"], + defaultValue: "transparent", + }, + ]; + webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( + "BlobPropertyBag", + blobPropertyBagDictionary, + ); + + const _Name = Symbol("[[Name]]"); + const _LastModfied = Symbol("[[LastModified]]"); + + class File extends Blob { + get [Symbol.toStringTag]() { + return "File"; + } + + /** @type {string} */ + [_Name]; + /** @type {number} */ + [_LastModfied]; + + /** + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} options + */ + constructor(fileBits, fileName, options = {}) { + const prefix = "Failed to construct 'File'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + fileBits = webidl.converters["sequence"](fileBits, { + context: "Argument 1", + prefix, + }); + fileName = webidl.converters["USVString"](fileName, { + context: "Argument 2", + prefix, + }); + options = webidl.converters["FilePropertyBag"](options, { + context: "Argument 3", + prefix, + }); + + super(fileBits, options); + + /** @type {string} */ + this[_Name] = fileName; + if (options.lastModified === undefined) { + /** @type {number} */ + this[_LastModfied] = new Date().getTime(); + } else { + /** @type {number} */ + this[_LastModfied] = options.lastModified; + } + } + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, File); + return this[_Name]; + } + + /** @returns {number} */ + get lastModified() { + webidl.assertBranded(this, File); + return this[_LastModfied]; + } + } + + webidl.configurePrototype(File); + + webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( + "FilePropertyBag", + blobPropertyBagDictionary, + [ + { + key: "lastModified", + converter: webidl.converters["long long"], + }, + ], + ); + + window.__bootstrap.file = { + Blob, + _byteSequence, + File, + }; +})(this); diff --git a/extensions/web/10_filereader.js b/extensions/web/10_filereader.js new file mode 100644 index 000000000..b8bb6172a --- /dev/null +++ b/extensions/web/10_filereader.js @@ -0,0 +1,415 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { forgivingBase64Encode } = window.__bootstrap.infra; + const { decode, TextDecoder } = window.__bootstrap.encoding; + const { parseMimeType } = window.__bootstrap.mimesniff; + + const state = Symbol("[[state]]"); + const result = Symbol("[[result]]"); + const error = Symbol("[[error]]"); + const aborted = Symbol("[[aborted]]"); + + class FileReader extends EventTarget { + get [Symbol.toStringTag]() { + return "FileReader"; + } + + /** @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" | "BinaryString", encoding?: string}} readtype + */ + #readOperation(blob, readtype) { + // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. + if (this[state] === "loading") { + throw new DOMException( + "Invalid FileReader state.", + "InvalidStateError", + ); + } + // 2. Set fr’s state to "loading". + this[state] = "loading"; + // 3. Set fr’s result to null. + this[result] = null; + // 4. Set fr’s error to null. + this[error] = null; + + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream*/ = 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 + (async () => { + 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) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (this[aborted]) return; + // 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, + }); + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (this[aborted]) return; + 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) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (this[aborted]) return; + // 1. Set fr’s state to "done". + this[state] = "done"; + // 2. Let result be the result of package data given bytes, type, blob’s 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 "BinaryString": + this[result] = [...new Uint8Array(bytes.buffer)].map((v) => + String.fromCodePoint(v) + ).join(""); + break; + case "Text": { + let decoder = undefined; + if (readtype.encoding) { + try { + decoder = new TextDecoder(readtype.encoding); + } catch { + // don't care about the error + } + } + if (decoder === undefined) { + const mimeType = parseMimeType(blob.type); + if (mimeType) { + const charset = mimeType.parameters.get("charset"); + if (charset) { + try { + decoder = new TextDecoder(charset); + } catch { + // don't care about the error + } + } + } + } + if (decoder === undefined) { + decoder = new TextDecoder(); + } + this[result] = decode(bytes, decoder.encoding); + break; + } + case "DataUrl": { + const mediaType = blob.type || "application/octet-stream"; + this[result] = `data:${mediaType};base64,${ + forgivingBase64Encode(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 fr’s 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) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (this[aborted]) return; + + // chunkPromise rejected + this[state] = "done"; + this[error] = err; + + { + const ev = new ProgressEvent("error", {}); + this.dispatchEvent(ev); + } + + //If fr’s 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; + } + } + })(); + } + + 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: "BinaryString" }); + } + + /** @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 }); + } + } + + webidl.configurePrototype(FileReader); + + Object.defineProperty(FileReader, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, + }); + Object.defineProperty(FileReader, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, + }); + Object.defineProperty(FileReader, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, + }); + Object.defineProperty(FileReader.prototype, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, + }); + Object.defineProperty(FileReader.prototype, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, + }); + Object.defineProperty(FileReader.prototype, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 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 ?? null; + }, + 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); diff --git a/extensions/web/11_blob_url.js b/extensions/web/11_blob_url.js new file mode 100644 index 000000000..d030d79bd --- /dev/null +++ b/extensions/web/11_blob_url.js @@ -0,0 +1,62 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// +/// +/// +/// +/// +/// +"use strict"; + +((window) => { + const core = Deno.core; + const webidl = window.__bootstrap.webidl; + const { _byteSequence } = window.__bootstrap.file; + const { URL } = window.__bootstrap.url; + + /** + * @param {Blob} blob + * @returns {string} + */ + function createObjectURL(blob) { + const prefix = "Failed to execute 'createObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + blob = webidl.converters["Blob"](blob, { + context: "Argument 1", + prefix, + }); + + const url = core.opSync( + "op_file_create_object_url", + blob.type, + blob[_byteSequence], + ); + + return url; + } + + /** + * @param {string} url + * @returns {void} + */ + function revokeObjectURL(url) { + const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters["DOMString"](url, { + context: "Argument 1", + prefix, + }); + + core.opSync( + "op_file_revoke_object_url", + url, + ); + } + + URL.createObjectURL = createObjectURL; + URL.revokeObjectURL = revokeObjectURL; +})(globalThis); diff --git a/extensions/web/Cargo.toml b/extensions/web/Cargo.toml index a914d1b48..05a0f17f2 100644 --- a/extensions/web/Cargo.toml +++ b/extensions/web/Cargo.toml @@ -18,6 +18,7 @@ base64 = "0.13.0" deno_core = { version = "0.89.0", path = "../../core" } encoding_rs = "0.8.28" serde = "1.0" +uuid = { version = "0.8.2", features = ["v4"] } [dev-dependencies] futures = "0.3.15" diff --git a/extensions/web/README.md b/extensions/web/README.md index 03c1b7f89..d847ae52e 100644 --- a/extensions/web/README.md +++ b/extensions/web/README.md @@ -1,5 +1,6 @@ # deno web -Op crate that implements Event, TextEncoder, TextDecoder. +Op crate that implements Event, TextEncoder, TextDecoder and File API +(https://w3c.github.io/FileAPI). Testing for text encoding is done via WPT in cli/. diff --git a/extensions/web/internal.d.ts b/extensions/web/internal.d.ts index ebfa0d39e..4492e6554 100644 --- a/extensions/web/internal.d.ts +++ b/extensions/web/internal.d.ts @@ -70,5 +70,15 @@ declare namespace globalThis { atob(data: string): string; btoa(data: string): string; }; + + declare var file: { + Blob: typeof Blob & { + [globalThis.__bootstrap.file._byteSequence]: Uint8Array; + }; + readonly _byteSequence: unique symbol; + File: typeof File & { + [globalThis.__bootstrap.file._byteSequence]: Uint8Array; + }; + }; } } diff --git a/extensions/web/lib.deno_web.d.ts b/extensions/web/lib.deno_web.d.ts index 0c3673351..a1b6a0595 100644 --- a/extensions/web/lib.deno_web.d.ts +++ b/extensions/web/lib.deno_web.d.ts @@ -341,3 +341,39 @@ declare var FileReader: { readonly EMPTY: number; readonly LOADING: number; }; + +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; + slice(start?: number, end?: number, contentType?: string): Blob; + stream(): ReadableStream; + text(): Promise; +} + +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; +} diff --git a/extensions/web/lib.rs b/extensions/web/lib.rs index 95adb822a..b2906acaf 100644 --- a/extensions/web/lib.rs +++ b/extensions/web/lib.rs @@ -1,12 +1,15 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use deno_core::error::bad_resource_id; +use deno_core::error::null_opbuf; 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::url::Url; use deno_core::Extension; +use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -17,15 +20,21 @@ use encoding_rs::DecoderResult; use encoding_rs::Encoding; use serde::Deserialize; use serde::Serialize; - use std::borrow::Cow; use std::cell::RefCell; +use std::collections::HashMap; use std::fmt; use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; use std::usize; +use uuid::Uuid; /// Load and execute the javascript code. -pub fn init() -> Extension { +pub fn init( + blob_url_store: BlobUrlStore, + maybe_location: Option, +) -> Extension { Extension::builder() .js(include_js_files!( prefix "deno:extensions/web", @@ -38,6 +47,9 @@ pub fn init() -> Extension { "04_global_interfaces.js", "05_base64.js", "08_text_encoding.js", + "09_file.js", + "10_filereader.js", + "11_blob_url.js", "12_location.js", )) .ops(vec![ @@ -50,7 +62,22 @@ pub fn init() -> Extension { ("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)), + ( + "op_file_create_object_url", + op_sync(op_file_create_object_url), + ), + ( + "op_file_revoke_object_url", + op_sync(op_file_revoke_object_url), + ), ]) + .state(move |state| { + state.put(blob_url_store.clone()); + if let Some(location) = maybe_location.clone() { + state.put(Location(location)); + } + Ok(()) + }) .build() } @@ -318,3 +345,73 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .map(|_| "DOMExceptionInvalidCharacterError") }) } + +#[derive(Debug, Clone)] +pub struct Blob { + pub data: Vec, + pub media_type: String, +} + +pub struct Location(pub Url); + +#[derive(Debug, Default, Clone)] +pub struct BlobUrlStore(Arc>>); + +impl BlobUrlStore { + pub fn get(&self, mut url: Url) -> Result, AnyError> { + let blob_store = self.0.lock().unwrap(); + url.set_fragment(None); + Ok(blob_store.get(&url).cloned()) + } + + pub fn insert(&self, blob: Blob, maybe_location: Option) -> Url { + let origin = if let Some(location) = maybe_location { + location.origin().ascii_serialization() + } else { + "null".to_string() + }; + let id = Uuid::new_v4(); + let url = Url::parse(&format!("blob:{}/{}", origin, id)).unwrap(); + + let mut blob_store = self.0.lock().unwrap(); + blob_store.insert(url.clone(), blob); + + url + } + + pub fn remove(&self, url: &ModuleSpecifier) { + let mut blob_store = self.0.lock().unwrap(); + blob_store.remove(&url); + } +} + +pub fn op_file_create_object_url( + state: &mut deno_core::OpState, + media_type: String, + zero_copy: Option, +) -> Result { + let data = zero_copy.ok_or_else(null_opbuf)?; + let blob = Blob { + data: data.to_vec(), + media_type, + }; + + let maybe_location = state.try_borrow::(); + let blob_store = state.borrow::(); + + let url = + blob_store.insert(blob, maybe_location.map(|location| location.0.clone())); + + Ok(url.to_string()) +} + +pub fn op_file_revoke_object_url( + state: &mut deno_core::OpState, + url: String, + _: (), +) -> Result<(), AnyError> { + let url = Url::parse(&url)?; + let blob_store = state.borrow::(); + blob_store.remove(&url); + Ok(()) +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index a5245bb29..0bd8d13c4 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -23,7 +23,6 @@ deno_console = { version = "0.8.0", path = "../extensions/console" } deno_core = { version = "0.89.0", path = "../core" } deno_crypto = { version = "0.22.0", path = "../extensions/crypto" } deno_fetch = { version = "0.30.0", path = "../extensions/fetch" } -deno_file = { version = "0.7.0", path = "../extensions/file" } deno_timers = { version = "0.6.0", path = "../extensions/timers" } deno_url = { version = "0.8.0", path = "../extensions/url" } deno_web = { version = "0.39.0", path = "../extensions/web" } @@ -42,7 +41,6 @@ deno_console = { version = "0.8.0", path = "../extensions/console" } deno_core = { version = "0.89.0", path = "../core" } deno_crypto = { version = "0.22.0", path = "../extensions/crypto" } deno_fetch = { version = "0.30.0", path = "../extensions/fetch" } -deno_file = { version = "0.7.0", path = "../extensions/file" } deno_timers = { version = "0.6.0", path = "../extensions/timers" } deno_url = { version = "0.8.0", path = "../extensions/url" } deno_web = { version = "0.39.0", path = "../extensions/web" } diff --git a/runtime/build.rs b/runtime/build.rs index d228fffd6..701d027a4 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -41,8 +41,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec) { deno_webidl::init(), deno_console::init(), deno_url::init(), - deno_web::init(), - deno_file::init(Default::default(), Default::default()), + deno_web::init(Default::default(), Default::default()), deno_fetch::init::("".to_owned(), None), deno_websocket::init::( "".to_owned(), diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs index 95a82a25f..28ea64043 100644 --- a/runtime/examples/hello_runtime.rs +++ b/runtime/examples/hello_runtime.rs @@ -3,7 +3,7 @@ use deno_core::error::AnyError; use deno_core::FsModuleLoader; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; -use deno_runtime::deno_file::BlobUrlStore; +use deno_runtime::deno_web::BlobUrlStore; use deno_runtime::permissions::Permissions; use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; diff --git a/runtime/lib.rs b/runtime/lib.rs index 91bea8303..63829c2d2 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -4,7 +4,6 @@ pub use deno_broadcast_channel; pub use deno_console; pub use deno_crypto; pub use deno_fetch; -pub use deno_file; pub use deno_timers; pub use deno_url; pub use deno_web; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index d246843b4..948d063fc 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -28,7 +28,7 @@ use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::RuntimeOptions; use deno_core::ZeroCopyBuf; -use deno_file::BlobUrlStore; +use deno_web::BlobUrlStore; use log::debug; use std::cell::RefCell; use std::env; @@ -255,11 +255,7 @@ impl WebWorker { deno_webidl::init(), deno_console::init(), deno_url::init(), - deno_web::init(), - deno_file::init( - options.blob_url_store.clone(), - Some(main_module.clone()), - ), + deno_web::init(options.blob_url_store.clone(), Some(main_module.clone())), deno_fetch::init::( options.user_agent.clone(), options.ca_data.clone(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 971449859..83594fc4a 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -23,7 +23,7 @@ use deno_core::ModuleId; use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::RuntimeOptions; -use deno_file::BlobUrlStore; +use deno_web::BlobUrlStore; use log::debug; use std::env; use std::pin::Pin; @@ -96,8 +96,7 @@ impl MainWorker { deno_webidl::init(), deno_console::init(), deno_url::init(), - deno_web::init(), - deno_file::init(options.blob_url_store.clone(), options.location.clone()), + deno_web::init(options.blob_url_store.clone(), options.location.clone()), deno_fetch::init::( options.user_agent.clone(), options.ca_data.clone(), diff --git a/tools/cut_a_release.md b/tools/cut_a_release.md index 2b823738b..cff04b8f3 100644 --- a/tools/cut_a_release.md +++ b/tools/cut_a_release.md @@ -34,10 +34,8 @@ between the crates, it must be done in specific order: first - `bench_util` - crates in `extensions/` directory - - `deno_crypto` and `deno_webstorage` depend on `deno_web`, so the latter must - be bumped and released first - - `deno_fetch` depends on `deno_file`, so the latter must be bumped and - released first + - `deno_fetch`, `deno_crypto` and `deno_webstorage` depend on `deno_web`, so + the latter must be bumped and released first - `runtime` - this crate depends on `deno_core` and all crates in `extensions/` directory -- cgit v1.2.3