diff --git a/Cargo.lock b/Cargo.lock index ad65382f563..a6378afc057 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2744,12 +2744,14 @@ dependencies = [ "cssparser", "embedder_traits", "euclid", + "fnv", "gfx", "gfx_traits", "ipc-channel", "libc", "msg", "net_traits", + "parking_lot", "range", "rayon", "rayon_croissant", @@ -2758,6 +2760,7 @@ dependencies = [ "serde", "servo_arc", "servo_geometry", + "servo_url", "style", "style_traits", "unicode-script", @@ -2834,6 +2837,7 @@ dependencies = [ "metrics", "msg", "net_traits", + "parking_lot", "profile_traits", "range", "script", diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index 4fd783a66bc..e28e482c13d 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -18,12 +18,14 @@ atomic_refcell = "0.1" cssparser = "0.27" embedder_traits = {path = "../embedder_traits"} euclid = "0.20" +fnv = "1.0" gfx = {path = "../gfx"} gfx_traits = {path = "../gfx_traits"} ipc-channel = "0.12" libc = "0.2" msg = {path = "../msg"} net_traits = {path = "../net_traits"} +parking_lot = "0.9" range = {path = "../range"} rayon = "1" rayon_croissant = "0.2.0" @@ -32,6 +34,7 @@ script_traits = {path = "../script_traits"} serde = "1.0" servo_arc = { path = "../servo_arc" } servo_geometry = {path = "../geometry"} +servo_url = {path = "../url"} style = {path = "../style", features = ["servo", "servo-layout-2020"]} style_traits = {path = "../style_traits"} unicode-script = {version = "0.3", features = ["harfbuzz"]} diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 2caa66df068..f642dab60e4 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -2,18 +2,51 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::display_list::WebRenderImageInfo; +use fnv::FnvHashMap; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context::FontContext; use msg::constellation_msg::PipelineId; +use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState}; +use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; +use parking_lot::RwLock; +use script_layout_interface::{PendingImage, PendingImageState}; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::cell::RefCell; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use style::context::SharedStyleContext; +use style::dom::OpaqueNode; pub struct LayoutContext<'a> { pub id: PipelineId, pub use_rayon: bool, + pub origin: ImmutableOrigin, + + /// Bits shared by the layout and style system. pub style_context: SharedStyleContext<'a>, + + /// Interface to the font cache thread. pub font_cache_thread: Mutex, + + /// Reference to the script thread image cache. + pub image_cache: Arc, + + /// 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: Option>>, + + pub webrender_image_cache: + Arc>>, +} + +impl<'a> Drop for LayoutContext<'a> { + fn drop(&mut self) { + if !std::thread::panicking() { + if let Some(ref pending_images) = self.pending_images { + assert!(pending_images.lock().unwrap().is_empty()); + } + } + } } impl<'a> LayoutContext<'a> { @@ -21,6 +54,100 @@ impl<'a> LayoutContext<'a> { pub fn shared_context(&self) -> &SharedStyleContext { &self.style_context } + + pub fn get_or_request_image_or_meta( + &self, + node: OpaqueNode, + url: ServoUrl, + use_placeholder: UsePlaceholder, + ) -> Option { + //XXXjdm For cases where we do not request an image, we still need to + // ensure the node gets another script-initiated reflow or it + // won't be requested at all. + let can_request = if self.pending_images.is_some() { + CanRequestImages::Yes + } else { + CanRequestImages::No + }; + + // See if the image is already available + let result = self.image_cache.find_image_or_metadata( + url.clone(), + self.origin.clone(), + None, + use_placeholder, + can_request, + ); + match result { + Ok(image_or_metadata) => Some(image_or_metadata), + // Image failed to load, so just return nothing + Err(ImageState::LoadError) => None, + // Not yet requested - request image or metadata from the cache + Err(ImageState::NotRequested(id)) => { + let image = PendingImage { + state: PendingImageState::Unrequested(url), + node: node.into(), + id: id, + }; + self.pending_images + .as_ref() + .unwrap() + .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(id)) => { + //XXXjdm if self.pending_images is not available, we should make sure that + // this node gets marked dirty again so it gets a script-initiated + // reflow that deals with this properly. + if let Some(ref pending_images) = self.pending_images { + let image = PendingImage { + state: PendingImageState::PendingResponse, + node: node.into(), + id: id, + }; + pending_images.lock().unwrap().push(image); + } + None + }, + } + } + + pub fn get_webrender_image_for_url( + &self, + node: OpaqueNode, + url: ServoUrl, + use_placeholder: UsePlaceholder, + ) -> Option { + if let Some(existing_webrender_image) = self + .webrender_image_cache + .read() + .get(&(url.clone(), use_placeholder)) + { + return Some((*existing_webrender_image).clone()); + } + + match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { + Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => { + let image_info = WebRenderImageInfo { + width: image.width, + height: image.height, + key: image.id, + }; + if image_info.key.is_none() { + Some(image_info) + } else { + let mut webrender_image_cache = self.webrender_image_cache.write(); + webrender_image_cache.insert((url, use_placeholder), image_info); + Some(image_info) + } + }, + None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None, + } + } } pub(crate) type LayoutFontContext = FontContext; diff --git a/components/layout_2020/display_list.rs b/components/layout_2020/display_list.rs index 31492b5ca47..7e7e9579f26 100644 --- a/components/layout_2020/display_list.rs +++ b/components/layout_2020/display_list.rs @@ -14,6 +14,13 @@ use style::values::computed::{BorderStyle, Length, LengthPercentage}; use style::values::specified::ui::CursorKind; use webrender_api::{self as wr, units}; +#[derive(Clone, Copy)] +pub struct WebRenderImageInfo { + pub width: u32, + pub height: u32, + pub key: Option, +} + // `webrender_api::display_item::ItemTag` is private type ItemTag = (u64, u16); type HitInfo = Option; diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index 525a20d8649..df93ff10e13 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -32,6 +32,7 @@ malloc_size_of = { path = "../malloc_size_of" } metrics = {path = "../metrics"} msg = {path = "../msg"} net_traits = {path = "../net_traits"} +parking_lot = { version = "0.9", features = ["nightly"] } profile_traits = {path = "../profile_traits"} range = {path = "../range"} script = {path = "../script"} diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 83f96b1c65f..b7b638ba1f4 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -35,7 +35,7 @@ use gfx_traits::{node_id_from_scroll_id, Epoch}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use layout::context::LayoutContext; -use layout::display_list::DisplayListBuilder; +use layout::display_list::{DisplayListBuilder, WebRenderImageInfo}; use layout::query::{ process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData, }; @@ -56,7 +56,8 @@ use msg::constellation_msg::{ }; use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId}; use msg::constellation_msg::{MonitoredComponentId, TopLevelBrowsingContextId}; -use net_traits::image_cache::ImageCache; +use net_traits::image_cache::{ImageCache, UsePlaceholder}; +use parking_lot::RwLock; use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan}; use profile_traits::time::{self as profile_time, profile, TimerMetadata}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; @@ -147,6 +148,9 @@ pub struct LayoutThread { /// The channel on which messages can be sent to the memory profiler. mem_profiler_chan: profile_mem::ProfilerChan, + /// Reference to the script thread image cache. + image_cache: Arc, + /// Public interface to the font cache thread. font_cache_thread: FontCacheThread, @@ -189,6 +193,8 @@ pub struct LayoutThread { /// All the other elements of this struct are read-only. rw_data: Arc>, + webrender_image_cache: Arc>>, + /// The executors for paint worklets. registered_painters: RegisteredPaintersImpl, @@ -232,7 +238,7 @@ impl LayoutThreadFactory for LayoutThread { background_hang_monitor_register: Box, constellation_chan: IpcSender, script_chan: IpcSender, - _image_cache: Arc, + image_cache: Arc, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, @@ -280,6 +286,7 @@ impl LayoutThreadFactory for LayoutThread { background_hang_monitor, constellation_chan, script_chan, + image_cache, font_cache_thread, time_profiler_chan, mem_profiler_chan.clone(), @@ -443,6 +450,7 @@ impl LayoutThread { background_hang_monitor: Box, constellation_chan: IpcSender, script_chan: IpcSender, + image_cache: Arc, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, @@ -489,6 +497,7 @@ impl LayoutThread { time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, registered_painters: RegisteredPaintersImpl(Default::default()), + image_cache, font_cache_thread: font_cache_thread, first_reflow: Cell::new(true), font_cache_receiver: font_cache_receiver, @@ -523,6 +532,7 @@ impl LayoutThread { element_inner_text_response: String::new(), inner_window_dimensions_response: None, })), + webrender_image_cache: Default::default(), timer: if pref!(layout.animations.test.enabled) { Timer::test_mode() } else { @@ -553,6 +563,7 @@ impl LayoutThread { fn build_layout_context<'a>( &'a self, guards: StylesheetGuards<'a>, + script_initiated_layout: bool, snapshot_map: &'a SnapshotMap, ) -> LayoutContext<'a> { let thread_local_style_context_creation_data = @@ -560,6 +571,7 @@ impl LayoutThread { LayoutContext { id: self.id, + origin: self.url.origin(), style_context: SharedStyleContext { stylist: &self.stylist, options: GLOBAL_STYLE_DATA.options.clone(), @@ -573,7 +585,14 @@ impl LayoutThread { traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, }, + 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 script_initiated_layout { + Some(Mutex::new(Vec::new())) + } else { + None + }, use_rayon: STYLE_THREAD_POOL.pool().is_some(), } } @@ -1074,7 +1093,7 @@ impl LayoutThread { self.stylist.flush(&guards, Some(element), Some(&map)); // Create a layout context for use throughout the following passes. - let mut layout_context = self.build_layout_context(guards.clone(), &map); + let mut layout_context = self.build_layout_context(guards.clone(), true, &map); let traversal = RecalcStyle::new(layout_context); let token = { @@ -1132,7 +1151,12 @@ impl LayoutThread { } self.first_reflow.set(false); - self.respond_to_query_if_necessary(&data.reflow_goal, &mut *rw_data, &mut layout_context); + self.respond_to_query_if_necessary( + &data.reflow_goal, + &mut *rw_data, + &mut layout_context, + data.result.borrow_mut().as_mut().unwrap(), + ); } fn respond_to_query_if_necessary( @@ -1140,7 +1164,13 @@ impl LayoutThread { reflow_goal: &ReflowGoal, rw_data: &mut LayoutThreadData, context: &mut LayoutContext, + reflow_result: &mut ReflowComplete, ) { + let pending_images = match &context.pending_images { + Some(pending) => std::mem::take(&mut *pending.lock().unwrap()), + None => Vec::new(), + }; + reflow_result.pending_images = pending_images; match *reflow_goal { ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg { &QueryMsg::ContentBoxQuery(node) => {