servo: Merge #14962 - Remove network requests from image cache thread (from jdm:image_script_load); r=Ms2ger,glennw,emilio

The design of initiating network requests from the image cache thread was simple, but it makes it difficult to implement image loading that conforms to the HTML specification. These changes make the implementation of HTMLImageElement responsible for network requests for `<img>` elements, and CSS-based images (background-image, bullets, etc.) are requested by the script thread to ensure that the layout thread does not attempt to retain unsafe pointers to DOM nodes during asynchronous operations.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #7708
- [X] There are tests for these changes

Source-Repo: https://github.com/servo/servo
Source-Revision: 854d720b21dda68034233a25385c4f2564a4a2d5

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 845215025ea28e20cf0372075841172a4ed5206c
This commit is contained in:
Josh Matthews 2017-02-22 17:50:48 -08:00
Родитель 39855cf6b2
Коммит 814d03f8f0
22 изменённых файлов: 748 добавлений и 482 удалений

Просмотреть файл

@ -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)]
}

Просмотреть файл

@ -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, CanRequestImages};
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,20 @@ 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: Option<Mutex<Vec<PendingImage>>>
}
impl Drop for LayoutContext {
fn drop(&mut self) {
if !thread::panicking() {
if let Some(ref pending_images) = self.pending_images {
assert!(pending_images.lock().unwrap().is_empty());
}
}
}
}
impl LayoutContext {
@ -100,71 +111,60 @@ 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)
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));
}
//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_thread.lock().unwrap()
.find_image_or_metadata(url.clone(),
use_placeholder);
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, 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.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) => None,
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.to_untrusted_node_address(),
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<WebRenderImageInfo> {
@ -174,7 +174,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() {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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>,
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 {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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,
@ -506,7 +495,8 @@ impl LayoutThread {
fn build_layout_context(&self,
rw_data: &LayoutThreadData,
screen_size_changed: bool,
goal: ReflowGoal)
goal: ReflowGoal,
request_images: bool)
-> LayoutContext {
let thread_local_style_context_creation_data =
ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone());
@ -530,9 +520,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: if request_images { Some(Mutex::new(vec![])) } else { None },
}
}
@ -541,14 +531,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 +545,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 +574,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 +584,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,
@ -1166,7 +1116,8 @@ impl LayoutThread {
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(&*rw_data,
viewport_size_changed,
data.reflow_info.goal);
data.reflow_info.goal,
true);
// NB: Type inference falls apart here for some reason, so we need to be very verbose. :-(
let traversal_driver = if self.parallel_flag && self.parallel_traversal.is_some() {
@ -1247,6 +1198,12 @@ impl LayoutThread {
query_type: &ReflowQueryType,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext) {
let pending_images = match context.pending_images {
Some(ref pending) => std_mem::replace(&mut *pending.lock().unwrap(), vec![]),
None => vec![],
};
rw_data.pending_images = pending_images;
let mut root_flow = match self.root_flow.clone() {
Some(root_flow) => root_flow,
None => return,
@ -1367,7 +1324,8 @@ impl LayoutThread {
let mut layout_context = self.build_layout_context(&*rw_data,
false,
reflow_info.goal);
reflow_info.goal,
false);
if let Some(mut root_flow) = self.root_flow.clone() {
// Perform an abbreviated style recalc that operates without access to the DOM.
@ -1387,6 +1345,8 @@ impl LayoutThread {
None,
&mut *rw_data,
&mut layout_context);
assert!(layout_context.pending_images.is_none());
}
fn reflow_with_newly_loaded_web_font<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) {
@ -1400,7 +1360,8 @@ impl LayoutThread {
let mut layout_context = self.build_layout_context(&*rw_data,
false,
reflow_info.goal);
reflow_info.goal,
false);
// No need to do a style recalc here.
if self.root_flow.is_none() {

Просмотреть файл

@ -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, CanRequestImages};
use servo_config::resource_files::resources_dir_path;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
@ -58,20 +57,54 @@ fn is_image_opaque(format: webrender_traits::ImageFormat, bytes: &[u8]) -> bool
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: Vec<u8>,
bytes: ImageBytes,
// Image metadata, if available.
metadata: Option<ImageMetadata>,
// 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.
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 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
}
fn as_slice(&self) -> &[u8] {
match *self {
ImageBytes::InProgress(ref bytes) => &bytes,
ImageBytes::Complete(ref bytes) => &*bytes,
}
}
}
enum LoadResult {
Loaded(Image),
PlaceholderLoaded(Arc<Image>),
@ -81,7 +114,7 @@ enum LoadResult {
impl PendingLoad {
fn new(url: ServoUrl) -> PendingLoad {
PendingLoad {
bytes: vec!(),
bytes: ImageBytes::InProgress(vec!()),
metadata: None,
result: None,
listeners: vec!(),
@ -89,7 +122,7 @@ impl PendingLoad {
}
}
fn add_listener(&mut self, listener: ImageListener) {
fn add_listener(&mut self, listener: ImageResponder) {
self.listeners.push(listener);
}
}
@ -109,11 +142,12 @@ struct AllPendingLoads {
keygen: LoadKeyGenerator,
}
// Result of accessing a cache.
#[derive(Eq, PartialEq)]
enum CacheResult {
Hit, // The value was in the cache.
Miss, // The value was not in the cache and needed to be regenerated.
/// 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 {
@ -131,20 +165,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| {
@ -153,13 +178,18 @@ impl AllPendingLoads {
})
}
fn get_cached(&mut self, url: ServoUrl) -> (CacheResult, LoadKey, &mut PendingLoad) {
fn get_cached<'a>(&'a mut self, url: ServoUrl, can_request: CanRequestImages)
-> CacheResult<'a> {
match self.url_to_load_key.entry(url.clone()) {
Occupied(url_entry) => {
let load_key = url_entry.get();
(CacheResult::Hit, *load_key, self.loads.get_mut(load_key).unwrap())
CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap())
}
Vacant(url_entry) => {
if can_request == CanRequestImages::No {
return CacheResult::Miss(None);
}
let load_key = self.keygen.next();
url_entry.insert(load_key);
@ -168,7 +198,7 @@ impl AllPendingLoads {
Occupied(_) => unreachable!(),
Vacant(load_entry) => {
let mut_load = load_entry.insert(pending_load);
(CacheResult::Miss, load_key, mut_load)
CacheResult::Miss(Some((load_key, mut_load)))
}
}
}
@ -182,27 +212,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 +237,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)
}
}
@ -252,16 +250,11 @@ struct ResourceLoadInfo {
/// Implementation of the image cache
struct ImageCache {
progress_sender: Sender<ResourceLoadInfo>,
decoder_sender: Sender<DecoderMsg>,
// 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,
@ -284,7 +277,6 @@ struct DecoderMsg {
struct Receivers {
cmd_receiver: Receiver<ImageCacheCommand>,
decoder_receiver: Receiver<DecoderMsg>,
progress_receiver: Receiver<ResourceLoadInfo>,
}
impl Receivers {
@ -292,16 +284,12 @@ impl Receivers {
fn recv(&self) -> SelectResult {
let cmd_receiver = &self.cmd_receiver;
let decoder_receiver = &self.decoder_receiver;
let progress_receiver = &self.progress_receiver;
select! {
msg = cmd_receiver.recv() => {
SelectResult::Command(msg.unwrap())
},
msg = decoder_receiver.recv() => {
SelectResult::Decoder(msg.unwrap())
},
msg = progress_receiver.recv() => {
SelectResult::Progress(msg.unwrap())
}
}
}
@ -310,7 +298,6 @@ impl Receivers {
/// The types of messages that the main image cache thread receives.
enum SelectResult {
Command(ImageCacheCommand),
Progress(ResourceLoadInfo),
Decoder(DecoderMsg),
}
@ -347,8 +334,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();
@ -356,15 +342,12 @@ impl ImageCache {
// 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 (progress_sender, progress_receiver) = channel();
let (decoder_sender, decoder_receiver) = channel();
let mut cache = ImageCache {
progress_sender: progress_sender,
decoder_sender: decoder_sender,
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,
};
@ -372,7 +355,6 @@ impl ImageCache {
let receivers = Receivers {
cmd_receiver: cmd_receiver,
decoder_receiver: decoder_receiver,
progress_receiver: progress_receiver,
};
let mut exit_sender: Option<IpcSender<()>> = None;
@ -382,9 +364,6 @@ impl ImageCache {
SelectResult::Command(cmd) => {
exit_sender = cache.handle_cmd(cmd);
}
SelectResult::Progress(msg) => {
cache.handle_progress(msg);
}
SelectResult::Decoder(msg) => {
cache.handle_decoder(msg);
}
@ -406,22 +385,22 @@ impl ImageCache {
ImageCacheCommand::Exit(sender) => {
return Some(sender);
}
ImageCacheCommand::RequestImage(url, result_chan, responder) => {
self.request_image(url, result_chan, responder, false);
ImageCacheCommand::AddListener(id, responder) => {
self.add_listener(id, responder);
}
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);
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::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, data) => {
self.handle_progress(ResourceLoadInfo {
action: data,
key: id
});
}
};
@ -433,41 +412,41 @@ impl ImageCache {
match (msg.action, msg.key) {
(FetchResponseMsg::ProcessRequestBody, _) |
(FetchResponseMsg::ProcessRequestEOF, _) => return,
(FetchResponseMsg::ProcessResponse(_), _) => {}
(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) {
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 };
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()));
}
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 = mem::replace(&mut pending_load.bytes, vec!());
let bytes = pending_load.bytes.mark_complete();
let sender = self.decoder_sender.clone();
debug!("async decoding {} ({:?})", pending_load.url, key);
self.thread_pool.execute(move || {
let image = load_from_memory(&bytes);
let msg = DecoderMsg {
key: key,
image: image
};
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(
@ -492,7 +471,10 @@ impl ImageCache {
// Change state of a url from pending -> loaded.
fn complete_load(&mut self, key: LoadKey, mut load_result: LoadResult) {
let pending_load = self.pending_loads.remove(&key).unwrap();
let pending_load = match self.pending_loads.remove(&key) {
Some(load) => load,
None => return,
};
match load_result {
LoadResult::Loaded(ref mut image) => {
@ -518,145 +500,122 @@ 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),
}
}
fn get_image_or_meta_if_available(&mut self,
url: ServoUrl,
/// 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)
-> Result<ImageOrMetadataAvailable, ImageState> {
match self.completed_loads.get(&url) {
Some(completed_load) => {
match (completed_load.image_response.clone(), placeholder) {
(ImageResponse::Loaded(image), _) |
(ImageResponse::PlaceholderLoaded(image), UsePlaceholder::Yes) => {
Ok(ImageOrMetadataAvailable::ImageAvailable(image))
-> Option<Result<ImageOrMetadataAvailable, ImageState>> {
self.completed_loads.get(url).map(|completed_load| {
match (&completed_load.image_response, placeholder) {
(&ImageResponse::Loaded(ref image), _) |
(&ImageResponse::PlaceholderLoaded(ref image), UsePlaceholder::Yes) => {
Ok(ImageOrMetadataAvailable::ImageAvailable(image.clone()))
}
(ImageResponse::PlaceholderLoaded(_), UsePlaceholder::No) |
(ImageResponse::None, _) |
(ImageResponse::MetadataLoaded(_), _) => {
(&ImageResponse::PlaceholderLoaded(_), UsePlaceholder::No) |
(&ImageResponse::None, _) |
(&ImageResponse::MetadataLoaded(_), _) => {
Err(ImageState::LoadError)
}
}
})
}
/// 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) {
debug!("{} is available", url);
return result;
}
let decoded = {
let result = self.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);
decode_bytes_sync(key, &pl.bytes.as_slice())
}
(&None, &Some(ref meta)) => {
debug!("metadata available for {} ({:?})", url, key);
return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone()))
}
(&Some(Err(_)), _) | (&None, &None) => {
debug!("{} ({:?}) is still pending", url, key);
return Err(ImageState::Pending(key));
}
},
CacheResult::Miss(Some((key, _pl))) => {
debug!("should be requesting {} ({:?})", url, key);
return Err(ImageState::NotRequested(key));
}
CacheResult::Miss(None) => {
debug!("couldn't find an entry for {}", url);
return Err(ImageState::LoadError);
}
}
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()))
// 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) {
Some(result) => result,
None => Err(ImageState::LoadError),
}
}
}
fn store_decode_image(&mut self,
ref_url: ServoUrl,
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,
});
let action = FetchResponseMsg::ProcessResponseEOF(Ok(()));
let _ = self.progress_sender.send(ResourceLoadInfo {
action: action,
key: load_key,
});
}
}
/// 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)
}
fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg {
let image = load_from_memory(bytes);
DecoderMsg {
key: key,
image: image
}
}

Просмотреть файл

@ -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 FetchResponseMsg;
use image::base::{Image, ImageMetadata};
use ipc_channel::ipc::{self, IpcSender};
use servo_url::ServoUrl;
@ -13,27 +14,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 +75,22 @@ 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>>),
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(ServoUrl, Vec<u8>),
StoreDecodeImage(PendingImageId, FetchResponseMsg),
/// Clients must wait for a response before shutting down the ResourceThread
Exit(IpcSender<()>),
@ -106,6 +102,15 @@ pub enum UsePlaceholder {
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)]
@ -122,52 +127,41 @@ 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.
///
/// FIXME: We shouldn't do IPC for data uris!
pub fn find_image_or_metadata(&self,
url: ServoUrl,
use_placeholder: UsePlaceholder)
use_placeholder: UsePlaceholder,
can_request: CanRequestImages)
-> Result<ImageOrMetadataAvailable, ImageState> {
let (sender, receiver) = ipc::channel().unwrap();
let msg = ImageCacheCommand::GetImageOrMetadataIfAvailable(url, use_placeholder, sender);
let msg = ImageCacheCommand::GetImageOrMetadataIfAvailable(url,
use_placeholder,
can_request,
sender);
let _ = self.chan.send(msg);
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");
}
/// 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();

Просмотреть файл

@ -192,7 +192,7 @@ pub trait FetchTaskTarget {
fn process_response_eof(&mut self, response: &Response);
}
#[derive(Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize)]
pub enum FilteredMetadata {
Basic(Metadata),
Cors(Metadata),
@ -200,7 +200,7 @@ pub enum FilteredMetadata {
OpaqueRedirect
}
#[derive(Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize)]
pub enum FetchMetadata {
Unfiltered(Metadata),
Filtered {

Просмотреть файл

@ -116,6 +116,13 @@ impl DocumentLoader {
request: RequestInit,
fetch_target: IpcSender<FetchResponseMsg>) {
self.add_blocking_load(load);
self.fetch_async_background(request, fetch_target);
}
/// Initiate a new fetch that does not block the document load event.
pub fn fetch_async_background(&mut self,
request: RequestInit,
fetch_target: IpcSender<FetchResponseMsg>) {
self.resource_threads.sender().send(CoreResourceMsg::Fetch(request, fetch_target)).unwrap();
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;
}
};

Просмотреть файл

@ -337,15 +337,20 @@ 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, UsePlaceholder, ImageOrMetadataAvailable};
use net_traits::image_cache_thread::CanRequestImages;
use servo_url::ServoUrl;
pub fn request_image_from_cache(window: &Window, url: ServoUrl) -> ImageResponse {
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
let response =
image_cache.find_image_or_metadata(url.into(),
UsePlaceholder::No,
CanRequestImages::No);
match response {
Ok(ImageOrMetadataAvailable::ImageAvailable(image)) =>
ImageResponse::Loaded(image),
_ => ImageResponse::None,
}
}
}

Просмотреть файл

@ -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,18 @@ use euclid::point::Point2D;
use html5ever_atoms::LocalName;
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};
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::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 +60,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,7 +83,6 @@ impl HTMLImageElement {
}
}
struct ImageResponseHandlerRunnable {
element: Trusted<HTMLImageElement>,
image: ImageResponse,
@ -94,75 +102,225 @@ impl Runnable for ImageResponseHandlerRunnable {
fn name(&self) -> &'static str { "ImageResponseHandlerRunnable" }
fn handler(self: Box<Self>) {
// Update the image field
let element = self.element.root();
let (image, metadata, trigger_image_load, trigger_image_error) = match self.image {
element.process_image_response(self.image);
}
}
/// The context required for asynchronously loading an external image.
struct ImageContext {
/// The element that initiated the request.
elem: Trusted<HTMLImageElement>,
/// 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 ImageContext {
fn image_cache(&self) -> ImageCacheThread {
let elem = self.elem.root();
window_from_node(&*elem).image_cache_thread().clone()
}
}
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.image_cache().notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponse(metadata.clone()));
let metadata = metadata.ok().map(|meta| {
match meta {
FetchMetadata::Unfiltered(m) => m,
FetchMetadata::Filtered { unsafe_, .. } => unsafe_
}
});
let status_code = metadata.as_ref().and_then(|m| {
m.status.as_ref().map(|&(code, _)| code)
}).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, payload: Vec<u8>) {
if self.status.is_ok() {
self.image_cache().notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponseChunk(payload));
}
}
fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
let elem = self.elem.root();
let document = document_from_node(&*elem);
let image_cache = self.image_cache();
image_cache.notify_pending_response(self.id,
FetchResponseMsg::ProcessResponseEOF(response));
document.finish_load(LoadType::Image(self.url.clone()));
}
}
impl PreInvoke for ImageContext {}
impl HTMLImageElement {
/// Update the current image with a valid URL.
fn update_image_with_url(&self, img_url: ServoUrl, src: DOMString) {
{
let mut current_request = self.current_request.borrow_mut();
current_request.parsed_url = Some(img_url.clone());
current_request.source_url = Some(src);
LoadBlocker::terminate(&mut current_request.blocker);
let document = document_from_node(self);
current_request.blocker =
Some(LoadBlocker::new(&*document, LoadType::Image(img_url.clone())));
}
fn add_cache_listener_for_element(image_cache: &ImageCacheThread,
id: PendingImageId,
elem: &HTMLImageElement) {
let trusted_node = Trusted::new(elem);
let (responder_sender, responder_receiver) = ipc::channel().unwrap();
let window = window_from_node(elem);
let task_source = window.networking_task_source();
let wrapper = window.get_runnable_wrapper();
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 runnable = ImageResponseHandlerRunnable::new(
trusted_node.clone(), message.to().unwrap());
let _ = task_source.queue_with_wrapper(box runnable, &wrapper);
});
image_cache.add_listener(id, ImageResponder::new(responder_sender, id));
}
let window = window_from_node(self);
let image_cache = window.image_cache_thread();
let response =
image_cache.find_image_or_metadata(img_url.clone().into(),
UsePlaceholder::Yes,
CanRequestImages::Yes);
match response {
Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => {
self.process_image_response(ImageResponse::Loaded(image));
}
Ok(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
self.process_image_response(ImageResponse::MetadataLoaded(m));
}
Err(ImageState::Pending(id)) => {
add_cache_listener_for_element(image_cache, id, self);
}
Err(ImageState::LoadError) => {
self.process_image_response(ImageResponse::None);
}
Err(ImageState::NotRequested(id)) => {
add_cache_listener_for_element(image_cache, id, self);
self.request_image(img_url, id);
}
}
}
fn request_image(&self, img_url: ServoUrl, id: PendingImageId) {
let document = document_from_node(self);
let window = window_from_node(self);
let context = Arc::new(Mutex::new(ImageContext {
elem: Trusted::new(self),
url: img_url.clone(),
status: Ok(()),
id: 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: img_url.clone(),
origin: document.url().clone(),
type_: RequestType::Image,
pipeline_id: Some(document.global().pipeline_id()),
.. RequestInit::default()
};
document.fetch_async(LoadType::Image(img_url), request, action_sender);
}
fn process_image_response(&self, image: ImageResponse) {
let (image, metadata, trigger_image_load, trigger_image_error) = match image {
ImageResponse::Loaded(image) | ImageResponse::PlaceholderLoaded(image) => {
(Some(image.clone()), Some(ImageMetadata { height: image.height, width: image.width } ), true, false)
(Some(image.clone()),
Some(ImageMetadata { height: image.height, width: image.width }),
true,
false)
}
ImageResponse::MetadataLoaded(meta) => {
(None, Some(meta), false, false)
}
ImageResponse::None => (None, None, false, true)
};
element.current_request.borrow_mut().image = image;
element.current_request.borrow_mut().metadata = metadata;
self.current_request.borrow_mut().image = image;
self.current_request.borrow_mut().metadata = metadata;
// Mark the node dirty
let document = document_from_node(&*element);
element.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
// Fire image.onload
if trigger_image_load {
element.upcast::<EventTarget>().fire_event(atom!("load"));
self.upcast::<EventTarget>().fire_event(atom!("load"));
}
// Fire image.onerror
if trigger_image_error {
element.upcast::<EventTarget>().fire_event(atom!("error"));
self.upcast::<EventTarget>().fire_event(atom!("error"));
}
LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
// Trigger reflow
let window = window_from_node(&*document);
let window = window_from_node(self);
window.add_pending_reflow();
}
}
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)) => {
let img_url = base_url.join(&src);
if let Ok(img_url) = img_url {
self.current_request.borrow_mut().parsed_url = Some(img_url.clone());
self.current_request.borrow_mut().source_url = Some(src);
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();
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);
});
image_cache.request_image_and_metadata(img_url.into(),
window.image_cache_chan(),
Some(ImageResponder::new(responder_sender)));
self.update_image_with_url(img_url, src);
} else {
// https://html.spec.whatwg.org/multipage/#update-the-image-data
// Step 11 (error substeps)
@ -202,6 +360,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 +369,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,
}),
}
}

