servo: Merge #16238 - Implement "update the image data" (from gterzian:implement_update_the_image_data); r=jdm
authorGregory Terzian <gterzian@users.noreply.github.com>
Tue, 30 May 2017 10:27:08 -0500
changeset 409456 ac591ec1000a9afe35f585254de10032b2709080
parent 409455 78f2a6212c535f9ee16f55d219316fff2c8b5699
child 409457 77756ce1743dc2b440ecdd34b248c4bca27cab71
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs16238, 11517
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16238 - Implement "update the image data" (from gterzian:implement_update_the_image_data); r=jdm <!-- Please describe your changes on the following line: --> Spec compliant implementation of the [update the image data algorithm](https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data). Currently still a work in progress, the ['async src complete test`](https://github.com/servo/servo/blob/master/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-img-element/img.complete.html#L33) is still passing as it was before, even though I switched to the new code, so I guess that's something. @jdm I will be picking this up next weekend, I left a bunch of TODO and NOTES in the code, if you or someone else have time this week I would appreciate an initial scan and feedback. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [ ] `./mach build -d` does not report any errors - [ ] `./mach test-tidy` does not report any errors - [ ] These changes fix #11517 (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: fe7d039416ea93f9a5a120cda9a6114ec1438c3e
servo/components/script/dom/htmlimageelement.rs
servo/components/script/microtask.rs
--- a/servo/components/script/dom/htmlimageelement.rs
+++ b/servo/components/script/dom/htmlimageelement.rs
@@ -17,73 +17,81 @@ use dom::bindings::error::Fallible;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{LayoutJS, MutNullableJS, 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};
 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;
 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;
 use dom_struct::dom_struct;
 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};
 use net_traits::image_cache::{ImageResponder, ImageResponse, ImageState, PendingImageId};
 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,
     PartiallyAvailable,
     CompletelyAvailable,
     Broken,
 }
+#[derive(Copy, Clone, JSTraceable, HeapSizeOf)]
+enum ImageRequestPhase {
+    Pending,
+    Current
+}
 #[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>,
     final_url: Option<ServoUrl>,
 }
 #[dom_struct]
 pub struct HTMLImageElement {
     htmlelement: HTMLElement,
+    image_request: Cell<ImageRequestPhase>,
     current_request: DOMRefCell<ImageRequest>,
     pending_request: DOMRefCell<ImageRequest>,
     form_owner: MutNullableJS<HTMLFormElement>,
     generation: Cell<u32>,
 }
 
 impl HTMLImageElement {
     pub fn get_url(&self) -> Option<ServoUrl> {
@@ -171,28 +179,17 @@ impl FetchResponseListener for ImageCont
             FetchResponseMsg::ProcessResponseEOF(response));
     }
 }
 
 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) {
             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();
@@ -230,22 +227,22 @@ impl HTMLImageElement {
             }
 
             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);
+                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);
 
         let context = Arc::new(Mutex::new(ImageContext {
             image_cache: window.image_cache(),
             status: Ok(()),
             id: id,
         }));
@@ -268,117 +265,380 @@ impl HTMLImageElement {
             .. RequestInit::default()
         };
 
         // This is a background load because the load blocker already fulfills the
         // purpose of delaying the document's load event.
         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);
+                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);
-                (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);
+                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
         let window = window_from_node(self);
         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();
+        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();
-        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 = 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();
+        }
+
+        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());
+    }
 
-                    // 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)
+    /// 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())));
+    }
 
-                    struct ImgParseErrorRunnable {
-                        img: Trusted<HTMLImageElement>,
-                        src: String,
+    /// 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
                         }
-                    }
-
-                    let runnable = box ImgParseErrorRunnable {
-                        img: Trusted::new(self),
-                        src: src.into(),
-                    };
-                    let task = window.dom_manipulation_task_source();
-                    let _ = task.queue(runnable, window.upcast());
+                        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;
+        }
+
+        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,
                 source_url: None,
                 image: None,
                 metadata: None,
                 blocker: None,
                 final_url: None,
@@ -451,16 +711,38 @@ impl HTMLImageElement {
         match self.current_request.borrow_mut().final_url {
             Some(ref url) => Some(url.origin()),
             None => None
         }
     }
 
 }
 
+#[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>>;
 
     #[allow(unsafe_code)]
     unsafe fn image_url(&self) -> Option<ServoUrl>;
 
     fn get_width(&self) -> LengthOrPercentageOrAuto;
@@ -577,18 +859,31 @@ impl HTMLImageElementMethods for HTMLIma
         match *metadata {
             Some(ref metadata) => metadata.height,
             None => 0,
         }
     }
 
     // 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
     fn CurrentSrc(&self) -> DOMString {
         let ref url = self.current_request.borrow().source_url;
         match *url {
             Some(ref url) => url.clone(),
             None => DOMString::from(""),
@@ -634,32 +929,23 @@ impl HTMLImageElementMethods for HTMLIma
 
 impl VirtualMethods for HTMLImageElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     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(),
             _ => {},
         }
     }
 
     fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
         match name {
             &local_name!("name") => AttrValue::from_atomic(value.into()),
             &local_name!("width") | &local_name!("height") => AttrValue::from_dimension(value.into()),
--- a/servo/components/script/microtask.rs
+++ b/servo/components/script/microtask.rs
@@ -6,16 +6,17 @@
 //! microtask queues. It is up to implementations of event loops to store a queue and
 //! perform checkpoints at appropriate times, as well as enqueue microtasks as required.
 
 use dom::bindings::callback::ExceptionHandling;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
 use dom::bindings::js::Root;
 use dom::globalscope::GlobalScope;
+use dom::htmlimageelement::ImageElementMicrotask;
 use dom::htmlmediaelement::MediaElementMicrotask;
 use dom::mutationobserver::MutationObserver;
 use msg::constellation_msg::PipelineId;
 use std::cell::Cell;
 use std::mem;
 use std::rc::Rc;
 
 /// A collection of microtasks in FIFO order.
@@ -26,16 +27,17 @@ pub struct MicrotaskQueue {
     /// https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint
     performing_a_microtask_checkpoint: Cell<bool>,
 }
 
 #[derive(JSTraceable, HeapSizeOf)]
 pub enum Microtask {
     Promise(EnqueuedPromiseCallback),
     MediaElement(MediaElementMicrotask),
+    ImageElement(ImageElementMicrotask),
     NotifyMutationObservers,
 }
 
 pub trait MicrotaskRunnable {
     fn handler(&self) {}
 }
 
 /// A promise callback scheduled to run during the next microtask checkpoint (#4283).
@@ -76,17 +78,20 @@ impl MicrotaskQueue {
                 match *job {
                     Microtask::Promise(ref job) => {
                         if let Some(target) = target_provider(job.pipeline) {
                             let _ = job.callback.Call_(&*target, ExceptionHandling::Report);
                         }
                     },
                     Microtask::MediaElement(ref task) => {
                         task.handler();
-                    }
+                    },
+                    Microtask::ImageElement(ref task) => {
+                        task.handler();
+                    },
                     Microtask::NotifyMutationObservers => {
                         MutationObserver::notify_mutation_observers();
                     }
                 }
             }
         }
 
         //TODO: Step 8 - notify about rejected promises