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.rs482
1 files changed, 384 insertions, 98 deletions
diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
index e38b615f9ad..e1de326d305 100644
--- a/components/script/dom/htmlimageelement.rs
+++ b/components/script/dom/htmlimageelement.rs
@@ -22,7 +22,7 @@ use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
use dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute};
-use dom::event::Event;
+use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
use dom::htmlareaelement::HTMLAreaElement;
use dom::htmlelement::HTMLElement;
@@ -30,6 +30,7 @@ use dom::htmlformelement::{FormControl, HTMLFormElement};
use dom::htmlmapelement::HTMLMapElement;
use dom::mouseevent::MouseEvent;
use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
+use dom::progressevent::ProgressEvent;
use dom::values::UNSIGNED_LONG_MAX;
use dom::virtualmethods::VirtualMethods;
use dom::window::Window;
@@ -38,6 +39,7 @@ use euclid::point::Point2D;
use html5ever::{LocalName, Prefix};
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
+use microtask::{Microtask, MicrotaskRunnable};
use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable};
@@ -46,17 +48,17 @@ use net_traits::image_cache::UsePlaceholder;
use net_traits::request::{RequestInit, Type as RequestType};
use network_listener::{NetworkListener, PreInvoke};
use num_traits::ToPrimitive;
-use script_thread::Runnable;
+use script_thread::{Runnable, ScriptThread};
use servo_url::ServoUrl;
use servo_url::origin::ImmutableOrigin;
-use std::cell::Cell;
+use std::cell::{Cell, RefMut};
use std::default::Default;
use std::i32;
use std::sync::{Arc, Mutex};
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use task_source::TaskSource;
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(Clone, Copy, JSTraceable, HeapSizeOf)]
#[allow(dead_code)]
enum State {
Unavailable,
@@ -64,6 +66,11 @@ enum State {
CompletelyAvailable,
Broken,
}
+#[derive(Copy, Clone, JSTraceable, HeapSizeOf)]
+enum ImageRequestPhase {
+ Pending,
+ Current
+}
#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
struct ImageRequest {
@@ -79,6 +86,7 @@ struct ImageRequest {
#[dom_struct]
pub struct HTMLImageElement {
htmlelement: HTMLElement,
+ image_request: Cell<ImageRequestPhase>,
current_request: DOMRefCell<ImageRequest>,
pending_request: DOMRefCell<ImageRequest>,
form_owner: MutNullableJS<HTMLFormElement>,
@@ -176,18 +184,7 @@ 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 fetch_image(&self, img_url: &ServoUrl) {
fn add_cache_listener_for_element(image_cache: Arc<ImageCache>,
id: PendingImageId,
elem: &HTMLImageElement) {
@@ -235,12 +232,12 @@ impl HTMLImageElement {
Err(ImageState::NotRequested(id)) => {
add_cache_listener_for_element(image_cache, id, self);
- self.request_image(img_url, id);
+ self.fetch_request(img_url, id);
}
}
}
- fn request_image(&self, img_url: ServoUrl, id: PendingImageId) {
+ fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
let document = document_from_node(self);
let window = window_from_node(self);
@@ -273,38 +270,71 @@ impl HTMLImageElement {
document.loader().fetch_async_background(request, action_sender);
}
+ /// Step 14 of https://html.spec.whatwg.org/multipage/#update-the-image-data
fn process_image_response(&self, image: ImageResponse) {
- let (image, metadata, trigger_image_load, trigger_image_error) = match image {
- ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
+ // TODO: Handle multipart/x-mixed-replace
+ let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
+ (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) |
+ (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
+ self.current_request.borrow_mut().metadata = Some(ImageMetadata {
+ height: image.height,
+ width: image.width
+ });
self.current_request.borrow_mut().final_url = Some(url);
- (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)
+ self.current_request.borrow_mut().image = Some(image);
+ self.current_request.borrow_mut().state = State::CompletelyAvailable;
+ LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+ // Mark the node dirty
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ (true, false)
+ },
+ (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) |
+ (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
+ self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ self.image_request.set(ImageRequestPhase::Current);
+ self.current_request.borrow_mut().metadata = Some(ImageMetadata {
+ height: image.height,
+ width: image.width
+ });
+ self.current_request.borrow_mut().final_url = Some(url);
+ self.current_request.borrow_mut().image = Some(image);
+ self.current_request.borrow_mut().state = State::CompletelyAvailable;
+ LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ (true, false)
+ },
+ (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
+ self.current_request.borrow_mut().state = State::PartiallyAvailable;
+ self.current_request.borrow_mut().metadata = Some(meta);
+ (false, false)
+ },
+ (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
+ self.pending_request.borrow_mut().state = State::PartiallyAvailable;
+ (false, false)
+ },
+ (ImageResponse::None, ImageRequestPhase::Current) => {
+ self.abort_request(State::Broken, ImageRequestPhase::Current);
+ (false, true)
+ },
+ (ImageResponse::None, ImageRequestPhase::Pending) => {
+ self.abort_request(State::Broken, ImageRequestPhase::Current);
+ self.abort_request(State::Broken, ImageRequestPhase::Pending);
+ self.image_request.set(ImageRequestPhase::Current);
+ (false, true)
+ },
};
- self.current_request.borrow_mut().image = image;
- self.current_request.borrow_mut().metadata = metadata;
-
- // Mark the node dirty
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
- // Fire image.onload
+ // Fire image.onload and loadend
if trigger_image_load {
+ // TODO: https://html.spec.whatwg.org/multipage/#fire-a-progress-event-or-event
self.upcast::<EventTarget>().fire_event(atom!("load"));
+ self.upcast::<EventTarget>().fire_event(atom!("loadend"));
}
// Fire image.onerror
if trigger_image_error {
self.upcast::<EventTarget>().fire_event(atom!("error"));
- }
-
- if trigger_image_load || trigger_image_error {
- LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+ self.upcast::<EventTarget>().fire_event(atom!("loadend"));
}
// Trigger reflow
@@ -312,68 +342,298 @@ impl HTMLImageElement {
window.add_pending_reflow();
}
- /// 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)>) {
- // Force any in-progress request to be ignored.
- self.generation.set(self.generation.get() + 1);
+ /// https://html.spec.whatwg.org/multipage/#abort-the-image-request
+ fn abort_request(&self, state: State, request: ImageRequestPhase) {
+ let mut request = match request {
+ ImageRequestPhase::Current => self.current_request.borrow_mut(),
+ ImageRequestPhase::Pending => self.pending_request.borrow_mut(),
+ };
+ LoadBlocker::terminate(&mut request.blocker);
+ request.state = state;
+ request.image = None;
+ request.metadata = None;
+ }
+
+ /// Step 11.4 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+ fn set_current_request_url_to_selected_fire_error_and_loadend(&self, src: DOMString) {
+ struct Task {
+ img: Trusted<HTMLImageElement>,
+ src: String,
+ }
+ impl Runnable for Task {
+ fn handler(self: Box<Self>) {
+ let img = self.img.root();
+ {
+ let mut current_request = img.current_request.borrow_mut();
+ current_request.source_url = Some(DOMString::from_string(self.src));
+ }
+ img.upcast::<EventTarget>().fire_event(atom!("error"));
+ img.upcast::<EventTarget>().fire_event(atom!("loadend"));
+ img.abort_request(State::Broken, ImageRequestPhase::Current);
+ img.abort_request(State::Broken, ImageRequestPhase::Pending);
+ }
+ }
+ let task = box Task {
+ img: Trusted::new(self),
+ src: src.into()
+ };
let document = document_from_node(self);
let window = document.window();
- 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;
+ let task_source = window.dom_manipulation_task_source();
+ let _ = task_source.queue(task, window.upcast());
+ }
+
+ /// Step 10 of html.spec.whatwg.org/multipage/#update-the-image-data
+ fn dispatch_loadstart_progress_event(&self) {
+ struct FireprogressEventTask {
+ img: Trusted<HTMLImageElement>,
+ }
+ impl Runnable for FireprogressEventTask {
+ fn handler(self: Box<Self>) {
+ let progressevent = ProgressEvent::new(&self.img.root().global(),
+ atom!("loadstart"), EventBubbles::DoesNotBubble, EventCancelable::NotCancelable,
+ false, 0, 0);
+ progressevent.upcast::<Event>().fire(self.img.root().upcast());
+ }
+ }
+ let runnable = box FireprogressEventTask {
+ img: Trusted::new(self),
+ };
+ let document = document_from_node(self);
+ let window = document.window();
+ let task = window.dom_manipulation_task_source();
+ let _ = task.queue(runnable, window.upcast());
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#update-the-source-set
+ fn update_source_set(&self) -> Vec<DOMString> {
+ let elem = self.upcast::<Element>();
+ // TODO: follow the algorithm
+ let src = elem.get_string_attribute(&local_name!("src"));
+ if src.is_empty() {
+ return vec![]
+ }
+ vec![src]
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#select-an-image-source
+ fn select_image_source(&self) -> Option<DOMString> {
+ // TODO: select an image source from source set
+ self.update_source_set().first().cloned()
+ }
+
+ /// Step 9.2 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+ fn set_current_request_url_to_none_fire_error(&self) {
+ struct SetUrlToNoneTask {
+ img: Trusted<HTMLImageElement>,
+ }
+ impl Runnable for SetUrlToNoneTask {
+ fn handler(self: Box<Self>) {
+ let img = self.img.root();
+ {
+ let mut current_request = img.current_request.borrow_mut();
+ current_request.source_url = None;
+ current_request.parsed_url = None;
+ }
+ let elem = img.upcast::<Element>();
+ if elem.has_attribute(&local_name!("src")) {
+ img.upcast::<EventTarget>().fire_event(atom!("error"));
+ }
+ img.abort_request(State::Broken, ImageRequestPhase::Current);
+ img.abort_request(State::Broken, ImageRequestPhase::Pending);
}
- Some((src, base_url)) => {
- let img_url = base_url.join(&src);
- if let Ok(img_url) = img_url {
- self.update_image_with_url(img_url, src);
- } else {
- // https://html.spec.whatwg.org/multipage/#update-the-image-data
- // Step 11 (error substeps)
- debug!("Failed to parse URL {} with base {}", src, base_url);
- let mut req = self.current_request.borrow_mut();
-
- // Substeps 1,2
- req.image = None;
- req.parsed_url = None;
- req.state = State::Broken;
- // todo: set pending request to null
- // (pending requests aren't being used yet)
-
-
- struct ImgParseErrorRunnable {
- img: Trusted<HTMLImageElement>,
- src: String,
+ }
+
+ let task = box SetUrlToNoneTask {
+ img: Trusted::new(self),
+ };
+ let document = document_from_node(self);
+ let window = document.window();
+ let task_source = window.dom_manipulation_task_source();
+ let _ = task_source.queue(task, window.upcast());
+ }
+
+ /// Step 5.3.7 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+ fn set_current_request_url_to_string_and_fire_load(&self, src: DOMString, url: ServoUrl) {
+ struct SetUrlToStringTask {
+ img: Trusted<HTMLImageElement>,
+ src: String,
+ url: ServoUrl
+ }
+ impl Runnable for SetUrlToStringTask {
+ fn handler(self: Box<Self>) {
+ let img = self.img.root();
+ {
+ let mut current_request = img.current_request.borrow_mut();
+ current_request.parsed_url = Some(self.url.clone());
+ current_request.source_url = Some(self.src.into());
+ }
+ // TODO: restart animation, if set
+ img.upcast::<EventTarget>().fire_event(atom!("load"));
+ }
+ }
+ let runnable = box SetUrlToStringTask {
+ img: Trusted::new(self),
+ src: src.into(),
+ url: url
+ };
+ let document = document_from_node(self);
+ let window = document.window();
+ let task = window.dom_manipulation_task_source();
+ let _ = task.queue(runnable, window.upcast());
+ }
+
+ fn init_image_request(&self,
+ request: &mut RefMut<ImageRequest>,
+ url: &ServoUrl,
+ src: &DOMString) {
+ request.parsed_url = Some(url.clone());
+ request.source_url = Some(src.clone());
+ request.image = None;
+ request.metadata = None;
+ let document = document_from_node(self);
+ request.blocker = Some(LoadBlocker::new(&*document, LoadType::Image(url.clone())));
+ }
+
+ /// Step 12 of html.spec.whatwg.org/multipage/#update-the-image-data
+ fn prepare_image_request(&self, url: &ServoUrl, src: &DOMString) {
+ match self.image_request.get() {
+ ImageRequestPhase::Pending => {
+ if let Some(pending_url) = self.pending_request.borrow().parsed_url.clone() {
+ // Step 12.1
+ if pending_url == *url {
+ return
}
- impl Runnable for ImgParseErrorRunnable {
- fn handler(self: Box<Self>) {
- // https://html.spec.whatwg.org/multipage/#update-the-image-data
- // Step 11, substep 5
- let img = self.img.root();
- img.current_request.borrow_mut().source_url = Some(self.src.into());
- img.upcast::<EventTarget>().fire_event(atom!("error"));
- img.upcast::<EventTarget>().fire_event(atom!("loadend"));
+ }
+ },
+ ImageRequestPhase::Current => {
+ let mut current_request = self.current_request.borrow_mut();
+ let mut pending_request = self.pending_request.borrow_mut();
+ // step 12.4, create a new "image_request"
+ match (current_request.parsed_url.clone(), current_request.state) {
+ (Some(parsed_url), State::PartiallyAvailable) => {
+ // Step 12.2
+ if parsed_url == *url {
+ // 12.3 abort pending request
+ pending_request.image = None;
+ pending_request.parsed_url = None;
+ LoadBlocker::terminate(&mut pending_request.blocker);
+ // TODO: queue a task to restart animation, if restart-animation is set
+ return
}
+ self.image_request.set(ImageRequestPhase::Pending);
+ self.init_image_request(&mut pending_request, &url, &src);
+ self.fetch_image(&url);
+ },
+ (_, State::Broken) | (_, State::Unavailable) => {
+ // Step 12.5
+ self.init_image_request(&mut current_request, &url, &src);
+ self.fetch_image(&url);
+ },
+ (_, _) => {
+ // step 12.6
+ self.image_request.set(ImageRequestPhase::Pending);
+ self.init_image_request(&mut pending_request, &url, &src);
+ self.fetch_image(&url);
+ },
+ }
+ }
+ }
+ }
+
+ /// Step 8-12 of html.spec.whatwg.org/multipage/#update-the-image-data
+ fn update_the_image_data_sync_steps(&self) {
+ let document = document_from_node(self);
+ // Step 8
+ // TODO: take pixel density into account
+ match self.select_image_source() {
+ Some(src) => {
+ // Step 10
+ self.dispatch_loadstart_progress_event();
+ // Step 11
+ let base_url = document.base_url();
+ let parsed_url = base_url.join(&src);
+ match parsed_url {
+ Ok(url) => {
+ // Step 12
+ self.prepare_image_request(&url, &src);
+ },
+ Err(_) => {
+ // Step 11.1-11.5
+ self.set_current_request_url_to_selected_fire_error_and_loadend(src);
}
+ }
+ },
+ None => {
+ // Step 9
+ self.set_current_request_url_to_none_fire_error();
+ },
+ }
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#update-the-image-data
+ fn update_the_image_data(&self) {
+ let document = document_from_node(self);
+ let window = document.window();
+ let elem = self.upcast::<Element>();
+ let src = elem.get_string_attribute(&local_name!("src"));
+ let base_url = document.base_url();
+
+ // https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations
+ // Always first set the current request to unavailable,
+ // ensuring img.complete is false.
+ {
+ let mut current_request = self.current_request.borrow_mut();
+ current_request.state = State::Unavailable;
+ }
- let runnable = box ImgParseErrorRunnable {
- img: Trusted::new(self),
- src: src.into(),
- };
- let task = window.dom_manipulation_task_source();
- let _ = task.queue(runnable, window.upcast());
+ if !document.is_active() {
+ // Step 1 (if the document is inactive)
+ // TODO: use GlobalScope::enqueue_microtask,
+ // to queue micro task to come back to this algorithm
+ }
+ // Step 2 abort if user-agent does not supports images
+ // NOTE: Servo only supports images, skipping this step
+
+ // step 3, 4
+ // TODO: take srcset and parent images into account
+ if !src.is_empty() {
+ // TODO: take pixel density into account
+ if let Ok(img_url) = base_url.join(&src) {
+ // step 5, check the list of available images
+ let image_cache = window.image_cache();
+ let response = image_cache.find_image_or_metadata(img_url.clone().into(),
+ UsePlaceholder::No,
+ CanRequestImages::No);
+ if let Ok(ImageOrMetadataAvailable::ImageAvailable(image, url)) = response {
+ // Step 5.3
+ let metadata = ImageMetadata { height: image.height, width: image.width };
+ // Step 5.3.2 abort requests
+ self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current);
+ self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Pending);
+ let mut current_request = self.current_request.borrow_mut();
+ current_request.final_url = Some(url);
+ current_request.image = Some(image.clone());
+ current_request.metadata = Some(metadata);
+ self.set_current_request_url_to_string_and_fire_load(src, img_url);
+ return
}
}
}
+ // step 6, await a stable state.
+ self.generation.set(self.generation.get() + 1);
+ let task = ImageElementMicrotask::StableStateUpdateImageDataTask {
+ elem: Root::from_ref(self),
+ generation: self.generation.get(),
+ };
+ ScriptThread::await_stable_state(Microtask::ImageElement(task));
}
fn new_inherited(local_name: LocalName, prefix: Option<Prefix>, document: &Document) -> HTMLImageElement {
HTMLImageElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
+ image_request: Cell::new(ImageRequestPhase::Current),
current_request: DOMRefCell::new(ImageRequest {
state: State::Unavailable,
parsed_url: None,
@@ -456,6 +716,28 @@ impl HTMLImageElement {
}
+#[derive(JSTraceable, HeapSizeOf)]
+pub enum ImageElementMicrotask {
+ StableStateUpdateImageDataTask {
+ elem: Root<HTMLImageElement>,
+ generation: u32,
+ }
+}
+
+impl MicrotaskRunnable for ImageElementMicrotask {
+ fn handler(&self) {
+ match self {
+ &ImageElementMicrotask::StableStateUpdateImageDataTask { ref elem, ref generation } => {
+ // Step 7 of https://html.spec.whatwg.org/multipage/#update-the-image-data,
+ // stop here if other instances of this algorithm have been scheduled
+ if elem.generation.get() == *generation {
+ elem.update_the_image_data_sync_steps();
+ }
+ },
+ }
+ }
+}
+
pub trait LayoutHTMLImageElementHelpers {
#[allow(unsafe_code)]
unsafe fn image(&self) -> Option<Arc<Image>>;
@@ -582,8 +864,21 @@ impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-complete
fn Complete(&self) -> bool {
- let ref image = self.current_request.borrow().image;
- image.is_some()
+ let elem = self.upcast::<Element>();
+ // TODO: take srcset into account
+ if !elem.has_attribute(&local_name!("src")) {
+ return true
+ }
+ let src = elem.get_string_attribute(&local_name!("src"));
+ if src.is_empty() {
+ return true
+ }
+ let request = self.current_request.borrow();
+ let request_state = request.state;
+ match request_state {
+ State::CompletelyAvailable | State::Broken => return true,
+ State::PartiallyAvailable | State::Unavailable => return false,
+ }
}
// https://html.spec.whatwg.org/multipage/#dom-img-currentsrc
@@ -639,22 +934,13 @@ impl VirtualMethods for HTMLImageElement {
fn adopting_steps(&self, old_doc: &Document) {
self.super_type().unwrap().adopting_steps(old_doc);
-
- let elem = self.upcast::<Element>();
- let document = document_from_node(self);
- self.update_image(Some((elem.get_string_attribute(&local_name!("src")),
- document.base_url())));
+ self.update_the_image_data();
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match attr.local_name() {
- &local_name!("src") => {
- self.update_image(mutation.new_value(attr).map(|value| {
- // FIXME(ajeffrey): convert directly from AttrValue to DOMString
- (DOMString::from(&**value), document_from_node(self).base_url())
- }));
- },
+ &local_name!("src") => self.update_the_image_data(),
_ => {},
}
}