Просмотреть файл

@ -42,7 +42,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, NodeDamage};
use dom::performance::Performance;
use dom::promise::Promise;
use dom::screen::Screen;
@ -52,20 +52,23 @@ 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 layout_image::fetch_image_for_layout;
use msg::constellation_msg::{FrameType, PipelineId};
use net_traits::{ResourceThreads, ReferrerPolicy};
use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
use net_traits::image_cache_thread::{ImageResponder, ImageResponse};
use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread, PendingImageId};
use net_traits::storage_thread::StorageType;
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 +76,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 +90,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 +169,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 +257,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 +305,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 +352,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 +1196,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, self.image_cache_thread.clone());
}
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 +1280,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 +1689,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 +1771,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 {

Просмотреть файл

@ -0,0 +1,81 @@
/* 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/. */
//! Infrastructure to initiate network requests for images needed by the layout
//! thread. The script thread needs to be responsible for them because there's
//! no guarantee that the responsible nodes will still exist in the future if the
//! layout thread holds on to them during asynchronous operations.
use dom::bindings::reflector::DomObject;
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::request::{Type as RequestType, RequestInit as FetchRequestInit};
use network_listener::{NetworkListener, PreInvoke};
use servo_url::ServoUrl;
use std::sync::{Arc, Mutex};
struct LayoutImageContext {
id: PendingImageId,
cache: ImageCacheThread,
}
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>) {
self.cache.notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponse(metadata));
}
fn process_response_chunk(&mut self, payload: Vec<u8>) {
self.cache.notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponseChunk(payload));
}
fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
self.cache.notify_pending_response(self.id,
FetchResponseMsg::ProcessResponseEOF(response));
}
}
impl PreInvoke for LayoutImageContext {}
pub fn fetch_image_for_layout(url: ServoUrl,
node: &Node,
id: PendingImageId,
cache: ImageCacheThread) {
let context = Arc::new(Mutex::new(LayoutImageContext {
id: id,
cache: cache,
}));
let document = document_from_node(node);
let window = document.window();
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,
origin: document.url().clone(),
type_: RequestType::Image,
pipeline_id: Some(document.global().pipeline_id()),
.. FetchRequestInit::default()
};
// Layout image loads do not delay the document load event.
document.mut_loader().fetch_async_background(request, action_sender);
}

Просмотреть файл

@ -110,6 +110,7 @@ pub mod document_loader;
#[macro_use]
mod dom;
pub mod fetch;
mod layout_image;
pub mod layout_wrapper;
mod mem;
mod microtask;

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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,
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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.

Просмотреть файл

@ -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()));