summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/fetch/23_request.js8
-rw-r--r--ext/fetch/26_fetch.js28
-rw-r--r--ext/fetch/lib.rs47
-rw-r--r--ext/web/09_file.js44
-rw-r--r--ext/web/blob.rs45
-rw-r--r--ext/web/internal.d.ts1
-rw-r--r--ext/web/lib.rs2
-rw-r--r--tools/wpt/expectation.json4
8 files changed, 128 insertions, 51 deletions
diff --git a/ext/fetch/23_request.js b/ext/fetch/23_request.js
index 1372125c1..36c63db2a 100644
--- a/ext/fetch/23_request.js
+++ b/ext/fetch/23_request.js
@@ -19,6 +19,7 @@
const { mixinBody, extractBody } = window.__bootstrap.fetchBody;
const { getLocationHref } = window.__bootstrap.location;
const mimesniff = window.__bootstrap.mimesniff;
+ const { blobFromObjectUrl } = window.__bootstrap.file;
const {
headersFromHeaderList,
headerListFromHeaders,
@@ -59,6 +60,7 @@
* @property {number} redirectCount
* @property {string[]} urlList
* @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`.
+ * @property {Blob | null} blobUrlEntry
*/
const defaultInnerRequest = {
@@ -81,11 +83,16 @@
* @returns
*/
function newInnerRequest(method, url, headerList = [], body = null) {
+ let blobUrlEntry = null;
+ if (url.startsWith("blob:")) {
+ blobUrlEntry = blobFromObjectUrl(url);
+ }
return {
method: method,
headerList,
body,
urlList: [url],
+ blobUrlEntry,
...defaultInnerRequest,
};
}
@@ -118,6 +125,7 @@
redirectCount: request.redirectCount,
urlList: request.urlList,
clientRid: request.clientRid,
+ blobUrlEntry: request.blobUrlEntry,
};
}
diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js
index edd14abf1..ada524fcb 100644
--- a/ext/fetch/26_fetch.js
+++ b/ext/fetch/26_fetch.js
@@ -35,6 +35,7 @@
Promise,
PromisePrototypeThen,
PromisePrototypeCatch,
+ String,
StringPrototypeToLowerCase,
TypedArrayPrototypeSubarray,
TypeError,
@@ -172,6 +173,33 @@
* @returns {Promise<InnerResponse>}
*/
async function mainFetch(req, recursive, terminator) {
+ if (req.blobUrlEntry !== null) {
+ if (req.method !== "GET") {
+ throw new TypeError("Blob URL fetch only supports GET method.");
+ }
+
+ const body = new InnerBody(req.blobUrlEntry.stream());
+ terminator[abortSignal.add](() =>
+ body.error(new DOMException("Ongoing fetch was aborted.", "AbortError"))
+ );
+
+ return {
+ headerList: [
+ ["content-length", String(req.blobUrlEntry.size)],
+ ["content-type", req.blobUrlEntry.type],
+ ],
+ status: 200,
+ statusMessage: "OK",
+ body,
+ type: "basic",
+ url() {
+ if (this.urlList.length == 0) return null;
+ return this.urlList[this.urlList.length - 1];
+ },
+ urlList: recursive ? [] : [...req.urlList],
+ };
+ }
+
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
let reqBody = null;
diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs
index c419180c5..70ed40358 100644
--- a/ext/fetch/lib.rs
+++ b/ext/fetch/lib.rs
@@ -26,7 +26,6 @@ use deno_core::ZeroCopyBuf;
use deno_tls::create_http_client;
use deno_tls::rustls::RootCertStore;
use deno_tls::Proxy;
-use deno_web::BlobStore;
use http::header::CONTENT_LENGTH;
use reqwest::header::HeaderName;
use reqwest::header::HeaderValue;
@@ -275,49 +274,9 @@ where
(request_rid, None, None)
}
"blob" => {
- let blob_store = state.try_borrow::<BlobStore>().ok_or_else(|| {
- type_error("Blob URLs are not supported in this context.")
- })?;
-
- let blob = blob_store
- .get_object_url(url)?
- .ok_or_else(|| type_error("Blob for the given URL not found."))?;
-
- if method != "GET" {
- return Err(type_error("Blob URL fetch only supports GET method."));
- }
-
- let cancel_handle = CancelHandle::new_rc();
- let cancel_handle_ = cancel_handle.clone();
-
- let fut = async move {
- // TODO(lucacsonato): this should be a stream!
- let chunk = match blob.read_all().or_cancel(cancel_handle_).await? {
- Ok(chunk) => chunk,
- Err(err) => return Ok(Err(err)),
- };
-
- let res = http::Response::builder()
- .status(http::StatusCode::OK)
- .header(http::header::CONTENT_LENGTH, chunk.len())
- .header(http::header::CONTENT_TYPE, blob.media_type.clone())
- .body(reqwest::Body::from(chunk))
- .map_err(|err| type_error(err.to_string()));
-
- match res {
- Ok(response) => Ok(Ok(Response::from(response))),
- Err(err) => Ok(Err(err)),
- }
- };
-
- let request_rid = state
- .resource_table
- .add(FetchRequestResource(Box::pin(fut)));
-
- let cancel_handle_rid =
- state.resource_table.add(FetchCancelHandle(cancel_handle));
-
- (request_rid, None, Some(cancel_handle_rid))
+ // Blob URL resolution happens in the JS side of fetch. If we got here is
+ // because the URL isn't an object URL.
+ return Err(type_error("Blob for the given URL not found."));
}
_ => return Err(type_error(format!("scheme '{}' not supported", scheme))),
};
diff --git a/ext/web/09_file.js b/ext/web/09_file.js
index 43d9ffcb3..64cdb7b8a 100644
--- a/ext/web/09_file.js
+++ b/ext/web/09_file.js
@@ -167,11 +167,12 @@
return bag;
}
+ const _type = Symbol("Type");
const _size = Symbol("Size");
const _parts = Symbol("Parts");
class Blob {
- #type = "";
+ [_type] = "";
[_size] = 0;
[_parts];
@@ -199,7 +200,7 @@
this[_parts] = parts;
this[_size] = size;
- this.#type = normalizeType(options.type);
+ this[_type] = normalizeType(options.type);
}
/** @returns {number} */
@@ -211,7 +212,7 @@
/** @returns {string} */
get type() {
webidl.assertBranded(this, Blob);
- return this.#type;
+ return this[_type];
}
/**
@@ -561,7 +562,44 @@
}
}
+ /**
+ * Construct a new Blob object from an object URL.
+ *
+ * This new object will not duplicate data in memory with the original Blob
+ * object from which this URL was created or with other Blob objects created
+ * from the same URL, but they will be different objects.
+ *
+ * The object returned from this function will not be a File object, even if
+ * the original object from which the object URL was constructed was one. This
+ * means that the `name` and `lastModified` properties are lost.
+ *
+ * @param {string} url
+ * @returns {Blob | null}
+ */
+ function blobFromObjectUrl(url) {
+ const blobData = core.opSync("op_blob_from_object_url", url);
+ if (blobData === null) {
+ return null;
+ }
+
+ /** @type {BlobReference[]} */
+ const parts = [];
+ let totalSize = 0;
+
+ for (const { uuid, size } of blobData.parts) {
+ ArrayPrototypePush(parts, new BlobReference(uuid, size));
+ totalSize += size;
+ }
+
+ const blob = webidl.createBranded(Blob);
+ blob[_type] = blobData.media_type;
+ blob[_size] = totalSize;
+ blob[_parts] = parts;
+ return blob;
+ }
+
window.__bootstrap.file = {
+ blobFromObjectUrl,
getParts,
Blob,
File,
diff --git a/ext/web/blob.rs b/ext/web/blob.rs
index 2179de4a6..37e93c853 100644
--- a/ext/web/blob.rs
+++ b/ext/web/blob.rs
@@ -3,7 +3,7 @@ use deno_core::error::type_error;
use deno_core::parking_lot::Mutex;
use deno_core::url::Url;
use deno_core::ZeroCopyBuf;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
@@ -260,3 +260,46 @@ pub fn op_blob_revoke_object_url(
blob_store.remove_object_url(&url);
Ok(())
}
+
+#[derive(Serialize)]
+pub struct ReturnBlob {
+ pub media_type: String,
+ pub parts: Vec<ReturnBlobPart>,
+}
+
+#[derive(Serialize)]
+pub struct ReturnBlobPart {
+ pub uuid: Uuid,
+ pub size: usize,
+}
+
+pub fn op_blob_from_object_url(
+ state: &mut deno_core::OpState,
+ url: String,
+ _: (),
+) -> Result<Option<ReturnBlob>, AnyError> {
+ let url = Url::parse(&url)?;
+ if url.scheme() != "blob" {
+ return Ok(None);
+ }
+
+ let blob_store = state.try_borrow::<BlobStore>().ok_or_else(|| {
+ type_error("Blob URLs are not supported in this context.")
+ })?;
+ if let Some(blob) = blob_store.get_object_url(url)? {
+ let parts = blob
+ .parts
+ .iter()
+ .map(|part| ReturnBlobPart {
+ uuid: blob_store.insert_part(part.clone()),
+ size: part.size(),
+ })
+ .collect();
+ Ok(Some(ReturnBlob {
+ media_type: blob.media_type.clone(),
+ parts,
+ }))
+ } else {
+ Ok(None)
+ }
+}
diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts
index 6e72c1a80..94fc9c196 100644
--- a/ext/web/internal.d.ts
+++ b/ext/web/internal.d.ts
@@ -73,6 +73,7 @@ declare namespace globalThis {
};
declare var file: {
+ blobFromObjectUrl(url: string): Blob | null;
getParts(blob: Blob): string[];
Blob: typeof Blob;
File: typeof File;
diff --git a/ext/web/lib.rs b/ext/web/lib.rs
index 87ae46f2f..5f8ab1625 100644
--- a/ext/web/lib.rs
+++ b/ext/web/lib.rs
@@ -29,6 +29,7 @@ use std::usize;
use crate::blob::op_blob_create_object_url;
use crate::blob::op_blob_create_part;
+use crate::blob::op_blob_from_object_url;
use crate::blob::op_blob_read_part;
use crate::blob::op_blob_remove_part;
use crate::blob::op_blob_revoke_object_url;
@@ -88,6 +89,7 @@ pub fn init(blob_store: BlobStore, maybe_location: Option<Url>) -> Extension {
"op_blob_revoke_object_url",
op_sync(op_blob_revoke_object_url),
),
+ ("op_blob_from_object_url", op_sync(op_blob_from_object_url)),
(
"op_message_port_create_entangled",
op_sync(op_message_port_create_entangled),
diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json
index 1c0ef521a..a8683e1bb 100644
--- a/tools/wpt/expectation.json
+++ b/tools/wpt/expectation.json
@@ -13998,9 +13998,7 @@
"fileReader.any.html": true,
"url": {
"url-format.any.html": true,
- "url-with-fetch.any.html": [
- "Revoke blob URL after creating Request, will fetch"
- ]
+ "url-with-fetch.any.html": true
},
"reading-data-section": {
"Determining-Encoding.any.html": true,