diff options
Diffstat (limited to 'components/script/dom/htmlimageelement.rs')
-rw-r--r-- | components/script/dom/htmlimageelement.rs | 482 |
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(), _ => {}, } } |