diff options
25 files changed, 441 insertions, 642 deletions
diff --git a/Cargo.lock b/Cargo.lock index ee28bed693f..4b74259ba71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1755,6 +1755,7 @@ dependencies = [ "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", "hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 0682cbe526b..109e8bfd59e 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -87,7 +87,6 @@ use msg::constellation_msg::{FrameId, FrameType, PipelineId}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection}; use net_traits::{self, IpcSend, ResourceThreads}; -use net_traits::image_cache_thread::ImageCacheThread; use net_traits::pub_domains::reg_host; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; use offscreen_gl_context::{GLContextAttributes, GLLimits}; @@ -175,10 +174,6 @@ pub struct Constellation<Message, LTF, STF> { /// browsing. private_resource_threads: ResourceThreads, - /// A channel for the constellation to send messages to the image - /// cache thread. - image_cache_thread: ImageCacheThread, - /// A channel for the constellation to send messages to the font /// cache thread. font_cache_thread: FontCacheThread, @@ -302,9 +297,6 @@ pub struct InitialConstellationState { /// A channel to the bluetooth thread. pub bluetooth_thread: IpcSender<BluetoothRequest>, - /// A channel to the image cache thread. - pub image_cache_thread: ImageCacheThread, - /// A channel to the font cache thread. pub font_cache_thread: FontCacheThread, @@ -518,7 +510,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> bluetooth_thread: state.bluetooth_thread, public_resource_threads: state.public_resource_threads, private_resource_threads: state.private_resource_threads, - image_cache_thread: state.image_cache_thread, font_cache_thread: state.font_cache_thread, swmanager_chan: None, swmanager_receiver: swmanager_receiver, @@ -657,7 +648,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> devtools_chan: self.devtools_chan.clone(), bluetooth_thread: self.bluetooth_thread.clone(), swmanager_thread: self.swmanager_sender.clone(), - image_cache_thread: self.image_cache_thread.clone(), font_cache_thread: self.font_cache_thread.clone(), resource_threads: resource_threads, time_profiler_chan: self.time_profiler_chan.clone(), @@ -1210,9 +1200,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let (core_sender, core_receiver) = ipc::channel().expect("Failed to create IPC channel!"); let (storage_sender, storage_receiver) = ipc::channel().expect("Failed to create IPC channel!"); - debug!("Exiting image cache."); - self.image_cache_thread.exit(); - debug!("Exiting core resource threads."); if let Err(e) = self.public_resource_threads.send(net_traits::CoreResourceMsg::Exit(core_sender)) { warn!("Exit resource thread failed ({})", e); diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 877c850a3cb..637b3787775 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -17,7 +17,7 @@ use ipc_channel::router::ROUTER; use layout_traits::LayoutThreadFactory; use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespaceId}; use net_traits::{IpcSend, ResourceThreads}; -use net_traits::image_cache_thread::ImageCacheThread; +use net_traits::image_cache::ImageCache; use profile_traits::mem as profile_mem; use profile_traits::time; use script_traits::{ConstellationControlMsg, DevicePixel, DiscardBrowsingContext}; @@ -133,9 +133,6 @@ pub struct InitialPipelineState { /// A channel to the service worker manager thread pub swmanager_thread: IpcSender<SWManagerMsg>, - /// A channel to the image cache thread. - pub image_cache_thread: ImageCacheThread, - /// A channel to the font cache thread. pub font_cache_thread: FontCacheThread, @@ -250,7 +247,6 @@ impl Pipeline { devtools_chan: script_to_devtools_chan, bluetooth_thread: state.bluetooth_thread, swmanager_thread: state.swmanager_thread, - image_cache_thread: state.image_cache_thread, font_cache_thread: state.font_cache_thread, resource_threads: state.resource_threads, time_profiler_chan: state.time_profiler_chan, @@ -451,7 +447,6 @@ pub struct UnprivilegedPipelineContent { devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, bluetooth_thread: IpcSender<BluetoothRequest>, swmanager_thread: IpcSender<SWManagerMsg>, - image_cache_thread: ImageCacheThread, font_cache_thread: FontCacheThread, resource_threads: ResourceThreads, time_profiler_chan: time::ProfilerChan, @@ -477,6 +472,7 @@ impl UnprivilegedPipelineContent { where LTF: LayoutThreadFactory<Message=Message>, STF: ScriptThreadFactory<Message=Message> { + let image_cache = ImageCache::new(self.webrender_api_sender.create_api()); let layout_pair = STF::create(InitialScriptState { id: self.id, frame_id: self.frame_id, @@ -489,7 +485,7 @@ impl UnprivilegedPipelineContent { scheduler_chan: self.scheduler_chan, bluetooth_thread: self.bluetooth_thread, resource_threads: self.resource_threads, - image_cache_thread: self.image_cache_thread.clone(), + image_cache: image_cache.clone(), time_profiler_chan: self.time_profiler_chan.clone(), mem_profiler_chan: self.mem_profiler_chan.clone(), devtools_chan: self.devtools_chan, @@ -507,7 +503,7 @@ impl UnprivilegedPipelineContent { self.pipeline_port, self.layout_to_constellation_chan, self.script_chan, - self.image_cache_thread, + image_cache.clone(), self.font_cache_thread, self.time_profiler_chan, self.mem_profiler_chan, diff --git a/components/layout/context.rs b/components/layout/context.rs index e2ffeb1cbd9..72be2555720 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -9,8 +9,8 @@ use gfx::display_list::{WebRenderImageInfo, OpaqueNode}; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context::FontContext; use heapsize::HeapSizeOf; -use net_traits::image_cache_thread::{ImageCacheThread, ImageState, CanRequestImages}; -use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder}; +use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState}; +use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use opaque_node::OpaqueNodeMethods; use parking_lot::RwLock; use script_layout_interface::{PendingImage, PendingImageState}; @@ -79,8 +79,8 @@ pub struct LayoutContext<'a> { /// Bits shared by the layout and style system. pub style_context: SharedStyleContext<'a>, - /// The shared image cache thread. - pub image_cache_thread: Mutex<ImageCacheThread>, + /// Reference to the script thread image cache. + pub image_cache: Arc<ImageCache>, /// Interface to the font cache thread. pub font_cache_thread: Mutex<FontCacheThread>, @@ -126,10 +126,9 @@ impl<'a> LayoutContext<'a> { }; // See if the image is already available - let result = self.image_cache_thread.lock().unwrap() - .find_image_or_metadata(url.clone(), - use_placeholder, - can_request); + let result = self.image_cache.find_image_or_metadata(url.clone(), + use_placeholder, + can_request); match result { Ok(image_or_metadata) => Some(image_or_metadata), // Image failed to load, so just return nothing diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index f97c3ad63b1..f2aac544103 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -36,7 +36,7 @@ use list_item::ListItemFlow; use model::{self, MaybeAuto}; use msg::constellation_msg::PipelineId; use net_traits::image::base::PixelFormat; -use net_traits::image_cache_thread::UsePlaceholder; +use net_traits::image_cache::UsePlaceholder; use range::Range; use servo_config::opts; use servo_url::ServoUrl; diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 51976521428..cf2f892878f 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -27,7 +27,7 @@ use model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeC use model::{style_length, ToGfxMatrix}; use msg::constellation_msg::PipelineId; use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder}; +use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use range::*; use script_layout_interface::HTMLCanvasData; use script_layout_interface::SVGSVGData; diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 2279b8f2058..629e5ff6a73 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -76,8 +76,7 @@ use layout::wrapper::LayoutNodeLayoutData; use layout::wrapper::drop_style_and_layout_data; use layout_traits::LayoutThreadFactory; use msg::constellation_msg::{FrameId, PipelineId}; -use net_traits::image_cache_thread::ImageCacheThread; -use net_traits::image_cache_thread::UsePlaceholder; +use net_traits::image_cache::{ImageCache, UsePlaceholder}; use parking_lot::RwLock; use profile_traits::mem::{self, Report, ReportKind, ReportsChan}; use profile_traits::time::{self, TimerMetadata, profile}; @@ -157,8 +156,8 @@ pub struct LayoutThread { /// The channel on which messages can be sent to the memory profiler. mem_profiler_chan: mem::ProfilerChan, - /// The channel on which messages can be sent to the image cache. - image_cache_thread: ImageCacheThread, + /// Reference to the script thread image cache. + image_cache: Arc<ImageCache>, /// Public interface to the font cache thread. font_cache_thread: FontCacheThread, @@ -245,7 +244,7 @@ impl LayoutThreadFactory for LayoutThread { pipeline_port: IpcReceiver<LayoutControlMsg>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, - image_cache_thread: ImageCacheThread, + image_cache: Arc<ImageCache>, font_cache_thread: FontCacheThread, time_profiler_chan: time::ProfilerChan, mem_profiler_chan: mem::ProfilerChan, @@ -268,7 +267,7 @@ impl LayoutThreadFactory for LayoutThread { pipeline_port, constellation_chan, script_chan, - image_cache_thread, + image_cache.clone(), font_cache_thread, time_profiler_chan, mem_profiler_chan.clone(), @@ -382,7 +381,7 @@ impl LayoutThread { pipeline_port: IpcReceiver<LayoutControlMsg>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, - image_cache_thread: ImageCacheThread, + image_cache: Arc<ImageCache>, font_cache_thread: FontCacheThread, time_profiler_chan: time::ProfilerChan, mem_profiler_chan: mem::ProfilerChan, @@ -432,7 +431,7 @@ impl LayoutThread { constellation_chan: constellation_chan.clone(), time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, - image_cache_thread: image_cache_thread, + image_cache: image_cache.clone(), font_cache_thread: font_cache_thread, first_reflow: true, font_cache_receiver: font_cache_receiver, @@ -522,7 +521,7 @@ impl LayoutThread { quirks_mode: self.quirks_mode.unwrap(), animation_only_restyle: false, }, - image_cache_thread: Mutex::new(self.image_cache_thread.clone()), + image_cache: self.image_cache.clone(), font_cache_thread: Mutex::new(self.font_cache_thread.clone()), webrender_image_cache: self.webrender_image_cache.clone(), pending_images: if request_images { Some(Mutex::new(vec![])) } else { None }, @@ -693,7 +692,7 @@ impl LayoutThread { info.pipeline_port, info.constellation_chan, info.script_chan.clone(), - self.image_cache_thread.clone(), + info.image_cache.clone(), self.font_cache_thread.clone(), self.time_profiler_chan.clone(), self.mem_profiler_chan.clone(), diff --git a/components/layout_traits/lib.rs b/components/layout_traits/lib.rs index 632300aa61b..9d97a89a53c 100644 --- a/components/layout_traits/lib.rs +++ b/components/layout_traits/lib.rs @@ -21,11 +21,12 @@ extern crate webrender_traits; use gfx::font_cache_thread::FontCacheThread; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use msg::constellation_msg::{FrameId, PipelineId}; -use net_traits::image_cache_thread::ImageCacheThread; +use net_traits::image_cache::ImageCache; use profile_traits::{mem, time}; use script_traits::{ConstellationControlMsg, LayoutControlMsg}; use script_traits::LayoutMsg as ConstellationMsg; use servo_url::ServoUrl; +use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; // A static method creating a layout thread @@ -40,7 +41,7 @@ pub trait LayoutThreadFactory { pipeline_port: IpcReceiver<LayoutControlMsg>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, - image_cache_thread: ImageCacheThread, + image_cache: Arc<ImageCache>, font_cache_thread: FontCacheThread, time_profiler_chan: time::ProfilerChan, mem_profiler_chan: mem::ProfilerChan, diff --git a/components/net/lib.rs b/components/net/lib.rs index f7724b08222..c850d1c3662 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -4,8 +4,6 @@ #![deny(unsafe_code)] #![feature(box_syntax)] -#![feature(mpsc_select)] -#![feature(step_by)] extern crate base64; extern crate brotli; @@ -14,7 +12,6 @@ extern crate devtools_traits; extern crate flate2; extern crate hyper; extern crate hyper_serde; -extern crate immeta; extern crate ipc_channel; #[macro_use] extern crate log; #[macro_use] #[no_link] extern crate matches; @@ -32,14 +29,12 @@ extern crate serde_derive; extern crate serde_json; extern crate servo_config; extern crate servo_url; -extern crate threadpool; extern crate time; #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] extern crate tinyfiledialogs; extern crate unicase; extern crate url; extern crate uuid; -extern crate webrender_traits; extern crate websocket; mod blob_loader; @@ -51,7 +46,6 @@ mod data_loader; pub mod filemanager_thread; pub mod hsts; mod http_loader; -pub mod image_cache_thread; pub mod mime_classifier; pub mod resource_thread; mod storage_thread; diff --git a/components/net_traits/Cargo.toml b/components/net_traits/Cargo.toml index 565b3d81c6c..52fb9370079 100644 --- a/components/net_traits/Cargo.toml +++ b/components/net_traits/Cargo.toml @@ -16,6 +16,7 @@ heapsize_derive = "0.1" hyper = "0.9.9" hyper_serde = "0.5" image = "0.12" +immeta = "0.3.1" ipc-channel = "0.7" lazy_static = "0.2" log = "0.3.5" diff --git a/components/net/image_cache_thread.rs b/components/net_traits/image_cache.rs index a9cdca099d7..92ca3d27373 100644 --- a/components/net/image_cache_thread.rs +++ b/components/net_traits/image_cache.rs @@ -2,35 +2,81 @@ * 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 FetchResponseMsg; +use NetworkError; +use image::base::{Image, ImageMetadata, PixelFormat, load_from_memory}; use immeta::load_from_buf; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; -use ipc_channel::router::ROUTER; -use net_traits::{NetworkError, FetchResponseMsg}; -use net_traits::image::base::{Image, ImageMetadata, PixelFormat, load_from_memory}; -use net_traits::image_cache_thread::{ImageCacheCommand, ImageCacheThread, ImageState}; -use net_traits::image_cache_thread::{ImageOrMetadataAvailable, ImageResponse, UsePlaceholder}; -use net_traits::image_cache_thread::{ImageResponder, PendingImageId, CanRequestImages}; +use ipc_channel::ipc::IpcSender; use servo_config::resource_files::resources_dir_path; use servo_url::ServoUrl; -use std::borrow::ToOwned; use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::fs::File; use std::io::{self, Read}; use std::mem; -use std::sync::Arc; -use std::sync::mpsc::{Receiver, Sender, channel}; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::channel; use std::thread; -use threadpool::ThreadPool; use webrender_traits; /// /// TODO(gw): Remaining work on image cache: /// * Make use of the prefetch support in various parts of the code. -/// * Profile time in GetImageIfAvailable - might be worth caching these results per paint / layout thread. +/// * Profile time in GetImageIfAvailable - might be worth caching these +/// results per paint / layout thread. /// /// MAYBE(Yoric): -/// * For faster lookups, it might be useful to store the LoadKey in the DOM once we have performed a first load. +/// * For faster lookups, it might be useful to store the LoadKey in the +/// DOM once we have performed a first load. + + +// ====================================================================== +// Helper functions. +// ====================================================================== + +fn convert_format(format: PixelFormat) -> webrender_traits::ImageFormat { + match format { + PixelFormat::K8 | PixelFormat::KA8 => { + panic!("Not support by webrender yet"); + } + PixelFormat::RGB8 => webrender_traits::ImageFormat::RGB8, + PixelFormat::RGBA8 => webrender_traits::ImageFormat::RGBA8, + } +} + +fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg { + let image = load_from_memory(bytes); + DecoderMsg { + key: key, + image: image + } +} + +fn get_placeholder_image(webrender_api: &webrender_traits::RenderApi) -> io::Result<Arc<Image>> { + let mut placeholder_path = try!(resources_dir_path()); + placeholder_path.push("rippy.png"); + let mut file = try!(File::open(&placeholder_path)); + let mut image_data = vec![]; + try!(file.read_to_end(&mut image_data)); + let mut image = load_from_memory(&image_data).unwrap(); + let format = convert_format(image.format); + let mut bytes = Vec::new(); + bytes.extend_from_slice(&*image.bytes); + let descriptor = webrender_traits::ImageDescriptor { + width: image.width, + height: image.height, + stride: None, + format: format, + offset: 0, + is_opaque: is_image_opaque(format, &bytes), + }; + let data = webrender_traits::ImageData::new(bytes); + let image_key = webrender_api.generate_image_key(); + webrender_api.add_image(image_key, descriptor, data, None); + image.id = Some(image_key); + Ok(Arc::new(image)) +} + // TODO(gw): This is a port of the old is_image_opaque code from WR. // Consider using SIMD to speed this up if it shows in profiles. @@ -52,80 +98,24 @@ fn is_image_opaque(format: webrender_traits::ImageFormat, bytes: &[u8]) -> bool } } -/// Represents an image that is either being loaded -/// by the resource thread, or decoded by a worker thread. -struct PendingLoad { - // The bytes loaded so far. Reset to an empty vector once loading - // is complete and the buffer has been transmitted to the decoder. - bytes: ImageBytes, - - // Image metadata, if available. - metadata: Option<ImageMetadata>, - - // Once loading is complete, the result of the operation. - result: Option<Result<(), NetworkError>>, - listeners: Vec<ImageResponder>, - - // The url being loaded. Do not forget that this may be several Mb - // if we are loading a data: url. - url: ServoUrl, -} - -enum ImageBytes { - InProgress(Vec<u8>), - Complete(Arc<Vec<u8>>), -} - -impl ImageBytes { - fn extend_from_slice(&mut self, data: &[u8]) { - match *self { - ImageBytes::InProgress(ref mut bytes) => bytes.extend_from_slice(data), - ImageBytes::Complete(_) => panic!("attempted modification of complete image bytes"), - } - } +fn premultiply(data: &mut [u8]) { + let length = data.len(); - fn mark_complete(&mut self) -> Arc<Vec<u8>> { - let bytes = { - let own_bytes = match *self { - ImageBytes::InProgress(ref mut bytes) => bytes, - ImageBytes::Complete(_) => panic!("attempted modification of complete image bytes"), - }; - mem::replace(own_bytes, vec![]) - }; - let bytes = Arc::new(bytes); - *self = ImageBytes::Complete(bytes.clone()); - bytes - } + for i in (0..length).step_by(4) { + let b = data[i + 0] as u32; + let g = data[i + 1] as u32; + let r = data[i + 2] as u32; + let a = data[i + 3] as u32; - fn as_slice(&self) -> &[u8] { - match *self { - ImageBytes::InProgress(ref bytes) => &bytes, - ImageBytes::Complete(ref bytes) => &*bytes, - } + data[i + 0] = (b * a / 255) as u8; + data[i + 1] = (g * a / 255) as u8; + data[i + 2] = (r * a / 255) as u8; } } -enum LoadResult { - Loaded(Image), - PlaceholderLoaded(Arc<Image>), - None -} - -impl PendingLoad { - fn new(url: ServoUrl) -> PendingLoad { - PendingLoad { - bytes: ImageBytes::InProgress(vec!()), - metadata: None, - result: None, - listeners: vec!(), - url: url, - } - } - - fn add_listener(&mut self, listener: ImageResponder) { - self.listeners.push(listener); - } -} +// ====================================================================== +// Aux structs and enums. +// ====================================================================== // Represents all the currently pending loads/decodings. For // performance reasons, loads are indexed by a dedicated load key. @@ -142,14 +132,6 @@ struct AllPendingLoads { keygen: LoadKeyGenerator, } -/// Result of accessing a cache. -enum CacheResult<'a> { - /// The value was in the cache. - Hit(LoadKey, &'a mut PendingLoad), - /// The value was not in the cache and needed to be regenerated. - Miss(Option<(LoadKey, &'a mut PendingLoad)>), -} - impl AllPendingLoads { fn new() -> AllPendingLoads { AllPendingLoads { @@ -159,12 +141,6 @@ impl AllPendingLoads { } } - // `true` if there is no currently pending load, `false` otherwise. - fn is_empty(&self) -> bool { - assert!(self.loads.is_empty() == self.url_to_load_key.is_empty()); - self.loads.is_empty() - } - // get a PendingLoad from its LoadKey. fn get_by_key_mut(&mut self, key: &LoadKey) -> Option<&mut PendingLoad> { self.loads.get_mut(key) @@ -206,6 +182,23 @@ impl AllPendingLoads { } } +/// Result of accessing a cache. +enum CacheResult<'a> { + /// The value was in the cache. + Hit(LoadKey, &'a mut PendingLoad), + /// The value was not in the cache and needed to be regenerated. + Miss(Option<(LoadKey, &'a mut PendingLoad)>), +} + +/// Whether a consumer is in a position to request images or not. This can occur +/// when animations are being processed by the layout thread while the script +/// thread is executing in parallel. +#[derive(Copy, Clone, PartialEq, Deserialize, Serialize)] +pub enum CanRequestImages { + No, + Yes, +} + /// Represents an image that has completed loading. /// Images that fail to load (due to network or decode /// failure) are still stored here, so that they aren't @@ -224,272 +217,203 @@ impl CompletedLoad { } } -// A key used to communicate during loading. -type LoadKey = PendingImageId; +/// Message that the decoder worker threads send to the image cache. +struct DecoderMsg { + key: LoadKey, + image: Option<Image>, +} -struct LoadKeyGenerator { - counter: u64 +enum ImageBytes { + InProgress(Vec<u8>), + Complete(Arc<Vec<u8>>), } -impl LoadKeyGenerator { - fn new() -> LoadKeyGenerator { - LoadKeyGenerator { - counter: 0 +impl ImageBytes { + fn extend_from_slice(&mut self, data: &[u8]) { + match *self { + ImageBytes::InProgress(ref mut bytes) => bytes.extend_from_slice(data), + ImageBytes::Complete(_) => panic!("attempted modification of complete image bytes"), } } - fn next(&mut self) -> PendingImageId { - self.counter += 1; - PendingImageId(self.counter) + + fn mark_complete(&mut self) -> Arc<Vec<u8>> { + let bytes = { + let own_bytes = match *self { + ImageBytes::InProgress(ref mut bytes) => bytes, + ImageBytes::Complete(_) => panic!("attempted modification of complete image bytes"), + }; + mem::replace(own_bytes, vec![]) + }; + let bytes = Arc::new(bytes); + *self = ImageBytes::Complete(bytes.clone()); + bytes } -} -struct ResourceLoadInfo { - action: FetchResponseMsg, - key: LoadKey, + fn as_slice(&self) -> &[u8] { + match *self { + ImageBytes::InProgress(ref bytes) => &bytes, + ImageBytes::Complete(ref bytes) => &*bytes, + } + } } -/// Implementation of the image cache -struct ImageCache { - decoder_sender: Sender<DecoderMsg>, - - // Worker threads for decoding images. - thread_pool: ThreadPool, - - // Images that are loading over network, or decoding. - pending_loads: AllPendingLoads, +/// Indicating either entire image or just metadata availability +#[derive(Clone, Deserialize, Serialize, HeapSizeOf)] +pub enum ImageOrMetadataAvailable { + ImageAvailable(Arc<Image>), + MetadataAvailable(ImageMetadata), +} - // Images that have finished loading (successful or not) - completed_loads: HashMap<ServoUrl, CompletedLoad>, +/// 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. +#[derive(Clone, Deserialize, Serialize)] +pub struct ImageResponder { + id: PendingImageId, + sender: IpcSender<PendingImageResponse>, +} - // The placeholder image used when an image fails to load - placeholder_image: Option<Arc<Image>>, +impl ImageResponder { + pub fn new(sender: IpcSender<PendingImageResponse>, id: PendingImageId) -> ImageResponder { + ImageResponder { + sender: sender, + id: id, + } + } - // Webrender API instance. - webrender_api: webrender_traits::RenderApi, + pub fn respond(&self, response: ImageResponse) { + debug!("Notifying listener"); + // This send can fail if thread waiting for this notification has panicked. + // That's not a case that's worth warning about. + // TODO(#15501): are there cases in which we should perform cleanup? + let _ = self.sender.send(PendingImageResponse { + response: response, + id: self.id, + }); + } } -/// Message that the decoder worker threads send to main image cache thread. -struct DecoderMsg { - key: LoadKey, - image: Option<Image>, +/// The returned image. +#[derive(Clone, Deserialize, Serialize, HeapSizeOf)] +pub enum ImageResponse { + /// The requested image was loaded. + Loaded(Arc<Image>), + /// The request image metadata was loaded. + MetadataLoaded(ImageMetadata), + /// The requested image failed to load, so a placeholder was loaded instead. + PlaceholderLoaded(Arc<Image>), + /// Neither the requested image nor the placeholder could be loaded. + None, } -struct Receivers { - cmd_receiver: Receiver<ImageCacheCommand>, - decoder_receiver: Receiver<DecoderMsg>, +/// The current state of an image in the cache. +#[derive(PartialEq, Copy, Clone, Deserialize, Serialize)] +pub enum ImageState { + Pending(PendingImageId), + LoadError, + NotRequested(PendingImageId), } -impl Receivers { - #[allow(unsafe_code)] - fn recv(&self) -> SelectResult { - let cmd_receiver = &self.cmd_receiver; - let decoder_receiver = &self.decoder_receiver; - select! { - msg = cmd_receiver.recv() => { - SelectResult::Command(msg.unwrap()) - }, - msg = decoder_receiver.recv() => { - SelectResult::Decoder(msg.unwrap()) - } - } - } -} +// A key used to communicate during loading. +type LoadKey = PendingImageId; -/// The types of messages that the main image cache thread receives. -enum SelectResult { - Command(ImageCacheCommand), - Decoder(DecoderMsg), +struct LoadKeyGenerator { + counter: u64 } -fn convert_format(format: PixelFormat) -> webrender_traits::ImageFormat { - match format { - PixelFormat::K8 | PixelFormat::KA8 => { - panic!("Not support by webrender yet"); +impl LoadKeyGenerator { + fn new() -> LoadKeyGenerator { + LoadKeyGenerator { + counter: 0 } - PixelFormat::RGB8 => webrender_traits::ImageFormat::RGB8, - PixelFormat::RGBA8 => webrender_traits::ImageFormat::RGBA8, + } + fn next(&mut self) -> PendingImageId { + self.counter += 1; + PendingImageId(self.counter) } } -fn get_placeholder_image(webrender_api: &webrender_traits::RenderApi) -> io::Result<Arc<Image>> { - let mut placeholder_path = try!(resources_dir_path()); - placeholder_path.push("rippy.png"); - let mut file = try!(File::open(&placeholder_path)); - let mut image_data = vec![]; - try!(file.read_to_end(&mut image_data)); - let mut image = load_from_memory(&image_data).unwrap(); - let format = convert_format(image.format); - let mut bytes = Vec::new(); - bytes.extend_from_slice(&*image.bytes); - let descriptor = webrender_traits::ImageDescriptor { - width: image.width, - height: image.height, - stride: None, - format: format, - offset: 0, - is_opaque: is_image_opaque(format, &bytes), - }; - let data = webrender_traits::ImageData::new(bytes); - let image_key = webrender_api.generate_image_key(); - webrender_api.add_image(image_key, descriptor, data, None); - image.id = Some(image_key); - Ok(Arc::new(image)) +enum LoadResult { + Loaded(Image), + PlaceholderLoaded(Arc<Image>), + None } -fn premultiply(data: &mut [u8]) { - let length = data.len(); +/// The unique id for an image that has previously been requested. +#[derive(Copy, Clone, PartialEq, Eq, Deserialize, Serialize, HeapSizeOf, Hash, Debug)] +pub struct PendingImageId(pub u64); - for i in (0..length).step_by(4) { - let b = data[i + 0] as u32; - let g = data[i + 1] as u32; - let r = data[i + 2] as u32; - let a = data[i + 3] as u32; - - data[i + 0] = (b * a / 255) as u8; - data[i + 1] = (g * a / 255) as u8; - data[i + 2] = (r * a / 255) as u8; - } +#[derive(Deserialize, Serialize)] +pub struct PendingImageResponse { + pub response: ImageResponse, + pub id: PendingImageId, } -impl ImageCache { - fn run(webrender_api: webrender_traits::RenderApi, - ipc_command_receiver: IpcReceiver<ImageCacheCommand>) { - // Preload the placeholder image, used when images fail to load. - let placeholder_image = get_placeholder_image(&webrender_api).ok(); - - // Ask the router to proxy messages received over IPC to us. - let cmd_receiver = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_command_receiver); - - let (decoder_sender, decoder_receiver) = channel(); - let mut cache = ImageCache { - decoder_sender: decoder_sender, - thread_pool: ThreadPool::new(4), - pending_loads: AllPendingLoads::new(), - completed_loads: HashMap::new(), - placeholder_image: placeholder_image, - webrender_api: webrender_api, - }; +/// Represents an image that is either being loaded +/// by the resource thread, or decoded by a worker thread. +struct PendingLoad { + // The bytes loaded so far. Reset to an empty vector once loading + // is complete and the buffer has been transmitted to the decoder. + bytes: ImageBytes, - let receivers = Receivers { - cmd_receiver: cmd_receiver, - decoder_receiver: decoder_receiver, - }; + // Image metadata, if available. + metadata: Option<ImageMetadata>, - let mut exit_sender: Option<IpcSender<()>> = None; + // Once loading is complete, the result of the operation. + result: Option<Result<(), NetworkError>>, + listeners: Vec<ImageResponder>, - loop { - match receivers.recv() { - SelectResult::Command(cmd) => { - exit_sender = cache.handle_cmd(cmd); - } - SelectResult::Decoder(msg) => { - cache.handle_decoder(msg); - } - } + // The url being loaded. Do not forget that this may be several Mb + // if we are loading a data: url. + url: ServoUrl, +} - // Can only exit when all pending loads are complete. - if let Some(ref exit_sender) = exit_sender { - if cache.pending_loads.is_empty() { - exit_sender.send(()).unwrap(); - break; - } - } +impl PendingLoad { + fn new(url: ServoUrl) -> PendingLoad { + PendingLoad { + bytes: ImageBytes::InProgress(vec!()), + metadata: None, + result: None, + listeners: vec!(), + url: url, } } - // Handle a request from a client - fn handle_cmd(&mut self, cmd: ImageCacheCommand) -> Option<IpcSender<()>> { - match cmd { - ImageCacheCommand::Exit(sender) => { - return Some(sender); - } - ImageCacheCommand::AddListener(id, responder) => { - self.add_listener(id, responder); - } - ImageCacheCommand::GetImageOrMetadataIfAvailable(url, - use_placeholder, - can_request, - consumer) => { - let result = self.get_image_or_meta_if_available(url, use_placeholder, can_request); - // TODO(#15501): look for opportunities to clean up cache if this send fails. - let _ = consumer.send(result); - } - ImageCacheCommand::StoreDecodeImage(id, data) => { - self.handle_progress(ResourceLoadInfo { - action: data, - key: id - }); - } - }; - - None + fn add_listener(&mut self, listener: ImageResponder) { + self.listeners.push(listener); } +} - // Handle progress messages from the resource thread - fn handle_progress(&mut self, msg: ResourceLoadInfo) { - match (msg.action, msg.key) { - (FetchResponseMsg::ProcessRequestBody, _) | - (FetchResponseMsg::ProcessRequestEOF, _) => return, - (FetchResponseMsg::ProcessResponse(_response), _) => {} - (FetchResponseMsg::ProcessResponseChunk(data), _) => { - debug!("got some data for {:?}", msg.key); - let pending_load = self.pending_loads.get_by_key_mut(&msg.key).unwrap(); - pending_load.bytes.extend_from_slice(&data); - //jmr0 TODO: possibly move to another task? - if let None = pending_load.metadata { - if let Ok(metadata) = load_from_buf(&pending_load.bytes.as_slice()) { - let dimensions = metadata.dimensions(); - let img_metadata = ImageMetadata { width: dimensions.width, - height: dimensions.height }; - for listener in &pending_load.listeners { - listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone())); - } - pending_load.metadata = Some(img_metadata); - } - } - } - (FetchResponseMsg::ProcessResponseEOF(result), key) => { - debug!("received EOF for {:?}", key); - match result { - Ok(()) => { - let pending_load = self.pending_loads.get_by_key_mut(&msg.key).unwrap(); - pending_load.result = Some(result); - let bytes = pending_load.bytes.mark_complete(); - let sender = self.decoder_sender.clone(); - debug!("async decoding {} ({:?})", pending_load.url, key); +#[derive(Copy, Clone, PartialEq, Hash, Eq, Deserialize, Serialize)] +pub enum UsePlaceholder { + No, + Yes, +} - self.thread_pool.execute(move || { - let msg = decode_bytes_sync(key, &*bytes); - sender.send(msg).unwrap(); - }); - } - Err(_) => { - debug!("processing error for {:?}", key); - match self.placeholder_image.clone() { - Some(placeholder_image) => { - self.complete_load(msg.key, LoadResult::PlaceholderLoaded( - placeholder_image)) - } - None => self.complete_load(msg.key, LoadResult::None), - } - } - } - } - } - } +// ====================================================================== +// ImageCache implementation. +// ====================================================================== - // Handle a message from one of the decoder worker threads - fn handle_decoder(&mut self, msg: DecoderMsg) { - let image = match msg.image { - None => LoadResult::None, - Some(image) => LoadResult::Loaded(image), - }; - self.complete_load(msg.key, image); - } +pub struct ImageCache { + // Images that are loading over network, or decoding. + pending_loads: Mutex<AllPendingLoads>, + + // Images that have finished loading (successful or not) + completed_loads: Mutex<HashMap<ServoUrl, CompletedLoad>>, + + // The placeholder image used when an image fails to load + placeholder_image: Option<Arc<Image>>, + // Webrender API instance. + webrender_api: Mutex<webrender_traits::RenderApi>, +} + +impl ImageCache { // Change state of a url from pending -> loaded. - fn complete_load(&mut self, key: LoadKey, mut load_result: LoadResult) { - let pending_load = match self.pending_loads.remove(&key) { + fn complete_load(&self, key: LoadKey, mut load_result: LoadResult) { + let mut pending_loads = self.pending_loads.lock().unwrap(); + let pending_load = match pending_loads.remove(&key) { Some(load) => load, None => return, }; @@ -511,8 +435,9 @@ impl ImageCache { is_opaque: is_image_opaque(format, &bytes), }; let data = webrender_traits::ImageData::new(bytes); - let image_key = self.webrender_api.generate_image_key(); - self.webrender_api.add_image(image_key, descriptor, data, None); + let webrender_api = self.webrender_api.lock().unwrap(); + let image_key = webrender_api.generate_image_key(); + webrender_api.add_image(image_key, descriptor, data, None); image.id = Some(image_key); } LoadResult::PlaceholderLoaded(..) | LoadResult::None => {} @@ -525,39 +450,20 @@ impl ImageCache { }; let completed_load = CompletedLoad::new(image_response.clone(), key); - self.completed_loads.insert(pending_load.url.into(), completed_load); + self.completed_loads.lock().unwrap().insert(pending_load.url.into(), completed_load); for listener in pending_load.listeners { listener.respond(image_response.clone()); } } - /// Add a listener for a given image if it is still pending, or notify the - /// listener if the image is complete. - fn add_listener(&mut self, - id: PendingImageId, - listener: ImageResponder) { - if let Some(load) = self.pending_loads.get_by_key_mut(&id) { - if let Some(ref metadata) = load.metadata { - listener.respond(ImageResponse::MetadataLoaded(metadata.clone())); - } - load.add_listener(listener); - return; - } - if let Some(load) = self.completed_loads.values().find(|l| l.id == id) { - listener.respond(load.image_response.clone()); - return; - } - warn!("Couldn't find cached entry for listener {:?}", id); - } - /// Return a completed image if it exists, or None if there is no complete load /// or the complete load is not fully decoded or is unavailable. fn get_completed_image_if_available(&self, url: &ServoUrl, placeholder: UsePlaceholder) -> Option<Result<ImageOrMetadataAvailable, ImageState>> { - self.completed_loads.get(url).map(|completed_load| { + self.completed_loads.lock().unwrap().get(url).map(|completed_load| { match (&completed_load.image_response, placeholder) { (&ImageResponse::Loaded(ref image), _) | (&ImageResponse::PlaceholderLoaded(ref image), UsePlaceholder::Yes) => { @@ -572,29 +478,53 @@ impl ImageCache { }) } - /// Return any available metadata or image for the given URL, or an indication that - /// the image is not yet available if it is in progress, or else reserve a slot in - /// the cache for the URL if the consumer can request images. - fn get_image_or_meta_if_available(&mut self, - url: ServoUrl, - placeholder: UsePlaceholder, - can_request: CanRequestImages) - -> Result<ImageOrMetadataAvailable, ImageState> { - if let Some(result) = self.get_completed_image_if_available(&url, placeholder) { + /// Handle a message from one of the decoder worker threads or from a sync + /// decoding operation. + fn handle_decoder(&self, msg: DecoderMsg) { + let image = match msg.image { + None => LoadResult::None, + Some(image) => LoadResult::Loaded(image), + }; + self.complete_load(msg.key, image); + } + + // Public API + + pub fn new(webrender_api: webrender_traits::RenderApi) -> Arc<ImageCache> { + debug!("New image cache"); + Arc::new(ImageCache { + pending_loads: Mutex::new(AllPendingLoads::new()), + completed_loads: Mutex::new(HashMap::new()), + placeholder_image: get_placeholder_image(&webrender_api).ok(), + webrender_api: Mutex::new(webrender_api), + }) + } + + /// Return any available metadata or image for the given URL, + /// or an indication that the image is not yet available if it is in progress, + /// or else reserve a slot in the cache for the URL if the consumer can request images. + pub fn find_image_or_metadata(&self, + url: ServoUrl, + use_placeholder: UsePlaceholder, + can_request: CanRequestImages) + -> Result<ImageOrMetadataAvailable, ImageState> { + debug!("Find image or metadata for {}", url); + if let Some(result) = self.get_completed_image_if_available(&url, use_placeholder) { debug!("{} is available", url); return result; } let decoded = { - let result = self.pending_loads.get_cached(url.clone(), can_request); + let mut pending_loads = self.pending_loads.lock().unwrap(); + let result = pending_loads.get_cached(url.clone(), can_request); match result { CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) { (&Some(Ok(_)), _) => { - debug!("sync decoding {} ({:?})", url, key); + debug!("Sync decoding {} ({:?})", url, key); decode_bytes_sync(key, &pl.bytes.as_slice()) } (&None, &Some(ref meta)) => { - debug!("metadata available for {} ({:?})", url, key); + debug!("Metadata available for {} ({:?})", url, key); return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone())) } (&Some(Err(_)), _) | (&None, &None) => { @@ -603,43 +533,104 @@ impl ImageCache { } }, CacheResult::Miss(Some((key, _pl))) => { - debug!("should be requesting {} ({:?})", url, key); + debug!("Should be requesting {} ({:?})", url, key); return Err(ImageState::NotRequested(key)); } CacheResult::Miss(None) => { - debug!("couldn't find an entry for {}", url); + debug!("Couldn't find an entry for {}", url); return Err(ImageState::LoadError); } } }; - // In the case where a decode is ongoing (or waiting in a queue) but we have the - // full response available, we decode the bytes synchronously and ignore the - // async decode when it finishes later. + // In the case where a decode is ongoing (or waiting in a queue) but we + // have the full response available, we decode the bytes synchronously + // and ignore the async decode when it finishes later. // TODO: make this behaviour configurable according to the caller's needs. self.handle_decoder(decoded); - match self.get_completed_image_if_available(&url, placeholder) { + match self.get_completed_image_if_available(&url, use_placeholder) { Some(result) => result, None => Err(ImageState::LoadError), } } -} - -/// Create a new image cache. -pub fn new_image_cache_thread(webrender_api: webrender_traits::RenderApi) -> ImageCacheThread { - let (ipc_command_sender, ipc_command_receiver) = ipc::channel().unwrap(); - thread::Builder::new().name("ImageCacheThread".to_owned()).spawn(move || { - ImageCache::run(webrender_api, ipc_command_receiver) - }).expect("Thread spawning failed"); + /// Add a new listener for the given pending image id. If the image is already present, + /// the responder will still receive the expected response. + pub fn add_listener(&self, id: PendingImageId, listener: ImageResponder) { + if let Some(load) = self.pending_loads.lock().unwrap().get_by_key_mut(&id) { + if let Some(ref metadata) = load.metadata { + listener.respond(ImageResponse::MetadataLoaded(metadata.clone())); + } + load.add_listener(listener); + return; + } + if let Some(load) = self.completed_loads.lock().unwrap().values().find(|l| l.id == id) { + listener.respond(load.image_response.clone()); + return; + } + warn!("Couldn't find cached entry for listener {:?}", id); + } - ImageCacheThread::new(ipc_command_sender) -} + /// Inform the image cache about a response for a pending request. + pub fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg) { + match (action, id) { + (FetchResponseMsg::ProcessRequestBody, _) | + (FetchResponseMsg::ProcessRequestEOF, _) => return, + (FetchResponseMsg::ProcessResponse(_response), _) => {} + (FetchResponseMsg::ProcessResponseChunk(data), _) => { + debug!("Got some data for {:?}", id); + let mut pending_loads = self.pending_loads.lock().unwrap(); + let pending_load = pending_loads.get_by_key_mut(&id).unwrap(); + pending_load.bytes.extend_from_slice(&data); + //jmr0 TODO: possibly move to another task? + if let None = pending_load.metadata { + if let Ok(metadata) = load_from_buf(&pending_load.bytes.as_slice()) { + let dimensions = metadata.dimensions(); + let img_metadata = ImageMetadata { width: dimensions.width, + height: dimensions.height }; + for listener in &pending_load.listeners { + listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone())); + } + pending_load.metadata = Some(img_metadata); + } + } + } + (FetchResponseMsg::ProcessResponseEOF(result), key) => { + debug!("Received EOF for {:?}", key); + match result { + Ok(()) => { + let bytes = { + let mut pending_loads = self.pending_loads.lock().unwrap(); + let pending_load = pending_loads.get_by_key_mut(&id).unwrap(); + pending_load.result = Some(result); + debug!("Async decoding {} ({:?})", pending_load.url, key); + pending_load.bytes.mark_complete() + }; + + let (tx, rx) = channel(); + thread::Builder::new().name( + "Image decoding async operation".to_owned()).spawn(move || { + let msg = decode_bytes_sync(key, &*bytes); + tx.send(msg).unwrap(); + }).expect("Thread spawning failed"); -fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg { - let image = load_from_memory(bytes); - DecoderMsg { - key: key, - image: image + if let Some(msg) = rx.recv().ok() { + debug!("Image decoded {:?}", key); + self.handle_decoder(msg); + }; + } + Err(_) => { + debug!("Processing error for {:?}", key); + match self.placeholder_image.clone() { + Some(placeholder_image) => { + self.complete_load(id, LoadResult::PlaceholderLoaded( + placeholder_image)) + } + None => self.complete_load(id, LoadResult::None), + } + } + } + } + } } } diff --git a/components/net_traits/image_cache_thread.rs b/components/net_traits/image_cache_thread.rs deleted file mode 100644 index 49bd1ea1c8f..00000000000 --- a/components/net_traits/image_cache_thread.rs +++ /dev/null @@ -1,169 +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 FetchResponseMsg; -use image::base::{Image, ImageMetadata}; -use ipc_channel::ipc::{self, IpcSender}; -use servo_url::ServoUrl; -use std::sync::Arc; - -/// 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. -#[derive(Clone, Deserialize, Serialize)] -pub struct ImageResponder { - id: PendingImageId, - sender: IpcSender<PendingImageResponse>, -} - -#[derive(Deserialize, Serialize)] -pub struct PendingImageResponse { - pub response: ImageResponse, - pub id: PendingImageId, -} - -impl ImageResponder { - pub fn new(sender: IpcSender<PendingImageResponse>, id: PendingImageId) -> ImageResponder { - ImageResponder { - sender: sender, - id: id, - } - } - - pub fn respond(&self, response: ImageResponse) { - // This send can fail if thread waiting for this notification has panicked. - // That's not a case that's worth warning about. - // TODO(#15501): are there cases in which we should perform cleanup? - let _ = self.sender.send(PendingImageResponse { - response: response, - id: self.id, - }); - } -} - -/// The unique id for an image that has previously been requested. -#[derive(Copy, Clone, PartialEq, Eq, Deserialize, Serialize, HeapSizeOf, Hash, Debug)] -pub struct PendingImageId(pub u64); - -/// The current state of an image in the cache. -#[derive(PartialEq, Copy, Clone, Deserialize, Serialize)] -pub enum ImageState { - Pending(PendingImageId), - LoadError, - NotRequested(PendingImageId), -} - -/// The returned image. -#[derive(Clone, Deserialize, Serialize, HeapSizeOf)] -pub enum ImageResponse { - /// The requested image was loaded. - Loaded(Arc<Image>), - /// The request image metadata was loaded. - MetadataLoaded(ImageMetadata), - /// The requested image failed to load, so a placeholder was loaded instead. - PlaceholderLoaded(Arc<Image>), - /// Neither the requested image nor the placeholder could be loaded. - None, -} - -/// Indicating either entire image or just metadata availability -#[derive(Clone, Deserialize, Serialize, HeapSizeOf)] -pub enum ImageOrMetadataAvailable { - ImageAvailable(Arc<Image>), - MetadataAvailable(ImageMetadata), -} - -/// Commands that the image cache understands. -#[derive(Deserialize, Serialize)] -pub enum ImageCacheCommand { - /// Synchronously check the state of an image in the cache. If the image is in a loading - /// state and but its metadata has been made available, it will be sent as a response. - GetImageOrMetadataIfAvailable(ServoUrl, - UsePlaceholder, - CanRequestImages, - IpcSender<Result<ImageOrMetadataAvailable, ImageState>>), - - /// Add a new listener for the given pending image. - AddListener(PendingImageId, ImageResponder), - - /// Instruct the cache to store this data as a newly-complete network request and continue - /// decoding the result into pixel data - StoreDecodeImage(PendingImageId, FetchResponseMsg), - - /// Clients must wait for a response before shutting down the ResourceThread - Exit(IpcSender<()>), -} - -#[derive(Copy, Clone, PartialEq, Hash, Eq, Deserialize, Serialize)] -pub enum UsePlaceholder { - No, - Yes, -} - -/// Whether a consumer is in a position to request images or not. This can occur when -/// animations are being processed by the layout thread while the script thread is executing -/// in parallel. -#[derive(Copy, Clone, PartialEq, Deserialize, Serialize)] -pub enum CanRequestImages { - No, - Yes, -} - -/// The client side of the image cache thread. This can be safely cloned -/// and passed to different threads. -#[derive(Clone, Deserialize, Serialize)] -pub struct ImageCacheThread { - chan: IpcSender<ImageCacheCommand>, -} - -/// The public API for the image cache thread. -impl ImageCacheThread { - /// Construct a new image cache - pub fn new(chan: IpcSender<ImageCacheCommand>) -> ImageCacheThread { - ImageCacheThread { - chan: chan, - } - } - - /// Get the current state of an image, returning its metadata if available. - /// See ImageCacheCommand::GetImageOrMetadataIfAvailable. - /// - /// FIXME: We shouldn't do IPC for data uris! - pub fn find_image_or_metadata(&self, - url: ServoUrl, - use_placeholder: UsePlaceholder, - can_request: CanRequestImages) - -> Result<ImageOrMetadataAvailable, ImageState> { - let (sender, receiver) = ipc::channel().unwrap(); - let msg = ImageCacheCommand::GetImageOrMetadataIfAvailable(url, - use_placeholder, - can_request, - sender); - let _ = self.chan.send(msg); - try!(receiver.recv().map_err(|_| ImageState::LoadError)) - } - - /// Add a new listener for the given pending image id. If the image is already present, - /// the responder will still receive the expected response. - pub fn add_listener(&self, id: PendingImageId, responder: ImageResponder) { - let msg = ImageCacheCommand::AddListener(id, responder); - self.chan.send(msg).expect("Image cache thread is not available"); - } - - /// Inform the image cache about a response for a pending request. - pub fn notify_pending_response(&self, id: PendingImageId, data: FetchResponseMsg) { - let msg = ImageCacheCommand::StoreDecodeImage(id, data); - self.chan.send(msg).expect("Image cache thread is not available"); - } - - /// Shutdown the image cache thread. - pub fn exit(&self) { - // If the image cache is not available when we're trying to shut it down, - // that is not worth warning about. - let (response_chan, response_port) = ipc::channel().unwrap(); - let _ = self.chan.send(ImageCacheCommand::Exit(response_chan)); - let _ = response_port.recv(); - } -} diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index f11f2e2a28a..bc4c8938e76 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -15,6 +15,7 @@ extern crate heapsize_derive; extern crate hyper; extern crate hyper_serde; extern crate image as piston_image; +extern crate immeta; extern crate ipc_channel; #[macro_use] extern crate lazy_static; @@ -50,7 +51,7 @@ use storage_thread::StorageThreadMsg; pub mod blob_url_store; pub mod filemanager_thread; pub mod hosts; -pub mod image_cache_thread; +pub mod image_cache; pub mod net_error_list; pub mod pub_domains; pub mod request; diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index ee90766b1e2..25e738a0d1c 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -62,7 +62,7 @@ use msg::constellation_msg::{FrameId, FrameType, PipelineId}; use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache_thread::{ImageCacheThread, PendingImageId}; +use net_traits::image_cache::{ImageCache, PendingImageId}; use net_traits::request::{Request, RequestInit}; use net_traits::response::{Response, ResponseBody}; use net_traits::response::HttpsState; @@ -321,7 +321,7 @@ unsafe_no_jsmanaged_fields!(bool, f32, f64, String, AtomicBool, AtomicUsize, Uui unsafe_no_jsmanaged_fields!(usize, u8, u16, u32, u64); unsafe_no_jsmanaged_fields!(isize, i8, i16, i32, i64); unsafe_no_jsmanaged_fields!(ServoUrl, ImmutableOrigin, MutableOrigin); -unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheThread, PendingImageId); +unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCache, PendingImageId); unsafe_no_jsmanaged_fields!(Metadata); unsafe_no_jsmanaged_fields!(NetworkError); unsafe_no_jsmanaged_fields!(Atom, Prefix, LocalName, Namespace, QualName); diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 07338d8b731..432dbe800aa 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -40,7 +40,7 @@ use euclid::rect::Rect; use euclid::size::Size2D; use ipc_channel::ipc::{self, IpcSender}; use net_traits::image::base::PixelFormat; -use net_traits::image_cache_thread::ImageResponse; +use net_traits::image_cache::ImageResponse; use num_traits::ToPrimitive; use script_traits::ScriptMsg as ConstellationMsg; use servo_url::ServoUrl; diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index ed8aca71c1d..dad4ebcc207 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -338,12 +338,12 @@ impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes { pub mod utils { use dom::window::Window; - use net_traits::image_cache_thread::{ImageResponse, UsePlaceholder, ImageOrMetadataAvailable}; - use net_traits::image_cache_thread::CanRequestImages; + use net_traits::image_cache::{ImageResponse, UsePlaceholder, ImageOrMetadataAvailable}; + use net_traits::image_cache::CanRequestImages; use servo_url::ServoUrl; pub fn request_image_from_cache(window: &Window, url: ServoUrl) -> ImageResponse { - let image_cache = window.image_cache_thread(); + let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata(url.into(), UsePlaceholder::No, diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 610b0a3fae0..83691c174d2 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -40,9 +40,9 @@ use ipc_channel::ipc; use ipc_channel::router::ROUTER; use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg}; use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache_thread::{ImageResponder, ImageResponse, PendingImageId, ImageState}; -use net_traits::image_cache_thread::{UsePlaceholder, ImageOrMetadataAvailable, CanRequestImages}; -use net_traits::image_cache_thread::ImageCacheThread; +use net_traits::image_cache::{ImageResponder, ImageResponse, PendingImageId, ImageState}; +use net_traits::image_cache::{UsePlaceholder, ImageOrMetadataAvailable, CanRequestImages}; +use net_traits::image_cache::ImageCache; use net_traits::request::{RequestInit, Type as RequestType}; use network_listener::{NetworkListener, PreInvoke}; use num_traits::ToPrimitive; @@ -120,8 +120,8 @@ impl Runnable for ImageResponseHandlerRunnable { /// The context required for asynchronously loading an external image. struct ImageContext { - /// A handle with which to communicate with the image cache. - image_cache: ImageCacheThread, + /// Reference to the script thread image cache. + image_cache: Arc<ImageCache>, /// Indicates whether the request failed, and why status: Result<(), NetworkError>, /// The cache ID for this request. @@ -186,7 +186,7 @@ impl HTMLImageElement { Some(LoadBlocker::new(&*document, LoadType::Image(img_url.clone()))); } - fn add_cache_listener_for_element(image_cache: &ImageCacheThread, + fn add_cache_listener_for_element(image_cache: Arc<ImageCache>, id: PendingImageId, elem: &HTMLImageElement) { let trusted_node = Trusted::new(elem); @@ -197,8 +197,9 @@ impl HTMLImageElement { let wrapper = window.get_runnable_wrapper(); let generation = elem.generation.get(); ROUTER.add_route(responder_receiver.to_opaque(), box move |message| { - // Return the image via a message to the script thread, which marks the element - // as dirty and triggers a reflow. + debug!("Got image {:?}", message); + // Return the image via a message to the script thread, which marks + // the element as dirty and triggers a reflow. let runnable = ImageResponseHandlerRunnable::new( trusted_node.clone(), message.to().unwrap(), generation); let _ = task_source.queue_with_wrapper(box runnable, &wrapper); @@ -208,7 +209,7 @@ impl HTMLImageElement { } let window = window_from_node(self); - let image_cache = window.image_cache_thread(); + let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata(img_url.clone().into(), UsePlaceholder::Yes, @@ -223,7 +224,7 @@ impl HTMLImageElement { } Err(ImageState::Pending(id)) => { - add_cache_listener_for_element(image_cache, id, self); + add_cache_listener_for_element(image_cache.clone(), id, self); } Err(ImageState::LoadError) => { @@ -242,7 +243,7 @@ impl HTMLImageElement { let window = window_from_node(self); let context = Arc::new(Mutex::new(ImageContext { - image_cache: window.image_cache_thread().clone(), + image_cache: window.image_cache(), status: Ok(()), id: id, })); diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 5261b96d1c5..f13f7f02d9b 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -42,7 +42,7 @@ use js::jsapi::{JSContext, JSObject, Type, Rooted}; use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, UndefinedValue}; use js::typedarray::{TypedArray, TypedArrayElement, Float32, Int32}; use net_traits::image::base::PixelFormat; -use net_traits::image_cache_thread::ImageResponse; +use net_traits::image_cache::ImageResponse; use offscreen_gl_context::{GLContextAttributes, GLLimits}; use script_traits::ScriptMsg as ConstellationMsg; use std::cell::Cell; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 9f25edc3a07..a21d09c97ec 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -62,8 +62,8 @@ use js::rust::Runtime; use layout_image::fetch_image_for_layout; use msg::constellation_msg::{FrameType, PipelineId}; use net_traits::{ResourceThreads, ReferrerPolicy}; -use net_traits::image_cache_thread::{ImageResponder, ImageResponse}; -use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread, PendingImageId}; +use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; +use net_traits::image_cache::{PendingImageId, PendingImageResponse}; use net_traits::storage_thread::StorageType; use num_traits::ToPrimitive; use open; @@ -167,8 +167,8 @@ pub struct Window { #[ignore_heap_size_of = "task sources are hard"] file_reading_task_source: FileReadingTaskSource, navigator: MutNullableJS<Navigator>, - #[ignore_heap_size_of = "channels are hard"] - image_cache_thread: ImageCacheThread, + #[ignore_heap_size_of = "Arc"] + image_cache: Arc<ImageCache>, #[ignore_heap_size_of = "channels are hard"] image_cache_chan: Sender<ImageCacheMsg>, browsing_context: MutNullableJS<BrowsingContext>, @@ -315,8 +315,8 @@ impl Window { (box SendableMainThreadScriptChan(tx), box rx) } - pub fn image_cache_thread(&self) -> &ImageCacheThread { - &self.image_cache_thread + pub fn image_cache(&self) -> Arc<ImageCache> { + self.image_cache.clone() } /// This can panic if it is called after the browsing context has been discarded @@ -1227,7 +1227,7 @@ impl Window { let node = from_untrusted_node_address(js_runtime.rt(), image.node); if let PendingImageState::Unrequested(ref url) = image.state { - fetch_image_for_layout(url.clone(), &*node, id, self.image_cache_thread.clone()); + fetch_image_for_layout(url.clone(), &*node, id, self.image_cache.clone()); } let mut images = self.pending_layout_images.borrow_mut(); @@ -1239,7 +1239,7 @@ impl Window { ROUTER.add_route(responder_listener.to_opaque(), box move |message| { let _ = image_cache_chan.send((pipeline, message.to().unwrap())); }); - self.image_cache_thread.add_listener(id, ImageResponder::new(responder, id)); + self.image_cache.add_listener(id, ImageResponder::new(responder, id)); nodes.push(JS::from_ref(&*node)); } } @@ -1703,7 +1703,7 @@ impl Window { history_task_source: HistoryTraversalTaskSource, file_task_source: FileReadingTaskSource, image_cache_chan: Sender<ImageCacheMsg>, - image_cache_thread: ImageCacheThread, + image_cache: Arc<ImageCache>, resource_threads: ResourceThreads, bluetooth_thread: IpcSender<BluetoothRequest>, mem_profiler_chan: MemProfilerChan, @@ -1747,8 +1747,8 @@ impl Window { history_traversal_task_source: history_task_source, file_reading_task_source: file_task_source, image_cache_chan: image_cache_chan, + image_cache: image_cache.clone(), navigator: Default::default(), - image_cache_thread: image_cache_thread, history: Default::default(), browsing_context: Default::default(), document: Default::default(), diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs index d903b435c26..1d573c9b94d 100644 --- a/components/script/layout_image.rs +++ b/components/script/layout_image.rs @@ -12,7 +12,7 @@ use dom::node::{Node, document_from_node}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use net_traits::{FetchResponseMsg, FetchResponseListener, FetchMetadata, NetworkError}; -use net_traits::image_cache_thread::{ImageCacheThread, PendingImageId}; +use net_traits::image_cache::{ImageCache, PendingImageId}; use net_traits::request::{Type as RequestType, RequestInit as FetchRequestInit}; use network_listener::{NetworkListener, PreInvoke}; use servo_url::ServoUrl; @@ -20,7 +20,7 @@ use std::sync::{Arc, Mutex}; struct LayoutImageContext { id: PendingImageId, - cache: ImageCacheThread, + cache: Arc<ImageCache>, } impl FetchResponseListener for LayoutImageContext { @@ -49,7 +49,7 @@ impl PreInvoke for LayoutImageContext {} pub fn fetch_image_for_layout(url: ServoUrl, node: &Node, id: PendingImageId, - cache: ImageCacheThread) { + cache: Arc<ImageCache>) { let context = Arc::new(Mutex::new(LayoutImageContext { id: id, cache: cache, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index ac96836d92a..4399fb32630 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -73,7 +73,7 @@ use microtask::{MicrotaskQueue, Microtask}; use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; -use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread}; +use net_traits::image_cache::{ImageCache, PendingImageResponse}; use net_traits::request::{CredentialsMode, Destination, RequestInit}; use net_traits::storage_thread::StorageType; use network_listener::NetworkListener; @@ -233,7 +233,7 @@ enum MixedMessage { FromScript(MainThreadScriptMsg), FromDevtools(DevtoolScriptControlMsg), FromImageCache((PipelineId, PendingImageResponse)), - FromScheduler(TimerEvent) + FromScheduler(TimerEvent), } /// Messages used to control the script event loop @@ -408,8 +408,8 @@ pub struct ScriptThread { registration_map: DOMRefCell<HashMap<ServoUrl, JS<ServiceWorkerRegistration>>>, /// A job queue for Service Workers keyed by their scope url job_queue_map: Rc<JobQueue>, - /// A handle to the image cache thread. - image_cache_thread: ImageCacheThread, + /// Image cache for this script thread. + image_cache: Arc<ImageCache>, /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if /// there are many iframes. resource_threads: ResourceThreads, @@ -450,7 +450,6 @@ pub struct ScriptThread { /// The channel on which the image cache can send messages to ourself. image_cache_channel: Sender<ImageCacheMsg>, - /// For providing contact with the time profiler. time_profiler_chan: time::ProfilerChan, @@ -685,7 +684,7 @@ impl ScriptThread { registration_map: DOMRefCell::new(HashMap::new()), job_queue_map: Rc::new(JobQueue::new()), - image_cache_thread: state.image_cache_thread, + image_cache: state.image_cache.clone(), image_cache_channel: image_cache_channel, image_cache_port: image_cache_port, @@ -1267,7 +1266,7 @@ impl ScriptThread { pipeline_port: pipeline_port, constellation_chan: self.layout_to_constellation_chan.clone(), script_chan: self.control_chan.clone(), - image_cache_thread: self.image_cache_thread.clone(), + image_cache: self.image_cache.clone(), content_process_shutdown_chan: content_process_shutdown_chan, layout_threads: layout_threads, }); @@ -1756,7 +1755,7 @@ impl ScriptThread { HistoryTraversalTaskSource(history_sender.clone()), self.file_reading_task_source.clone(), self.image_cache_channel.clone(), - self.image_cache_thread.clone(), + self.image_cache.clone(), self.resource_threads.clone(), self.bluetooth_thread.clone(), self.mem_profiler_chan.clone(), diff --git a/components/script_layout_interface/lib.rs b/components/script_layout_interface/lib.rs index 1793e06909d..674b5325e81 100644 --- a/components/script_layout_interface/lib.rs +++ b/components/script_layout_interface/lib.rs @@ -43,7 +43,7 @@ use canvas_traits::CanvasMsg; use core::nonzero::NonZero; use ipc_channel::ipc::IpcSender; use libc::c_void; -use net_traits::image_cache_thread::PendingImageId; +use net_traits::image_cache::PendingImageId; use script_traits::UntrustedNodeAddress; use servo_url::ServoUrl; use std::sync::atomic::AtomicIsize; diff --git a/components/script_layout_interface/message.rs b/components/script_layout_interface/message.rs index feb2b533fd2..69dfd125e12 100644 --- a/components/script_layout_interface/message.rs +++ b/components/script_layout_interface/message.rs @@ -9,7 +9,7 @@ use euclid::rect::Rect; use gfx_traits::Epoch; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use msg::constellation_msg::PipelineId; -use net_traits::image_cache_thread::ImageCacheThread; +use net_traits::image_cache::ImageCache; use profile_traits::mem::ReportsChan; use rpc::LayoutRPC; use script_traits::{ConstellationControlMsg, LayoutControlMsg}; @@ -143,7 +143,7 @@ pub struct NewLayoutThreadInfo { pub pipeline_port: IpcReceiver<LayoutControlMsg>, pub constellation_chan: IpcSender<ConstellationMsg>, pub script_chan: IpcSender<ConstellationControlMsg>, - pub image_cache_thread: ImageCacheThread, + pub image_cache: Arc<ImageCache>, pub content_process_shutdown_chan: Option<IpcSender<()>>, pub layout_threads: usize, } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 96042083d39..63d3818d5b1 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -57,7 +57,7 @@ use msg::constellation_msg::{FrameId, FrameType, Key, KeyModifiers, KeyState}; use msg::constellation_msg::{PipelineId, PipelineNamespaceId, TraversalDirection}; use net_traits::{ReferrerPolicy, ResourceThreads}; use net_traits::image::base::Image; -use net_traits::image_cache_thread::ImageCacheThread; +use net_traits::image_cache::ImageCache; use net_traits::response::HttpsState; use net_traits::storage_thread::StorageType; use profile_traits::mem; @@ -67,6 +67,7 @@ use servo_url::ImmutableOrigin; use servo_url::ServoUrl; use std::collections::HashMap; use std::fmt; +use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; use style_traits::{CSSPixel, UnsafeNode}; use webdriver_msg::{LoadStatus, WebDriverScriptCommand}; @@ -483,8 +484,8 @@ pub struct InitialScriptState { pub resource_threads: ResourceThreads, /// A channel to the bluetooth thread. pub bluetooth_thread: IpcSender<BluetoothRequest>, - /// A channel to the image cache thread. - pub image_cache_thread: ImageCacheThread, + /// The image cache for this script thread. + pub image_cache: Arc<ImageCache>, /// A channel to the time profiler thread. pub time_profiler_chan: profile_traits::time::ProfilerChan, /// A channel to the memory profiler thread. diff --git a/components/servo/lib.rs b/components/servo/lib.rs index a493c62fdb6..e3afaada420 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -82,7 +82,6 @@ use gaol::sandbox::{ChildSandbox, ChildSandboxMethods}; use gfx::font_cache_thread::FontCacheThread; use ipc_channel::ipc::{self, IpcSender}; use log::{Log, LogMetadata, LogRecord}; -use net::image_cache_thread::new_image_cache_thread; use net::resource_thread::new_resource_threads; use net_traits::IpcSend; use profile::mem as profile_mem; @@ -290,7 +289,6 @@ fn create_constellation(user_agent: Cow<'static, str>, devtools_chan.clone(), time_profiler_chan.clone(), config_dir); - let image_cache_thread = new_image_cache_thread(webrender_api_sender.create_api()); let font_cache_thread = FontCacheThread::new(public_resource_threads.sender(), Some(webrender_api_sender.create_api())); @@ -301,7 +299,6 @@ fn create_constellation(user_agent: Cow<'static, str>, debugger_chan: debugger_chan, devtools_chan: devtools_chan, bluetooth_thread: bluetooth_thread, - image_cache_thread: image_cache_thread, font_cache_thread: font_cache_thread, public_resource_threads: public_resource_threads, private_resource_threads: private_resource_threads, |