/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/file/filereader.rs
647 строк
22 KB
Tim van der Lippe
script: Move task related files to `script/tasks/` (#47071)
08 авг 2026, 07:17
Не верифицирован
08 авг 2026, 07:17
1aab9ba
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use std::ptr; use std::rc::Rc; use base64::Engine; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; use js::jsapi::{Heap, JSObject}; use js::jsval::{self, JSVal}; use js::rust::HandleObject; use js::typedarray::{ArrayBuffer, CreateWith}; use mime::{self, Mime}; use script_bindings::cell::DomRefCell; use script_bindings::num::Finite; use script_bindings::reflector::reflect_dom_object_with_proto; use stylo_atoms::Atom; use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{ FileReaderConstants, FileReaderMethods, }; use crate::dom::bindings::codegen::UnionTypes::StringOrObject; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::blob::Blob; use crate::dom::domexception::{DOMErrorName, DOMException}; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::progressevent::ProgressEvent; use crate::realms::enter_auto_realm; use crate::tasks::task::TaskOnce; pub(crate) enum FileReadingTask { ProcessRead(TrustedFileReader, GenerationId), ProcessReadData(TrustedFileReader, GenerationId), ProcessReadError(TrustedFileReader, GenerationId, DOMErrorName), ProcessReadEOF(TrustedFileReader, GenerationId, ReadMetaData, Vec<u8>), } impl TaskOnce for FileReadingTask { fn run_once(self, cx: &mut js::context::JSContext) { self.handle_task(cx); } } impl FileReadingTask { pub(crate) fn handle_task(self, cx: &mut js::context::JSContext) { use self::FileReadingTask::*; match self { ProcessRead(reader, gen_id) => FileReader::process_read(cx, reader, gen_id), ProcessReadData(reader, gen_id) => FileReader::process_read_data(cx, reader, gen_id), ProcessReadError(reader, gen_id, error) => { FileReader::process_read_error(cx, reader, gen_id, error) }, ProcessReadEOF(reader, gen_id, metadata, blob_contents) => { FileReader::process_read_eof(cx, reader, gen_id, metadata, blob_contents) }, } } } #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum FileReaderFunction { Text, DataUrl, ArrayBuffer, BinaryString, } pub(crate) type TrustedFileReader = Trusted<FileReader>; #[derive(Clone, MallocSizeOf)] pub(crate) struct ReadMetaData { pub(crate) blobtype: String, pub(crate) encoding: Option<String>, pub(crate) function: FileReaderFunction, } impl ReadMetaData { pub(crate) fn new( blobtype: String, encoding: Option<String>, function: FileReaderFunction, ) -> ReadMetaData { ReadMetaData { blobtype, encoding, function, } } } #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) struct GenerationId(u32); #[repr(u16)] #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum FileReaderReadyState { Empty = FileReaderConstants::EMPTY, Loading = FileReaderConstants::LOADING, Done = FileReaderConstants::DONE, } #[derive(JSTraceable, MallocSizeOf)] pub(crate) enum FileReaderResult { ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] RootedTraceableBox<Heap<JSVal>>), String(DOMString), } pub(crate) struct FileReaderSharedFunctionality; impl FileReaderSharedFunctionality { /// <https://w3c.github.io/FileAPI/#blob-package-data> pub(crate) fn dataurl_for_bytes(bytes: &[u8], blob_type: &str) -> DOMString { // If mimeType (blobType) is not available return a Data URL without a media-type. [RFC2397]. // Spec says a Data URL without a media-type when blob_type is unavailable. // However, all other browsers use "application/octet-stream" in this case. let mime_type = if blob_type.is_empty() { "application/octet-stream" } else { blob_type }; Self::dataurl_format(bytes, mime_type) } /// [RFC2397] /// <https://www.rfc-editor.org/rfc/rfc2397.html> fn dataurl_format(bytes: &[u8], mime_type: &str) -> DOMString { let base64 = base64::engine::general_purpose::STANDARD.encode(bytes); let dataurl = format!("data:{};base64,{}", mime_type, base64); DOMString::from(dataurl) } /// <https://w3c.github.io/FileAPI/#blob-package-data> pub(crate) fn binary_string_for_bytes(bytes: &[u8]) -> DOMString { DOMString::from(bytes.iter().map(|&byte| byte as char).collect::<String>()) } /// <https://w3c.github.io/FileAPI/#blob-package-data> pub(crate) fn text_for_bytes( bytes: &[u8], blob_type: &str, encoding: &Option<String>, ) -> DOMString { // https://w3c.github.io/FileAPI/#encoding-determination // FIXME: This url is non-existent. Fixing later... // Steps 1 & 2 & 3 let mut encoding = encoding .as_ref() .map(|string| string.as_bytes()) .and_then(Encoding::for_label); // Step 4 & 5 encoding = encoding.or_else(|| { let resultmime = blob_type.parse::<Mime>().ok(); resultmime.and_then(|mime| { mime.params() .find(|(k, _)| &mime::CHARSET == k) .and_then(|(_, v)| Encoding::for_label(v.as_ref().as_bytes())) }) }); // Step 6 let enc = encoding.unwrap_or(UTF_8); let convert = bytes; // Step 7 // https://encoding.spec.whatwg.org/#decode let (output, _, _) = enc.decode(convert); DOMString::from(output) } } #[dom_struct] pub(crate) struct FileReader { eventtarget: EventTarget, ready_state: Cell<FileReaderReadyState>, error: MutNullableDom<DOMException>, result: DomRefCell<Option<FileReaderResult>>, generation_id: Cell<GenerationId>, } impl FileReader { pub(crate) fn new_inherited() -> FileReader { FileReader { eventtarget: EventTarget::new_inherited(), ready_state: Cell::new(FileReaderReadyState::Empty), error: MutNullableDom::new(None), result: DomRefCell::new(None), generation_id: Cell::new(GenerationId(0)), } } fn new( cx: &mut js::context::JSContext, global: &GlobalScope, proto: Option<HandleObject>, ) -> DomRoot<FileReader> { reflect_dom_object_with_proto(cx, Box::new(FileReader::new_inherited()), global, proto) } // https://w3c.github.io/FileAPI/#dfn-error-steps pub(crate) fn process_read_error( cx: &mut js::context::JSContext, filereader: TrustedFileReader, gen_id: GenerationId, error: DOMErrorName, ) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 1 fr.change_ready_state(FileReaderReadyState::Done); *fr.result.borrow_mut() = None; let exception = DOMException::new(cx, &fr.global(), error); fr.error.set(Some(&exception)); fr.dispatch_progress_event(cx, atom!("error"), 0, None); return_on_abort!(); // Step 3 fr.dispatch_progress_event(cx, atom!("loadend"), 0, None); return_on_abort!(); // Step 4 fr.terminate_ongoing_reading(); } // https://w3c.github.io/FileAPI/#dfn-readAsText pub(crate) fn process_read_data( cx: &mut js::context::JSContext, filereader: TrustedFileReader, gen_id: GenerationId, ) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // FIXME Step 7 send current progress fr.dispatch_progress_event(cx, atom!("progress"), 0, None); } // https://w3c.github.io/FileAPI/#dfn-readAsText pub(crate) fn process_read( cx: &mut js::context::JSContext, filereader: TrustedFileReader, gen_id: GenerationId, ) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 6 fr.dispatch_progress_event(cx, atom!("loadstart"), 0, None); } // https://w3c.github.io/FileAPI/#readOperation pub(crate) fn process_read_eof( cx: &mut js::context::JSContext, filereader: TrustedFileReader, gen_id: GenerationId, data: ReadMetaData, blob_contents: Vec<u8>, ) { let fr = filereader.root(); macro_rules! return_on_abort( () => ( if gen_id != fr.generation_id.get() { return } ); ); return_on_abort!(); // Step 8.1 fr.change_ready_state(FileReaderReadyState::Done); // Step 10.5.2: Let result be the result of package data given bytes, // type, blob’s type, and encodingName. // <https://w3c.github.io/FileAPI/#blob-package-data> match data.function { FileReaderFunction::DataUrl => { FileReader::perform_readasdataurl(&fr.result, data, &blob_contents) }, FileReaderFunction::Text => { FileReader::perform_readastext(&fr.result, data, &blob_contents) }, FileReaderFunction::ArrayBuffer => { let mut realm = enter_auto_realm(cx, &*fr); let cx = &mut realm.current_realm(); FileReader::perform_readasarraybuffer(cx, &fr.result, &blob_contents) }, FileReaderFunction::BinaryString => { FileReader::perform_readasbinarystring(&fr.result, &blob_contents) }, }; // Step 8.3 fr.dispatch_progress_event(cx, atom!("load"), 0, None); return_on_abort!(); // Step 8.4 if fr.ready_state.get() != FileReaderReadyState::Loading { fr.dispatch_progress_event(cx, atom!("loadend"), 0, None); } return_on_abort!(); } /// <https://w3c.github.io/FileAPI/#packaging-data> fn perform_readastext( result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, blob_bytes: &[u8], ) { *result.borrow_mut() = Some(FileReaderResult::String( FileReaderSharedFunctionality::text_for_bytes( blob_bytes, &data.blobtype, &data.encoding, ), )); } /// <https://w3c.github.io/FileAPI/#packaging-data> fn perform_readasdataurl( result: &DomRefCell<Option<FileReaderResult>>, data: ReadMetaData, bytes: &[u8], ) { *result.borrow_mut() = Some(FileReaderResult::String( FileReaderSharedFunctionality::dataurl_for_bytes(bytes, &data.blobtype), )); } /// <https://w3c.github.io/FileAPI/#packaging-data> /// > Return bytes as a binary string, in which every byte /// > is represented by a code unit of equal value [0..255]. fn perform_readasbinarystring(result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8]) { *result.borrow_mut() = Some(FileReaderResult::String( FileReaderSharedFunctionality::binary_string_for_bytes(bytes), )); } /// <https://w3c.github.io/FileAPI/#packaging-data> /// > Return a new ArrayBuffer whose contents are bytes. #[expect(unsafe_code)] fn perform_readasarraybuffer( cx: &mut js::context::JSContext, result: &DomRefCell<Option<FileReaderResult>>, bytes: &[u8], ) { unsafe { rooted!(&in(cx) let mut array_buffer = ptr::null_mut::<JSObject>()); assert!( ArrayBuffer::create( cx.raw_cx(), CreateWith::Slice(bytes), array_buffer.handle_mut() ) .is_ok() ); *result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(RootedTraceableBox::default())); if let Some(FileReaderResult::ArrayBuffer(ref mut heap)) = *result.borrow_mut() { heap.set(jsval::ObjectValue(array_buffer.get())); }; } } } impl FileReaderMethods<crate::DomTypeHolder> for FileReader { /// <https://w3c.github.io/FileAPI/#filereaderConstrctr> fn Constructor( cx: &mut js::context::JSContext, global: &GlobalScope, proto: Option<HandleObject>, ) -> Fallible<DomRoot<FileReader>> { Ok(FileReader::new(cx, global, proto)) } // https://w3c.github.io/FileAPI/#dfn-onloadstart event_handler!(loadstart, GetOnloadstart, SetOnloadstart); // https://w3c.github.io/FileAPI/#dfn-onprogress event_handler!(progress, GetOnprogress, SetOnprogress); // https://w3c.github.io/FileAPI/#dfn-onload event_handler!(load, GetOnload, SetOnload); // https://w3c.github.io/FileAPI/#dfn-onabort event_handler!(abort, GetOnabort, SetOnabort); // https://w3c.github.io/FileAPI/#dfn-onerror event_handler!(error, GetOnerror, SetOnerror); // https://w3c.github.io/FileAPI/#dfn-onloadend event_handler!(loadend, GetOnloadend, SetOnloadend); /// <https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer> fn ReadAsArrayBuffer(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult { // > The readAsArrayBuffer(blob) method, when invoked, // must initiate a read operation for blob with ArrayBuffer. self.read(cx, FileReaderFunction::ArrayBuffer, blob, None) } /// <https://w3c.github.io/FileAPI/#dfn-readAsBinaryString> fn ReadAsBinaryString(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult { // > The readAsBinaryString(blob) method, when invoked, // must initiate a read operation for blob with BinaryString. self.read(cx, FileReaderFunction::BinaryString, blob, None) } /// <https://w3c.github.io/FileAPI/#dfn-readAsDataURL> fn ReadAsDataURL(&self, cx: &mut js::context::JSContext, blob: &Blob) -> ErrorResult { // > The readAsDataURL(blob) method, when invoked, // must initiate a read operation for blob with DataURL. self.read(cx, FileReaderFunction::DataUrl, blob, None) } /// <https://w3c.github.io/FileAPI/#dfn-readAsText> fn ReadAsText( &self, cx: &mut js::context::JSContext, blob: &Blob, encoding: Option<DOMString>, ) -> ErrorResult { // > The readAsText(blob, encoding) method, when invoked, // must initiate a read operation for blob with Text and encoding. self.read(cx, FileReaderFunction::Text, blob, encoding) } /// <https://w3c.github.io/FileAPI/#dfn-abort> fn Abort(&self, cx: &mut js::context::JSContext) { // Step 2 if self.ready_state.get() == FileReaderReadyState::Loading { self.change_ready_state(FileReaderReadyState::Done); } // Steps 1 & 3 *self.result.borrow_mut() = None; let exception = DOMException::new(cx, &self.global(), DOMErrorName::AbortError); self.error.set(Some(&exception)); self.terminate_ongoing_reading(); // Steps 5 & 6 self.dispatch_progress_event(cx, atom!("abort"), 0, None); self.dispatch_progress_event(cx, atom!("loadend"), 0, None); } /// <https://w3c.github.io/FileAPI/#dfn-error> fn GetError(&self) -> Option<DomRoot<DOMException>> { self.error.get() } #[expect(unsafe_code)] /// <https://w3c.github.io/FileAPI/#dfn-result> fn GetResult(&self) -> Option<StringOrObject> { self.result.borrow().as_ref().map(|r| match *r { FileReaderResult::String(ref string) => StringOrObject::String(string.clone()), FileReaderResult::ArrayBuffer(ref arr_buffer) => { let result = RootedTraceableBox::new(Heap::default()); unsafe { result.set((*arr_buffer.ptr.get()).to_object()); } StringOrObject::Object(result) }, }) } /// <https://w3c.github.io/FileAPI/#dfn-readyState> fn ReadyState(&self) -> u16 { self.ready_state.get() as u16 } } impl FileReader { fn dispatch_progress_event( &self, cx: &mut js::context::JSContext, type_: Atom, loaded: u64, total: Option<u64>, ) { let progressevent = ProgressEvent::new( cx, &self.global(), type_, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, total.is_some(), Finite::wrap(loaded as f64), Finite::wrap(total.unwrap_or(0) as f64), ); progressevent.upcast::<Event>().fire(cx, self.upcast()); } fn terminate_ongoing_reading(&self) { let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); } /// <https://w3c.github.io/FileAPI/#readOperation> fn read( &self, cx: &mut js::context::JSContext, function: FileReaderFunction, blob: &Blob, encoding: Option<DOMString>, ) -> ErrorResult { // If fr’s state is "loading", throw an InvalidStateError DOMException. if self.ready_state.get() == FileReaderReadyState::Loading { return Err(Error::InvalidState(None)); } // Set fr’s state to "loading". self.change_ready_state(FileReaderReadyState::Loading); // Set fr’s result to null. *self.result.borrow_mut() = None; // Set fr’s error to null. // See the note below in the error steps. // Let stream be the result of calling get stream on blob. let stream = blob.get_stream(cx); // Let reader be the result of getting a reader from stream. let reader = stream.and_then(|s| s.acquire_default_reader(cx))?; let load_data = ReadMetaData::new( String::from(blob.Type()), encoding.map(String::from), function, ); let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); let gen_id = self.generation_id.get(); let filereader_success = DomRoot::from_ref(self); let filereader_error = DomRoot::from_ref(self); // In parallel, while true: // Wait for chunkPromise to be fulfilled or rejected. // Note: the spec appears wrong or outdated, // so for now we use the simple `read_all_bytes` call, // which means we cannot fire the progress event at each chunk. // This can be revisisted following the discussion at // <https://github.com/w3c/FileAPI/issues/208> // Read all bytes from stream with reader. reader.read_all_bytes( cx, Rc::new(move |_cx, blob_contents| { let global = filereader_success.global(); let task_manager = global.task_manager(); let task_source = task_manager.file_reading_task_source(); // If chunkPromise is fulfilled, // and isFirstChunk is true, // queue a task // Note: this should be done for the first chunk, // see issue above. task_source.queue(FileReadingTask::ProcessRead( Trusted::new(&filereader_success.clone()), gen_id, )); // If chunkPromise is fulfilled // with an object whose done property is false // and whose value property is a Uint8Array object // Note: this should be done for each chunk, // see issue above. if !blob_contents.is_empty() { task_source.queue(FileReadingTask::ProcessReadData( Trusted::new(&filereader_success.clone()), gen_id, )); } // Otherwise, // if chunkPromise is fulfilled with an object whose done property is true, // queue a task // Note: we are in the succes steps of `read_all_bytes`, // so the last chunk has been received. task_source.queue(FileReadingTask::ProcessReadEOF( Trusted::new(&filereader_success.clone()), gen_id, load_data.clone(), blob_contents.to_vec(), )); }), Rc::new(move |_cx, _error| { let global = filereader_error.global(); let task_manager = global.task_manager(); let task_source = task_manager.file_reading_task_source(); // Otherwise, if chunkPromise is rejected with an error error, // queue a task // Note: not using the error from `read_all_bytes`, // see issue above. task_source.queue(FileReadingTask::ProcessReadError( Trusted::new(&filereader_error), gen_id, DOMErrorName::OperationError, )); }), ); Ok(()) } fn change_ready_state(&self, state: FileReaderReadyState) { self.ready_state.set(state); } }