1use std::ptr;
6use std::rc::Rc;
7
8use base::id::{BlobId, BlobIndex};
9use constellation_traits::{BlobData, BlobImpl};
10use dom_struct::dom_struct;
11use encoding_rs::UTF_8;
12use js::jsapi::JSObject;
13use js::realm::CurrentRealm;
14use js::rust::HandleObject;
15use js::typedarray::{ArrayBufferU8, Uint8};
16use net_traits::filemanager_thread::RelativePos;
17use rustc_hash::FxHashMap;
18use uuid::Uuid;
19
20use crate::dom::bindings::buffer_source::create_buffer_source;
21use crate::dom::bindings::codegen::Bindings::BlobBinding;
22use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
23use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::reflector::{
26 DomGlobal, Reflector, reflect_dom_object_with_proto, reflect_dom_object_with_proto_and_cx,
27};
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::bindings::serializable::Serializable;
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::structuredclone::StructuredData;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::promise::Promise;
34use crate::dom::stream::readablestream::ReadableStream;
35use crate::realms::InRealm;
36use crate::script_runtime::CanGc;
37
38#[dom_struct]
40pub(crate) struct Blob {
41 reflector_: Reflector,
42 #[no_trace]
43 blob_id: BlobId,
44}
45
46impl Blob {
47 pub(crate) fn new(global: &GlobalScope, blob_impl: BlobImpl, can_gc: CanGc) -> DomRoot<Blob> {
48 Self::new_with_proto(global, None, blob_impl, can_gc)
49 }
50
51 fn new_with_proto(
52 global: &GlobalScope,
53 proto: Option<HandleObject>,
54 blob_impl: BlobImpl,
55 can_gc: CanGc,
56 ) -> DomRoot<Blob> {
57 let dom_blob = reflect_dom_object_with_proto(
58 Box::new(Blob::new_inherited(&blob_impl)),
59 global,
60 proto,
61 can_gc,
62 );
63 global.track_blob(&dom_blob, blob_impl);
64 dom_blob
65 }
66
67 fn new_with_proto_and_cx(
68 global: &GlobalScope,
69 proto: Option<HandleObject>,
70 blob_impl: BlobImpl,
71 cx: &mut js::context::JSContext,
72 ) -> DomRoot<Blob> {
73 let dom_blob = reflect_dom_object_with_proto_and_cx(
74 Box::new(Blob::new_inherited(&blob_impl)),
75 global,
76 proto,
77 cx,
78 );
79 global.track_blob(&dom_blob, blob_impl);
80 dom_blob
81 }
82
83 pub(crate) fn new_inherited(blob_impl: &BlobImpl) -> Blob {
84 Blob {
85 reflector_: Reflector::new(),
86 blob_id: blob_impl.blob_id(),
87 }
88 }
89
90 pub(crate) fn get_bytes(&self) -> Result<Vec<u8>, ()> {
92 self.global().get_blob_bytes(&self.blob_id)
93 }
94
95 pub(crate) fn type_string(&self) -> String {
97 self.global().get_blob_type_string(&self.blob_id)
98 }
99
100 pub(crate) fn get_blob_url_id(&self) -> Uuid {
103 self.global().get_blob_url_id(&self.blob_id)
104 }
105
106 pub(crate) fn get_stream(&self, can_gc: CanGc) -> Fallible<DomRoot<ReadableStream>> {
108 self.global().get_blob_stream(&self.blob_id, can_gc)
109 }
110}
111
112impl Serializable for Blob {
113 type Index = BlobIndex;
114 type Data = BlobImpl;
115
116 fn serialize(&self) -> Result<(BlobId, BlobImpl), ()> {
118 let blob_id = self.blob_id;
119
120 let blob_impl = self.global().serialize_blob(&blob_id);
122
123 let new_blob_id = blob_impl.blob_id();
125
126 Ok((new_blob_id, blob_impl))
127 }
128
129 fn deserialize(
131 owner: &GlobalScope,
132 serialized: BlobImpl,
133 can_gc: CanGc,
134 ) -> Result<DomRoot<Self>, ()> {
135 let deserialized_blob = Blob::new(owner, serialized, can_gc);
136 Ok(deserialized_blob)
137 }
138
139 fn serialized_storage<'a>(
140 reader: StructuredData<'a, '_>,
141 ) -> &'a mut Option<FxHashMap<BlobId, Self::Data>> {
142 match reader {
143 StructuredData::Reader(r) => &mut r.blob_impls,
144 StructuredData::Writer(w) => &mut w.blobs,
145 }
146 }
147}
148
149#[expect(unsafe_code)]
152pub(crate) fn blob_parts_to_bytes(
153 mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
154) -> Result<Vec<u8>, ()> {
155 let mut ret = vec![];
156 for blobpart in &mut blobparts {
157 match blobpart {
158 ArrayBufferOrArrayBufferViewOrBlobOrString::String(s) => {
159 ret.extend_from_slice(&s.as_bytes());
160 },
161 ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(b) => {
162 let bytes = b.get_bytes().unwrap_or(vec![]);
163 ret.extend(bytes);
164 },
165 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(a) => unsafe {
166 let bytes = a.as_slice();
167 ret.extend(bytes);
168 },
169 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(a) => unsafe {
170 let bytes = a.as_slice();
171 ret.extend(bytes);
172 },
173 }
174 }
175
176 Ok(ret)
177}
178
179impl BlobMethods<crate::DomTypeHolder> for Blob {
180 #[expect(non_snake_case)]
182 fn Constructor(
183 cx: &mut js::context::JSContext,
184 global: &GlobalScope,
185 proto: Option<HandleObject>,
186 blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>,
187 blobPropertyBag: &BlobBinding::BlobPropertyBag,
188 ) -> Fallible<DomRoot<Blob>> {
189 let bytes: Vec<u8> = match blobParts {
190 None => Vec::new(),
191 Some(blobparts) => match blob_parts_to_bytes(blobparts) {
192 Ok(bytes) => bytes,
193 Err(_) => return Err(Error::InvalidCharacter(None)),
194 },
195 };
196
197 let type_string = normalize_type_string(&blobPropertyBag.type_.str());
198 let blob_impl = BlobImpl::new_from_bytes(bytes, type_string);
199
200 Ok(Blob::new_with_proto_and_cx(global, proto, blob_impl, cx))
201 }
202
203 fn Size(&self) -> u64 {
205 self.global().get_blob_size(&self.blob_id)
206 }
207
208 fn Type(&self) -> DOMString {
210 DOMString::from(self.type_string())
211 }
212
213 fn Stream(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<ReadableStream>> {
215 self.get_stream(CanGc::from_cx(cx))
216 }
217
218 fn Slice(
220 &self,
221 cx: &mut js::context::JSContext,
222 start: Option<i64>,
223 end: Option<i64>,
224 content_type: Option<DOMString>,
225 ) -> DomRoot<Blob> {
226 let global = self.global();
227 let type_string = normalize_type_string(&content_type.unwrap_or_default().str());
228
229 let (parent, range) = match *global.get_blob_data(&self.blob_id) {
232 BlobData::Sliced(grandparent, parent_range) => {
233 let range = RelativePos {
234 start: parent_range.start + start.unwrap_or_default(),
235 end: end.map(|end| end + parent_range.start).or(parent_range.end),
236 };
237 (grandparent, range)
238 },
239 _ => (self.blob_id, RelativePos::from_opts(start, end)),
240 };
241
242 let blob_impl = BlobImpl::new_sliced(range, parent, type_string);
243 Blob::new(&global, blob_impl, CanGc::from_cx(cx))
244 }
245
246 fn Text(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
248 let global = self.global();
249 let p = Promise::new_in_realm(cx);
250 let id = self.get_blob_url_id();
251 global.read_file_async(
252 id,
253 p.clone(),
254 Box::new(|cx, promise, bytes| match bytes {
255 Ok(b) => {
256 let (text, _) = UTF_8.decode_with_bom_removal(&b);
257 let text = DOMString::from(text);
258 promise.resolve_native(&text, CanGc::from_cx(cx));
259 },
260 Err(e) => {
261 promise.reject_error(e, CanGc::from_cx(cx));
262 },
263 }),
264 );
265 p
266 }
267
268 fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
270 let cx = GlobalScope::get_cx();
271 let promise = Promise::new_in_current_realm(in_realm, can_gc);
272
273 let stream = self.get_stream(can_gc);
275
276 let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
279 Ok(reader) => reader,
280 Err(error) => {
281 promise.reject_error(error, can_gc);
282 return promise;
283 },
284 };
285
286 let success_promise = promise.clone();
288 let failure_promise = promise.clone();
289 reader.read_all_bytes(
290 cx,
291 Rc::new(move |bytes| {
292 rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
293 let array_buffer = create_buffer_source::<ArrayBufferU8>(
296 cx,
297 bytes,
298 js_object.handle_mut(),
299 can_gc,
300 )
301 .expect("Converting input to ArrayBufferU8 should never fail");
302 success_promise.resolve_native(&array_buffer, can_gc);
303 }),
304 Rc::new(move |cx, value| {
305 failure_promise.reject(cx, value, can_gc);
306 }),
307 can_gc,
308 );
309
310 promise
311 }
312
313 fn Bytes(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
315 let cx = GlobalScope::get_cx();
316 let p = Promise::new_in_current_realm(in_realm, can_gc);
317
318 let stream = self.get_stream(can_gc);
320
321 let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
324 Ok(r) => r,
325 Err(e) => {
326 p.reject_error(e, can_gc);
327 return p;
328 },
329 };
330
331 let p_success = p.clone();
333 let p_failure = p.clone();
334 reader.read_all_bytes(
335 cx,
336 Rc::new(move |bytes| {
337 rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
338 let arr = create_buffer_source::<Uint8>(cx, bytes, js_object.handle_mut(), can_gc)
339 .expect("Converting input to uint8 array should never fail");
340 p_success.resolve_native(&arr, can_gc);
341 }),
342 Rc::new(move |cx, v| {
343 p_failure.reject(cx, v, can_gc);
344 }),
345 can_gc,
346 );
347 p
348 }
349}
350
351pub(crate) fn normalize_type_string(s: &str) -> String {
357 if is_ascii_printable(s) {
358 s.to_ascii_lowercase()
359 } else {
363 "".to_string()
364 }
365}
366
367fn is_ascii_printable(string: &str) -> bool {
368 string.chars().all(|c| ('\x20'..='\x7E').contains(&c))
371}