Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rfc] Implement an ObjectUrl wrapper #231

Merged
merged 2 commits into from
Jul 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/file/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ features = [
"BlobPropertyBag",
"FilePropertyBag",
"DomException",
"Url",
]

[dev-dependencies]
futures_rs = { version = "0.3", package = "futures" }
wasm-bindgen-test = "0.3.4"
wasm-bindgen-futures = "0.4"
chrono = { version = "0.4.10", features = ["wasmbind"] }

[dev-dependencies.web-sys]
version = "0.3.31"
features = ["Window", "Response"]

[features]
default = []
futures = ["futures-channel"]
6 changes: 1 addition & 5 deletions crates/file/src/blob.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
use crate::Sealed;
use std::{
ops::Deref,
time::{Duration, SystemTime, UNIX_EPOCH},
};

use wasm_bindgen::{prelude::*, throw_str, JsCast};

mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;

/// This trait is used to overload the `Blob::new_with_options` function, allowing a variety of
/// types to be used to create a `Blob`. Ignore this, and use &\[u8], &str, etc to create a `Blob`.
///
Expand Down
7 changes: 7 additions & 0 deletions crates/file/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
mod blob;
mod file_list;
mod file_reader;
mod object_url;

pub use blob::*;
pub use file_list::*;
pub use file_reader::*;
pub use object_url::*;

mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;
70 changes: 70 additions & 0 deletions crates/file/src/object_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{Blob, File};
use std::{ops::Deref, rc::Rc};

use wasm_bindgen::UnwrapThrowExt;
use web_sys::Url;

struct ObjectUrlAllocation {
url: String,
}

impl Drop for ObjectUrlAllocation {
fn drop(&mut self) {
web_sys::Url::revoke_object_url(&self.url).unwrap_throw();
}
}

/// A resource wrapper around [`URL.createObjectURL`] / [`URL.revokeObjectURL`].
///
/// A [`Blob`], in particular a [`File`], can be converted to a short URL representing its data with the above methods.
/// An [`ObjectUrl`] can be cheaply cloned and shared and revokes the underlying URL when the last reference is dropped.
///
/// Note that multiple urls can be created for the same blob, without being guaranteed to be de-deduplicated.
///
/// # Example
///
/// ```rust,no_run
/// use gloo_file::{Blob, ObjectUrl};
///
/// let blob = Blob::new("hello world");
/// let object_url = ObjectUrl::from(blob);
/// ```
///
/// [`URL.createObjectURL`]: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
/// [`URL.revokeObjectURL`]: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL
/// [`File`]: crate::File
#[derive(Clone)]
pub struct ObjectUrl {
inner: Rc<ObjectUrlAllocation>,
WorldSEnder marked this conversation as resolved.
Show resolved Hide resolved
}

impl From<File> for ObjectUrl {
fn from(file: File) -> Self {
Blob::from(file).into()
}
}

impl From<Blob> for ObjectUrl {
fn from(blob: Blob) -> Self {
web_sys::Blob::from(blob).into()
}
}

impl From<web_sys::Blob> for ObjectUrl {
fn from(blob: web_sys::Blob) -> Self {
let url = Url::create_object_url_with_blob(&blob).unwrap_throw();
let inner = Rc::new(ObjectUrlAllocation { url });
Self { inner }
}
}

// Note: some browsers support Url::create_object_url_with_source but this is deprecated!
// https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL#using_object_urls_for_media_streams

impl Deref for ObjectUrl {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.inner.url
}
}
22 changes: 21 additions & 1 deletion crates/file/tests/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

use futures_rs::channel::mpsc;
use futures_rs::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_test::*;

use gloo_file::{callbacks::read_as_text, Blob, File};
use gloo_file::{callbacks::read_as_text, Blob, File, ObjectUrl};
use web_sys::{window, Response};

wasm_bindgen_test_configure!(run_in_browser);

Expand Down Expand Up @@ -104,3 +107,20 @@ const PNG_FILE: &'static [u8] = &[
#[cfg(feature = "futures")]
const PNG_FILE_DATA: &'static str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAA\
Al21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=";

#[cfg(feature = "futures")]
#[wasm_bindgen_test]
async fn blob_to_url() {
let blob = Blob::new("hello world");
let object_url = ObjectUrl::from(blob);
// simulate a fetch, and expect to get a string containing the content back
let request: JsFuture = window().unwrap().fetch_with_str(&object_url).into();
let response = request.await.unwrap().unchecked_into::<Response>();
let body: JsFuture = response.blob().unwrap().into();
let body = body.await.unwrap().unchecked_into::<web_sys::Blob>();

let body = gloo_file::futures::read_as_text(&body.into())
.await
.unwrap();
assert_eq!(&body, "hello world");
}