diff --git a/components/net/blob_loader.rs b/components/net/blob_loader.rs index 1008b405e3ee..e12cdab9804f 100644 --- a/components/net/blob_loader.rs +++ b/components/net/blob_loader.rs @@ -19,9 +19,9 @@ use std::sync::Arc; // TODO: Check on GET // https://w3c.github.io/FileAPI/#requestResponseModel -pub fn load_blob(load_data: &LoadData, start_chan: LoadConsumer, +pub fn load_blob(load_data: LoadData, start_chan: LoadConsumer, classifier: Arc, opt_filename: Option, - rel_pos: &RelativePos, entry: &BlobURLStoreEntry) { + rel_pos: RelativePos, entry: BlobURLStoreEntry) { let content_type: Mime = entry.type_string.parse().unwrap_or(mime!(Text / Plain)); let charset = content_type.get_param(Attr::Charset); diff --git a/components/net/filemanager_thread.rs b/components/net/filemanager_thread.rs index 2a32bc910405..3ff8579e0d25 100644 --- a/components/net/filemanager_thread.rs +++ b/components/net/filemanager_thread.rs @@ -11,12 +11,13 @@ use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult, Fi use net_traits::filemanager_thread::{SelectedFile, RelativePos, FileManagerThreadError, SelectedFileId}; use net_traits::{LoadConsumer, LoadData, NetworkError}; use resource_thread::send_error; -use std::cell::Cell; use std::collections::HashMap; use std::fs::File; use std::io::Read; +use std::ops::Index; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::sync::atomic::{self, AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; #[cfg(any(target_os = "macos", target_os = "linux"))] use tinyfiledialogs; use url::Url; @@ -96,10 +97,11 @@ struct FileStoreEntry { /// Backend implementation file_impl: FileImpl, /// Reference counting - refs: Cell, + refs: AtomicUsize, } /// File backend implementation +#[derive(Clone)] enum FileImpl { PathOnly(PathBuf), Memory(BlobURLStoreEntry), @@ -108,57 +110,76 @@ enum FileImpl { struct FileManager { receiver: IpcReceiver, - store: HashMap, + store: Arc>, classifier: Arc, - ui: &'static UI, } impl FileManager { fn new(recv: IpcReceiver, ui: &'static UI) -> FileManager { FileManager { receiver: recv, - store: HashMap::new(), + store: Arc::new(FileManagerStore::new(ui)), classifier: Arc::new(MimeClassifier::new()), - ui: ui, } } /// Start the file manager event loop fn start(&mut self) { loop { + let store = self.store.clone(); match self.receiver.recv().unwrap() { - FileManagerThreadMsg::SelectFile(filter, sender, origin) => self.select_file(filter, sender, origin), - FileManagerThreadMsg::SelectFiles(filter, sender, origin) => self.select_files(filter, sender, origin), + FileManagerThreadMsg::SelectFile(filter, sender, origin) => { + spawn_named("select file".to_owned(), move || { + store.select_file(filter, sender, origin); + }); + } + FileManagerThreadMsg::SelectFiles(filter, sender, origin) => { + spawn_named("select files".to_owned(), move || { + store.select_files(filter, sender, origin); + }) + } FileManagerThreadMsg::ReadFile(sender, id, origin) => { - match self.try_read_file(id, origin) { - Ok(buffer) => { let _ = sender.send(Ok(buffer)); } - Err(_) => { let _ = sender.send(Err(FileManagerThreadError::ReadFileError)); } - } + spawn_named("read file".to_owned(), move || { + match store.try_read_file(id, origin) { + Ok(buffer) => { let _ = sender.send(Ok(buffer)); } + Err(_) => { let _ = sender.send(Err(FileManagerThreadError::ReadFileError)); } + } + }) + } + FileManagerThreadMsg::TransferMemory(entry, sender, origin) => { + spawn_named("transfer memory".to_owned(), move || { + store.transfer_memory(entry, sender, origin); + }) + } + FileManagerThreadMsg::AddSlicedEntry(id, rel_pos, sender, origin) =>{ + spawn_named("add sliced entry".to_owned(), move || { + store.add_sliced_entry(id, rel_pos, sender, origin); + }) } - FileManagerThreadMsg::TransferMemory(entry, sender, origin) => - self.transfer_memory(entry, sender, origin), - FileManagerThreadMsg::AddSlicedEntry(id, rel_pos, sender, origin) => - self.add_sliced_entry(id, rel_pos, sender, origin), FileManagerThreadMsg::LoadBlob(load_data, consumer) => { - match parse_blob_url(&load_data.url) { + match parse_blob_url(&load_data.url.clone()) { None => { let e = format!("Invalid blob URL format {:?}", load_data.url); let format_err = NetworkError::Internal(e); send_error(load_data.url.clone(), format_err, consumer); } Some((id, _fragment)) => { - self.process_request(&load_data, consumer, &RelativePos::full_range(), &id); + self.process_request(load_data, consumer, RelativePos::full_range(), id); } } }, - FileManagerThreadMsg::DecRef(id, origin) => { + FileManagerThreadMsg::DecRef(id, origin, sender) => { if let Ok(id) = Uuid::parse_str(&id.0) { - self.dec_ref(id, origin); + spawn_named("dec ref".to_owned(), move || { + let _ = sender.send(store.dec_ref(&id, &origin)); + }) } } FileManagerThreadMsg::IncRef(id, origin) => { if let Ok(id) = Uuid::parse_str(&id.0) { - self.inc_ref(id, origin); + spawn_named("inc ref".to_owned(), move || { + let _ = store.inc_ref(&id, &origin); + }) } } FileManagerThreadMsg::Exit => break, @@ -166,51 +187,127 @@ impl FileManager { } } - fn inc_ref(&mut self, id: Uuid, origin_in: FileOrigin) { - match self.store.get(&id) { + fn process_request(&self, load_data: LoadData, consumer: LoadConsumer, + rel_pos: RelativePos, id: Uuid) { + let origin_in = load_data.url.origin().unicode_serialization(); + match self.store.get_impl(&id, &origin_in) { + Ok(file_impl) => { + match file_impl { + FileImpl::Memory(buffered) => { + let classifier = self.classifier.clone(); + spawn_named("load blob".to_owned(), move || + load_blob(load_data, consumer, classifier, + None, rel_pos, buffered)); + } + FileImpl::PathOnly(filepath) => { + let opt_filename = filepath.file_name() + .and_then(|osstr| osstr.to_str()) + .map(|s| s.to_string()); + + let mut bytes = vec![]; + let mut handler = File::open(&filepath).unwrap(); + let mime = guess_mime_type_opt(filepath); + let size = handler.read_to_end(&mut bytes).unwrap(); + + let entry = BlobURLStoreEntry { + type_string: match mime { + Some(x) => format!("{}", x), + None => "".to_string(), + }, + size: size as u64, + bytes: bytes, + }; + let classifier = self.classifier.clone(); + spawn_named("load blob".to_owned(), move || + load_blob(load_data, consumer, classifier, + opt_filename, rel_pos, entry)); + }, + FileImpl::Sliced(id, rel_pos) => { + self.process_request(load_data, consumer, rel_pos, id); + } + } + } + Err(e) => { + send_error(load_data.url.clone(), NetworkError::Internal(format!("{:?}", e)), consumer); + } + } + } +} + +struct FileManagerStore { + entries: RwLock>, + ui: &'static UI, +} + +impl FileManagerStore { + fn new(ui: &'static UI) -> Self { + FileManagerStore { + entries: RwLock::new(HashMap::new()), + ui: ui, + } + } + + /// Copy out the file backend implementation content + fn get_impl(&self, id: &Uuid, origin_in: &FileOrigin) -> Result { + match self.entries.read().unwrap().get(id) { + Some(ref e) => { + if *origin_in != *e.origin { + Err(BlobURLStoreError::InvalidOrigin) + } else { + Ok(e.file_impl.clone()) + } + } + None => Err(BlobURLStoreError::InvalidFileID), + } + } + + fn insert(&self, id: Uuid, entry: FileStoreEntry) { + self.entries.write().unwrap().insert(id, entry); + } + + fn remove(&self, id: &Uuid) { + self.entries.write().unwrap().remove(id); + } + + fn inc_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError>{ + match self.entries.read().unwrap().get(id) { Some(entry) => { - if entry.origin == origin_in { - entry.refs.set(entry.refs.get() + 1); + if entry.origin == *origin_in { + entry.refs.fetch_add(1, Ordering::Relaxed); + Ok(()) + } else { + Err(BlobURLStoreError::InvalidOrigin) } } - None => return, // Invalid UUID + None => Err(BlobURLStoreError::InvalidFileID), } } - fn add_sliced_entry(&mut self, id: SelectedFileId, rel_pos: RelativePos, + fn add_sliced_entry(&self, parent_id: SelectedFileId, rel_pos: RelativePos, sender: IpcSender>, origin_in: FileOrigin) { - if let Ok(id) = Uuid::parse_str(&id.0) { - match self.store.get(&id) { - Some(entry) => { - if entry.origin == origin_in { - // inc_ref on parent entry - entry.refs.set(entry.refs.get() + 1); - } else { - let _ = sender.send(Err(BlobURLStoreError::InvalidOrigin)); - return; - } - }, - None => { - let _ = sender.send(Err(BlobURLStoreError::InvalidFileID)); - return; + if let Ok(parent_id) = Uuid::parse_str(&parent_id.0) { + match self.inc_ref(&parent_id, &origin_in) { + Ok(_) => { + let new_id = Uuid::new_v4(); + self.insert(new_id, FileStoreEntry { + origin: origin_in, + file_impl: FileImpl::Sliced(parent_id, rel_pos), + refs: AtomicUsize::new(1), + }); + + let _ = sender.send(Ok(SelectedFileId(new_id.simple().to_string()))); } - }; - - let new_id = Uuid::new_v4(); - self.store.insert(new_id, FileStoreEntry { - origin: origin_in.clone(), - file_impl: FileImpl::Sliced(id, rel_pos), - refs: Cell::new(1), - }); - - let _ = sender.send(Ok(SelectedFileId(new_id.simple().to_string()))); + Err(e) => { + let _ = sender.send(Err(e)); + } + } } else { let _ = sender.send(Err(BlobURLStoreError::InvalidFileID)); } } - fn select_file(&mut self, patterns: Vec, + fn select_file(&self, patterns: Vec, sender: IpcSender>, origin: FileOrigin) { match self.ui.open_file_dialog("", patterns) { @@ -229,7 +326,7 @@ impl FileManager { } } - fn select_files(&mut self, patterns: Vec, + fn select_files(&self, patterns: Vec, sender: IpcSender>>, origin: FileOrigin) { match self.ui.open_file_dialog_multi("", patterns) { @@ -258,16 +355,16 @@ impl FileManager { } } - fn create_entry(&mut self, file_path: &Path, origin: &str) -> Option { + fn create_entry(&self, file_path: &Path, origin: &str) -> Option { match File::open(file_path) { Ok(handler) => { let id = Uuid::new_v4(); let file_impl = FileImpl::PathOnly(file_path.to_path_buf()); - self.store.insert(id, FileStoreEntry { + self.insert(id, FileStoreEntry { origin: origin.to_string(), file_impl: file_impl, - refs: Cell::new(1), + refs: AtomicUsize::new(1), }); // Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html @@ -303,42 +400,40 @@ impl FileManager { } } - fn try_read_file(&self, id: SelectedFileId, origin_in: String) -> Result, ()> { - let id = try!(Uuid::parse_str(&id.0).map_err(|_| ())); - - match self.store.get(&id) { - Some(entry) => { - match entry.file_impl { - FileImpl::PathOnly(ref filepath) => { - if *entry.origin == origin_in { - let mut buffer = vec![]; - let mut handler = try!(File::open(filepath).map_err(|_| ())); - try!(handler.read_to_end(&mut buffer).map_err(|_| ())); - Ok(buffer) - } else { - Err(()) - } + fn try_read_file(&self, id: SelectedFileId, origin_in: FileOrigin) -> Result, BlobURLStoreError> { + let id = try!(Uuid::parse_str(&id.0).map_err(|_| BlobURLStoreError::InvalidFileID)); + + match self.get_impl(&id, &origin_in) { + Ok(file_impl) => { + match file_impl { + FileImpl::PathOnly(filepath) => { + let mut buffer = vec![]; + let mut handler = try!(File::open(filepath) + .map_err(|_| BlobURLStoreError::InvalidEntry)); + try!(handler.read_to_end(&mut buffer) + .map_err(|_| BlobURLStoreError::External)); + Ok(buffer) }, - FileImpl::Memory(ref buffered) => { - Ok(buffered.bytes.clone()) + FileImpl::Memory(buffered) => { + Ok(buffered.bytes) }, - FileImpl::Sliced(ref id, ref _rel_pos) => { + FileImpl::Sliced(id, rel_pos) => { self.try_read_file(SelectedFileId(id.simple().to_string()), origin_in) + .map(|bytes| bytes.index(rel_pos.to_abs_range(bytes.len())).to_vec()) } } }, - None => Err(()), + Err(e) => Err(e), } } - fn dec_ref(&mut self, id: Uuid, origin_in: FileOrigin) { - let (is_last_ref, opt_parent_id) = match self.store.get(&id) { + fn dec_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> { + let (is_last_ref, opt_parent_id) = match self.entries.read().unwrap().get(id) { Some(entry) => { - if *entry.origin == origin_in { - let r = entry.refs.get(); + if *entry.origin == *origin_in { + let old_refs = entry.refs.fetch_sub(1, Ordering::Release); - if r > 1 { - entry.refs.set(r - 1); + if old_refs > 1 { (false, None) } else { if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl { @@ -349,85 +444,33 @@ impl FileManager { } } } else { // Invalid origin - return; + return Err(BlobURLStoreError::InvalidOrigin); } } - None => return, // Invalid UUID + None => return Err(BlobURLStoreError::InvalidFileID), }; if is_last_ref { - self.store.remove(&id); + atomic::fence(Ordering::Acquire); + self.remove(id); if let Some(parent_id) = opt_parent_id { - self.dec_ref(parent_id, origin_in); + return self.dec_ref(&parent_id, origin_in); } } - } - fn process_request(&self, load_data: &LoadData, consumer: LoadConsumer, - rel_pos: &RelativePos, id: &Uuid) { - let origin_in = load_data.url.origin().unicode_serialization(); - match self.store.get(id) { - Some(entry) => { - match entry.file_impl { - FileImpl::Memory(ref buffered) => { - if *entry.origin == origin_in { - load_blob(&load_data, consumer, self.classifier.clone(), - None, rel_pos, buffered); - } else { - let e = format!("Invalid blob URL origin {:?}", origin_in); - send_error(load_data.url.clone(), NetworkError::Internal(e), consumer); - } - }, - FileImpl::PathOnly(ref filepath) => { - let opt_filename = filepath.file_name() - .and_then(|osstr| osstr.to_str()) - .map(|s| s.to_string()); - - if *entry.origin == origin_in { - let mut bytes = vec![]; - let mut handler = File::open(filepath).unwrap(); - let mime = guess_mime_type_opt(filepath); - let size = handler.read_to_end(&mut bytes).unwrap(); - - let entry = BlobURLStoreEntry { - type_string: match mime { - Some(x) => format!("{}", x), - None => "".to_string(), - }, - size: size as u64, - bytes: bytes, - }; - - load_blob(&load_data, consumer, self.classifier.clone(), - opt_filename, rel_pos, &entry); - } else { - let e = format!("Invalid blob URL origin {:?}", origin_in); - send_error(load_data.url.clone(), NetworkError::Internal(e), consumer); - } - }, - FileImpl::Sliced(ref id, ref rel_pos) => { - self.process_request(load_data, consumer, rel_pos, id); - } - } - } - _ => { - let e = format!("Invalid blob URL key {:?}", id.simple().to_string()); - send_error(load_data.url.clone(), NetworkError::Internal(e), consumer); - } - } + Ok(()) } - fn transfer_memory(&mut self, entry: BlobURLStoreEntry, - sender: IpcSender>, - origin: FileOrigin) { + fn transfer_memory(&self, entry: BlobURLStoreEntry, + sender: IpcSender>, origin: FileOrigin) { match Url::parse(&origin) { // parse to check sanity Ok(_) => { let id = Uuid::new_v4(); - self.store.insert(id, FileStoreEntry { + self.insert(id, FileStoreEntry { origin: origin.clone(), file_impl: FileImpl::Memory(entry), - refs: Cell::new(1), + refs: AtomicUsize::new(1), }); let _ = sender.send(Ok(SelectedFileId(id.simple().to_string()))); diff --git a/components/net_traits/blob_url_store.rs b/components/net_traits/blob_url_store.rs index 6959f658a2f5..aa68038606a4 100644 --- a/components/net_traits/blob_url_store.rs +++ b/components/net_traits/blob_url_store.rs @@ -13,6 +13,10 @@ pub enum BlobURLStoreError { InvalidFileID, /// Invalid URL origin InvalidOrigin, + /// Invalid entry content + InvalidEntry, + /// External error, from like file system, I/O etc. + External, } /// Blob URL store entry, a packaged form of Blob DOM object diff --git a/components/net_traits/filemanager_thread.rs b/components/net_traits/filemanager_thread.rs index bc9d8773f4e2..78efc142a808 100644 --- a/components/net_traits/filemanager_thread.rs +++ b/components/net_traits/filemanager_thread.rs @@ -136,7 +136,7 @@ pub enum FileManagerThreadMsg { AddSlicedEntry(SelectedFileId, RelativePos, IpcSender>, FileOrigin), /// Decrease reference count - DecRef(SelectedFileId, FileOrigin), + DecRef(SelectedFileId, FileOrigin, IpcSender>), /// Increase reference count IncRef(SelectedFileId, FileOrigin), diff --git a/components/script/dom/url.rs b/components/script/dom/url.rs index 9a47be034f80..c2d7516dd6f1 100644 --- a/components/script/dom/url.rs +++ b/components/script/dom/url.rs @@ -13,6 +13,7 @@ use dom::bindings::str::{DOMString, USVString}; use dom::blob::Blob; use dom::urlhelper::UrlHelper; use dom::urlsearchparams::URLSearchParams; +use ipc_channel::ipc; use net_traits::IpcSend; use net_traits::blob_url_store::parse_blob_url; use net_traits::filemanager_thread::{SelectedFileId, FileManagerThreadMsg}; @@ -142,17 +143,16 @@ impl URL { */ let origin = global.get_url().origin().unicode_serialization(); - match Url::parse(&url) { - Ok(url) => match parse_blob_url(&url) { - Some((id, _)) => { - let filemanager = global.resource_threads().sender(); - let id = SelectedFileId(id.simple().to_string()); - let msg = FileManagerThreadMsg::DecRef(id, origin); - let _ = filemanager.send(msg); - } - None => {} - }, - Err(_) => {} + if let Ok(url) = Url::parse(&url) { + if let Some((id, _)) = parse_blob_url(&url) { + let filemanager = global.resource_threads().sender(); + let id = SelectedFileId(id.simple().to_string()); + let (tx, rx) = ipc::channel().unwrap(); + let msg = FileManagerThreadMsg::DecRef(id, origin, tx); + let _ = filemanager.send(msg); + + let _ = rx.recv().unwrap(); + } } } diff --git a/tests/unit/net/filemanager_thread.rs b/tests/unit/net/filemanager_thread.rs index 6a283669b70d..27ff56d6a1e8 100644 --- a/tests/unit/net/filemanager_thread.rs +++ b/tests/unit/net/filemanager_thread.rs @@ -41,7 +41,7 @@ fn test_filemanager() { // Try to select a dummy file "tests/unit/net/test.txt" let (tx, rx) = ipc::channel().unwrap(); chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone())).unwrap(); - let selected = rx.recv().expect("File manager channel is broken") + let selected = rx.recv().expect("Broken channel") .expect("The file manager failed to find test.txt"); // Expecting attributes conforming the spec @@ -53,21 +53,27 @@ fn test_filemanager() { let (tx2, rx2) = ipc::channel().unwrap(); chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), origin.clone())).unwrap(); - let msg = rx2.recv().expect("File manager channel is broken"); + let msg = rx2.recv().expect("Broken channel"); let vec = msg.expect("File manager reading failure is unexpected"); assert!(test_file_content == vec, "Read content differs"); } // Delete the id - chan.send(FileManagerThreadMsg::DecRef(selected.id.clone(), origin.clone())).unwrap(); + { + let (tx2, rx2) = ipc::channel().unwrap(); + chan.send(FileManagerThreadMsg::DecRef(selected.id.clone(), origin.clone(), tx2)).unwrap(); + + let ret = rx2.recv().expect("Broken channel"); + assert!(ret.is_ok(), "DecRef is not okay"); + } // Test by reading again, expecting read error because we invalidated the id { let (tx2, rx2) = ipc::channel().unwrap(); chan.send(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), origin.clone())).unwrap(); - let msg = rx2.recv().expect("File manager channel is broken"); + let msg = rx2.recv().expect("Broken channel"); match msg { Err(FileManagerThreadError::ReadFileError) => {},