summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/disk_cache.rs2
-rw-r--r--cli/file_fetcher.rs132
-rw-r--r--cli/http_cache.rs2
-rw-r--r--cli/main.rs2
-rw-r--r--cli/program_state.rs8
-rw-r--r--cli/source_maps.rs1
-rw-r--r--cli/specifier_handler.rs6
-rw-r--r--cli/standalone.rs3
-rw-r--r--cli/tests/import_blob_url.ts13
-rw-r--r--cli/tests/import_blob_url.ts.out3
-rw-r--r--cli/tests/import_blob_url_error_stack.ts13
-rw-r--r--cli/tests/import_blob_url_error_stack.ts.out6
-rw-r--r--cli/tests/import_blob_url_import_relative.ts8
-rw-r--r--cli/tests/import_blob_url_import_relative.ts.out4
-rw-r--r--cli/tests/import_blob_url_imports.ts11
-rw-r--r--cli/tests/import_blob_url_imports.ts.out1
-rw-r--r--cli/tests/import_blob_url_jsx.ts16
-rw-r--r--cli/tests/import_blob_url_jsx.ts.out1
-rw-r--r--cli/tests/integration_tests.rs28
-rw-r--r--cli/tests/unsupported_dynamic_import_scheme.out1
-rw-r--r--cli/tsc.rs48
-rw-r--r--op_crates/file/03_blob_url.js63
-rw-r--r--op_crates/file/Cargo.toml1
-rw-r--r--op_crates/file/lib.rs82
-rw-r--r--op_crates/url/00_url.js8
-rw-r--r--op_crates/url/lib.deno_url.d.ts4
-rw-r--r--runtime/examples/hello_runtime.rs2
-rw-r--r--runtime/ops/file.rs31
-rw-r--r--runtime/ops/mod.rs1
-rw-r--r--runtime/permissions.rs1
-rw-r--r--runtime/web_worker.rs10
-rw-r--r--runtime/worker.rs8
33 files changed, 488 insertions, 33 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f5af15f8c..75b328418 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -621,6 +621,7 @@ name = "deno_file"
version = "0.1.0"
dependencies = [
"deno_core",
+ "uuid",
]
[[package]]
diff --git a/cli/disk_cache.rs b/cli/disk_cache.rs
index 2f2cc9e4f..0f5d16f98 100644
--- a/cli/disk_cache.rs
+++ b/cli/disk_cache.rs
@@ -67,7 +67,7 @@ impl DiskCache {
out.push(path_seg);
}
}
- "http" | "https" | "data" => out = url_to_filename(url)?,
+ "http" | "https" | "data" | "blob" => out = url_to_filename(url)?,
"file" => {
let path = match url.to_file_path() {
Ok(path) => path,
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index 1c97d7018..0c1d9519d 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -18,9 +18,11 @@ 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::permissions::Permissions;
use log::debug;
use log::info;
+use std::borrow::Borrow;
use std::collections::HashMap;
use std::env;
use std::fs;
@@ -32,7 +34,8 @@ use std::sync::Arc;
use std::sync::Mutex;
static DENO_AUTH_TOKENS: &str = "DENO_AUTH_TOKENS";
-pub const SUPPORTED_SCHEMES: [&str; 4] = ["data", "file", "http", "https"];
+pub const SUPPORTED_SCHEMES: [&str; 5] =
+ ["data", "blob", "file", "http", "https"];
/// A structure representing a source file.
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -317,6 +320,7 @@ pub struct FileFetcher {
cache_setting: CacheSetting,
http_cache: HttpCache,
http_client: reqwest::Client,
+ blob_url_store: BlobUrlStore,
}
impl FileFetcher {
@@ -325,6 +329,7 @@ impl FileFetcher {
cache_setting: CacheSetting,
allow_remote: bool,
ca_data: Option<Vec<u8>>,
+ blob_url_store: BlobUrlStore,
) -> Result<Self, AnyError> {
Ok(Self {
auth_tokens: AuthTokens::new(env::var(DENO_AUTH_TOKENS).ok()),
@@ -333,6 +338,7 @@ impl FileFetcher {
cache_setting,
http_cache,
http_client: create_http_client(get_user_agent(), ca_data)?,
+ blob_url_store,
})
}
@@ -446,6 +452,62 @@ impl FileFetcher {
})
}
+ /// Get a blob URL.
+ fn fetch_blob_url(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Result<File, AnyError> {
+ debug!("FileFetcher::fetch_blob_url() - specifier: {}", specifier);
+ match self.fetch_cached(specifier, 0) {
+ Ok(Some(file)) => return Ok(file),
+ Ok(None) => {}
+ Err(err) => return Err(err),
+ }
+
+ if self.cache_setting == CacheSetting::Only {
+ return Err(custom_error(
+ "NotFound",
+ format!(
+ "Specifier not found in cache: \"{}\", --cached-only is specified.",
+ specifier
+ ),
+ ));
+ }
+
+ let blob_url_storage = self.blob_url_store.borrow();
+ let blob = blob_url_storage.get(specifier)?.ok_or_else(|| {
+ custom_error(
+ "NotFound",
+ format!("Blob URL not found: \"{}\".", specifier),
+ )
+ })?;
+
+ let content_type = blob.media_type;
+
+ let (media_type, maybe_charset) =
+ map_content_type(specifier, Some(content_type.clone()));
+ let source =
+ strip_shebang(get_source_from_bytes(blob.data, maybe_charset)?);
+
+ let local =
+ self
+ .http_cache
+ .get_cache_filename(specifier)
+ .ok_or_else(|| {
+ generic_error("Cannot convert specifier to cached filename.")
+ })?;
+ let mut headers = HashMap::new();
+ headers.insert("content-type".to_string(), content_type);
+ self.http_cache.set(specifier, headers, source.as_bytes())?;
+
+ Ok(File {
+ local,
+ maybe_types: None,
+ media_type,
+ source,
+ specifier: specifier.clone(),
+ })
+ }
/// Asynchronously fetch remote source file specified by the URL following
/// redirects.
///
@@ -555,6 +617,12 @@ impl FileFetcher {
self.cache.insert(specifier.clone(), file.clone());
}
result
+ } else if scheme == "blob" {
+ let result = self.fetch_blob_url(specifier);
+ if let Ok(file) = &result {
+ self.cache.insert(specifier.clone(), file.clone());
+ }
+ result
} else if !self.allow_remote {
Err(custom_error(
"NoRemote",
@@ -604,6 +672,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 std::rc::Rc;
use tempfile::TempDir;
@@ -611,14 +680,29 @@ mod tests {
cache_setting: CacheSetting,
maybe_temp_dir: Option<Rc<TempDir>>,
) -> (FileFetcher, Rc<TempDir>) {
+ let (file_fetcher, temp_dir, _) =
+ setup_with_blob_url_store(cache_setting, maybe_temp_dir);
+ (file_fetcher, temp_dir)
+ }
+
+ fn setup_with_blob_url_store(
+ cache_setting: CacheSetting,
+ maybe_temp_dir: Option<Rc<TempDir>>,
+ ) -> (FileFetcher, Rc<TempDir>, BlobUrlStore) {
let temp_dir = maybe_temp_dir.unwrap_or_else(|| {
Rc::new(TempDir::new().expect("failed to create temp directory"))
});
let location = temp_dir.path().join("deps");
- let file_fetcher =
- FileFetcher::new(HttpCache::new(&location), cache_setting, true, None)
- .expect("setup failed");
- (file_fetcher, temp_dir)
+ let blob_url_store = BlobUrlStore::default();
+ let file_fetcher = FileFetcher::new(
+ HttpCache::new(&location),
+ cache_setting,
+ true,
+ None,
+ blob_url_store.clone(),
+ )
+ .expect("setup failed");
+ (file_fetcher, temp_dir, blob_url_store)
}
macro_rules! file_url {
@@ -989,6 +1073,36 @@ mod tests {
}
#[tokio::test]
+ async fn test_fetch_blob_url() {
+ let (file_fetcher, _, blob_url_store) =
+ setup_with_blob_url_store(CacheSetting::Use, None);
+
+ let specifier = blob_url_store.insert(
+ Blob {
+ data:
+ "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n"
+ .as_bytes()
+ .to_vec(),
+ media_type: "application/typescript".to_string(),
+ },
+ None,
+ );
+
+ let result = file_fetcher
+ .fetch(&specifier, &Permissions::allow_all())
+ .await;
+ assert!(result.is_ok());
+ let file = result.unwrap();
+ assert_eq!(
+ file.source,
+ "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n"
+ );
+ assert_eq!(file.media_type, MediaType::TypeScript);
+ assert_eq!(file.maybe_types, None);
+ assert_eq!(file.specifier, specifier);
+ }
+
+ #[tokio::test]
async fn test_fetch_complex() {
let _http_server_guard = test_util::http_server();
let (file_fetcher, temp_dir) = setup(CacheSetting::Use, None);
@@ -1061,6 +1175,7 @@ mod tests {
CacheSetting::ReloadAll,
true,
None,
+ BlobUrlStore::default(),
)
.expect("setup failed");
let result = file_fetcher
@@ -1087,6 +1202,7 @@ mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let specifier =
@@ -1114,6 +1230,7 @@ mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let result = file_fetcher_02
@@ -1274,6 +1391,7 @@ mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let specifier =
@@ -1304,6 +1422,7 @@ mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let result = file_fetcher_02
@@ -1413,6 +1532,7 @@ mod tests {
CacheSetting::Use,
false,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let specifier =
@@ -1439,6 +1559,7 @@ mod tests {
CacheSetting::Only,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let file_fetcher_02 = FileFetcher::new(
@@ -1446,6 +1567,7 @@ mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not create file fetcher");
let specifier =
diff --git a/cli/http_cache.rs b/cli/http_cache.rs
index 7eff2b9be..37da00983 100644
--- a/cli/http_cache.rs
+++ b/cli/http_cache.rs
@@ -39,7 +39,7 @@ fn base_url_to_filename(url: &Url) -> Option<PathBuf> {
};
out.push(host_port);
}
- "data" => (),
+ "data" | "blob" => (),
scheme => {
error!("Don't know how to create cache name for scheme: {}", scheme);
return None;
diff --git a/cli/main.rs b/cli/main.rs
index e9e29c9db..7146d8548 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -122,6 +122,7 @@ fn create_web_worker_callback(
ts_version: version::TYPESCRIPT.to_string(),
no_color: !colors::use_color(),
get_error_class_fn: Some(&crate::errors::get_error_class_name),
+ blob_url_store: program_state.blob_url_store.clone(),
};
let mut worker = WebWorker::from_options(
@@ -199,6 +200,7 @@ pub fn create_main_worker(
no_color: !colors::use_color(),
get_error_class_fn: Some(&crate::errors::get_error_class_name),
location: program_state.flags.location.clone(),
+ blob_url_store: program_state.blob_url_store.clone(),
};
let mut worker = MainWorker::from_options(main_module, permissions, &options);
diff --git a/cli/program_state.rs b/cli/program_state.rs
index f47dcf0e9..a41acdec3 100644
--- a/cli/program_state.rs
+++ b/cli/program_state.rs
@@ -14,6 +14,7 @@ use crate::module_graph::TypeLib;
use crate::source_maps::SourceMapGetter;
use crate::specifier_handler::FetchHandler;
use crate::version;
+use deno_runtime::deno_file::BlobUrlStore;
use deno_runtime::inspector::InspectorServer;
use deno_runtime::permissions::Permissions;
@@ -56,6 +57,7 @@ pub struct ProgramState {
pub maybe_import_map: Option<ImportMap>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub ca_data: Option<Vec<u8>>,
+ pub blob_url_store: BlobUrlStore,
}
impl ProgramState {
@@ -80,11 +82,14 @@ impl ProgramState {
CacheSetting::Use
};
+ let blob_url_store = BlobUrlStore::default();
+
let file_fetcher = FileFetcher::new(
http_cache,
cache_usage,
!flags.no_remote,
ca_data.clone(),
+ blob_url_store.clone(),
)?;
let lockfile = if let Some(filename) = &flags.lock {
@@ -131,6 +136,7 @@ impl ProgramState {
maybe_import_map,
maybe_inspector_server,
ca_data,
+ blob_url_store,
};
Ok(Arc::new(program_state))
}
@@ -257,7 +263,7 @@ impl ProgramState {
match url.scheme() {
// we should only be looking for emits for schemes that denote external
// modules, which the disk_cache supports
- "wasm" | "file" | "http" | "https" | "data" => (),
+ "wasm" | "file" | "http" | "https" | "data" | "blob" => (),
_ => {
return None;
}
diff --git a/cli/source_maps.rs b/cli/source_maps.rs
index 8bc070418..d62c0f3e0 100644
--- a/cli/source_maps.rs
+++ b/cli/source_maps.rs
@@ -142,6 +142,7 @@ pub fn get_orig_position<G: SourceMapGetter>(
// around it. Use the `file_name` we get from V8 if
// `source_file_name` does not parse as a URL.
let file_name = match deno_core::resolve_url(source_file_name) {
+ Ok(m) if m.scheme() == "blob" => file_name,
Ok(m) => m.to_string(),
Err(_) => file_name,
};
diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs
index 9f329819e..58815e15c 100644
--- a/cli/specifier_handler.rs
+++ b/cli/specifier_handler.rs
@@ -306,7 +306,9 @@ impl SpecifierHandler for FetchHandler {
}
})?;
let url = &source_file.specifier;
- let is_remote = !(url.scheme() == "file" || url.scheme() == "data");
+ let is_remote = !(url.scheme() == "file"
+ || url.scheme() == "data"
+ || url.scheme() == "blob");
let filename = disk_cache.get_cache_filename_with_extension(url, "meta");
let maybe_version = if let Some(filename) = filename {
if let Ok(bytes) = disk_cache.get(&filename) {
@@ -569,6 +571,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 tempfile::TempDir;
macro_rules! map (
@@ -593,6 +596,7 @@ pub mod tests {
CacheSetting::Use,
true,
None,
+ BlobUrlStore::default(),
)
.expect("could not setup");
let disk_cache = deno_dir.gen_cache;
diff --git a/cli/standalone.rs b/cli/standalone.rs
index e3fb6fe43..83879a163 100644
--- a/cli/standalone.rs
+++ b/cli/standalone.rs
@@ -15,6 +15,7 @@ use deno_core::v8_set_flags;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
+use deno_runtime::deno_file::BlobUrlStore;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsOptions;
use deno_runtime::worker::MainWorker;
@@ -158,6 +159,7 @@ pub async fn run(
) -> Result<(), AnyError> {
let main_module = resolve_url(SPECIFIER)?;
let permissions = Permissions::from_options(&metadata.permissions);
+ let blob_url_store = BlobUrlStore::default();
let module_loader = Rc::new(EmbeddedModuleLoader(source_code));
let create_web_worker_cb = Arc::new(|_| {
todo!("Worker are currently not supported in standalone binaries");
@@ -189,6 +191,7 @@ pub async fn run(
no_color: !colors::use_color(),
get_error_class_fn: Some(&get_error_class_name),
location: metadata.location,
+ blob_url_store,
};
let mut worker =
MainWorker::from_options(main_module.clone(), permissions, &options);
diff --git a/cli/tests/import_blob_url.ts b/cli/tests/import_blob_url.ts
new file mode 100644
index 000000000..86bb634e1
--- /dev/null
+++ b/cli/tests/import_blob_url.ts
@@ -0,0 +1,13 @@
+const blob = new Blob(
+ ['export const a = "a";\n\nexport enum A {\n A,\n B,\n C,\n}\n'],
+ {
+ type: "application/typescript",
+ },
+);
+const url = URL.createObjectURL(blob);
+
+const a = await import(url);
+
+console.log(a.a);
+console.log(a.A);
+console.log(a.A.A);
diff --git a/cli/tests/import_blob_url.ts.out b/cli/tests/import_blob_url.ts.out
new file mode 100644
index 000000000..bfa0b9d94
--- /dev/null
+++ b/cli/tests/import_blob_url.ts.out
@@ -0,0 +1,3 @@
+a
+{ "0": "A", "1": "B", "2": "C", A: 0, B: 1, C: 2 }
+0
diff --git a/cli/tests/import_blob_url_error_stack.ts b/cli/tests/import_blob_url_error_stack.ts
new file mode 100644
index 000000000..f9c4f2e9d
--- /dev/null
+++ b/cli/tests/import_blob_url_error_stack.ts
@@ -0,0 +1,13 @@
+const blob = new Blob(
+ [
+ "enum A {\n A,\n B,\n C,\n }\n \n export function a() {\n throw new Error(`Hello ${A.C}`);\n }\n ",
+ ],
+ {
+ type: "application/typescript",
+ },
+);
+const url = URL.createObjectURL(blob);
+
+const { a } = await import(url);
+
+a();
diff --git a/cli/tests/import_blob_url_error_stack.ts.out b/cli/tests/import_blob_url_error_stack.ts.out
new file mode 100644
index 000000000..18ca023e3
--- /dev/null
+++ b/cli/tests/import_blob_url_error_stack.ts.out
@@ -0,0 +1,6 @@
+[WILDCARD]error: Uncaught (in promise) Error: Hello 2
+ throw new Error(`Hello ${A.C}`);
+ ^
+ at a (blob:null/[WILDCARD]:8:10)
+ at file:///[WILDCARD]/cli/tests/import_blob_url_error_stack.ts:13:1
+[WILDCARD]
diff --git a/cli/tests/import_blob_url_import_relative.ts b/cli/tests/import_blob_url_import_relative.ts
new file mode 100644
index 000000000..ad130bdac
--- /dev/null
+++ b/cli/tests/import_blob_url_import_relative.ts
@@ -0,0 +1,8 @@
+const blob = new Blob(['export { a } from "./a.ts";'], {
+ type: "application/javascript",
+});
+const url = URL.createObjectURL(blob);
+
+const a = await import(url);
+
+console.log(a);
diff --git a/cli/tests/import_blob_url_import_relative.ts.out b/cli/tests/import_blob_url_import_relative.ts.out
new file mode 100644
index 000000000..9d47ab719
--- /dev/null
+++ b/cli/tests/import_blob_url_import_relative.ts.out
@@ -0,0 +1,4 @@
+error: Uncaught (in promise) TypeError: invalid URL: relative URL with a cannot-be-a-base base
+const a = await import(url);
+ ^
+ at async file://[WILDCARD]/cli/tests/import_blob_url_import_relative.ts:6:11
diff --git a/cli/tests/import_blob_url_imports.ts b/cli/tests/import_blob_url_imports.ts
new file mode 100644
index 000000000..7c2b10865
--- /dev/null
+++ b/cli/tests/import_blob_url_imports.ts
@@ -0,0 +1,11 @@
+const blob = new Blob(
+ [
+ 'export { printHello } from "http://localhost:4545/cli/tests/subdir/mod2.ts"',
+ ],
+ { type: "application/javascript" },
+);
+const url = URL.createObjectURL(blob);
+
+const { printHello } = await import(url);
+
+printHello();
diff --git a/cli/tests/import_blob_url_imports.ts.out b/cli/tests/import_blob_url_imports.ts.out
new file mode 100644
index 000000000..e965047ad
--- /dev/null
+++ b/cli/tests/import_blob_url_imports.ts.out
@@ -0,0 +1 @@
+Hello
diff --git a/cli/tests/import_blob_url_jsx.ts b/cli/tests/import_blob_url_jsx.ts
new file mode 100644
index 000000000..8d645796a
--- /dev/null
+++ b/cli/tests/import_blob_url_jsx.ts
@@ -0,0 +1,16 @@
+const blob = new Blob(
+ ["export default function() {\n return <div>Hello Deno!</div>\n}\n"],
+ { type: "text/jsx" },
+);
+const url = URL.createObjectURL(blob);
+
+const { default: render } = await import(url);
+
+// deno-lint-ignore no-explicit-any
+(globalThis as any).React = {
+ createElement(...args: unknown[]) {
+ console.log(...args);
+ },
+};
+
+render();
diff --git a/cli/tests/import_blob_url_jsx.ts.out b/cli/tests/import_blob_url_jsx.ts.out
new file mode 100644
index 000000000..c1c85f250
--- /dev/null
+++ b/cli/tests/import_blob_url_jsx.ts.out
@@ -0,0 +1 @@
+div null Hello Deno!
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 5f8d5d403..73fdcc994 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -3683,6 +3683,34 @@ console.log("finish");
output: "import_dynamic_data_url.ts.out",
});
+ itest!(import_blob_url_error_stack {
+ args: "run --quiet --reload import_blob_url_error_stack.ts",
+ output: "import_blob_url_error_stack.ts.out",
+ exit_code: 1,
+ });
+
+ itest!(import_blob_url_import_relative {
+ args: "run --quiet --reload import_blob_url_import_relative.ts",
+ output: "import_blob_url_import_relative.ts.out",
+ exit_code: 1,
+ });
+
+ itest!(import_blob_url_imports {
+ args: "run --quiet --reload import_blob_url_imports.ts",
+ output: "import_blob_url_imports.ts.out",
+ http_server: true,
+ });
+
+ itest!(import_blob_url_jsx {
+ args: "run --quiet --reload import_blob_url_jsx.ts",
+ output: "import_blob_url_jsx.ts.out",
+ });
+
+ itest!(import_blob_url {
+ args: "run --quiet --reload import_blob_url.ts",
+ output: "import_blob_url.ts.out",
+ });
+
itest!(import_file_with_colon {
args: "run --quiet --reload import_file_with_colon.ts",
output: "import_file_with_colon.ts.out",
diff --git a/cli/tests/unsupported_dynamic_import_scheme.out b/cli/tests/unsupported_dynamic_import_scheme.out
index 9160f935f..c708fced4 100644
--- a/cli/tests/unsupported_dynamic_import_scheme.out
+++ b/cli/tests/unsupported_dynamic_import_scheme.out
@@ -1,5 +1,6 @@
error: Uncaught (in promise) TypeError: Unsupported scheme "xxx" for module "xxx:". Supported schemes: [
"data",
+ "blob",
"file",
"http",
"https",
diff --git a/cli/tsc.rs b/cli/tsc.rs
index 7c54905c5..834b0cc58 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -110,6 +110,19 @@ fn hash_data_url(
format!("data:///{}{}", hash, media_type.as_ts_extension())
}
+fn hash_blob_url(
+ specifier: &ModuleSpecifier,
+ media_type: &MediaType,
+) -> String {
+ assert_eq!(
+ specifier.scheme(),
+ "blob",
+ "Specifier must be a blob: specifier."
+ );
+ let hash = crate::checksum::gen(&[specifier.path().as_bytes()]);
+ format!("blob:///{}{}", hash, media_type.as_ts_extension())
+}
+
/// tsc only supports `.ts`, `.tsx`, `.d.ts`, `.js`, or `.jsx` as root modules
/// and so we have to detect the apparent media type based on extensions it
/// supports.
@@ -378,15 +391,19 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
resolved_specifier
)
};
- let resolved_specifier_str = if resolved_specifier.scheme() == "data"
- {
- let specifier_str = hash_data_url(&resolved_specifier, &media_type);
- state
- .data_url_map
- .insert(specifier_str.clone(), resolved_specifier);
- specifier_str
- } else {
- resolved_specifier.to_string()
+ let resolved_specifier_str = match resolved_specifier.scheme() {
+ "data" | "blob" => {
+ let specifier_str = if resolved_specifier.scheme() == "data" {
+ hash_data_url(&resolved_specifier, &media_type)
+ } else {
+ hash_blob_url(&resolved_specifier, &media_type)
+ };
+ state
+ .data_url_map
+ .insert(specifier_str.clone(), resolved_specifier);
+ specifier_str
+ }
+ _ => resolved_specifier.to_string(),
};
resolved.push((
resolved_specifier_str,
@@ -439,12 +456,17 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
let root_names: Vec<String> = request
.root_names
.iter()
- .map(|(s, mt)| {
- if s.scheme() == "data" {
- let specifier_str = hash_data_url(s, mt);
+ .map(|(s, mt)| match s.scheme() {
+ "data" | "blob" => {
+ let specifier_str = if s.scheme() == "data" {
+ hash_data_url(&s, &mt)
+ } else {
+ hash_blob_url(&s, &mt)
+ };
data_url_map.insert(specifier_str.clone(), s.clone());
specifier_str
- } else {
+ }
+ _ => {
let ext_media_type = get_tsc_media_type(s);
if mt != &ext_media_type {
let new_specifier = format!("{}{}", s, mt.as_ts_extension());
diff --git a/op_crates/file/03_blob_url.js b/op_crates/file/03_blob_url.js
new file mode 100644
index 000000000..29019cd84
--- /dev/null
+++ b/op_crates/file/03_blob_url.js
@@ -0,0 +1,63 @@
+// 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="../url/internal.d.ts" />
+/// <reference path="../url/lib.deno_url.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./lib.deno_file.d.ts" />
+/// <reference lib="esnext" />
+"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.jsonOpSync(
+ "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.jsonOpSync(
+ "op_file_revoke_object_url",
+ url,
+ );
+ }
+
+ URL.createObjectURL = createObjectURL;
+ URL.revokeObjectURL = revokeObjectURL;
+})(globalThis);
diff --git a/op_crates/file/Cargo.toml b/op_crates/file/Cargo.toml
index 54476c783..9897e8cf4 100644
--- a/op_crates/file/Cargo.toml
+++ b/op_crates/file/Cargo.toml
@@ -15,3 +15,4 @@ path = "lib.rs"
[dependencies]
deno_core = { version = "0.83.0", path = "../../core" }
+uuid = { version = "0.8.2", features = ["v4"] }
diff --git a/op_crates/file/lib.rs b/op_crates/file/lib.rs
index c7e690433..4cfe4eed4 100644
--- a/op_crates/file/lib.rs
+++ b/op_crates/file/lib.rs
@@ -1,7 +1,85 @@
// 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::url::Url;
use deno_core::JsRuntime;
+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(Clone)]
+pub struct Blob {
+ pub data: Vec<u8>,
+ pub media_type: String,
+}
+
+pub struct Location(pub Url);
+
+#[derive(Default, Clone)]
+pub struct BlobUrlStore(Arc<Mutex<HashMap<Url, Blob>>>);
+
+impl BlobUrlStore {
+ pub fn get(&self, url: &ModuleSpecifier) -> Result<Option<Blob>, AnyError> {
+ let blob_store = self.0.lock().unwrap();
+ Ok(blob_store.get(url).cloned())
+ }
+
+ pub fn insert(&self, blob: Blob, maybe_location: Option<Url>) -> 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<ZeroCopyBuf>,
+) -> Result<String, AnyError> {
+ let data = zero_copy.ok_or_else(null_opbuf)?;
+ let blob = Blob {
+ data: data.to_vec(),
+ media_type,
+ };
+
+ let maybe_location = state.try_borrow::<Location>();
+ let blob_store = state.borrow::<BlobUrlStore>();
+
+ 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,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ let url = Url::parse(&url)?;
+ let blob_store = state.borrow::<BlobUrlStore>();
+ blob_store.remove(&url);
+ Ok(())
+}
/// Load and execute the javascript code.
pub fn init(isolate: &mut JsRuntime) {
@@ -11,6 +89,10 @@ pub fn init(isolate: &mut JsRuntime) {
"deno:op_crates/file/02_filereader.js",
include_str!("02_filereader.js"),
),
+ (
+ "deno:op_crates/file/03_blob_url.js",
+ include_str!("03_blob_url.js"),
+ ),
];
for (url, source_code) in files {
isolate.execute(url, source_code).unwrap();
diff --git a/op_crates/url/00_url.js b/op_crates/url/00_url.js
index 9dd2b7800..bf1ed6059 100644
--- a/op_crates/url/00_url.js
+++ b/op_crates/url/00_url.js
@@ -389,14 +389,6 @@
toJSON() {
return this.href;
}
-
- static createObjectURL() {
- throw new Error("Not implemented");
- }
-
- static revokeObjectURL() {
- throw new Error("Not implemented");
- }
}
window.__bootstrap.url = {
diff --git a/op_crates/url/lib.deno_url.d.ts b/op_crates/url/lib.deno_url.d.ts
index 2a27fe693..3f9745352 100644
--- a/op_crates/url/lib.deno_url.d.ts
+++ b/op_crates/url/lib.deno_url.d.ts
@@ -155,8 +155,8 @@ declare class URLSearchParams {
/** The URL interface represents an object providing static methods used for creating object URLs. */
declare class URL {
constructor(url: string, base?: string | URL);
- createObjectURL(object: any): string;
- revokeObjectURL(url: string): void;
+ static createObjectURL(blob: Blob): string;
+ static revokeObjectURL(url: string): void;
hash: string;
host: string;
diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs
index e8bf9841e..9c3cbd67e 100644
--- a/runtime/examples/hello_runtime.rs
+++ b/runtime/examples/hello_runtime.rs
@@ -2,6 +2,7 @@
use deno_core::error::AnyError;
use deno_core::FsModuleLoader;
+use deno_runtime::deno_file::BlobUrlStore;
use deno_runtime::permissions::Permissions;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
@@ -39,6 +40,7 @@ async fn main() -> Result<(), AnyError> {
no_color: false,
get_error_class_fn: Some(&get_error_class_name),
location: None,
+ blob_url_store: BlobUrlStore::default(),
};
let js_path =
diff --git a/runtime/ops/file.rs b/runtime/ops/file.rs
new file mode 100644
index 000000000..e7f68b207
--- /dev/null
+++ b/runtime/ops/file.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use deno_core::url::Url;
+use deno_file::op_file_create_object_url;
+use deno_file::op_file_revoke_object_url;
+use deno_file::BlobUrlStore;
+use deno_file::Location;
+
+pub fn init(
+ rt: &mut deno_core::JsRuntime,
+ blob_url_store: BlobUrlStore,
+ maybe_location: Option<Url>,
+) {
+ {
+ let op_state = rt.op_state();
+ let mut op_state = op_state.borrow_mut();
+ op_state.put(blob_url_store);
+ if let Some(location) = maybe_location {
+ op_state.put(Location(location));
+ }
+ }
+ super::reg_json_sync(
+ rt,
+ "op_file_create_object_url",
+ op_file_create_object_url,
+ );
+ super::reg_json_sync(
+ rt,
+ "op_file_revoke_object_url",
+ op_file_revoke_object_url,
+ );
+}
diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs
index 6574546b1..67eae27b2 100644
--- a/runtime/ops/mod.rs
+++ b/runtime/ops/mod.rs
@@ -2,6 +2,7 @@
pub mod crypto;
pub mod fetch;
+pub mod file;
pub mod fs;
pub mod fs_events;
pub mod io;
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index 6e9000e16..45b7c0bf1 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -587,6 +587,7 @@ impl Permissions {
))),
},
"data" => Ok(()),
+ "blob" => Ok(()),
_ => self.net.check_url(specifier),
}
}
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index f5c81e6d4..c30b715e4 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -23,6 +23,7 @@ use deno_core::JsRuntime;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::RuntimeOptions;
+use deno_file::BlobUrlStore;
use log::debug;
use std::env;
use std::rc::Rc;
@@ -155,6 +156,7 @@ pub struct WebWorkerOptions {
/// Sets `Deno.noColor` in JS runtime.
pub no_color: bool,
pub get_error_class_fn: Option<GetErrorClassFn>,
+ pub blob_url_store: BlobUrlStore,
}
impl WebWorker {
@@ -217,7 +219,7 @@ impl WebWorker {
}
ops::web_worker::init(js_runtime, sender.clone(), handle);
- ops::runtime::init(js_runtime, main_module);
+ ops::runtime::init(js_runtime, main_module.clone());
ops::fetch::init(
js_runtime,
options.user_agent.clone(),
@@ -232,6 +234,11 @@ impl WebWorker {
ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
ops::url::init(js_runtime);
+ ops::file::init(
+ js_runtime,
+ options.blob_url_store.clone(),
+ Some(main_module),
+ );
ops::io::init(js_runtime);
ops::webgpu::init(js_runtime);
ops::websocket::init(
@@ -518,6 +525,7 @@ mod tests {
ts_version: "x".to_string(),
no_color: true,
get_error_class_fn: None,
+ blob_url_store: BlobUrlStore::default(),
};
let mut worker = WebWorker::from_options(
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 00434b313..982198d65 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -21,6 +21,7 @@ use deno_core::ModuleId;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::RuntimeOptions;
+use deno_file::BlobUrlStore;
use log::debug;
use std::env;
use std::rc::Rc;
@@ -66,6 +67,7 @@ pub struct WorkerOptions {
pub no_color: bool,
pub get_error_class_fn: Option<GetErrorClassFn>,
pub location: Option<Url>,
+ pub blob_url_store: BlobUrlStore,
}
impl MainWorker {
@@ -129,6 +131,11 @@ impl MainWorker {
ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
ops::url::init(js_runtime);
+ ops::file::init(
+ js_runtime,
+ options.blob_url_store.clone(),
+ options.location.clone(),
+ );
ops::fs_events::init(js_runtime);
ops::fs::init(js_runtime);
ops::io::init(js_runtime);
@@ -298,6 +305,7 @@ mod tests {
no_color: true,
get_error_class_fn: None,
location: None,
+ blob_url_store: BlobUrlStore::default(),
};
MainWorker::from_options(main_module, permissions, &options)