From 72521a033911018ef92987a5625b59bddbd05d64 Mon Sep 17 00:00:00 2001 From: Adenilson Cavalcanti Date: Mon, 30 Mar 2015 12:40:12 -0600 Subject: [PATCH] servo: Merge #5366 - Load a placeholder when a url to an image is broken (from Adenilson:loadPlaceholder01); r=jdm I decided to use the old Netscape broken image link icon (later we may replace the image asset for something more trendier). Source-Repo: https://github.com/servo/servo Source-Revision: 1e282d55d7e36f0e488a395b90e961e9200c3817 --- servo/components/net/image_cache_task.rs | 94 ++++++++++++++++------- servo/components/servo/lib.rs | 6 +- servo/ports/gonk/src/lib.rs | 6 +- servo/resources/rippy.jpg | Bin 0 -> 35425 bytes 4 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 servo/resources/rippy.jpg diff --git a/servo/components/net/image_cache_task.rs b/servo/components/net/image_cache_task.rs index 4719a3054851..e881e80dd41b 100644 --- a/servo/components/net/image_cache_task.rs +++ b/servo/components/net/image_cache_task.rs @@ -15,6 +15,7 @@ use std::mem::replace; use std::sync::{Arc, Mutex}; use std::sync::mpsc::{channel, Receiver, Sender}; use url::Url; +use util::resource_files::resources_dir_path; use util::task::spawn_named; use util::taskpool::TaskPool; @@ -75,7 +76,8 @@ pub struct ImageCacheTask { impl ImageCacheTask { pub fn new(resource_task: ResourceTask, task_pool: TaskPool, - time_profiler_chan: time::ProfilerChan) -> ImageCacheTask { + time_profiler_chan: time::ProfilerChan, + load_placeholder: LoadPlaceholder) -> ImageCacheTask { let (chan, port) = channel(); let chan_clone = chan.clone(); @@ -89,8 +91,9 @@ impl ImageCacheTask { need_exit: None, task_pool: task_pool, time_profiler_chan: time_profiler_chan, + placeholder_data: Arc::new(vec!()), }; - cache.run(); + cache.run(load_placeholder); }); ImageCacheTask { @@ -99,12 +102,13 @@ impl ImageCacheTask { } pub fn new_sync(resource_task: ResourceTask, task_pool: TaskPool, - time_profiler_chan: time::ProfilerChan) -> ImageCacheTask { + time_profiler_chan: time::ProfilerChan, + load_placeholder: LoadPlaceholder) -> ImageCacheTask { let (chan, port) = channel(); spawn_named("ImageCacheTask (sync)".to_owned(), move || { let inner_cache = ImageCacheTask::new(resource_task, task_pool, - time_profiler_chan); + time_profiler_chan, load_placeholder); loop { let msg: Msg = port.recv().unwrap(); @@ -142,6 +146,8 @@ struct ImageCache { need_exit: Option>, task_pool: TaskPool, time_profiler_chan: time::ProfilerChan, + // Default image used when loading fails. + placeholder_data: Arc>, } #[derive(Clone)] @@ -160,8 +166,32 @@ enum AfterPrefetch { DoNotDecode } +pub enum LoadPlaceholder { + Preload, + Ignore +} + impl ImageCache { - pub fn run(&mut self) { + // Used to preload the default placeholder. + fn init(&mut self) { + let mut placeholder_url = resources_dir_path(); + // TODO (Savago): replace for a prettier one. + placeholder_url.push("rippy.jpg"); + let image = load_image_data(Url::from_file_path(&*placeholder_url).unwrap(), self.resource_task.clone(), &self.placeholder_data); + + match image { + Err(..) => debug!("image_cache_task: failed loading the placeholder."), + Ok(image_data) => self.placeholder_data = Arc::new(image_data), + } + } + + pub fn run(&mut self, load_placeholder: LoadPlaceholder) { + // We have to load the placeholder before running. + match load_placeholder { + LoadPlaceholder::Preload => self.init(), + LoadPlaceholder::Ignore => debug!("image_cache_task: use old behavior."), + } + let mut store_chan: Option> = None; let mut store_prefetched_chan: Option> = None; @@ -245,12 +275,12 @@ impl ImageCache { let to_cache = self.chan.clone(); let resource_task = self.resource_task.clone(); let url_clone = url.clone(); - + let placeholder = self.placeholder_data.clone(); spawn_named("ImageCacheTask (prefetch)".to_owned(), move || { let url = url_clone; debug!("image_cache_task: started fetch for {}", url.serialize()); - let image = load_image_data(url.clone(), resource_task.clone()); + let image = load_image_data(url.clone(), resource_task.clone(), &placeholder); to_cache.send(Msg::StorePrefetchedImageData(url.clone(), image)).unwrap(); debug!("image_cache_task: ended fetch for {}", url.serialize()); }); @@ -446,9 +476,9 @@ impl ImageCacheTask { } } -fn load_image_data(url: Url, resource_task: ResourceTask) -> Result, ()> { +fn load_image_data(url: Url, resource_task: ResourceTask, placeholder: &[u8]) -> Result, ()> { let (response_chan, response_port) = channel(); - resource_task.send(resource_task::ControlMsg::Load(LoadData::new(url, response_chan))).unwrap(); + resource_task.send(resource_task::ControlMsg::Load(LoadData::new(url.clone(), response_chan))).unwrap(); let mut image_data = vec!(); @@ -462,7 +492,19 @@ fn load_image_data(url: Url, resource_task: ResourceTask) -> Result, ()> return Ok(image_data); } Done(Err(..)) => { - return Err(()); + // Failure to load the requested image will return the + // placeholder instead. In case it failed to load at init(), + // we still recover and return Err() but nothing will be drawn. + if placeholder.len() != 0 { + debug!("image_cache_task: failed to load {:?}, use placeholder instead.", url); + // Clean in case there was an error after started loading the image. + image_data.clear(); + image_data.push_all(&placeholder); + return Ok(image_data); + } else { + debug!("image_cache_task: invalid placeholder."); + return Err(()); + } } } } @@ -595,7 +637,7 @@ mod tests { fn should_exit_on_request() { let mock_resource_task = mock_resource_task(box DoesNothing); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); image_cache_task.exit(); mock_resource_task.send(resource_task::ControlMsg::Exit); @@ -606,7 +648,7 @@ mod tests { fn should_panic_if_unprefetched_image_is_requested() { let mock_resource_task = mock_resource_task(box DoesNothing); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let (chan, port) = channel(); @@ -620,7 +662,7 @@ mod tests { let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan}); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url)); @@ -635,7 +677,7 @@ mod tests { let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan}); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -655,7 +697,7 @@ mod tests { let mock_resource_task = mock_resource_task(box WaitSendTestImage{wait_port: wait_port}); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -672,7 +714,7 @@ mod tests { fn should_return_decoded_image_data_if_data_has_arrived() { let mock_resource_task = mock_resource_task(box SendTestImage); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store(); @@ -698,7 +740,7 @@ mod tests { fn should_return_decoded_image_data_for_multiple_requests() { let mock_resource_task = mock_resource_task(box SendTestImage); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store(); @@ -752,7 +794,7 @@ mod tests { } }); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -804,7 +846,7 @@ mod tests { } }); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -833,7 +875,7 @@ mod tests { fn should_return_failed_if_image_bin_cannot_be_fetched() { let mock_resource_task = mock_resource_task(box SendTestImageErr); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store_prefetched(); @@ -859,7 +901,7 @@ mod tests { fn should_return_failed_for_multiple_get_image_requests_if_image_bin_cannot_be_fetched() { let mock_resource_task = mock_resource_task(box SendTestImageErr); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store_prefetched(); @@ -893,7 +935,7 @@ mod tests { fn should_return_failed_if_image_decode_fails() { let mock_resource_task = mock_resource_task(box SendBogusImage); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store(); @@ -921,7 +963,7 @@ mod tests { fn should_return_image_on_wait_if_image_is_already_loaded() { let mock_resource_task = mock_resource_task(box SendTestImage); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); let join_port = image_cache_task.wait_for_store(); @@ -949,7 +991,7 @@ mod tests { let mock_resource_task = mock_resource_task(box WaitSendTestImage {wait_port: wait_port}); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -975,7 +1017,7 @@ mod tests { let mock_resource_task = mock_resource_task(box WaitSendTestImageErr{wait_port: wait_port}); - let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Ignore); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); @@ -999,7 +1041,7 @@ mod tests { fn sync_cache_should_wait_for_images() { let mock_resource_task = mock_resource_task(box SendTestImage); - let image_cache_task = ImageCacheTask::new_sync(mock_resource_task.clone(), TaskPool::new(4), profiler()); + let image_cache_task = ImageCacheTask::new_sync(mock_resource_task.clone(), TaskPool::new(4), profiler(), LoadPlaceholder::Preload); let url = Url::parse("file:///").unwrap(); image_cache_task.send(Prefetch(url.clone())); diff --git a/servo/components/servo/lib.rs b/servo/components/servo/lib.rs index 1afdbf8dca5f..b197ad30e6f5 100644 --- a/servo/components/servo/lib.rs +++ b/servo/components/servo/lib.rs @@ -33,7 +33,7 @@ use msg::constellation_msg::ConstellationChan; use script::dom::bindings::codegen::RegisterBindings; #[cfg(not(test))] -use net::image_cache_task::ImageCacheTask; +use net::image_cache_task::{ImageCacheTask, LoadPlaceholder}; #[cfg(not(test))] use net::resource_task::new_resource_task; #[cfg(not(test))] @@ -84,10 +84,10 @@ impl Browser { // image. let image_cache_task = if opts.output_file.is_some() { ImageCacheTask::new_sync(resource_task.clone(), shared_task_pool, - time_profiler_chan.clone()) + time_profiler_chan.clone(), LoadPlaceholder::Preload) } else { ImageCacheTask::new(resource_task.clone(), shared_task_pool, - time_profiler_chan.clone()) + time_profiler_chan.clone(), LoadPlaceholder::Preload) }; let font_cache_task = FontCacheTask::new(resource_task.clone()); diff --git a/servo/ports/gonk/src/lib.rs b/servo/ports/gonk/src/lib.rs index 46c12ef0cbae..ca01f431ae0f 100644 --- a/servo/ports/gonk/src/lib.rs +++ b/servo/ports/gonk/src/lib.rs @@ -38,7 +38,7 @@ use msg::constellation_msg::ConstellationChan; use script::dom::bindings::codegen::RegisterBindings; #[cfg(not(test))] -use net::image_cache_task::ImageCacheTask; +use net::image_cache_task::{ImageCacheTask, LoadPlaceholder}; #[cfg(not(test))] use net::storage_task::StorageTaskFactory; #[cfg(not(test))] @@ -89,10 +89,10 @@ impl Browser { // image. let image_cache_task = if opts.output_file.is_some() { ImageCacheTask::new_sync(resource_task.clone(), shared_task_pool, - time_profiler_chan.clone()) + time_profiler_chan.clone(), LoadPlaceholder::Preload) } else { ImageCacheTask::new(resource_task.clone(), shared_task_pool, - time_profiler_chan.clone()) + time_profiler_chan.clone(), LoadPlaceholder::Preload) }; let font_cache_task = FontCacheTask::new(resource_task.clone()); let storage_task = StorageTaskFactory::new(); diff --git a/servo/resources/rippy.jpg b/servo/resources/rippy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a94649ae3f28a1ee9733ba9ce6546468f1128de8 GIT binary patch literal 35425 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 literal 0 HcmV?d00001