diff --git a/servo/src/servo/image/base.rs b/servo/src/servo/image/base.rs index 7fd380045e9b..955d1859b254 100644 --- a/servo/src/servo/image/base.rs +++ b/servo/src/servo/image/base.rs @@ -1,7 +1,15 @@ +export ImageBuffer, SharedImageBuffer; export image; export load; + import stb_image::image::{image, load}; +import core::arc::arc; // FIXME: Images must not be copied every frame. Instead we should atomically // reference count them. +type SharedImageBuffer = arc; + +struct ImageBuffer { + data: ~[u8]; +} diff --git a/servo/src/servo/resource/image_cache_task.rs b/servo/src/servo/resource/image_cache_task.rs new file mode 100644 index 000000000000..bb617c32e5ff --- /dev/null +++ b/servo/src/servo/resource/image_cache_task.rs @@ -0,0 +1,201 @@ +export Msg, Prefetch, GetImage, Exit; +export ImageResponseMsg, ImageReady, ImageNotReady; +export ImageCacheTask; +export image_cache_task; + +import image::base::{ImageBuffer, SharedImageBuffer}; +import std::net::url::url; +import util::url::{make_url, UrlMap, url_map}; +import comm::{chan, port}; +import task::spawn_listener; +import resource::resource_task; +import resource_task::ResourceTask; + +enum Msg { + Prefetch(url), + GetImage(url, chan), + Exit +} + +enum ImageResponseMsg { + ImageReady(ImageBuffer), + ImageNotReady +} + +type ImageCacheTask = chan; + +fn image_cache_task(resource_task: ResourceTask) -> ImageCacheTask { + do spawn_listener |from_client| { + ImageCache { + resource_task: resource_task, + from_client: from_client, + prefetch_map: url_map() + }.run(); + } +} + +struct ImageCache { + resource_task: ResourceTask; + from_client: port; + prefetch_map: UrlMap; +} + +struct PrefetchData { + response_port: @port; + data: @mut ~[u8]; +} + +impl ImageCache { + + fn run() { + + loop { + match self.from_client.recv() { + Prefetch(url) => { + if self.prefetch_map.contains_key(url) { + // We're already waiting for this image + again + } + let response_port = port(); + self.resource_task.send(resource_task::Load(url, response_port.chan())); + + let prefetch_data = PrefetchData { + response_port: @response_port, + data: @mut ~[] + }; + + self.prefetch_map.insert(url, prefetch_data); + } + GetImage(url, response) => { + if self.prefetch_map.contains_key(url) { + response.send(ImageNotReady); + } else { + fail ~"got a request for image data without prefetch"; + } + } + Exit => break + } + } + } + +} + +#[test] +fn should_exit_on_request() { + + let mock_resource_task = do spawn_listener |from_client| { + + // infer me + let from_client: port = from_client; + + loop { + match from_client.recv() { + resource_task::Exit => break, + _ => () + } + } + }; + + let image_cache_task = image_cache_task(mock_resource_task); + let url = make_url(~"file", none); + + image_cache_task.send(Exit); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +#[should_fail] +fn should_fail_if_unprefetched_image_is_requested() { + + let mock_resource_task = do spawn_listener |from_client| { + }; + + let image_cache_task = image_cache_task(mock_resource_task); + let url = make_url(~"file", none); + + let request = port(); + image_cache_task.send(GetImage(url, request.chan())); + request.recv(); +} + +#[test] +fn should_request_url_from_resource_task_on_prefetch() { + let url_requested = port(); + let url_requested_chan = url_requested.chan(); + + let mock_resource_task = do spawn_listener |from_client| { + + // infer me + let from_client: port = from_client; + + loop { + match from_client.recv() { + resource_task::Load(url, _) => url_requested_chan.send(()), + 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(url)); + url_requested.recv(); + image_cache_task.send(Exit); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_not_request_url_from_resource_task_on_multiple_prefetches() { + let url_requested = port(); + let url_requested_chan = url_requested.chan(); + + let mock_resource_task = do spawn_listener |from_client| { + + // infer me + let from_client: port = from_client; + + loop { + match from_client.recv() { + resource_task::Load(url, _) => url_requested_chan.send(()), + 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(url)); + image_cache_task.send(Prefetch(url)); + url_requested.recv(); + image_cache_task.send(Exit); + mock_resource_task.send(resource_task::Exit); + assert !url_requested.peek() +} + +#[test] +fn should_return_image_not_ready_if_data_has_not_arrived() { + let mock_resource_task = do spawn_listener |from_client| { + + // infer me + let from_client: port = from_client; + + loop { + match from_client.recv() { + 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(url)); + let response_port = port(); + image_cache_task.send(GetImage(url, response_port.chan())); + assert response_port.recv() == ImageNotReady; + image_cache_task.send(Exit); + mock_resource_task.send(resource_task::Exit); +} \ No newline at end of file diff --git a/servo/src/servo/servo.rc b/servo/src/servo/servo.rc index d5efca544bb9..f6bb2b1d8696 100755 --- a/servo/src/servo/servo.rc +++ b/servo/src/servo/servo.rc @@ -114,6 +114,7 @@ mod resource { mod resource_task; mod file_loader; mod http_loader; + mod image_cache_task; } import servo_text = text; diff --git a/servo/src/servo/util/url.rs b/servo/src/servo/util/url.rs index d582223195f6..02a63e03a621 100644 --- a/servo/src/servo/util/url.rs +++ b/servo/src/servo/util/url.rs @@ -1,7 +1,8 @@ -export make_url; +export make_url, UrlMap, url_map; import std::net::url; import url::{get_scheme, url}; +import std::map::hashmap; /** Create a URL object from a string. Does various helpful browsery things like @@ -41,55 +42,68 @@ fn make_url(str_url: ~str, current_url: option) -> url { url::from_str(str_url).get() } -#[test] -fn should_create_absolute_file_url_if_current_url_is_none_and_str_url_looks_filey() { - let file = ~"local.html"; - let url = make_url(file, none); - #debug("url: %?", url); - assert url.scheme == ~"file"; - assert url.path.contains(os::getcwd()); +mod make_url_tests { + + #[test] + fn should_create_absolute_file_url_if_current_url_is_none_and_str_url_looks_filey() { + let file = ~"local.html"; + let url = make_url(file, none); + #debug("url: %?", url); + assert url.scheme == ~"file"; + assert url.path.contains(os::getcwd()); + } + + #[test] + fn should_create_url_based_on_old_url_1() { + let old_str = ~"http://example.com"; + let old_url = make_url(old_str, none); + let new_str = ~"index.html"; + let new_url = make_url(new_str, some(old_url)); + assert new_url.scheme == ~"http"; + assert new_url.host == ~"example.com"; + assert new_url.path == ~"/index.html"; + } + + #[test] + fn should_create_url_based_on_old_url_2() { + let old_str = ~"http://example.com/"; + let old_url = make_url(old_str, none); + let new_str = ~"index.html"; + let new_url = make_url(new_str, some(old_url)); + assert new_url.scheme == ~"http"; + assert new_url.host == ~"example.com"; + assert new_url.path == ~"/index.html"; + } + + #[test] + fn should_create_url_based_on_old_url_3() { + let old_str = ~"http://example.com/index.html"; + let old_url = make_url(old_str, none); + let new_str = ~"crumpet.html"; + let new_url = make_url(new_str, some(old_url)); + assert new_url.scheme == ~"http"; + assert new_url.host == ~"example.com"; + assert new_url.path == ~"/crumpet.html"; + } + + #[test] + fn should_create_url_based_on_old_url_4() { + let old_str = ~"http://example.com/snarf/index.html"; + let old_url = make_url(old_str, none); + let new_str = ~"crumpet.html"; + let new_url = make_url(new_str, some(old_url)); + assert new_url.scheme == ~"http"; + assert new_url.host == ~"example.com"; + assert new_url.path == ~"/snarf/crumpet.html"; + } + } -#[test] -fn should_create_url_based_on_old_url_1() { - let old_str = ~"http://example.com"; - let old_url = make_url(old_str, none); - let new_str = ~"index.html"; - let new_url = make_url(new_str, some(old_url)); - assert new_url.scheme == ~"http"; - assert new_url.host == ~"example.com"; - assert new_url.path == ~"/index.html"; -} +type UrlMap = hashmap; -#[test] -fn should_create_url_based_on_old_url_2() { - let old_str = ~"http://example.com/"; - let old_url = make_url(old_str, none); - let new_str = ~"index.html"; - let new_url = make_url(new_str, some(old_url)); - assert new_url.scheme == ~"http"; - assert new_url.host == ~"example.com"; - assert new_url.path == ~"/index.html"; -} +fn url_map() -> UrlMap { + import core::to_str::to_str; -#[test] -fn should_create_url_based_on_old_url_3() { - let old_str = ~"http://example.com/index.html"; - let old_url = make_url(old_str, none); - let new_str = ~"crumpet.html"; - let new_url = make_url(new_str, some(old_url)); - assert new_url.scheme == ~"http"; - assert new_url.host == ~"example.com"; - assert new_url.path == ~"/crumpet.html"; -} - -#[test] -fn should_create_url_based_on_old_url_4() { - let old_str = ~"http://example.com/snarf/index.html"; - let old_url = make_url(old_str, none); - let new_str = ~"crumpet.html"; - let new_url = make_url(new_str, some(old_url)); - assert new_url.scheme == ~"http"; - assert new_url.host == ~"example.com"; - assert new_url.path == ~"/snarf/crumpet.html"; + hashmap::(|a| str::hash(&a.to_str()), + |a, b| str::eq(&a.to_str(), &b.to_str())) }