ferridriver_script/bindings/
blob.rs1use rquickjs::function::Opt;
10use rquickjs::{Class, Ctx, Object, TypedArray, Value, class::Trace};
11
12#[derive(Trace)]
13#[rquickjs::class(rename = "Blob")]
14pub struct BlobJs {
15 #[qjs(skip_trace)]
16 data: Vec<u8>,
17 #[qjs(skip_trace)]
18 type_: String,
19}
20
21#[allow(unsafe_code)]
22unsafe impl rquickjs::JsLifetime<'_> for BlobJs {
23 type Changed<'to> = BlobJs;
24}
25
26impl BlobJs {
27 pub fn new_parts(data: Vec<u8>, type_: String) -> Self {
28 Self { data, type_ }
29 }
30
31 pub fn bytes_ref(&self) -> &[u8] {
32 &self.data
33 }
34
35 pub fn mime(&self) -> &str {
36 &self.type_
37 }
38
39 pub fn from_js_blob(v: &Value<'_>) -> Option<(Vec<u8>, String)> {
41 Class::<BlobJs>::from_value(v)
42 .ok()
43 .map(|b| (b.borrow().data.clone(), b.borrow().type_.clone()))
44 }
45}
46
47fn push_part(out: &mut Vec<u8>, elem: &Value<'_>) {
50 if let Some(s) = elem.as_string().and_then(|s| s.to_string().ok()) {
51 out.extend_from_slice(s.as_bytes());
52 return;
53 }
54 if let Some((bytes, _)) = BlobJs::from_js_blob(elem) {
55 out.extend_from_slice(&bytes);
56 return;
57 }
58 if let Ok(ta) = TypedArray::<u8>::from_value(elem.clone()) {
59 let b: &[u8] = ta.as_ref();
60 out.extend_from_slice(b);
61 return;
62 }
63 if let Some(ab) = rquickjs::ArrayBuffer::from_value(elem.clone())
64 && let Some(b) = ab.as_bytes()
65 {
66 out.extend_from_slice(b);
67 }
68}
69
70#[rquickjs::methods(rename_all = "camelCase")]
71impl BlobJs {
72 #[qjs(constructor)]
73 pub fn new<'js>(parts: Opt<Value<'js>>, options: Opt<Object<'js>>) -> Self {
74 let mut data = Vec::new();
75 if let Some(arr) = parts.0.as_ref().and_then(|v| v.as_array()) {
76 for i in 0..arr.len() {
77 if let Ok(elem) = arr.get::<Value<'js>>(i) {
78 push_part(&mut data, &elem);
79 }
80 }
81 }
82 let type_ = options
85 .0
86 .and_then(|o| o.get::<_, String>("type").ok())
87 .map(|t| t.to_ascii_lowercase())
88 .filter(|t| t.chars().all(|c| ('\u{20}'..='\u{7e}').contains(&c)))
89 .unwrap_or_default();
90 Self { data, type_ }
91 }
92
93 #[qjs(get, rename = "size")]
94 pub fn size(&self) -> usize {
95 self.data.len()
96 }
97
98 #[qjs(get, rename = "type")]
99 pub fn type_(&self) -> String {
100 self.type_.clone()
101 }
102
103 #[qjs(rename = "text")]
104 pub fn text(&self) -> String {
105 String::from_utf8_lossy(&self.data).into_owned()
106 }
107
108 #[qjs(rename = "arrayBuffer")]
109 pub fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> rquickjs::Result<Value<'js>> {
110 rquickjs::ArrayBuffer::new(ctx, self.data.clone()).map(rquickjs::ArrayBuffer::into_value)
111 }
112
113 #[qjs(rename = "bytes")]
114 pub fn bytes<'js>(&self, ctx: Ctx<'js>) -> rquickjs::Result<Value<'js>> {
115 Ok(TypedArray::<u8>::new(ctx, self.data.clone())?.into_value())
116 }
117
118 #[qjs(rename = "slice")]
121 pub fn slice(&self, start: Opt<i64>, end: Opt<i64>, content_type: Opt<String>) -> BlobJs {
122 let len = i64::try_from(self.data.len()).unwrap_or(i64::MAX);
123 let norm = |v: i64| if v < 0 { (len + v).max(0) } else { v.min(len) };
124 let s = norm(start.0.unwrap_or(0)) as usize;
125 let e = norm(end.0.unwrap_or(len)) as usize;
126 BlobJs {
127 data: if s < e { self.data[s..e].to_vec() } else { Vec::new() },
128 type_: content_type.0.map(|t| t.to_ascii_lowercase()).unwrap_or_default(),
129 }
130 }
131
132 #[qjs(rename = "stream")]
134 pub fn stream<'js>(&self, ctx: Ctx<'js>) -> rquickjs::Result<Class<'js, crate::bindings::streams::ReadableStreamJs>> {
135 Class::instance(
136 ctx,
137 crate::bindings::streams::ReadableStreamJs::from_bytes(self.data.clone()),
138 )
139 }
140}