diff options
-rw-r--r-- | components/layout/construct.rs | 3 | ||||
-rw-r--r-- | components/layout/context.rs | 99 | ||||
-rw-r--r-- | components/layout/display_list_builder.rs | 3 | ||||
-rw-r--r-- | components/layout/fragment.rs | 9 | ||||
-rw-r--r-- | components/layout/query.rs | 11 | ||||
-rw-r--r-- | components/layout_thread/lib.rs | 70 | ||||
-rw-r--r-- | components/net/image_cache_thread.rs | 214 | ||||
-rw-r--r-- | components/net_traits/image_cache_thread.rs | 103 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 4 | ||||
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 4 | ||||
-rw-r--r-- | components/script/dom/htmlcanvaselement.rs | 11 | ||||
-rw-r--r-- | components/script/dom/htmlimageelement.rs | 188 | ||||
-rw-r--r-- | components/script/dom/window.rs | 145 | ||||
-rw-r--r-- | components/script/script_thread.rs | 26 | ||||
-rw-r--r-- | components/script_layout_interface/lib.rs | 18 | ||||
-rw-r--r-- | components/script_layout_interface/rpc.rs | 4 | ||||
-rw-r--r-- | components/script_plugins/unrooted_must_root.rs | 1 | ||||
-rw-r--r-- | components/servo/lib.rs | 3 |
18 files changed, 528 insertions, 388 deletions
diff --git a/components/layout/construct.rs b/components/layout/construct.rs index bbfe17d22b7..afdf903c500 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -351,11 +351,13 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> } Some(LayoutNodeType::Element(LayoutElementType::HTMLImageElement)) => { let image_info = box ImageFragmentInfo::new(node.image_url(), + node, &self.layout_context); SpecificFragmentInfo::Image(image_info) } Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => { let image_info = box ImageFragmentInfo::new(node.object_data(), + node, &self.layout_context); SpecificFragmentInfo::Image(image_info) } @@ -1219,6 +1221,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> let marker_fragments = match node.style(self.style_context()).get_list().list_style_image { Either::First(ref url_value) => { let image_info = box ImageFragmentInfo::new(url_value.url().map(|u| u.clone()), + node, &self.layout_context); vec![Fragment::new(node, SpecificFragmentInfo::Image(image_info), self.layout_context)] } diff --git a/components/layout/context.rs b/components/layout/context.rs index e67e7da6fb1..13029dd80bf 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -5,22 +5,22 @@ //! Data needed by the layout thread. use fnv::FnvHasher; -use gfx::display_list::WebRenderImageInfo; +use gfx::display_list::{WebRenderImageInfo, OpaqueNode}; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context::FontContext; use heapsize::HeapSizeOf; -use ipc_channel::ipc; -use net_traits::image::base::Image; -use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread, ImageResponse, ImageState}; +use net_traits::image_cache_thread::{ImageCacheThread, ImageState}; use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder}; +use opaque_node::OpaqueNodeMethods; use parking_lot::RwLock; -use servo_config::opts; +use script_layout_interface::{PendingImage, PendingImageState}; use servo_url::ServoUrl; use std::borrow::{Borrow, BorrowMut}; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::sync::{Arc, Mutex}; +use std::thread; use style::context::{SharedStyleContext, ThreadLocalStyleContext}; use style::dom::TElement; @@ -82,9 +82,6 @@ pub struct LayoutContext { /// The shared image cache thread. pub image_cache_thread: Mutex<ImageCacheThread>, - /// A channel for the image cache to send responses to. - pub image_cache_sender: Mutex<ImageCacheChan>, - /// Interface to the font cache thread. pub font_cache_thread: Mutex<FontCacheThread>, @@ -92,6 +89,18 @@ pub struct LayoutContext { pub webrender_image_cache: Arc<RwLock<HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>>>, + + /// A list of in-progress image loads to be shared with the script thread. + /// A None value means that this layout was not initiated by the script thread. + pub pending_images: Mutex<Vec<PendingImage>> +} + +impl Drop for LayoutContext { + fn drop(&mut self) { + if !thread::panicking() { + assert!(self.pending_images.lock().unwrap().is_empty()); + } + } } impl LayoutContext { @@ -100,49 +109,11 @@ impl LayoutContext { &self.style_context } - fn get_or_request_image_synchronously(&self, url: ServoUrl, use_placeholder: UsePlaceholder) - -> Option<Arc<Image>> { - debug_assert!(opts::get().output_file.is_some() || opts::get().exit_after_load); - - // See if the image is already available - let result = self.image_cache_thread.lock().unwrap() - .find_image(url.clone(), use_placeholder); - - match result { - Ok(image) => return Some(image), - Err(ImageState::LoadError) => { - // Image failed to load, so just return nothing - return None - } - Err(_) => {} - } - - // If we are emitting an output file, then we need to block on - // image load or we risk emitting an output file missing the image. - let (sync_tx, sync_rx) = ipc::channel().unwrap(); - self.image_cache_thread.lock().unwrap().request_image(url, ImageCacheChan(sync_tx), None); - loop { - match sync_rx.recv() { - Err(_) => return None, - Ok(response) => { - match response.image_response { - ImageResponse::Loaded(image) | ImageResponse::PlaceholderLoaded(image) => { - return Some(image) - } - ImageResponse::None | ImageResponse::MetadataLoaded(_) => {} - } - } - } - } - } - - pub fn get_or_request_image_or_meta(&self, url: ServoUrl, use_placeholder: UsePlaceholder) - -> Option<ImageOrMetadataAvailable> { - // If we are emitting an output file, load the image synchronously. - if opts::get().output_file.is_some() || opts::get().exit_after_load { - return self.get_or_request_image_synchronously(url, use_placeholder) - .map(|img| ImageOrMetadataAvailable::ImageAvailable(img)); - } + pub fn get_or_request_image_or_meta(&self, + node: OpaqueNode, + url: ServoUrl, + use_placeholder: UsePlaceholder) + -> Option<ImageOrMetadataAvailable> { // See if the image is already available let result = self.image_cache_thread.lock().unwrap() .find_image_or_metadata(url.clone(), @@ -151,20 +122,32 @@ impl LayoutContext { Ok(image_or_metadata) => Some(image_or_metadata), // Image failed to load, so just return nothing Err(ImageState::LoadError) => None, - // Not yet requested, async mode - request image or metadata from the cache - Err(ImageState::NotRequested) => { - let sender = self.image_cache_sender.lock().unwrap().clone(); - self.image_cache_thread.lock().unwrap() - .request_image_and_metadata(url, sender, None); + // Not yet requested - request image or metadata from the cache + Err(ImageState::NotRequested(id)) => { + let image = PendingImage { + state: PendingImageState::Unrequested(url), + node: node.to_untrusted_node_address(), + id: id, + }; + self.pending_images.lock().unwrap().push(image); None } // Image has been requested, is still pending. Return no image for this paint loop. // When the image loads it will trigger a reflow and/or repaint. - Err(ImageState::Pending) => None, + Err(ImageState::Pending(id)) => { + let image = PendingImage { + state: PendingImageState::PendingResponse, + node: node.to_untrusted_node_address(), + id: id, + }; + self.pending_images.lock().unwrap().push(image); + None + } } } pub fn get_webrender_image_for_url(&self, + node: OpaqueNode, url: ServoUrl, use_placeholder: UsePlaceholder) -> Option<WebRenderImageInfo> { @@ -174,7 +157,7 @@ impl LayoutContext { return Some((*existing_webrender_image).clone()) } - match self.get_or_request_image_or_meta(url.clone(), use_placeholder) { + match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { Some(ImageOrMetadataAvailable::ImageAvailable(image)) => { let image_info = WebRenderImageInfo::from_image(&*image); if image_info.key.is_none() { diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 848188d14a0..5eb4534a0c5 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -683,7 +683,8 @@ impl FragmentDisplayListBuilding for Fragment { index: usize) { let background = style.get_background(); let webrender_image = state.layout_context - .get_webrender_image_for_url(image_url.clone(), + .get_webrender_image_for_url(self.node, + image_url.clone(), UsePlaceholder::No); if let Some(webrender_image) = webrender_image { diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index ec1e87e6dd2..9cc0efbce6e 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -367,11 +367,14 @@ impl ImageFragmentInfo { /// /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little /// sense to me. - pub fn new(url: Option<ServoUrl>, - layout_context: &LayoutContext) + pub fn new<N: ThreadSafeLayoutNode>(url: Option<ServoUrl>, + node: &N, + layout_context: &LayoutContext) -> ImageFragmentInfo { let image_or_metadata = url.and_then(|url| { - layout_context.get_or_request_image_or_meta(url, UsePlaceholder::Yes) + layout_context.get_or_request_image_or_meta(node.opaque(), + url, + UsePlaceholder::Yes) }); let (image, metadata) = match image_or_metadata { diff --git a/components/layout/query.rs b/components/layout/query.rs index 5c78a1bc75d..2f26fede59f 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -17,6 +17,7 @@ use gfx_traits::ScrollRootId; use inline::LAST_FRAGMENT_OF_ELEMENT; use ipc_channel::ipc::IpcSender; use opaque_node::OpaqueNodeMethods; +use script_layout_interface::PendingImage; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse}; use script_layout_interface::rpc::{HitTestResponse, LayoutRPC}; use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse}; @@ -27,6 +28,7 @@ use script_traits::LayoutMsg as ConstellationMsg; use script_traits::UntrustedNodeAddress; use sequential; use std::cmp::{min, max}; +use std::mem; use std::ops::Deref; use std::sync::{Arc, Mutex}; use style::computed_values; @@ -89,6 +91,9 @@ pub struct LayoutThreadData { /// Index in a text fragment. We need this do determine the insertion point. pub text_index_response: TextIndexResponse, + + /// A list of images requests that need to be initiated. + pub pending_images: Vec<PendingImage>, } pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>); @@ -216,6 +221,12 @@ impl LayoutRPC for LayoutRPCImpl { let rw_data = rw_data.lock().unwrap(); rw_data.text_index_response.clone() } + + fn pending_images(&self) -> Vec<PendingImage> { + let &LayoutRPCImpl(ref rw_data) = self; + let mut rw_data = rw_data.lock().unwrap(); + mem::replace(&mut rw_data.pending_images, vec![]) + } } struct UnioningFragmentBorderBoxIterator { diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 8626385a026..af56555e399 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -75,7 +75,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::{ImageCacheChan, ImageCacheResult, ImageCacheThread}; +use net_traits::image_cache_thread::ImageCacheThread; use net_traits::image_cache_thread::UsePlaceholder; use parking_lot::RwLock; use profile_traits::mem::{self, Report, ReportKind, ReportsChan}; @@ -98,6 +98,7 @@ use servo_url::ServoUrl; use std::borrow::ToOwned; use std::collections::HashMap; use std::hash::BuildHasherDefault; +use std::mem as std_mem; use std::ops::{Deref, DerefMut}; use std::process; use std::sync::{Arc, Mutex, MutexGuard}; @@ -137,12 +138,6 @@ pub struct LayoutThread { /// The port on which we receive messages from the constellation. pipeline_port: Receiver<LayoutControlMsg>, - /// The port on which we receive messages from the image cache - image_cache_receiver: Receiver<ImageCacheResult>, - - /// The channel on which the image cache can send messages to ourself. - image_cache_sender: ImageCacheChan, - /// The port on which we receive messages from the font cache thread. font_cache_receiver: Receiver<()>, @@ -404,11 +399,6 @@ impl LayoutThread { // Proxy IPC messages from the pipeline to the layout thread. let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(pipeline_port); - // Ask the router to proxy IPC messages from the image cache thread to the layout thread. - let (ipc_image_cache_sender, ipc_image_cache_receiver) = ipc::channel().unwrap(); - let image_cache_receiver = - ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_image_cache_receiver); - // Ask the router to proxy IPC messages from the font cache thread to the layout thread. let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap(); let font_cache_receiver = @@ -437,8 +427,6 @@ impl LayoutThread { image_cache_thread: image_cache_thread, font_cache_thread: font_cache_thread, first_reflow: true, - image_cache_receiver: image_cache_receiver, - image_cache_sender: ImageCacheChan(ipc_image_cache_sender), font_cache_receiver: font_cache_receiver, font_cache_sender: ipc_font_cache_sender, parallel_traversal: parallel_traversal, @@ -470,6 +458,7 @@ impl LayoutThread { margin_style_response: MarginStyleResponse::empty(), stacking_context_scroll_offsets: HashMap::new(), text_index_response: TextIndexResponse(None), + pending_images: vec![], })), error_reporter: CSSErrorReporter { pipelineid: id, @@ -530,9 +519,9 @@ impl LayoutThread { default_computed_values: Arc::new(ComputedValues::initial_values().clone()), }, image_cache_thread: Mutex::new(self.image_cache_thread.clone()), - image_cache_sender: Mutex::new(self.image_cache_sender.clone()), font_cache_thread: Mutex::new(self.font_cache_thread.clone()), webrender_image_cache: self.webrender_image_cache.clone(), + pending_images: Mutex::new(vec![]), } } @@ -541,14 +530,12 @@ impl LayoutThread { enum Request { FromPipeline(LayoutControlMsg), FromScript(Msg), - FromImageCache, FromFontCache, } let request = { let port_from_script = &self.port; let port_from_pipeline = &self.pipeline_port; - let port_from_image_cache = &self.image_cache_receiver; let port_from_font_cache = &self.font_cache_receiver; select! { msg = port_from_pipeline.recv() => { @@ -557,10 +544,6 @@ impl LayoutThread { msg = port_from_script.recv() => { Request::FromScript(msg.unwrap()) }, - msg = port_from_image_cache.recv() => { - msg.unwrap(); - Request::FromImageCache - }, msg = port_from_font_cache.recv() => { msg.unwrap(); Request::FromFontCache @@ -590,9 +573,6 @@ impl LayoutThread { Request::FromScript(msg) => { self.handle_request_helper(msg, possibly_locked_rw_data) }, - Request::FromImageCache => { - self.repaint(possibly_locked_rw_data) - }, Request::FromFontCache => { let _rw_data = possibly_locked_rw_data.lock(); self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst); @@ -603,37 +583,6 @@ impl LayoutThread { } } - /// Repaint the scene, without performing style matching. This is typically - /// used when an image arrives asynchronously and triggers a relayout and - /// repaint. - /// TODO: In the future we could detect if the image size hasn't changed - /// since last time and avoid performing a complete layout pass. - fn repaint<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool { - let mut rw_data = possibly_locked_rw_data.lock(); - - if let Some(mut root_flow) = self.root_flow.clone() { - let flow = flow::mut_base(FlowRef::deref_mut(&mut root_flow)); - flow.restyle_damage.insert(REPAINT); - } - - let reflow_info = Reflow { - goal: ReflowGoal::ForDisplay, - page_clip_rect: max_rect(), - }; - let mut layout_context = self.build_layout_context(&*rw_data, - false, - reflow_info.goal); - - self.perform_post_style_recalc_layout_passes(&reflow_info, - None, - None, - &mut *rw_data, - &mut layout_context); - - - true - } - /// Receives and dispatches messages from other threads. fn handle_request_helper<'a, 'b>(&mut self, request: Msg, @@ -1247,6 +1196,9 @@ impl LayoutThread { query_type: &ReflowQueryType, rw_data: &mut LayoutThreadData, context: &mut LayoutContext) { + rw_data.pending_images = + std_mem::replace(&mut context.pending_images.lock().unwrap(), vec![]); + let mut root_flow = match self.root_flow.clone() { Some(root_flow) => root_flow, None => return, @@ -1387,6 +1339,14 @@ impl LayoutThread { None, &mut *rw_data, &mut layout_context); + + let mut pending_images = layout_context.pending_images.lock().unwrap(); + if pending_images.len() > 0 { + //XXXjdm we drop all the images on the floor, but there's no guarantee that + // the node references are valid since the script thread isn't paused. + // need to figure out what to do here! + pending_images.truncate(0); + } } fn reflow_with_newly_loaded_web_font<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) { diff --git a/components/net/image_cache_thread.rs b/components/net/image_cache_thread.rs index 51d8324bd48..17a852833c2 100644 --- a/components/net/image_cache_thread.rs +++ b/components/net/image_cache_thread.rs @@ -5,12 +5,11 @@ use immeta::load_from_buf; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; -use net_traits::{CoreResourceThread, NetworkError, fetch_async, FetchResponseMsg}; +use net_traits::{NetworkError, FetchResponseMsg}; use net_traits::image::base::{Image, ImageMetadata, PixelFormat, load_from_memory}; -use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheCommand, ImageCacheThread, ImageState}; -use net_traits::image_cache_thread::{ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, UsePlaceholder}; -use net_traits::image_cache_thread::ImageResponder; -use net_traits::request::{Destination, RequestInit, Type as RequestType}; +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}; use servo_config::resource_files::resources_dir_path; use servo_url::ServoUrl; use std::borrow::ToOwned; @@ -65,7 +64,7 @@ struct PendingLoad { // Once loading is complete, the result of the operation. result: Option<Result<(), NetworkError>>, - listeners: Vec<ImageListener>, + listeners: Vec<ImageResponder>, // The url being loaded. Do not forget that this may be several Mb // if we are loading a data: url. @@ -89,7 +88,7 @@ impl PendingLoad { } } - fn add_listener(&mut self, listener: ImageListener) { + fn add_listener(&mut self, listener: ImageResponder) { self.listeners.push(listener); } } @@ -131,20 +130,11 @@ impl AllPendingLoads { self.loads.is_empty() } - // get a PendingLoad from its LoadKey. Prefer this to `get_by_url`, - // for performance reasons. + // get a PendingLoad from its LoadKey. fn get_by_key_mut(&mut self, key: &LoadKey) -> Option<&mut PendingLoad> { self.loads.get_mut(key) } - // get a PendingLoad from its url. When possible, prefer `get_by_key_mut`. - fn get_by_url(&self, url: &ServoUrl) -> Option<&PendingLoad> { - self.url_to_load_key.get(url). - and_then(|load_key| - self.loads.get(load_key) - ) - } - fn remove(&mut self, key: &LoadKey) -> Option<PendingLoad> { self.loads.remove(key). and_then(|pending_load| { @@ -182,27 +172,20 @@ impl AllPendingLoads { /// fetched again. struct CompletedLoad { image_response: ImageResponse, + id: PendingImageId, } impl CompletedLoad { - fn new(image_response: ImageResponse) -> CompletedLoad { + fn new(image_response: ImageResponse, id: PendingImageId) -> CompletedLoad { CompletedLoad { image_response: image_response, + id: id, } } } -/// Stores information to notify a client when the state -/// of an image changes. -struct ImageListener { - sender: ImageCacheChan, - responder: Option<ImageResponder>, - send_metadata_msg: bool, -} - // A key used to communicate during loading. -#[derive(Eq, Hash, PartialEq, Clone, Copy)] -struct LoadKey(u64); +type LoadKey = PendingImageId; struct LoadKeyGenerator { counter: u64 @@ -214,34 +197,9 @@ impl LoadKeyGenerator { counter: 0 } } - fn next(&mut self) -> LoadKey { + fn next(&mut self) -> PendingImageId { self.counter += 1; - LoadKey(self.counter) - } -} - -impl ImageListener { - fn new(sender: ImageCacheChan, responder: Option<ImageResponder>, send_metadata_msg: bool) -> ImageListener { - ImageListener { - sender: sender, - responder: responder, - send_metadata_msg: send_metadata_msg, - } - } - - fn notify(&self, image_response: ImageResponse) { - if !self.send_metadata_msg { - if let ImageResponse::MetadataLoaded(_) = image_response { - return; - } - } - - let ImageCacheChan(ref sender) = self.sender; - let msg = ImageCacheResult { - responder: self.responder.clone(), - image_response: image_response, - }; - sender.send(msg).ok(); + PendingImageId(self.counter) } } @@ -259,9 +217,6 @@ struct ImageCache { // Worker threads for decoding images. thread_pool: ThreadPool, - // Resource thread handle - core_resource_thread: CoreResourceThread, - // Images that are loading over network, or decoding. pending_loads: AllPendingLoads, @@ -347,8 +302,7 @@ fn get_placeholder_image(webrender_api: &webrender_traits::RenderApi) -> io::Res } impl ImageCache { - fn run(core_resource_thread: CoreResourceThread, - webrender_api: webrender_traits::RenderApi, + 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(); @@ -364,7 +318,6 @@ impl ImageCache { thread_pool: ThreadPool::new(4), pending_loads: AllPendingLoads::new(), completed_loads: HashMap::new(), - core_resource_thread: core_resource_thread, placeholder_image: placeholder_image, webrender_api: webrender_api, }; @@ -406,22 +359,15 @@ impl ImageCache { ImageCacheCommand::Exit(sender) => { return Some(sender); } - ImageCacheCommand::RequestImage(url, result_chan, responder) => { - self.request_image(url, result_chan, responder, false); - } - ImageCacheCommand::RequestImageAndMetadata(url, result_chan, responder) => { - self.request_image(url, result_chan, responder, true); - } - ImageCacheCommand::GetImageIfAvailable(url, use_placeholder, consumer) => { - let result = self.get_image_if_available(url, use_placeholder); - let _ = consumer.send(result); + ImageCacheCommand::AddListener(id, responder) => { + self.add_listener(id, responder); } ImageCacheCommand::GetImageOrMetadataIfAvailable(url, use_placeholder, consumer) => { let result = self.get_image_or_meta_if_available(url, use_placeholder); let _ = consumer.send(result); } - ImageCacheCommand::StoreDecodeImage(url, image_vector) => { - self.store_decode_image(url, image_vector); + ImageCacheCommand::StoreDecodeImage(id, image_vector) => { + self.store_decode_image(id, image_vector); } }; @@ -445,7 +391,7 @@ impl ImageCache { height: dimensions.height }; pending_load.metadata = Some(img_metadata.clone()); for listener in &pending_load.listeners { - listener.notify(ImageResponse::MetadataLoaded(img_metadata.clone()).clone()); + listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone())); } } } @@ -518,85 +464,35 @@ impl ImageCache { LoadResult::None => ImageResponse::None, }; - let completed_load = CompletedLoad::new(image_response.clone()); + let completed_load = CompletedLoad::new(image_response.clone(), key); self.completed_loads.insert(pending_load.url.into(), completed_load); for listener in pending_load.listeners { - listener.notify(image_response.clone()); + listener.respond(image_response.clone()); } } - // Request an image from the cache. If the image hasn't been - // loaded/decoded yet, it will be loaded/decoded in the - // background. If send_metadata_msg is set, the channel will be notified - // that image metadata is available, possibly before the image has finished - // loading. - fn request_image(&mut self, - url: ServoUrl, - result_chan: ImageCacheChan, - responder: Option<ImageResponder>, - send_metadata_msg: bool) { - let image_listener = ImageListener::new(result_chan, responder, send_metadata_msg); - - // Check if already completed - match self.completed_loads.get(&url) { - Some(completed_load) => { - // It's already completed, return a notify straight away - image_listener.notify(completed_load.image_response.clone()); - } - None => { - // Check if the load is already pending - let (cache_result, load_key, mut pending_load) = self.pending_loads.get_cached(url.clone()); - pending_load.add_listener(image_listener); - match cache_result { - CacheResult::Miss => { - // A new load request! Request the load from - // the resource thread. - // https://html.spec.whatwg.org/multipage/#update-the-image-data - // step 12. - // - // TODO(emilio): ServoUrl in more places please! - let request = RequestInit { - url: url.clone(), - type_: RequestType::Image, - destination: Destination::Image, - origin: url.clone(), - .. RequestInit::default() - }; - - let progress_sender = self.progress_sender.clone(); - fetch_async(request, &self.core_resource_thread, move |action| { - let action = match action { - FetchResponseMsg::ProcessRequestBody | - FetchResponseMsg::ProcessRequestEOF => return, - a => a - }; - progress_sender.send(ResourceLoadInfo { - action: action, - key: load_key, - }).unwrap(); - }); - } - CacheResult::Hit => { - // Request is already on its way. - } - } + /// 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); } - fn get_image_if_available(&mut self, - url: ServoUrl, - placeholder: UsePlaceholder, ) - -> Result<Arc<Image>, ImageState> { - let img_or_metadata = self.get_image_or_meta_if_available(url, placeholder); - match img_or_metadata { - Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => Ok(image), - Ok(ImageOrMetadataAvailable::MetadataAvailable(_)) => Err(ImageState::Pending), - Err(err) => Err(err), - } - } - + /// Return a completed image if it exists, or None if there is no complete load + /// of the complete load is not fully decoded or is unavailable. fn get_image_or_meta_if_available(&mut self, url: ServoUrl, placeholder: UsePlaceholder) @@ -616,46 +512,42 @@ impl ImageCache { } } None => { - let pl = match self.pending_loads.get_by_url(&url) { - Some(pl) => pl, - None => return Err(ImageState::NotRequested), - }; - - let meta = match pl.metadata { - Some(ref meta) => meta, - None => return Err(ImageState::Pending), - }; - - Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone())) + let (result, key, pl) = self.pending_loads.get_cached(url); + match result { + CacheResult::Hit => match pl.metadata { + Some(ref meta) => + Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone())), + None => + Err(ImageState::Pending(key)), + }, + CacheResult::Miss => Err(ImageState::NotRequested(key)), + } } } } fn store_decode_image(&mut self, - ref_url: ServoUrl, + id: PendingImageId, loaded_bytes: Vec<u8>) { - let (cache_result, load_key, _) = self.pending_loads.get_cached(ref_url.clone()); - assert!(cache_result == CacheResult::Miss); let action = FetchResponseMsg::ProcessResponseChunk(loaded_bytes); let _ = self.progress_sender.send(ResourceLoadInfo { action: action, - key: load_key, + key: id, }); let action = FetchResponseMsg::ProcessResponseEOF(Ok(())); let _ = self.progress_sender.send(ResourceLoadInfo { action: action, - key: load_key, + key: id, }); } } /// Create a new image cache. -pub fn new_image_cache_thread(core_resource_thread: CoreResourceThread, - webrender_api: webrender_traits::RenderApi) -> ImageCacheThread { +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(core_resource_thread, webrender_api, ipc_command_receiver) + ImageCache::run(webrender_api, ipc_command_receiver) }).expect("Thread spawning failed"); ImageCacheThread::new(ipc_command_sender) diff --git a/components/net_traits/image_cache_thread.rs b/components/net_traits/image_cache_thread.rs index 4fb7aedee04..a000bd8b6bc 100644 --- a/components/net_traits/image_cache_thread.rs +++ b/components/net_traits/image_cache_thread.rs @@ -13,27 +13,45 @@ use std::sync::Arc; /// and/or repaint. #[derive(Clone, Deserialize, Serialize)] pub struct ImageResponder { - sender: IpcSender<ImageResponse>, + id: PendingImageId, + sender: IpcSender<PendingImageResponse>, +} + +#[derive(Deserialize, Serialize)] +pub struct PendingImageResponse { + pub response: ImageResponse, + pub id: PendingImageId, } impl ImageResponder { - pub fn new(sender: IpcSender<ImageResponse>) -> ImageResponder { + pub fn new(sender: IpcSender<PendingImageResponse>, id: PendingImageId) -> ImageResponder { ImageResponder { sender: sender, + id: id, } } pub fn respond(&self, response: ImageResponse) { - self.sender.send(response).unwrap() + // 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, + Pending(PendingImageId), LoadError, - NotRequested, + NotRequested(PendingImageId), } /// The returned image. @@ -56,45 +74,19 @@ pub enum ImageOrMetadataAvailable { MetadataAvailable(ImageMetadata), } -/// Channel used by the image cache to send results. -#[derive(Clone, Deserialize, Serialize)] -pub struct ImageCacheChan(pub IpcSender<ImageCacheResult>); - -/// The result of an image cache command that is returned to the -/// caller. -#[derive(Deserialize, Serialize)] -pub struct ImageCacheResult { - pub responder: Option<ImageResponder>, - pub image_response: ImageResponse, -} - /// Commands that the image cache understands. #[derive(Deserialize, Serialize)] 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(ServoUrl, ImageCacheChan, Option<ImageResponder>), - - /// Requests an image and a "metadata-ready" notification message asynchronously from the - /// cache. The cache will make an effort to send metadata before the image is completely - /// loaded. Supply a channel to receive the results, and optionally an image responder - /// that is passed to the result channel. - RequestImageAndMetadata(ServoUrl, ImageCacheChan, Option<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 thread. - GetImageIfAvailable(ServoUrl, UsePlaceholder, IpcSender<Result<Arc<Image>, ImageState>>), - /// 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, 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(ServoUrl, Vec<u8>), + StoreDecodeImage(PendingImageId, Vec<u8>), /// Clients must wait for a response before shutting down the ResourceThread Exit(IpcSender<()>), @@ -122,30 +114,6 @@ impl ImageCacheThread { } } - /// Asynchronously request an image. See ImageCacheCommand::RequestImage. - pub fn request_image(&self, url: ServoUrl, result_chan: ImageCacheChan, responder: Option<ImageResponder>) { - let msg = ImageCacheCommand::RequestImage(url, result_chan, responder); - let _ = self.chan.send(msg); - } - - /// Asynchronously request an image and metadata. - /// See ImageCacheCommand::RequestImageAndMetadata - pub fn request_image_and_metadata(&self, - url: ServoUrl, - result_chan: ImageCacheChan, - responder: Option<ImageResponder>) { - let msg = ImageCacheCommand::RequestImageAndMetadata(url, result_chan, responder); - let _ = self.chan.send(msg); - } - - /// Get the current state of an image. See ImageCacheCommand::GetImageIfAvailable. - pub fn find_image(&self, url: ServoUrl, use_placeholder: UsePlaceholder) -> Result<Arc<Image>, ImageState> { - let (sender, receiver) = ipc::channel().unwrap(); - let msg = ImageCacheCommand::GetImageIfAvailable(url, use_placeholder, sender); - let _ = self.chan.send(msg); - try!(receiver.recv().map_err(|_| ImageState::LoadError)) - } - /// Get the current state of an image, returning its metadata if available. /// See ImageCacheCommand::GetImageOrMetadataIfAvailable. /// @@ -160,14 +128,23 @@ impl ImageCacheThread { try!(receiver.recv().map_err(|_| ImageState::LoadError)) } - /// Decode the given image bytes and cache the result for the given URL. - pub fn store_complete_image_bytes(&self, url: ServoUrl, image_data: Vec<u8>) { - let msg = ImageCacheCommand::StoreDecodeImage(url, image_data); - let _ = self.chan.send(msg); + /// 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"); + } + + /// Decode the given image bytes and cache the result for the given pending ID. + pub fn store_complete_image_bytes(&self, id: PendingImageId, image_data: Vec<u8>) { + let msg = ImageCacheCommand::StoreDecodeImage(id, image_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/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 0e605780a20..4d38b39d333 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::{ImageCacheChan, ImageCacheThread}; +use net_traits::image_cache_thread::{ImageCacheThread, PendingImageId}; use net_traits::request::{Request, RequestInit}; use net_traits::response::{Response, ResponseBody}; use net_traits::response::HttpsState; @@ -320,7 +320,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, ImageCacheChan, ImageCacheThread); +unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheThread, 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 fff339e1cc7..065b966d939 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -429,7 +429,9 @@ impl CanvasRenderingContext2D { let img = match self.request_image_from_cache(url) { ImageResponse::Loaded(img) => img, - ImageResponse::PlaceholderLoaded(_) | ImageResponse::None | ImageResponse::MetadataLoaded(_) => { + ImageResponse::PlaceholderLoaded(_) | + ImageResponse::None | + ImageResponse::MetadataLoaded(_) => { return None; } }; diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index b174d2ec9a8..47dc1d46a3d 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -337,15 +337,18 @@ impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes { pub mod utils { use dom::window::Window; - use ipc_channel::ipc; - use net_traits::image_cache_thread::{ImageCacheChan, ImageResponse}; + use net_traits::image_cache_thread::ImageResponse; use servo_url::ServoUrl; pub fn request_image_from_cache(window: &Window, url: ServoUrl) -> ImageResponse { - let image_cache = window.image_cache_thread(); + panic!() + /*let image_cache = window.image_cache_thread(); let (response_chan, response_port) = ipc::channel().unwrap(); image_cache.request_image(url.into(), ImageCacheChan(response_chan), None); let result = response_port.recv().unwrap(); - result.image_response + match result { + ImageCacheResult::InitiateRequest(..) => panic!("unexpected image request initiator"), + ImageCacheResult::Response(result) => result.image_response, + }*/ } } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index ec9f2e1ec80..e762675bc70 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::{Au, AU_PER_PX}; +use document_loader::{LoadType, LoadBlocker}; use dom::activation::Activatable; use dom::attr::Attr; use dom::bindings::cell::DOMRefCell; @@ -16,6 +17,7 @@ use dom::bindings::error::Fallible; use dom::bindings::inheritance::Castable; use dom::bindings::js::{LayoutJS, Root}; use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::DomObject; use dom::bindings::str::DOMString; use dom::document::Document; use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; @@ -34,13 +36,17 @@ use euclid::point::Point2D; use html5ever_atoms::LocalName; use ipc_channel::ipc; use ipc_channel::router::ROUTER; +use net_traits::{FetchResponseListener, FetchMetadata, Metadata, NetworkError}; use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache_thread::{ImageResponder, ImageResponse}; +use net_traits::image_cache_thread::{ImageResponder, ImageResponse, PendingImageId, ImageState}; +use net_traits::image_cache_thread::{UsePlaceholder, ImageOrMetadataAvailable}; +use net_traits::request::{RequestInit, Type as RequestType}; +use network_listener::{NetworkListener, PreInvoke}; use num_traits::ToPrimitive; use script_thread::Runnable; use servo_url::ServoUrl; use std::i32; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use task_source::TaskSource; @@ -53,10 +59,12 @@ enum State { Broken, } #[derive(JSTraceable, HeapSizeOf)] +#[must_root] struct ImageRequest { state: State, parsed_url: Option<ServoUrl>, source_url: Option<DOMString>, + blocker: Option<LoadBlocker>, #[ignore_heap_size_of = "Arc"] image: Option<Arc<Image>>, metadata: Option<ImageMetadata>, @@ -74,6 +82,64 @@ impl HTMLImageElement { } } +struct ImageRequestRunnable { + element: Trusted<HTMLImageElement>, + img_url: ServoUrl, + id: PendingImageId, +} + +impl ImageRequestRunnable { + fn new(element: Trusted<HTMLImageElement>, + img_url: ServoUrl, + id: PendingImageId) + -> ImageRequestRunnable { + ImageRequestRunnable { + element: element, + img_url: img_url, + id: id, + } + } +} + +impl Runnable for ImageRequestRunnable { + fn handler(self: Box<Self>) { + let this = *self; + let trusted_node = this.element.clone(); + let element = this.element.root(); + + let document = document_from_node(&*element); + let window = window_from_node(&*element); + + let context = Arc::new(Mutex::new(ImageContext { + elem: trusted_node, + data: vec!(), + metadata: None, + url: this.img_url.clone(), + status: Ok(()), + id: this.id, + })); + + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let listener = NetworkListener { + context: context, + task_source: window.networking_task_source(), + wrapper: Some(window.get_runnable_wrapper()), + }; + ROUTER.add_route(action_receiver.to_opaque(), box move |message| { + listener.notify_fetch(message.to().unwrap()); + }); + + let request = RequestInit { + url: this.img_url.clone(), + origin: document.url().clone(), + type_: RequestType::Image, + pipeline_id: Some(document.global().pipeline_id()), + .. RequestInit::default() + }; + + document.fetch_async(LoadType::Image(this.img_url), request, action_sender); + } +} struct ImageResponseHandlerRunnable { element: Trusted<HTMLImageElement>, @@ -122,23 +188,83 @@ impl Runnable for ImageResponseHandlerRunnable { element.upcast::<EventTarget>().fire_event(atom!("error")); } + LoadBlocker::terminate(&mut element.current_request.borrow_mut().blocker); + // Trigger reflow let window = window_from_node(&*document); window.add_pending_reflow(); } } +/// The context required for asynchronously loading an external image. +struct ImageContext { + /// The element that initiated the request. + elem: Trusted<HTMLImageElement>, + /// The response body received to date. + data: Vec<u8>, + /// The response metadata received to date. + metadata: Option<Metadata>, + /// The initial URL requested. + url: ServoUrl, + /// Indicates whether the request failed, and why + status: Result<(), NetworkError>, + /// The cache ID for this request. + id: PendingImageId, +} + +impl FetchResponseListener for ImageContext { + fn process_request_body(&mut self) {} + fn process_request_eof(&mut self) {} + + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { + self.metadata = metadata.ok().map(|meta| match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + }); + + let status_code = self.metadata.as_ref().and_then(|m| { + match m.status { + Some((c, _)) => Some(c), + _ => None, + } + }).unwrap_or(0); + + self.status = match status_code { + 0 => Err(NetworkError::Internal("No http status code received".to_owned())), + 200...299 => Ok(()), // HTTP ok status codes + _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code))) + }; + } + + fn process_response_chunk(&mut self, mut payload: Vec<u8>) { + if self.status.is_ok() { + self.data.append(&mut payload); + } + } + + fn process_response_eof(&mut self, _response: Result<(), NetworkError>) { + let elem = self.elem.root(); + let document = document_from_node(&*elem); + let window = document.window(); + let image_cache = window.image_cache_thread(); + image_cache.store_complete_image_bytes(self.id, self.data.clone()); + document.finish_load(LoadType::Image(self.url.clone())); + } +} + +impl PreInvoke for ImageContext {} + impl HTMLImageElement { /// Makes the local `image` member match the status of the `src` attribute and starts /// prefetching the image. This method must be called after `src` is changed. fn update_image(&self, value: Option<(DOMString, ServoUrl)>) { let document = document_from_node(self); let window = document.window(); - let image_cache = window.image_cache_thread(); match value { None => { self.current_request.borrow_mut().parsed_url = None; self.current_request.borrow_mut().source_url = None; + LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker); self.current_request.borrow_mut().image = None; } Some((src, base_url)) => { @@ -147,22 +273,57 @@ impl HTMLImageElement { self.current_request.borrow_mut().parsed_url = Some(img_url.clone()); self.current_request.borrow_mut().source_url = Some(src); + LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker); + self.current_request.borrow_mut().blocker = + Some(LoadBlocker::new(&*document, LoadType::Image(img_url.clone()))); + let trusted_node = Trusted::new(self); let (responder_sender, responder_receiver) = ipc::channel().unwrap(); let task_source = window.networking_task_source(); let wrapper = window.get_runnable_wrapper(); + let img_url_cloned = img_url.clone(); + let trusted_node_clone = trusted_node.clone(); 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. - let image_response = message.to().unwrap(); - let runnable = box ImageResponseHandlerRunnable::new( - trusted_node.clone(), image_response); - let _ = task_source.queue_with_wrapper(runnable, &wrapper); + let runnable = ImageResponseHandlerRunnable::new( + trusted_node_clone.clone(), message.to().unwrap()); + let _ = task_source.queue_with_wrapper(box runnable, &wrapper); }); - image_cache.request_image_and_metadata(img_url.into(), - window.image_cache_chan(), - Some(ImageResponder::new(responder_sender))); + let image_cache = window.image_cache_thread(); + let response = + image_cache.find_image_or_metadata(img_url_cloned.into(), UsePlaceholder::Yes); + match response { + Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => { + let event = box ImageResponseHandlerRunnable::new( + trusted_node, ImageResponse::Loaded(image)); + event.handler(); + } + + Ok(ImageOrMetadataAvailable::MetadataAvailable(m)) => { + let event = box ImageResponseHandlerRunnable::new( + trusted_node, ImageResponse::MetadataLoaded(m)); + event.handler(); + } + + Err(ImageState::Pending(id)) => { + image_cache.add_listener(id, ImageResponder::new(responder_sender, id)); + } + + Err(ImageState::LoadError) => { + let event = box ImageResponseHandlerRunnable::new( + trusted_node, ImageResponse::None); + event.handler(); + } + + Err(ImageState::NotRequested(id)) => { + image_cache.add_listener(id, ImageResponder::new(responder_sender, id)); + let runnable = box ImageRequestRunnable::new( + Trusted::new(self), img_url, id); + runnable.handler(); + } + } } else { // https://html.spec.whatwg.org/multipage/#update-the-image-data // Step 11 (error substeps) @@ -202,6 +363,7 @@ impl HTMLImageElement { } } } + fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLImageElement { HTMLImageElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), @@ -210,14 +372,16 @@ impl HTMLImageElement { parsed_url: None, source_url: None, image: None, - metadata: None + metadata: None, + blocker: None, }), pending_request: DOMRefCell::new(ImageRequest { state: State::Unavailable, parsed_url: None, source_url: None, image: None, - metadata: None + metadata: None, + blocker: None, }), } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index b6392fbdd19..fcf6a0019c0 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -6,6 +6,7 @@ use app_units::Au; use bluetooth_traits::BluetoothRequest; use cssparser::Parser; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; +use document_loader::LoadType; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; @@ -42,7 +43,7 @@ use dom::location::Location; use dom::mediaquerylist::{MediaQueryList, WeakMediaQueryListVec}; use dom::messageevent::MessageEvent; use dom::navigator::Navigator; -use dom::node::{Node, from_untrusted_node_address, window_from_node}; +use dom::node::{Node, from_untrusted_node_address, window_from_node, document_from_node, NodeDamage}; use dom::performance::Performance; use dom::promise::Promise; use dom::screen::Screen; @@ -52,20 +53,25 @@ use euclid::{Point2D, Rect, Size2D}; use fetch; use gfx_traits::ScrollRootId; use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; use js::jsapi::{HandleObject, HandleValue, JSAutoCompartment, JSContext}; use js::jsapi::{JS_GC, JS_GetRuntime}; use js::jsval::UndefinedValue; use js::rust::Runtime; use msg::constellation_msg::{FrameType, PipelineId}; -use net_traits::{ResourceThreads, ReferrerPolicy}; -use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; +use net_traits::{ResourceThreads, ReferrerPolicy, FetchResponseListener, FetchMetadata}; +use net_traits::NetworkError; +use net_traits::image_cache_thread::{ImageResponder, ImageResponse}; +use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread, PendingImageId}; +use net_traits::request::{Type as RequestType, RequestInit as FetchRequestInit}; use net_traits::storage_thread::StorageType; +use network_listener::{NetworkListener, PreInvoke}; use num_traits::ToPrimitive; use open; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; use rustc_serialize::base64::{FromBase64, STANDARD, ToBase64}; -use script_layout_interface::TrustedNodeAddress; +use script_layout_interface::{TrustedNodeAddress, PendingImageState}; use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow}; use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; @@ -73,7 +79,7 @@ use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory}; use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper}; -use script_thread::SendableMainThreadScriptChan; +use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg}; use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress}; use script_traits::{DocumentState, TimerEvent, TimerEventId}; use script_traits::{ScriptMsg as ConstellationMsg, TimerEventRequest, WindowSizeData, WindowSizeType}; @@ -87,6 +93,7 @@ use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::cell::Cell; use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry; use std::default::Default; use std::io::{Write, stderr, stdout}; use std::mem; @@ -165,7 +172,7 @@ pub struct Window { #[ignore_heap_size_of = "channels are hard"] image_cache_thread: ImageCacheThread, #[ignore_heap_size_of = "channels are hard"] - image_cache_chan: ImageCacheChan, + image_cache_chan: Sender<ImageCacheMsg>, browsing_context: MutNullableJS<BrowsingContext>, document: MutNullableJS<Document>, history: MutNullableJS<History>, @@ -253,7 +260,13 @@ pub struct Window { webvr_thread: Option<IpcSender<WebVRMsg>>, /// A map for storing the previous permission state read results. - permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>> + permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>, + + /// All of the elements that have an outstanding image request that was + /// initiated by layout during a reflow. They are stored in the script thread + /// to ensure that the element can be marked dirty when the image data becomes + /// available at some point in the future. + pending_layout_images: DOMRefCell<HashMap<PendingImageId, Vec<JS<Node>>>>, } impl Window { @@ -295,10 +308,6 @@ impl Window { &self.script_chan.0 } - pub fn image_cache_chan(&self) -> ImageCacheChan { - self.image_cache_chan.clone() - } - pub fn parent_info(&self) -> Option<(PipelineId, FrameType)> { self.parent_info } @@ -346,6 +355,28 @@ impl Window { pub fn permission_state_invocation_results(&self) -> &DOMRefCell<HashMap<String, PermissionState>> { &self.permission_state_invocation_results } + + pub fn pending_image_notification(&self, response: PendingImageResponse) { + //XXXjdm could be more efficient to send the responses to the layout thread, + // rather than making the layout thread talk to the image cache to + // obtain the same data. + let mut images = self.pending_layout_images.borrow_mut(); + let nodes = images.entry(response.id); + let nodes = match nodes { + Entry::Occupied(nodes) => nodes, + Entry::Vacant(_) => return, + }; + for node in nodes.get() { + node.dirty(NodeDamage::OtherNodeDamage); + } + match response.response { + ImageResponse::MetadataLoaded(_) => {} + ImageResponse::Loaded(_) | + ImageResponse::PlaceholderLoaded(_) | + ImageResponse::None => { nodes.remove(); } + } + self.add_pending_reflow(); + } } #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] @@ -1168,6 +1199,31 @@ impl Window { self.emit_timeline_marker(marker.end()); } + let pending_images = self.layout_rpc.pending_images(); + for image in pending_images { + let id = image.id; + let js_runtime = self.js_runtime.borrow(); + let js_runtime = js_runtime.as_ref().unwrap(); + 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); + } + + let mut images = self.pending_layout_images.borrow_mut(); + let nodes = images.entry(id).or_insert(vec![]); + if nodes.iter().find(|n| &***n as *const _ == &*node as *const _).is_none() { + let (responder, responder_listener) = ipc::channel().unwrap(); + let pipeline = self.upcast::<GlobalScope>().pipeline_id(); + let image_cache_chan = self.image_cache_chan.clone(); + 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)); + nodes.push(JS::from_ref(&*node)); + } + } + true } @@ -1227,7 +1283,8 @@ impl Window { let ready_state = document.ReadyState(); - if ready_state == DocumentReadyState::Complete && !reftest_wait { + let pending_images = self.pending_layout_images.borrow().is_empty(); + if ready_state == DocumentReadyState::Complete && !reftest_wait && pending_images { let global_scope = self.upcast::<GlobalScope>(); let event = ConstellationMsg::SetDocumentState(global_scope.pipeline_id(), DocumentState::Idle); global_scope.constellation_chan().send(event).unwrap(); @@ -1635,7 +1692,7 @@ impl Window { network_task_source: NetworkingTaskSource, history_task_source: HistoryTraversalTaskSource, file_task_source: FileReadingTaskSource, - image_cache_chan: ImageCacheChan, + image_cache_chan: Sender<ImageCacheMsg>, image_cache_thread: ImageCacheThread, resource_threads: ResourceThreads, bluetooth_thread: IpcSender<BluetoothRequest>, @@ -1717,6 +1774,7 @@ impl Window { test_runner: Default::default(), webvr_thread: webvr_thread, permission_state_invocation_results: DOMRefCell::new(HashMap::new()), + pending_layout_images: DOMRefCell::new(HashMap::new()), }; unsafe { @@ -1836,3 +1894,64 @@ impl Runnable for PostMessageHandler { message.handle()); } } + +struct LayoutImageContext { + node: Trusted<Node>, + data: Vec<u8>, + id: PendingImageId, + url: ServoUrl, +} + +impl FetchResponseListener for LayoutImageContext { + fn process_request_body(&mut self) {} + fn process_request_eof(&mut self) {} + fn process_response(&mut self, _metadata: Result<FetchMetadata, NetworkError>) {/*XXXjdm*/} + + fn process_response_chunk(&mut self, mut payload: Vec<u8>) { + self.data.append(&mut payload); + } + + fn process_response_eof(&mut self, _response: Result<(), NetworkError>) { + let node = self.node.root(); + let document = document_from_node(&*node); + let window = document.window(); + let image_cache = window.image_cache_thread(); + image_cache.store_complete_image_bytes(self.id, self.data.clone()); + document.finish_load(LoadType::Image(self.url.clone())); + } +} + +impl PreInvoke for LayoutImageContext {} + +fn fetch_image_for_layout(url: ServoUrl, node: &Node, id: PendingImageId) { + let context = Arc::new(Mutex::new(LayoutImageContext { + node: Trusted::new(node), + data: vec![], + id: id, + url: url.clone(), + })); + + let document = document_from_node(node); + let window = window_from_node(node); + + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let listener = NetworkListener { + context: context, + task_source: window.networking_task_source(), + wrapper: Some(window.get_runnable_wrapper()), + }; + ROUTER.add_route(action_receiver.to_opaque(), box move |message| { + listener.notify_fetch(message.to().unwrap()); + }); + + let request = FetchRequestInit { + url: url.clone(), + origin: document.url().clone(), + type_: RequestType::Image, + pipeline_id: Some(document.global().pipeline_id()), + .. FetchRequestInit::default() + }; + + //XXXjdm should not block load event + document.fetch_async(LoadType::Image(url), request, action_sender); +} diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 2ab421c707b..192923294a6 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::{ImageCacheChan, ImageCacheResult, ImageCacheThread}; +use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread}; use net_traits::request::{CredentialsMode, Destination, RequestInit}; use net_traits::storage_thread::StorageType; use network_listener::NetworkListener; @@ -120,6 +120,8 @@ use url::Position; use webdriver_handlers; use webvr_traits::WebVRMsg; +pub type ImageCacheMsg = (PipelineId, PendingImageResponse); + thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None)); thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = Cell::new(None)); @@ -230,7 +232,7 @@ enum MixedMessage { FromConstellation(ConstellationControlMsg), FromScript(MainThreadScriptMsg), FromDevtools(DevtoolScriptControlMsg), - FromImageCache(ImageCacheResult), + FromImageCache((PipelineId, PendingImageResponse)), FromScheduler(TimerEvent) } @@ -444,10 +446,10 @@ pub struct ScriptThread { layout_to_constellation_chan: IpcSender<LayoutMsg>, /// The port on which we receive messages from the image cache - image_cache_port: Receiver<ImageCacheResult>, + image_cache_port: Receiver<ImageCacheMsg>, /// The channel on which the image cache can send messages to ourself. - image_cache_channel: ImageCacheChan, + image_cache_channel: Sender<ImageCacheMsg>, /// For providing contact with the time profiler. time_profiler_chan: time::ProfilerChan, @@ -646,11 +648,6 @@ impl ScriptThread { let (ipc_devtools_sender, ipc_devtools_receiver) = ipc::channel().unwrap(); let devtools_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_devtools_receiver); - // Ask the router to proxy IPC messages from the image cache thread to us. - let (ipc_image_cache_channel, ipc_image_cache_port) = ipc::channel().unwrap(); - let image_cache_port = - ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_image_cache_port); - let (timer_event_chan, timer_event_port) = channel(); // Ask the router to proxy IPC messages from the control port to us. @@ -658,6 +655,8 @@ impl ScriptThread { let boxed_script_sender = MainThreadScriptChan(chan.clone()).clone(); + let (image_cache_channel, image_cache_port) = channel(); + ScriptThread { documents: DOMRefCell::new(Documents::new()), browsing_contexts: DOMRefCell::new(HashMap::new()), @@ -666,7 +665,7 @@ impl ScriptThread { job_queue_map: Rc::new(JobQueue::new()), image_cache_thread: state.image_cache_thread, - image_cache_channel: ImageCacheChan(ipc_image_cache_channel), + image_cache_channel: image_cache_channel, image_cache_port: image_cache_port, resource_threads: state.resource_threads, @@ -1111,8 +1110,11 @@ impl ScriptThread { } } - fn handle_msg_from_image_cache(&self, msg: ImageCacheResult) { - msg.responder.unwrap().respond(msg.image_response); + fn handle_msg_from_image_cache(&self, (id, response): (PipelineId, PendingImageResponse)) { + let window = self.documents.borrow().find_window(id); + if let Some(ref window) = window { + window.pending_image_notification(response); + } } fn handle_webdriver_msg(&self, pipeline_id: PipelineId, msg: WebDriverScriptCommand) { diff --git a/components/script_layout_interface/lib.rs b/components/script_layout_interface/lib.rs index 87b76763d7d..1793e06909d 100644 --- a/components/script_layout_interface/lib.rs +++ b/components/script_layout_interface/lib.rs @@ -43,6 +43,9 @@ 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 script_traits::UntrustedNodeAddress; +use servo_url::ServoUrl; use std::sync::atomic::AtomicIsize; use style::data::ElementData; @@ -137,3 +140,18 @@ pub fn is_image_data(uri: &str) -> bool { static TYPES: &'static [&'static str] = &["data:image/png", "data:image/gif", "data:image/jpeg"]; TYPES.iter().any(|&type_| uri.starts_with(type_)) } + +/// Whether the pending image needs to be fetched or is waiting on an existing fetch. +pub enum PendingImageState { + Unrequested(ServoUrl), + PendingResponse, +} + +/// The data associated with an image that is not yet present in the image cache. +/// Used by the script thread to hold on to DOM elements that need to be repainted +/// when an image fetch is complete. +pub struct PendingImage { + pub state: PendingImageState, + pub node: UntrustedNodeAddress, + pub id: PendingImageId, +} diff --git a/components/script_layout_interface/rpc.rs b/components/script_layout_interface/rpc.rs index 2fb75f6b959..78e99571ee7 100644 --- a/components/script_layout_interface/rpc.rs +++ b/components/script_layout_interface/rpc.rs @@ -2,6 +2,7 @@ * 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 PendingImage; use app_units::Au; use euclid::point::Point2D; use euclid::rect::Rect; @@ -37,7 +38,8 @@ pub trait LayoutRPC { fn offset_parent(&self) -> OffsetParentResponse; /// Query layout for the resolve values of the margin properties for an element. fn margin_style(&self) -> MarginStyleResponse; - + /// Requests the list of not-yet-loaded images that were encountered in the last reflow. + fn pending_images(&self) -> Vec<PendingImage>; fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>; fn text_index(&self) -> TextIndexResponse; diff --git a/components/script_plugins/unrooted_must_root.rs b/components/script_plugins/unrooted_must_root.rs index 660a0c58781..ad74f6c4b8f 100644 --- a/components/script_plugins/unrooted_must_root.rs +++ b/components/script_plugins/unrooted_must_root.rs @@ -52,6 +52,7 @@ fn is_unrooted_ty(cx: &LateContext, ty: &ty::TyS, in_new_function: bool) -> bool } else if match_def_path(cx, did.did, &["core", "cell", "Ref"]) || match_def_path(cx, did.did, &["core", "cell", "RefMut"]) || match_def_path(cx, did.did, &["core", "slice", "Iter"]) + || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "Entry"]) || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "OccupiedEntry"]) || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"]) { // Structures which are semantically similar to an &ptr. diff --git a/components/servo/lib.rs b/components/servo/lib.rs index afda35f1582..69b9bbeceb8 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -281,8 +281,7 @@ 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(public_resource_threads.sender(), - webrender_api_sender.create_api()); + 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())); |