mirror of
https://github.com/servo/servo.git
synced 2025-06-23 16:44:33 +01:00
This patch puts in the initial framework for incremental reflow. Nodes' styles are no longer recalculated unless the node has changed. I've been hacking on the general problem of incremental reflow for the past couple weeks, and I've yet to get a full implementation that actually passes all the reftests + wikipedia + cnn. Therefore, I'm going to try to land the different parts of it one by one. This patch only does incremental style recalc, without incremental flow construction, inline-size bubbling, reflow, or display lists. Those will be coming in that order as I finish them. At least with this strategy, I can land a working version of incremental reflow, even if not yet complete. r? @pcwalton
993 lines
32 KiB
Rust
993 lines
32 KiB
Rust
/* 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
use image::base::{Image, load_from_memory};
|
|
use resource_task;
|
|
use resource_task::{LoadData, ResourceTask};
|
|
|
|
use std::comm::{channel, Receiver, Sender};
|
|
use std::collections::hashmap::HashMap;
|
|
use std::mem::replace;
|
|
use std::task::spawn;
|
|
use std::result;
|
|
use sync::{Arc, Mutex};
|
|
use serialize::{Encoder, Encodable};
|
|
use url::Url;
|
|
|
|
pub enum Msg {
|
|
/// Tell the cache that we may need a particular image soon. Must be posted
|
|
/// before Decode
|
|
Prefetch(Url),
|
|
|
|
/// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
|
|
Decode(Url),
|
|
|
|
/// Request an Image object for a URL. If the image is not is not immediately
|
|
/// available then ImageNotReady is returned.
|
|
GetImage(Url, Sender<ImageResponseMsg>),
|
|
|
|
/// Wait for an image to become available (or fail to load).
|
|
WaitForImage(Url, Sender<ImageResponseMsg>),
|
|
|
|
/// Clients must wait for a response before shutting down the ResourceTask
|
|
Exit(Sender<()>),
|
|
|
|
/// Used by the prefetch tasks to post back image binaries
|
|
StorePrefetchedImageData(Url, Result<Vec<u8>, ()>),
|
|
|
|
/// Used by the decoder tasks to post decoded images back to the cache
|
|
StoreImage(Url, Option<Arc<Box<Image>>>),
|
|
|
|
/// For testing
|
|
WaitForStore(Sender<()>),
|
|
|
|
/// For testing
|
|
WaitForStorePrefetched(Sender<()>),
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
pub enum ImageResponseMsg {
|
|
ImageReady(Arc<Box<Image>>),
|
|
ImageNotReady,
|
|
ImageFailed
|
|
}
|
|
|
|
impl PartialEq for ImageResponseMsg {
|
|
fn eq(&self, other: &ImageResponseMsg) -> bool {
|
|
match (self, other) {
|
|
(&ImageReady(..), &ImageReady(..)) => fail!("unimplemented comparison"),
|
|
(&ImageNotReady, &ImageNotReady) => true,
|
|
(&ImageFailed, &ImageFailed) => true,
|
|
|
|
(&ImageReady(..), _) | (&ImageNotReady, _) | (&ImageFailed, _) => false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
pub struct ImageCacheTask {
|
|
chan: Sender<Msg>,
|
|
}
|
|
|
|
impl<E, S: Encoder<E>> Encodable<S, E> for ImageCacheTask {
|
|
fn encode(&self, _: &mut S) -> Result<(), E> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
type DecoderFactory = fn() -> (proc(&[u8]) : 'static -> Option<Image>);
|
|
|
|
impl ImageCacheTask {
|
|
pub fn new(resource_task: ResourceTask) -> ImageCacheTask {
|
|
let (chan, port) = channel();
|
|
let chan_clone = chan.clone();
|
|
|
|
spawn(proc() {
|
|
let mut cache = ImageCache {
|
|
resource_task: resource_task,
|
|
port: port,
|
|
chan: chan_clone,
|
|
state_map: HashMap::new(),
|
|
wait_map: HashMap::new(),
|
|
need_exit: None
|
|
};
|
|
cache.run();
|
|
});
|
|
|
|
ImageCacheTask {
|
|
chan: chan,
|
|
}
|
|
}
|
|
|
|
pub fn new_sync(resource_task: ResourceTask) -> ImageCacheTask {
|
|
let (chan, port) = channel();
|
|
|
|
spawn(proc() {
|
|
let inner_cache = ImageCacheTask::new(resource_task);
|
|
|
|
loop {
|
|
let msg: Msg = port.recv();
|
|
|
|
match msg {
|
|
GetImage(url, response) => {
|
|
inner_cache.send(WaitForImage(url, response));
|
|
}
|
|
Exit(response) => {
|
|
inner_cache.send(Exit(response));
|
|
break;
|
|
}
|
|
msg => inner_cache.send(msg)
|
|
}
|
|
}
|
|
});
|
|
|
|
ImageCacheTask {
|
|
chan: chan,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ImageCache {
|
|
/// A handle to the resource task for fetching the image binaries
|
|
resource_task: ResourceTask,
|
|
/// The port on which we'll receive client requests
|
|
port: Receiver<Msg>,
|
|
/// A copy of the shared chan to give to child tasks
|
|
chan: Sender<Msg>,
|
|
/// The state of processsing an image for a URL
|
|
state_map: HashMap<Url, ImageState>,
|
|
/// List of clients waiting on a WaitForImage response
|
|
wait_map: HashMap<Url, Arc<Mutex<Vec<Sender<ImageResponseMsg>>>>>,
|
|
need_exit: Option<Sender<()>>,
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
enum ImageState {
|
|
Init,
|
|
Prefetching(AfterPrefetch),
|
|
Prefetched(Vec<u8>),
|
|
Decoding,
|
|
Decoded(Arc<Box<Image>>),
|
|
Failed
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
enum AfterPrefetch {
|
|
DoDecode,
|
|
DoNotDecode
|
|
}
|
|
|
|
impl ImageCache {
|
|
pub fn run(&mut self) {
|
|
let mut store_chan: Option<Sender<()>> = None;
|
|
let mut store_prefetched_chan: Option<Sender<()>> = None;
|
|
|
|
loop {
|
|
let msg = self.port.recv();
|
|
|
|
debug!("image_cache_task: received: {:?}", msg);
|
|
|
|
match msg {
|
|
Prefetch(url) => self.prefetch(url),
|
|
StorePrefetchedImageData(url, data) => {
|
|
store_prefetched_chan.map(|chan| {
|
|
chan.send(());
|
|
});
|
|
store_prefetched_chan = None;
|
|
|
|
self.store_prefetched_image_data(url, data);
|
|
}
|
|
Decode(url) => self.decode(url),
|
|
StoreImage(url, image) => {
|
|
store_chan.map(|chan| {
|
|
chan.send(());
|
|
});
|
|
store_chan = None;
|
|
|
|
self.store_image(url, image)
|
|
}
|
|
GetImage(url, response) => self.get_image(url, response),
|
|
WaitForImage(url, response) => {
|
|
self.wait_for_image(url, response)
|
|
}
|
|
WaitForStore(chan) => store_chan = Some(chan),
|
|
WaitForStorePrefetched(chan) => store_prefetched_chan = Some(chan),
|
|
Exit(response) => {
|
|
assert!(self.need_exit.is_none());
|
|
self.need_exit = Some(response);
|
|
}
|
|
}
|
|
|
|
let need_exit = replace(&mut self.need_exit, None);
|
|
|
|
match need_exit {
|
|
Some(response) => {
|
|
// Wait until we have no outstanding requests and subtasks
|
|
// before exiting
|
|
let mut can_exit = true;
|
|
for (_, state) in self.state_map.iter() {
|
|
match *state {
|
|
Prefetching(..) => can_exit = false,
|
|
Decoding => can_exit = false,
|
|
|
|
Init | Prefetched(..) | Decoded(..) | Failed => ()
|
|
}
|
|
}
|
|
|
|
if can_exit {
|
|
response.send(());
|
|
break;
|
|
} else {
|
|
self.need_exit = Some(response);
|
|
}
|
|
}
|
|
None => ()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_state(&self, url: &Url) -> ImageState {
|
|
match self.state_map.find(url) {
|
|
Some(state) => state.clone(),
|
|
None => Init
|
|
}
|
|
}
|
|
|
|
fn set_state(&mut self, url: Url, state: ImageState) {
|
|
self.state_map.insert(url, state);
|
|
}
|
|
|
|
fn prefetch(&mut self, url: Url) {
|
|
match self.get_state(&url) {
|
|
Init => {
|
|
let to_cache = self.chan.clone();
|
|
let resource_task = self.resource_task.clone();
|
|
let url_clone = url.clone();
|
|
|
|
spawn(proc() {
|
|
let url = url_clone;
|
|
debug!("image_cache_task: started fetch for {:s}", url.serialize());
|
|
|
|
let image = load_image_data(url.clone(), resource_task.clone());
|
|
|
|
let result = if image.is_ok() {
|
|
Ok(image.unwrap())
|
|
} else {
|
|
Err(())
|
|
};
|
|
to_cache.send(StorePrefetchedImageData(url.clone(), result));
|
|
debug!("image_cache_task: ended fetch for {:s}", url.serialize());
|
|
});
|
|
|
|
self.set_state(url, Prefetching(DoNotDecode));
|
|
}
|
|
|
|
Prefetching(..) | Prefetched(..) | Decoding | Decoded(..) | Failed => {
|
|
// We've already begun working on this image
|
|
}
|
|
}
|
|
}
|
|
|
|
fn store_prefetched_image_data(&mut self, url: Url, data: Result<Vec<u8>, ()>) {
|
|
match self.get_state(&url) {
|
|
Prefetching(next_step) => {
|
|
match data {
|
|
Ok(data) => {
|
|
self.set_state(url.clone(), Prefetched(data));
|
|
match next_step {
|
|
DoDecode => self.decode(url),
|
|
_ => ()
|
|
}
|
|
}
|
|
Err(..) => {
|
|
self.set_state(url.clone(), Failed);
|
|
self.purge_waiters(url, || ImageFailed);
|
|
}
|
|
}
|
|
}
|
|
|
|
Init
|
|
| Prefetched(..)
|
|
| Decoding
|
|
| Decoded(..)
|
|
| Failed => {
|
|
fail!("wrong state for storing prefetched image")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn decode(&mut self, url: Url) {
|
|
match self.get_state(&url) {
|
|
Init => fail!("decoding image before prefetch"),
|
|
|
|
Prefetching(DoNotDecode) => {
|
|
// We don't have the data yet, queue up the decode
|
|
self.set_state(url, Prefetching(DoDecode))
|
|
}
|
|
|
|
Prefetching(DoDecode) => {
|
|
// We don't have the data yet, but the decode request is queued up
|
|
}
|
|
|
|
Prefetched(data) => {
|
|
let to_cache = self.chan.clone();
|
|
let url_clone = url.clone();
|
|
|
|
spawn(proc() {
|
|
let url = url_clone;
|
|
debug!("image_cache_task: started image decode for {:s}", url.serialize());
|
|
let image = load_from_memory(data.as_slice());
|
|
let image = if image.is_some() {
|
|
Some(Arc::new(box image.unwrap()))
|
|
} else {
|
|
None
|
|
};
|
|
to_cache.send(StoreImage(url.clone(), image));
|
|
debug!("image_cache_task: ended image decode for {:s}", url.serialize());
|
|
});
|
|
|
|
self.set_state(url, Decoding);
|
|
}
|
|
|
|
Decoding | Decoded(..) | Failed => {
|
|
// We've already begun decoding
|
|
}
|
|
}
|
|
}
|
|
|
|
fn store_image(&mut self, url: Url, image: Option<Arc<Box<Image>>>) {
|
|
|
|
match self.get_state(&url) {
|
|
Decoding => {
|
|
match image {
|
|
Some(image) => {
|
|
self.set_state(url.clone(), Decoded(image.clone()));
|
|
self.purge_waiters(url, || ImageReady(image.clone()) );
|
|
}
|
|
None => {
|
|
self.set_state(url.clone(), Failed);
|
|
self.purge_waiters(url, || ImageFailed );
|
|
}
|
|
}
|
|
}
|
|
|
|
Init
|
|
| Prefetching(..)
|
|
| Prefetched(..)
|
|
| Decoded(..)
|
|
| Failed => {
|
|
fail!("incorrect state in store_image")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fn purge_waiters(&mut self, url: Url, f: || -> ImageResponseMsg) {
|
|
match self.wait_map.pop(&url) {
|
|
Some(waiters) => {
|
|
let mut items = waiters.lock();
|
|
for response in items.iter() {
|
|
response.send(f());
|
|
}
|
|
}
|
|
None => ()
|
|
}
|
|
}
|
|
|
|
fn get_image(&self, url: Url, response: Sender<ImageResponseMsg>) {
|
|
match self.get_state(&url) {
|
|
Init => fail!("request for image before prefetch"),
|
|
Prefetching(DoDecode) => response.send(ImageNotReady),
|
|
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
|
|
Decoding => response.send(ImageNotReady),
|
|
Decoded(image) => response.send(ImageReady(image.clone())),
|
|
Failed => response.send(ImageFailed),
|
|
}
|
|
}
|
|
|
|
fn wait_for_image(&mut self, url: Url, response: Sender<ImageResponseMsg>) {
|
|
match self.get_state(&url) {
|
|
Init => fail!("request for image before prefetch"),
|
|
|
|
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
|
|
|
|
Prefetching(DoDecode) | Decoding => {
|
|
// We don't have this image yet
|
|
if self.wait_map.contains_key(&url) {
|
|
let waiters = self.wait_map.find_mut(&url).unwrap();
|
|
let mut response = Some(response);
|
|
let mut items = waiters.lock();
|
|
items.push(response.take().unwrap());
|
|
} else {
|
|
let response = vec!(response);
|
|
let wrapped = Arc::new(Mutex::new(response));
|
|
self.wait_map.insert(url, wrapped);
|
|
}
|
|
}
|
|
|
|
Decoded(image) => {
|
|
response.send(ImageReady(image.clone()));
|
|
}
|
|
|
|
Failed => {
|
|
response.send(ImageFailed);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
pub trait ImageCacheTaskClient {
|
|
fn exit(&self);
|
|
}
|
|
|
|
impl ImageCacheTaskClient for ImageCacheTask {
|
|
fn exit(&self) {
|
|
let (response_chan, response_port) = channel();
|
|
self.send(Exit(response_chan));
|
|
response_port.recv();
|
|
}
|
|
}
|
|
|
|
impl ImageCacheTask {
|
|
pub fn send(&self, msg: Msg) {
|
|
self.chan.send(msg);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn wait_for_store(&self) -> Receiver<()> {
|
|
let (chan, port) = channel();
|
|
self.send(WaitForStore(chan));
|
|
port
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn wait_for_store_prefetched(&self) -> Receiver<()> {
|
|
let (chan, port) = channel();
|
|
self.send(WaitForStorePrefetched(chan));
|
|
port
|
|
}
|
|
}
|
|
|
|
fn load_image_data(url: Url, resource_task: ResourceTask) -> Result<Vec<u8>, ()> {
|
|
let (response_chan, response_port) = channel();
|
|
resource_task.send(resource_task::Load(LoadData::new(url), response_chan));
|
|
|
|
let mut image_data = vec!();
|
|
|
|
let progress_port = response_port.recv().progress_port;
|
|
loop {
|
|
match progress_port.recv() {
|
|
resource_task::Payload(data) => {
|
|
image_data.push_all(data.as_slice());
|
|
}
|
|
resource_task::Done(result::Ok(..)) => {
|
|
return Ok(image_data.into_iter().collect());
|
|
}
|
|
resource_task::Done(result::Err(..)) => {
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub fn spawn_listener<A: Send>(f: proc(Receiver<A>):Send) -> Sender<A> {
|
|
let (setup_chan, setup_port) = channel();
|
|
|
|
spawn(proc() {
|
|
let (chan, port) = channel();
|
|
setup_chan.send(chan);
|
|
f(port);
|
|
});
|
|
setup_port.recv()
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use resource_task;
|
|
use resource_task::{ResourceTask, Metadata, start_sending};
|
|
use image::base::test_image_bin;
|
|
use std::comm;
|
|
use url::Url;
|
|
|
|
trait Closure {
|
|
fn invoke(&self, _response: Sender<resource_task::ProgressMsg>) { }
|
|
}
|
|
struct DoesNothing;
|
|
impl Closure for DoesNothing { }
|
|
|
|
struct JustSendOK {
|
|
url_requested_chan: Sender<()>,
|
|
}
|
|
impl Closure for JustSendOK {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
self.url_requested_chan.send(());
|
|
response.send(resource_task::Done(Ok(())));
|
|
}
|
|
}
|
|
|
|
struct SendTestImage;
|
|
impl Closure for SendTestImage {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
response.send(resource_task::Payload(test_image_bin()));
|
|
response.send(resource_task::Done(Ok(())));
|
|
}
|
|
}
|
|
|
|
struct SendBogusImage;
|
|
impl Closure for SendBogusImage {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
response.send(resource_task::Payload(vec!()));
|
|
response.send(resource_task::Done(Ok(())));
|
|
}
|
|
}
|
|
|
|
struct SendTestImageErr;
|
|
impl Closure for SendTestImageErr {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
response.send(resource_task::Payload(test_image_bin()));
|
|
response.send(resource_task::Done(Err("".to_string())));
|
|
}
|
|
}
|
|
|
|
struct WaitSendTestImage {
|
|
wait_port: Receiver<()>,
|
|
}
|
|
impl Closure for WaitSendTestImage {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
// Don't send the data until after the client requests
|
|
// the image
|
|
self.wait_port.recv();
|
|
response.send(resource_task::Payload(test_image_bin()));
|
|
response.send(resource_task::Done(Ok(())));
|
|
}
|
|
}
|
|
|
|
struct WaitSendTestImageErr {
|
|
wait_port: Receiver<()>,
|
|
}
|
|
impl Closure for WaitSendTestImageErr {
|
|
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
|
|
// Don't send the data until after the client requests
|
|
// the image
|
|
self.wait_port.recv();
|
|
response.send(resource_task::Payload(test_image_bin()));
|
|
response.send(resource_task::Done(Err("".to_string())));
|
|
}
|
|
}
|
|
|
|
fn mock_resource_task<T: Closure+Send>(on_load: Box<T>) -> ResourceTask {
|
|
spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
|
|
loop {
|
|
match port.recv() {
|
|
resource_task::Load(_, response) => {
|
|
let chan = start_sending(response, Metadata::default(
|
|
Url::parse("file:///fake").unwrap()));
|
|
on_load.invoke(chan);
|
|
}
|
|
resource_task::Exit => break
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn should_exit_on_request() {
|
|
let mock_resource_task = mock_resource_task(box DoesNothing);
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
#[should_fail]
|
|
fn should_fail_if_unprefetched_image_is_requested() {
|
|
let mock_resource_task = mock_resource_task(box DoesNothing);
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let (chan, port) = channel();
|
|
image_cache_task.send(GetImage(url, chan));
|
|
port.recv();
|
|
}
|
|
|
|
#[test]
|
|
fn should_request_url_from_resource_task_on_prefetch() {
|
|
let (url_requested_chan, url_requested) = channel();
|
|
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url));
|
|
url_requested.recv();
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
fn should_not_request_url_from_resource_task_on_multiple_prefetches() {
|
|
let (url_requested_chan, url_requested) = comm::channel();
|
|
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Prefetch(url));
|
|
url_requested.recv();
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
match url_requested.try_recv() {
|
|
Err(_) => (),
|
|
Ok(_) => fail!(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn should_return_image_not_ready_if_data_has_not_arrived() {
|
|
let (wait_chan, wait_port) = comm::channel();
|
|
|
|
let mock_resource_task = mock_resource_task(box WaitSendTestImage{wait_port: wait_port});
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
assert!(response_port.recv() == ImageNotReady);
|
|
wait_chan.send(());
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
match response_port.recv() {
|
|
ImageReady(_) => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
for _ in range(0u32, 2u32) {
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url.clone(), response_chan));
|
|
match response_port.recv() {
|
|
ImageReady(_) => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
fn should_not_request_image_from_resource_task_if_image_is_already_available() {
|
|
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
|
|
|
|
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
|
|
|
|
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
|
|
loop {
|
|
match port.recv() {
|
|
resource_task::Load(_, response) => {
|
|
let chan = start_sending(response, Metadata::default(
|
|
Url::parse("file:///fake").unwrap()));
|
|
chan.send(resource_task::Payload(test_image_bin()));
|
|
chan.send(resource_task::Done(Ok(())));
|
|
image_bin_sent_chan.send(());
|
|
}
|
|
resource_task::Exit => {
|
|
resource_task_exited_chan.send(());
|
|
break
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
image_bin_sent.recv();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
|
|
resource_task_exited.recv();
|
|
|
|
// Our resource task should not have received another request for the image
|
|
// because it's already cached
|
|
match image_bin_sent.try_recv() {
|
|
Err(_) => (),
|
|
Ok(_) => fail!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn should_not_request_image_from_resource_task_if_image_fetch_already_failed() {
|
|
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
|
|
|
|
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
|
|
|
|
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
|
|
loop {
|
|
match port.recv() {
|
|
resource_task::Load(_, response) => {
|
|
let chan = start_sending(response, Metadata::default(
|
|
Url::parse("file:///fake").unwrap()));
|
|
chan.send(resource_task::Payload(test_image_bin()));
|
|
chan.send(resource_task::Done(Err("".to_string())));
|
|
image_bin_sent_chan.send(());
|
|
}
|
|
resource_task::Exit => {
|
|
resource_task_exited_chan.send(());
|
|
break
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
image_bin_sent.recv();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
|
|
resource_task_exited.recv();
|
|
|
|
// Our resource task should not have received another request for the image
|
|
// because it's already cached
|
|
match image_bin_sent.try_recv() {
|
|
Err(_) => (),
|
|
Ok(_) => fail!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store_prefetched();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
match response_port.recv() {
|
|
ImageFailed => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store_prefetched();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url.clone(), response_chan));
|
|
match response_port.recv() {
|
|
ImageFailed => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
// And ask again, we should get the same response
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
match response_port.recv() {
|
|
ImageFailed => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
// Make the request
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
|
|
match response_port.recv() {
|
|
ImageFailed => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
let join_port = image_cache_task.wait_for_store();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
// Wait until our mock resource task has sent the image to the image cache
|
|
join_port.recv();
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(WaitForImage(url, response_chan));
|
|
match response_port.recv() {
|
|
ImageReady(..) => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
fn should_return_image_on_wait_if_image_is_not_yet_loaded() {
|
|
let (wait_chan, wait_port) = comm::channel();
|
|
|
|
let mock_resource_task = mock_resource_task(box WaitSendTestImage {wait_port: wait_port});
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(WaitForImage(url, response_chan));
|
|
|
|
wait_chan.send(());
|
|
|
|
match response_port.recv() {
|
|
ImageReady(..) => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
fn should_return_image_failed_on_wait_if_image_fails_to_load() {
|
|
let (wait_chan, wait_port) = comm::channel();
|
|
|
|
let mock_resource_task = mock_resource_task(box WaitSendTestImageErr{wait_port: wait_port});
|
|
|
|
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(WaitForImage(url, response_chan));
|
|
|
|
wait_chan.send(());
|
|
|
|
match response_port.recv() {
|
|
ImageFailed => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
let url = Url::parse("file:///").unwrap();
|
|
|
|
image_cache_task.send(Prefetch(url.clone()));
|
|
image_cache_task.send(Decode(url.clone()));
|
|
|
|
let (response_chan, response_port) = comm::channel();
|
|
image_cache_task.send(GetImage(url, response_chan));
|
|
match response_port.recv() {
|
|
ImageReady(_) => (),
|
|
_ => fail!("bleh")
|
|
}
|
|
|
|
image_cache_task.exit();
|
|
mock_resource_task.send(resource_task::Exit);
|
|
}
|
|
}
|