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