mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
improve spec compliance of update the image data
This commit is contained in:
parent
f05491166f
commit
71b0c10164
6 changed files with 401 additions and 118 deletions
|
@ -22,7 +22,7 @@ use dom::bindings::str::DOMString;
|
||||||
use dom::document::Document;
|
use dom::document::Document;
|
||||||
use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
|
use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
|
||||||
use dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute};
|
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::eventtarget::EventTarget;
|
||||||
use dom::htmlareaelement::HTMLAreaElement;
|
use dom::htmlareaelement::HTMLAreaElement;
|
||||||
use dom::htmlelement::HTMLElement;
|
use dom::htmlelement::HTMLElement;
|
||||||
|
@ -30,6 +30,7 @@ use dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||||
use dom::htmlmapelement::HTMLMapElement;
|
use dom::htmlmapelement::HTMLMapElement;
|
||||||
use dom::mouseevent::MouseEvent;
|
use dom::mouseevent::MouseEvent;
|
||||||
use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
|
use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
|
||||||
|
use dom::progressevent::ProgressEvent;
|
||||||
use dom::values::UNSIGNED_LONG_MAX;
|
use dom::values::UNSIGNED_LONG_MAX;
|
||||||
use dom::virtualmethods::VirtualMethods;
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
|
@ -38,6 +39,7 @@ use euclid::point::Point2D;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
use ipc_channel::ipc;
|
use ipc_channel::ipc;
|
||||||
use ipc_channel::router::ROUTER;
|
use ipc_channel::router::ROUTER;
|
||||||
|
use microtask::{Microtask, MicrotaskRunnable};
|
||||||
use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
|
use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
|
||||||
use net_traits::image::base::{Image, ImageMetadata};
|
use net_traits::image::base::{Image, ImageMetadata};
|
||||||
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable};
|
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 net_traits::request::{RequestInit, Type as RequestType};
|
||||||
use network_listener::{NetworkListener, PreInvoke};
|
use network_listener::{NetworkListener, PreInvoke};
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
use script_thread::Runnable;
|
use script_thread::{Runnable, ScriptThread};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use servo_url::origin::ImmutableOrigin;
|
use servo_url::origin::ImmutableOrigin;
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, RefMut};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::i32;
|
use std::i32;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||||
use task_source::TaskSource;
|
use task_source::TaskSource;
|
||||||
|
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
#[derive(Clone, Copy, JSTraceable, HeapSizeOf)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
enum State {
|
enum State {
|
||||||
Unavailable,
|
Unavailable,
|
||||||
|
@ -64,6 +66,11 @@ enum State {
|
||||||
CompletelyAvailable,
|
CompletelyAvailable,
|
||||||
Broken,
|
Broken,
|
||||||
}
|
}
|
||||||
|
#[derive(Copy, Clone, JSTraceable, HeapSizeOf)]
|
||||||
|
enum ImageRequestPhase {
|
||||||
|
Pending,
|
||||||
|
Current
|
||||||
|
}
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
#[must_root]
|
#[must_root]
|
||||||
struct ImageRequest {
|
struct ImageRequest {
|
||||||
|
@ -79,6 +86,7 @@ struct ImageRequest {
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct HTMLImageElement {
|
pub struct HTMLImageElement {
|
||||||
htmlelement: HTMLElement,
|
htmlelement: HTMLElement,
|
||||||
|
image_request: Cell<ImageRequestPhase>,
|
||||||
current_request: DOMRefCell<ImageRequest>,
|
current_request: DOMRefCell<ImageRequest>,
|
||||||
pending_request: DOMRefCell<ImageRequest>,
|
pending_request: DOMRefCell<ImageRequest>,
|
||||||
form_owner: MutNullableJS<HTMLFormElement>,
|
form_owner: MutNullableJS<HTMLFormElement>,
|
||||||
|
@ -176,18 +184,7 @@ impl PreInvoke for ImageContext {}
|
||||||
|
|
||||||
impl HTMLImageElement {
|
impl HTMLImageElement {
|
||||||
/// Update the current image with a valid URL.
|
/// Update the current image with a valid URL.
|
||||||
fn update_image_with_url(&self, img_url: ServoUrl, src: DOMString) {
|
fn fetch_image(&self, img_url: &ServoUrl) {
|
||||||
{
|
|
||||||
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: Arc<ImageCache>,
|
fn add_cache_listener_for_element(image_cache: Arc<ImageCache>,
|
||||||
id: PendingImageId,
|
id: PendingImageId,
|
||||||
elem: &HTMLImageElement) {
|
elem: &HTMLImageElement) {
|
||||||
|
@ -235,12 +232,12 @@ impl HTMLImageElement {
|
||||||
|
|
||||||
Err(ImageState::NotRequested(id)) => {
|
Err(ImageState::NotRequested(id)) => {
|
||||||
add_cache_listener_for_element(image_cache, id, self);
|
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 document = document_from_node(self);
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
|
|
||||||
|
@ -273,38 +270,71 @@ impl HTMLImageElement {
|
||||||
document.loader().fetch_async_background(request, action_sender);
|
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) {
|
fn process_image_response(&self, image: ImageResponse) {
|
||||||
let (image, metadata, trigger_image_load, trigger_image_error) = match image {
|
// TODO: Handle multipart/x-mixed-replace
|
||||||
ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
|
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().final_url = Some(url);
|
||||||
(Some(image.clone()),
|
self.current_request.borrow_mut().image = Some(image);
|
||||||
Some(ImageMetadata { height: image.height, width: image.width }),
|
self.current_request.borrow_mut().state = State::CompletelyAvailable;
|
||||||
true,
|
LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
|
||||||
false)
|
// Mark the node dirty
|
||||||
}
|
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
||||||
ImageResponse::MetadataLoaded(meta) => {
|
(true, false)
|
||||||
(None, Some(meta), false, false)
|
},
|
||||||
}
|
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) |
|
||||||
ImageResponse::None => (None, None, false, true)
|
(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
|
// Fire image.onload and loadend
|
||||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
|
||||||
|
|
||||||
// Fire image.onload
|
|
||||||
if trigger_image_load {
|
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!("load"));
|
||||||
|
self.upcast::<EventTarget>().fire_event(atom!("loadend"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire image.onerror
|
// Fire image.onerror
|
||||||
if trigger_image_error {
|
if trigger_image_error {
|
||||||
self.upcast::<EventTarget>().fire_event(atom!("error"));
|
self.upcast::<EventTarget>().fire_event(atom!("error"));
|
||||||
}
|
self.upcast::<EventTarget>().fire_event(atom!("loadend"));
|
||||||
|
|
||||||
if trigger_image_load || trigger_image_error {
|
|
||||||
LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger reflow
|
// Trigger reflow
|
||||||
|
@ -312,68 +342,298 @@ impl HTMLImageElement {
|
||||||
window.add_pending_reflow();
|
window.add_pending_reflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the local `image` member match the status of the `src` attribute and starts
|
/// https://html.spec.whatwg.org/multipage/#abort-the-image-request
|
||||||
/// prefetching the image. This method must be called after `src` is changed.
|
fn abort_request(&self, state: State, request: ImageRequestPhase) {
|
||||||
fn update_image(&self, value: Option<(DOMString, ServoUrl)>) {
|
let mut request = match request {
|
||||||
// Force any in-progress request to be ignored.
|
ImageRequestPhase::Current => self.current_request.borrow_mut(),
|
||||||
self.generation.set(self.generation.get() + 1);
|
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 document = document_from_node(self);
|
||||||
let window = document.window();
|
let window = document.window();
|
||||||
match value {
|
let task_source = window.dom_manipulation_task_source();
|
||||||
None => {
|
let _ = task_source.queue(task, window.upcast());
|
||||||
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);
|
/// Step 10 of html.spec.whatwg.org/multipage/#update-the-image-data
|
||||||
self.current_request.borrow_mut().image = None;
|
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());
|
||||||
}
|
}
|
||||||
Some((src, base_url)) => {
|
}
|
||||||
let img_url = base_url.join(&src);
|
let runnable = box FireprogressEventTask {
|
||||||
if let Ok(img_url) = img_url {
|
img: Trusted::new(self),
|
||||||
self.update_image_with_url(img_url, src);
|
};
|
||||||
} else {
|
let document = document_from_node(self);
|
||||||
// https://html.spec.whatwg.org/multipage/#update-the-image-data
|
let window = document.window();
|
||||||
// Step 11 (error substeps)
|
let task = window.dom_manipulation_task_source();
|
||||||
debug!("Failed to parse URL {} with base {}", src, base_url);
|
let _ = task.queue(runnable, window.upcast());
|
||||||
let mut req = self.current_request.borrow_mut();
|
}
|
||||||
|
|
||||||
// Substeps 1,2
|
/// https://html.spec.whatwg.org/multipage/#update-the-source-set
|
||||||
req.image = None;
|
fn update_source_set(&self) -> Vec<DOMString> {
|
||||||
req.parsed_url = None;
|
let elem = self.upcast::<Element>();
|
||||||
req.state = State::Broken;
|
// TODO: follow the algorithm
|
||||||
// todo: set pending request to null
|
let src = elem.get_string_attribute(&local_name!("src"));
|
||||||
// (pending requests aren't being used yet)
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
struct ImgParseErrorRunnable {
|
/// Step 9.2 of https://html.spec.whatwg.org/multipage/#update-the-image-data
|
||||||
img: Trusted<HTMLImageElement>,
|
fn set_current_request_url_to_none_fire_error(&self) {
|
||||||
src: String,
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
ImageRequestPhase::Current => {
|
||||||
// Step 11, substep 5
|
let mut current_request = self.current_request.borrow_mut();
|
||||||
let img = self.img.root();
|
let mut pending_request = self.pending_request.borrow_mut();
|
||||||
img.current_request.borrow_mut().source_url = Some(self.src.into());
|
// step 12.4, create a new "image_request"
|
||||||
img.upcast::<EventTarget>().fire_event(atom!("error"));
|
match (current_request.parsed_url.clone(), current_request.state) {
|
||||||
img.upcast::<EventTarget>().fire_event(atom!("loadend"));
|
(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);
|
||||||
let runnable = box ImgParseErrorRunnable {
|
self.fetch_image(&url);
|
||||||
img: Trusted::new(self),
|
},
|
||||||
src: src.into(),
|
(_, State::Broken) | (_, State::Unavailable) => {
|
||||||
};
|
// Step 12.5
|
||||||
let task = window.dom_manipulation_task_source();
|
self.init_image_request(&mut current_request, &url, &src);
|
||||||
let _ = task.queue(runnable, window.upcast());
|
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 {
|
fn new_inherited(local_name: LocalName, prefix: Option<Prefix>, document: &Document) -> HTMLImageElement {
|
||||||
HTMLImageElement {
|
HTMLImageElement {
|
||||||
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
||||||
|
image_request: Cell::new(ImageRequestPhase::Current),
|
||||||
current_request: DOMRefCell::new(ImageRequest {
|
current_request: DOMRefCell::new(ImageRequest {
|
||||||
state: State::Unavailable,
|
state: State::Unavailable,
|
||||||
parsed_url: None,
|
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 {
|
pub trait LayoutHTMLImageElementHelpers {
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
unsafe fn image(&self) -> Option<Arc<Image>>;
|
unsafe fn image(&self) -> Option<Arc<Image>>;
|
||||||
|
@ -582,8 +864,21 @@ impl HTMLImageElementMethods for HTMLImageElement {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-img-complete
|
// https://html.spec.whatwg.org/multipage/#dom-img-complete
|
||||||
fn Complete(&self) -> bool {
|
fn Complete(&self) -> bool {
|
||||||
let ref image = self.current_request.borrow().image;
|
let elem = self.upcast::<Element>();
|
||||||
image.is_some()
|
// 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
|
// https://html.spec.whatwg.org/multipage/#dom-img-currentsrc
|
||||||
|
@ -639,22 +934,13 @@ impl VirtualMethods for HTMLImageElement {
|
||||||
|
|
||||||
fn adopting_steps(&self, old_doc: &Document) {
|
fn adopting_steps(&self, old_doc: &Document) {
|
||||||
self.super_type().unwrap().adopting_steps(old_doc);
|
self.super_type().unwrap().adopting_steps(old_doc);
|
||||||
|
self.update_the_image_data();
|
||||||
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())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
||||||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||||
match attr.local_name() {
|
match attr.local_name() {
|
||||||
&local_name!("src") => {
|
&local_name!("src") => self.update_the_image_data(),
|
||||||
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())
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
|
use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
|
||||||
use dom::bindings::js::Root;
|
use dom::bindings::js::Root;
|
||||||
use dom::globalscope::GlobalScope;
|
use dom::globalscope::GlobalScope;
|
||||||
|
use dom::htmlimageelement::ImageElementMicrotask;
|
||||||
use dom::htmlmediaelement::MediaElementMicrotask;
|
use dom::htmlmediaelement::MediaElementMicrotask;
|
||||||
use dom::mutationobserver::MutationObserver;
|
use dom::mutationobserver::MutationObserver;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
|
@ -31,6 +32,7 @@ pub struct MicrotaskQueue {
|
||||||
pub enum Microtask {
|
pub enum Microtask {
|
||||||
Promise(EnqueuedPromiseCallback),
|
Promise(EnqueuedPromiseCallback),
|
||||||
MediaElement(MediaElementMicrotask),
|
MediaElement(MediaElementMicrotask),
|
||||||
|
ImageElement(ImageElementMicrotask),
|
||||||
NotifyMutationObservers,
|
NotifyMutationObservers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +83,10 @@ impl MicrotaskQueue {
|
||||||
},
|
},
|
||||||
Microtask::MediaElement(ref task) => {
|
Microtask::MediaElement(ref task) => {
|
||||||
task.handler();
|
task.handler();
|
||||||
}
|
},
|
||||||
|
Microtask::ImageElement(ref task) => {
|
||||||
|
task.handler();
|
||||||
|
},
|
||||||
Microtask::NotifyMutationObservers => {
|
Microtask::NotifyMutationObservers => {
|
||||||
MutationObserver::notify_mutation_observers();
|
MutationObserver::notify_mutation_observers();
|
||||||
}
|
}
|
||||||
|
|
|
@ -571006,7 +571006,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"html/semantics/embedded-content/the-img-element/img.complete.html": [
|
"html/semantics/embedded-content/the-img-element/img.complete.html": [
|
||||||
"723ded8aca956dc1f3cbc60feb0e502c2b943d1d",
|
"be6ea10ac070420948bcef0372c283f275a6b00f",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"html/semantics/embedded-content/the-img-element/invalid-src.html": [
|
"html/semantics/embedded-content/the-img-element/invalid-src.html": [
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
[img.complete.html]
|
[img.complete.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
[img src omitted]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[img src empty]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[IDL attribute complete returns true when image resource has been fetched but not run yet & image is not in broken state]
|
[IDL attribute complete returns true when image resource has been fetched but not run yet & image is not in broken state]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[async src broken test]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[update-src-complete.html]
|
|
||||||
type: testharness
|
|
||||||
[Changing the img src should retain the 'complete' property]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -33,16 +33,22 @@
|
||||||
var t = async_test("async src complete test");
|
var t = async_test("async src complete test");
|
||||||
|
|
||||||
t.step(function(){
|
t.step(function(){
|
||||||
document.getElementById("imgTestTag3").src = '3.jpg?nocache=' + Math.random();
|
var loaded = false;
|
||||||
|
document.getElementById("imgTestTag3").onload = t.step_func_done(function(){
|
||||||
|
assert_false(loaded);
|
||||||
|
loaded = true;
|
||||||
|
assert_true(document.getElementById("imgTestTag3").complete);
|
||||||
|
var currentSrc = document.getElementById("imgTestTag3").currentSrc
|
||||||
|
assert_equals(new URL(window.location.origin + "/" + currentSrc).pathname, "/3.jpg");
|
||||||
|
}, "Only one onload, despite setting the src twice");
|
||||||
|
|
||||||
|
document.getElementById("imgTestTag3").src = 'test' + Math.random();
|
||||||
//test if img.complete is set to false if src is changed
|
//test if img.complete is set to false if src is changed
|
||||||
assert_false(document.getElementById("imgTestTag3").complete, "src changed, should be set to false")
|
assert_false(document.getElementById("imgTestTag3").complete, "src changed, should be set to false")
|
||||||
|
//change src again, should make only one request as per 'await stable state'
|
||||||
|
document.getElementById("imgTestTag3").src = '3.jpg?nocache=' + Math.random();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("imgTestTag3").onload = t.step_func(function(){
|
|
||||||
assert_true(document.getElementById("imgTestTag3").complete);
|
|
||||||
t.done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data
|
// https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data
|
||||||
// says to "await a stable state" before fetching so we use a separate <script>
|
// says to "await a stable state" before fetching so we use a separate <script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue