Refactored image cache task - details below.

* Simpler image cache API for clients to use.
 * Significantly fewer threads.
   * One thread for image cache task (multiplexes commands, decoder threads and async resource requests).
   * 4 threads for decoder worker tasks.
 * Removed ReflowEvent hacks in script and layout tasks.
   * Image elements pass a Trusted<T> to image cache, which is used to dirty nodes via script task. Previous use of Untrusted addresses was unsafe.
   * Image requests such as background-image on layout / paint threads trigger repaint only rather than full reflow.
 * Add reflow batching for when multiple images load quickly.
   * Reduces the number of paints loading wikipedia from ~95 to ~35.
 * Reasonably simple to add proper prefetch support in a follow up PR.
 * Async loaded images always construct Image fragments now, instead of generic.
   * Image fragments support the image not being present.
 * Simpler implementation of synchronous image loading for reftests.
 * Removed image holder.
 * image.onload support.
 * image NaturalWidth and NaturalHeight support.
 * Updated WPT expectations.
This commit is contained in:
Glenn Watson 2015-04-20 13:34:26 +10:00
parent e278e5b9a2
commit d8aef7208e
33 changed files with 2785 additions and 1679 deletions

View file

@ -1,100 +0,0 @@
/* 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;
use image_cache_task::ImageResponseMsg;
use local_image_cache::LocalImageCache;
use geom::size::Size2D;
use std::sync::{Arc, Mutex};
use url::Url;
// FIXME: Nasty coupling here This will be a problem if we want to factor out image handling from
// the network stack. This should probably be factored out into an interface and use dependency
// injection.
/// A struct to store image data. The image will be loaded once the first time it is requested,
/// and an Arc will be stored. Clones of this Arc are given out on demand.
#[derive(Clone)]
pub struct ImageHolder<NodeAddress> {
url: Url,
image: Option<Arc<Image>>,
cached_size: Size2D<u32>,
local_image_cache: Arc<Mutex<LocalImageCache<NodeAddress>>>,
}
impl<NodeAddress: Send + 'static> ImageHolder<NodeAddress> {
pub fn new(url: Url, local_image_cache: Arc<Mutex<LocalImageCache<NodeAddress>>>)
-> ImageHolder<NodeAddress> {
debug!("ImageHolder::new() {}", url.serialize());
let holder = ImageHolder {
url: url,
image: None,
cached_size: Size2D(0,0),
local_image_cache: local_image_cache.clone(),
};
// Tell the image cache we're going to be interested in this url
// FIXME: These two messages must be sent to prep an image for use
// but they are intended to be spread out in time. Ideally prefetch
// should be done as early as possible and decode only once we
// are sure that the image will be used.
{
let val = holder.local_image_cache.lock().unwrap();
let mut local_image_cache = val;
local_image_cache.prefetch(&holder.url);
local_image_cache.decode(&holder.url);
}
holder
}
/// This version doesn't perform any computation, but may be stale w.r.t. newly-available image
/// data that determines size.
///
/// The intent is that the impure version is used during layout when dimensions are used for
/// computing layout.
pub fn size(&self) -> Size2D<u32> {
self.cached_size
}
/// Query and update the current image size.
pub fn get_size(&mut self, node_address: NodeAddress) -> Option<Size2D<u32>> {
debug!("get_size() {}", self.url.serialize());
self.get_image(node_address).map(|img| {
self.cached_size = Size2D(img.width, img.height);
self.cached_size.clone()
})
}
pub fn get_image_if_present(&self) -> Option<Arc<Image>> {
debug!("get_image_if_present() {}", self.url.serialize());
self.image.clone()
}
pub fn get_image(&mut self, node_address: NodeAddress) -> Option<Arc<Image>> {
debug!("get_image() {}", self.url.serialize());
// If this is the first time we've called this function, load
// the image and store it for the future
if self.image.is_none() {
let port = {
let val = self.local_image_cache.lock().unwrap();
let mut local_image_cache = val;
local_image_cache.get_image(node_address, &self.url)
};
match port.recv().unwrap() {
ImageResponseMsg::ImageReady(image) => self.image = Some(image),
ImageResponseMsg::ImageNotReady => debug!("image not ready for {}", self.url.serialize()),
ImageResponseMsg::ImageFailed => debug!("image decoding failed for {}", self.url.serialize()),
}
}
return self.image.clone();
}
pub fn url(&self) -> &Url {
&self.url
}
}

View file

@ -3,117 +3,92 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use image::base::Image;
use LoadConsumer::Channel;
use {ControlMsg, LoadData, ProgressMsg, ResourceTask};
use url::Url;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender};
pub enum Msg {
/// Tell the cache that we may need a particular image soon. Must be posted
/// before Decode
Prefetch(Url),
/// This is optionally passed to the image cache when requesting
/// and image, and returned to the specified event loop when the
/// image load completes. It is typically used to trigger a reflow
/// and/or repaint.
pub trait ImageResponder : Send {
fn respond(&self, Option<Arc<Image>>);
}
/// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
Decode(Url),
/// The current state of an image in the cache.
#[derive(PartialEq, Copy)]
pub enum ImageState {
Pending,
LoadError,
NotRequested,
}
/// Request an Image object for a URL. If the image is not is not immediately
/// available then ImageNotReady is returned.
GetImage(Url, Sender<ImageResponseMsg>),
/// Channel for sending commands to the image cache.
#[derive(Clone)]
pub struct ImageCacheChan(pub Sender<ImageCacheResult>);
/// Wait for an image to become available (or fail to load).
WaitForImage(Url, Sender<ImageResponseMsg>),
/// The result of an image cache command that is returned to the
/// caller.
pub struct ImageCacheResult {
pub responder: Option<Box<ImageResponder>>,
pub image: Option<Arc<Image>>,
}
/// Commands that the image cache understands.
pub enum ImageCacheCommand {
/// Request an image asynchronously from the cache. Supply a channel
/// to receive the result, and optionally an image responder
/// that is passed to the result channel.
RequestImage(Url, ImageCacheChan, Option<Box<ImageResponder>>),
/// Synchronously check the state of an image in the cache.
/// TODO(gw): Profile this on some real world sites and see
/// if it's worth caching the results of this locally in each
/// layout / paint task.
GetImageIfAvailable(Url, Sender<Result<Arc<Image>, ImageState>>),
/// 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<Image>>),
/// For testing
WaitForStore(Sender<()>),
/// For testing
WaitForStorePrefetched(Sender<()>),
}
#[derive(Clone)]
pub enum ImageResponseMsg {
ImageReady(Arc<Image>),
ImageNotReady,
ImageFailed
}
impl PartialEq for ImageResponseMsg {
fn eq(&self, other: &ImageResponseMsg) -> bool {
match (self, other) {
(&ImageResponseMsg::ImageReady(..), &ImageResponseMsg::ImageReady(..)) => panic!("unimplemented comparison"),
(&ImageResponseMsg::ImageNotReady, &ImageResponseMsg::ImageNotReady) => true,
(&ImageResponseMsg::ImageFailed, &ImageResponseMsg::ImageFailed) => true,
(&ImageResponseMsg::ImageReady(..), _) | (&ImageResponseMsg::ImageNotReady, _) | (&ImageResponseMsg::ImageFailed, _) => false
}
}
}
/// The client side of the image cache task. This can be safely cloned
/// and passed to different tasks.
#[derive(Clone)]
pub struct ImageCacheTask {
pub chan: Sender<Msg>,
chan: Sender<ImageCacheCommand>,
}
/// The public API for the image cache task.
impl ImageCacheTask {
pub fn send(&self, msg: Msg) {
/// Construct a new image cache
pub fn new(chan: Sender<ImageCacheCommand>) -> ImageCacheTask {
ImageCacheTask {
chan: chan,
}
}
/// Asynchronously request and image. See ImageCacheCommand::RequestImage.
pub fn request_image(&self,
url: Url,
result_chan: ImageCacheChan,
responder: Option<Box<ImageResponder>>) {
let msg = ImageCacheCommand::RequestImage(url, result_chan, responder);
self.chan.send(msg).unwrap();
}
}
pub trait ImageCacheTaskClient {
fn exit(&self);
}
/// Get the current state of an image. See ImageCacheCommand::GetImageIfAvailable.
pub fn get_image_if_available(&self, url: Url) -> Result<Arc<Image>, ImageState> {
let (sender, receiver) = channel();
let msg = ImageCacheCommand::GetImageIfAvailable(url, sender);
self.chan.send(msg).unwrap();
receiver.recv().unwrap()
}
impl ImageCacheTaskClient for ImageCacheTask {
fn exit(&self) {
/// Shutdown the image cache task.
pub fn exit(&self) {
let (response_chan, response_port) = channel();
self.send(Msg::Exit(response_chan));
self.chan.send(ImageCacheCommand::Exit(response_chan)).unwrap();
response_port.recv().unwrap();
}
}
pub fn load_image_data(url: Url, resource_task: ResourceTask, placeholder: &[u8]) -> Result<Vec<u8>, ()> {
let (response_chan, response_port) = channel();
resource_task.send(ControlMsg::Load(LoadData::new(url.clone()), Channel(response_chan))).unwrap();
let mut image_data = vec!();
let progress_port = response_port.recv().unwrap().progress_port;
loop {
match progress_port.recv().unwrap() {
ProgressMsg::Payload(data) => {
image_data.push_all(&data);
}
ProgressMsg::Done(Ok(..)) => {
return Ok(image_data);
}
ProgressMsg::Done(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(());
}
}
}
}
}

View file

@ -6,7 +6,6 @@
#![feature(collections)]
#![feature(core)]
#![feature(rustc_private)]
#![feature(std_misc)]
extern crate geom;
extern crate hyper;
@ -28,7 +27,6 @@ use std::borrow::IntoCow;
use std::sync::mpsc::{channel, Receiver, Sender};
pub mod image_cache_task;
pub mod local_image_cache;
pub mod storage_task;
/// Image handling.
@ -38,7 +36,6 @@ pub mod storage_task;
/// caching is involved) and as a result it must live in here.
pub mod image {
pub mod base;
pub mod holder;
}
#[derive(Clone)]

View file

@ -1,170 +0,0 @@
/* 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/. */
/*!
An adapter for ImageCacheTask that does local caching to avoid
extra message traffic, it also avoids waiting on the same image
multiple times and thus triggering reflows multiple times.
*/
use image_cache_task::{ImageResponseMsg, ImageCacheTask, Msg};
use url::Url;
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::sync::mpsc::{channel, Receiver};
use util::task::spawn_named;
pub trait ImageResponder<NodeAddress: Send> {
fn respond(&self) -> Box<Fn(ImageResponseMsg, NodeAddress)+Send>;
}
pub struct LocalImageCache<NodeAddress> {
image_cache_task: ImageCacheTask,
round_number: u32,
on_image_available: Option<Box<ImageResponder<NodeAddress>+Send>>,
state_map: HashMap<Url, ImageState>
}
impl<NodeAddress: Send> LocalImageCache<NodeAddress> {
pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache<NodeAddress> {
LocalImageCache {
image_cache_task: image_cache_task,
round_number: 1,
on_image_available: None,
state_map: HashMap::new()
}
}
}
#[derive(Clone)]
struct ImageState {
prefetched: bool,
decoded: bool,
last_request_round: u32,
last_response: ImageResponseMsg
}
impl<NodeAddress: Send + 'static> LocalImageCache<NodeAddress> {
/// The local cache will only do a single remote request for a given
/// URL in each 'round'. Layout should call this each time it begins
pub fn next_round(&mut self, on_image_available: Box<ImageResponder<NodeAddress> + Send>) {
self.round_number += 1;
self.on_image_available = Some(on_image_available);
}
pub fn prefetch(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.prefetched {
return
}
state.prefetched = true;
}
self.image_cache_task.send(Msg::Prefetch((*url).clone()));
}
pub fn decode(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.decoded {
return
}
state.decoded = true;
}
self.image_cache_task.send(Msg::Decode((*url).clone()));
}
// FIXME: Should return a Future
pub fn get_image(&mut self, node_address: NodeAddress, url: &Url) -> Receiver<ImageResponseMsg> {
{
let round_number = self.round_number;
let state = self.get_state(url);
// Save the previous round number for comparison
let last_round = state.last_request_round;
// Set the current round number for this image
state.last_request_round = round_number;
match state.last_response {
ImageResponseMsg::ImageReady(ref image) => {
let (chan, port) = channel();
chan.send(ImageResponseMsg::ImageReady(image.clone())).unwrap();
return port;
}
ImageResponseMsg::ImageNotReady => {
if last_round == round_number {
let (chan, port) = channel();
chan.send(ImageResponseMsg::ImageNotReady).unwrap();
return port;
} else {
// We haven't requested the image from the
// remote cache this round
}
}
ImageResponseMsg::ImageFailed => {
let (chan, port) = channel();
chan.send(ImageResponseMsg::ImageFailed).unwrap();
return port;
}
}
}
let (response_chan, response_port) = channel();
self.image_cache_task.send(Msg::GetImage((*url).clone(), response_chan));
let response = response_port.recv().unwrap();
match response {
ImageResponseMsg::ImageNotReady => {
// Need to reflow when the image is available
// FIXME: Instead we should be just passing a Future
// to the caller, then to the display list. Finally,
// the compositor should be responsible for waiting
// on the image to load and triggering layout
let image_cache_task = self.image_cache_task.clone();
assert!(self.on_image_available.is_some());
let on_image_available =
self.on_image_available.as_ref().unwrap().respond();
let url = (*url).clone();
spawn_named("LocalImageCache".to_owned(), move || {
let (response_chan, response_port) = channel();
image_cache_task.send(Msg::WaitForImage(url, response_chan));
on_image_available(response_port.recv().unwrap(), node_address);
});
}
_ => ()
}
// Put a copy of the response in the cache
let response_copy = match response {
ImageResponseMsg::ImageReady(ref image) => ImageResponseMsg::ImageReady(image.clone()),
ImageResponseMsg::ImageNotReady => ImageResponseMsg::ImageNotReady,
ImageResponseMsg::ImageFailed => ImageResponseMsg::ImageFailed
};
self.get_state(url).last_response = response_copy;
let (chan, port) = channel();
chan.send(response).unwrap();
return port;
}
fn get_state<'a>(&'a mut self, url: &Url) -> &'a mut ImageState {
match self.state_map.entry((*url).clone()) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) =>
entry.insert(ImageState {
prefetched: false,
decoded: false,
last_request_round: 0,
last_response: ImageResponseMsg::ImageNotReady,
})
}
}
}