Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Initiate image loads in the script thread #14781

Closed
wants to merge 8 commits into from
Next

Make script thread initiate requests for images needed by layout.

In support of this goal, the layout thread collects information about
CSS images that are missing image data and hands it off to the script
thread after layout completes. The script thread stores a list of
nodes that will need to be reflowed after the associated network
request is complete. The script thread ensures that the nodes are
not GCed while a request is ongoing, which the layout thread is
incapable of guaranteeing.

The image cache's API has also been redesigned in support of this
work. No network requests are made by the new image cache, since it
does not possess the document-specific information necessary to
initiate them. Instead, there is now a single, synchronous
query operation that optionally reserves a slot when a cache
entry for a URL cannot be found. This reserved slot is then
the responsibility of the queryer to populate with the contents
of the network response for the URL once it is complete. Any
subsequent queries for the same URL will be informed that the
response is pending until that occurs.

The changes to layout also remove the synchronous image loading
code path, which means that reftests now test the same code
that non-test binaries execute. The decision to take a screenshot
now considers whether there are any outstanding image
requests for layout in order to avoid intermittent failures in
reftests that use CSS images.
  • Loading branch information
jdm committed Jan 11, 2017
commit 24b227060debb9d4e176fea0a5783dbb80a253bb
@@ -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.shared);
SpecificFragmentInfo::Image(image_info)
}
Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => {
let image_info = box ImageFragmentInfo::new(node.object_data(),
node,
&self.layout_context.shared);
SpecificFragmentInfo::Image(image_info)
}
@@ -1211,6 +1213,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.shared);
vec![Fragment::new(node, SpecificFragmentInfo::Image(image_info), self.layout_context)]
}
@@ -5,16 +5,15 @@
//! 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};
@@ -103,16 +102,23 @@ pub struct SharedLayoutContext {
/// 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>,

/// A cache of WebRender image info.
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 SharedLayoutContext {
fn drop(&mut self) {
assert!(self.pending_images.lock().unwrap().is_empty());
}
}

pub struct LayoutContext<'a> {
@@ -151,49 +157,11 @@ impl<'a> LayoutContext<'a> {
}

impl SharedLayoutContext {
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)
pub fn get_or_request_image_or_meta(&self,
node: OpaqueNode,
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));
}
// See if the image is already available
let result = self.image_cache_thread.lock().unwrap()
.find_image_or_metadata(url.clone(),
@@ -203,19 +171,31 @@ impl SharedLayoutContext {
// 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);
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> {
@@ -225,7 +205,7 @@ impl SharedLayoutContext {
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() {
@@ -701,7 +701,8 @@ impl FragmentDisplayListBuilding for Fragment {
index: usize) {
let background = style.get_background();
let webrender_image = state.shared_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 {
@@ -359,11 +359,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>,
shared_layout_context: &SharedLayoutContext)
pub fn new<N: ThreadSafeLayoutNode>(url: Option<ServoUrl>,
node: &N,
shared_layout_context: &SharedLayoutContext)
-> ImageFragmentInfo {
let image_or_metadata = url.and_then(|url| {
shared_layout_context.get_or_request_image_or_meta(url, UsePlaceholder::Yes)
shared_layout_context.get_or_request_image_or_meta(node.opaque(),
url,
UsePlaceholder::Yes)
});

let (image, metadata) = match image_or_metadata {
@@ -16,6 +16,7 @@ use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, ScrollOffs
use gfx_traits::ScrollRootId;
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};
@@ -26,6 +27,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;
@@ -85,6 +87,9 @@ pub struct LayoutThreadData {

/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
pub stacking_context_scroll_offsets: ScrollOffsetMap,

/// A list of images requests that need to be initiated.
pub pending_images: Vec<PendingImage>,
}

pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
@@ -206,6 +211,12 @@ impl LayoutRPC for LayoutRPCImpl {
let rw_data = rw_data.lock().unwrap();
rw_data.margin_style_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 {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.