mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
Add a WaitForImage operation to ImageCacheTask
This commit is contained in:
parent
e297a9abf4
commit
7c122a660e
1 changed files with 197 additions and 10 deletions
|
@ -25,15 +25,19 @@ enum Msg {
|
||||||
/// Used be the prefetch tasks to post back image binaries
|
/// Used be the prefetch tasks to post back image binaries
|
||||||
/*priv*/ StorePrefetchedImageData(url, result<Cell<~[u8]>, ()>),
|
/*priv*/ StorePrefetchedImageData(url, result<Cell<~[u8]>, ()>),
|
||||||
|
|
||||||
/// Tell the cache to decode an image. Must be posted before GetImage
|
/// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
|
||||||
Decode(url),
|
Decode(url),
|
||||||
|
|
||||||
/// Used by the decoder tasks to post decoded images back to the cache
|
/// Used by the decoder tasks to post decoded images back to the cache
|
||||||
/*priv*/ StoreImage(url, option<arc<~Image>>),
|
/*priv*/ StoreImage(url, option<arc<~Image>>),
|
||||||
|
|
||||||
/// Request an Image object for a URL
|
/// Request an Image object for a URL. If the image is not is not immediately
|
||||||
|
/// available then ImageNotReady is returned.
|
||||||
GetImage(url, Chan<ImageResponseMsg>),
|
GetImage(url, Chan<ImageResponseMsg>),
|
||||||
|
|
||||||
|
/// Wait for an image to become available (or fail to load).
|
||||||
|
WaitForImage(url, Chan<ImageResponseMsg>),
|
||||||
|
|
||||||
/// For testing
|
/// For testing
|
||||||
/*priv*/ OnMsg(fn~(msg: &Msg)),
|
/*priv*/ OnMsg(fn~(msg: &Msg)),
|
||||||
|
|
||||||
|
@ -66,6 +70,7 @@ fn image_cache_task_(resource_task: ResourceTask, +decoder_factory: DecoderFacto
|
||||||
decoder_factory: decoder_factory_cell.take(),
|
decoder_factory: decoder_factory_cell.take(),
|
||||||
from_client: from_client,
|
from_client: from_client,
|
||||||
state_map: url_map(),
|
state_map: url_map(),
|
||||||
|
wait_map: url_map(),
|
||||||
need_exit: none
|
need_exit: none
|
||||||
}.run();
|
}.run();
|
||||||
}
|
}
|
||||||
|
@ -80,6 +85,8 @@ struct ImageCache {
|
||||||
from_client: Port<Msg>;
|
from_client: Port<Msg>;
|
||||||
/// The state of processsing an image for a URL
|
/// The state of processsing an image for a URL
|
||||||
state_map: UrlMap<ImageState>;
|
state_map: UrlMap<ImageState>;
|
||||||
|
/// List of clients waiting on a WaitForImage response
|
||||||
|
wait_map: UrlMap<@mut ~[Chan<ImageResponseMsg>]>;
|
||||||
mut need_exit: option<Chan<()>>;
|
mut need_exit: option<Chan<()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +125,7 @@ impl ImageCache {
|
||||||
Decode(url) => self.decode(copy url),
|
Decode(url) => self.decode(copy url),
|
||||||
StoreImage(url, image) => self.store_image(copy url, &image),
|
StoreImage(url, image) => self.store_image(copy url, &image),
|
||||||
GetImage(url, response) => self.get_image(copy url, response),
|
GetImage(url, response) => self.get_image(copy url, response),
|
||||||
|
WaitForImage(url, response) => self.wait_for_image(copy url, response),
|
||||||
OnMsg(handler) => msg_handlers += [copy handler],
|
OnMsg(handler) => msg_handlers += [copy handler],
|
||||||
Exit(response) => {
|
Exit(response) => {
|
||||||
assert self.need_exit.is_none();
|
assert self.need_exit.is_none();
|
||||||
|
@ -205,15 +213,15 @@ impl ImageCache {
|
||||||
ok(data_cell) => {
|
ok(data_cell) => {
|
||||||
let data = data_cell.take();
|
let data = data_cell.take();
|
||||||
self.set_state(copy url, Prefetched(@Cell(data)));
|
self.set_state(copy url, Prefetched(@Cell(data)));
|
||||||
|
if next_step == DoDecode {
|
||||||
|
self.decode(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err(*) => {
|
err(*) => {
|
||||||
self.set_state(copy url, Failed);
|
self.set_state(copy url, Failed);
|
||||||
|
self.purge_waiters(url, || ImageFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if next_step == DoDecode {
|
|
||||||
self.decode(url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Init
|
Init
|
||||||
|
@ -229,7 +237,7 @@ impl ImageCache {
|
||||||
/*priv*/ fn decode(+url: url) {
|
/*priv*/ fn decode(+url: url) {
|
||||||
|
|
||||||
match self.get_state(copy url) {
|
match self.get_state(copy url) {
|
||||||
Init => fail ~"Decoding image before prefetch",
|
Init => fail ~"decoding image before prefetch",
|
||||||
|
|
||||||
Prefetching(DoNotDecode) => {
|
Prefetching(DoNotDecode) => {
|
||||||
// We don't have the data yet, queue up the decode
|
// We don't have the data yet, queue up the decode
|
||||||
|
@ -278,10 +286,12 @@ impl ImageCache {
|
||||||
Decoding => {
|
Decoding => {
|
||||||
match *image {
|
match *image {
|
||||||
some(image) => {
|
some(image) => {
|
||||||
self.set_state(url, Decoded(@clone_arc(&image)));
|
self.set_state(copy url, Decoded(@clone_arc(&image)));
|
||||||
|
self.purge_waiters(url, || ImageReady(clone_arc(&image)) );
|
||||||
}
|
}
|
||||||
none => {
|
none => {
|
||||||
self.set_state(url, Failed);
|
self.set_state(copy url, Failed);
|
||||||
|
self.purge_waiters(url, || ImageFailed );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,10 +307,23 @@ impl ImageCache {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*priv*/ fn purge_waiters(+url: url, f: fn() -> ImageResponseMsg) {
|
||||||
|
match self.wait_map.find(copy url) {
|
||||||
|
some(@waiters) => {
|
||||||
|
for waiters.each |response| {
|
||||||
|
response.send(f());
|
||||||
|
}
|
||||||
|
self.wait_map.remove(url);
|
||||||
|
}
|
||||||
|
none => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*priv*/ fn get_image(+url: url, response: Chan<ImageResponseMsg>) {
|
/*priv*/ fn get_image(+url: url, response: Chan<ImageResponseMsg>) {
|
||||||
|
|
||||||
match self.get_state(copy url) {
|
match self.get_state(copy url) {
|
||||||
Init => fail ~"Request for image before prefetch",
|
Init => fail ~"request for image before prefetch",
|
||||||
|
|
||||||
Prefetching(DoDecode) => {
|
Prefetching(DoDecode) => {
|
||||||
response.send(ImageNotReady);
|
response.send(ImageNotReady);
|
||||||
|
@ -323,6 +346,37 @@ impl ImageCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*priv*/ fn wait_for_image(+url: url, response: Chan<ImageResponseMsg>) {
|
||||||
|
|
||||||
|
match self.get_state(copy 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
|
||||||
|
match self.wait_map.find(copy url) {
|
||||||
|
some(waiters) => {
|
||||||
|
vec::push(*waiters, response);
|
||||||
|
}
|
||||||
|
none => {
|
||||||
|
self.wait_map.insert(url, @mut ~[response]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Decoded(image) => {
|
||||||
|
response.send(ImageReady(clone_arc(image)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Failed => {
|
||||||
|
response.send(ImageFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1005,3 +1059,136 @@ fn should_return_failed_if_image_decode_fails() {
|
||||||
image_cache_task.exit();
|
image_cache_task.exit();
|
||||||
mock_resource_task.send(resource_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 = do spawn_listener |from_client| {
|
||||||
|
|
||||||
|
// infer me
|
||||||
|
let from_client: Port<resource_task::ControlMsg> = from_client;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match from_client.recv() {
|
||||||
|
resource_task::Load(_, response) => {
|
||||||
|
response.send(resource_task::Payload(test_image_bin()));
|
||||||
|
response.send(resource_task::Done(result::ok(())));
|
||||||
|
}
|
||||||
|
resource_task::Exit => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image_cache_task = image_cache_task(mock_resource_task);
|
||||||
|
let url = make_url(~"file", none);
|
||||||
|
|
||||||
|
let wait_for_decode = port();
|
||||||
|
let wait_for_decode_chan = wait_for_decode.chan();
|
||||||
|
|
||||||
|
image_cache_task.send(OnMsg(|msg| {
|
||||||
|
match *msg {
|
||||||
|
StoreImage(*) => wait_for_decode_chan.send(()),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
image_cache_task.send(Prefetch(copy url));
|
||||||
|
image_cache_task.send(Decode(copy url));
|
||||||
|
|
||||||
|
// Wait until our mock resource task has sent the image to the image cache
|
||||||
|
wait_for_decode.recv();
|
||||||
|
|
||||||
|
let response_port = port();
|
||||||
|
image_cache_task.send(WaitForImage(url, response_port.chan()));
|
||||||
|
match response_port.recv() {
|
||||||
|
ImageReady(*) => (),
|
||||||
|
_ => fail
|
||||||
|
}
|
||||||
|
|
||||||
|
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) = pipes::stream();
|
||||||
|
|
||||||
|
let mock_resource_task = do spawn_listener |from_client| {
|
||||||
|
|
||||||
|
// infer me
|
||||||
|
let from_client: Port<resource_task::ControlMsg> = from_client;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match from_client.recv() {
|
||||||
|
resource_task::Load(_, response) => {
|
||||||
|
wait_port.recv();
|
||||||
|
response.send(resource_task::Payload(test_image_bin()));
|
||||||
|
response.send(resource_task::Done(result::ok(())));
|
||||||
|
}
|
||||||
|
resource_task::Exit => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image_cache_task = image_cache_task(mock_resource_task);
|
||||||
|
let url = make_url(~"file", none);
|
||||||
|
|
||||||
|
image_cache_task.send(Prefetch(copy url));
|
||||||
|
image_cache_task.send(Decode(copy url));
|
||||||
|
|
||||||
|
let response_port = port();
|
||||||
|
image_cache_task.send(WaitForImage(url, response_port.chan()));
|
||||||
|
|
||||||
|
wait_chan.send(());
|
||||||
|
|
||||||
|
match response_port.recv() {
|
||||||
|
ImageReady(*) => (),
|
||||||
|
_ => fail
|
||||||
|
}
|
||||||
|
|
||||||
|
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) = pipes::stream();
|
||||||
|
|
||||||
|
let mock_resource_task = do spawn_listener |from_client| {
|
||||||
|
|
||||||
|
// infer me
|
||||||
|
let from_client: Port<resource_task::ControlMsg> = from_client;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match from_client.recv() {
|
||||||
|
resource_task::Load(_, response) => {
|
||||||
|
wait_port.recv();
|
||||||
|
response.send(resource_task::Payload(test_image_bin()));
|
||||||
|
response.send(resource_task::Done(result::err(())));
|
||||||
|
}
|
||||||
|
resource_task::Exit => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image_cache_task = image_cache_task(mock_resource_task);
|
||||||
|
let url = make_url(~"file", none);
|
||||||
|
|
||||||
|
image_cache_task.send(Prefetch(copy url));
|
||||||
|
image_cache_task.send(Decode(copy url));
|
||||||
|
|
||||||
|
let response_port = port();
|
||||||
|
image_cache_task.send(WaitForImage(url, response_port.chan()));
|
||||||
|
|
||||||
|
wait_chan.send(());
|
||||||
|
|
||||||
|
match response_port.recv() {
|
||||||
|
ImageFailed => (),
|
||||||
|
_ => fail
|
||||||
|
}
|
||||||
|
|
||||||
|
image_cache_task.exit();
|
||||||
|
mock_resource_task.send(resource_task::Exit);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue