aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/htmlimageelement.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/htmlimageelement.rs')
-rw-r--r--components/script/dom/htmlimageelement.rs235
1 files changed, 198 insertions, 37 deletions
diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
index ec9f2e1ec80..af98b013410 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,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,
}),
}
}