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.rs1959
1 files changed, 1590 insertions, 369 deletions
diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
index 389cd87b064..893080edd07 100644
--- a/components/script/dom/htmlimageelement.rs
+++ b/components/script/dom/htmlimageelement.rs
@@ -1,61 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::document_loader::{LoadBlocker, LoadType};
+use crate::dom::activation::Activatable;
+use crate::dom::attr::Attr;
+use crate::dom::bindings::cell::{DomRefCell, RefMut};
+use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods;
+use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
+use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
+use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
+use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::DomObject;
+use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
+use crate::dom::bindings::str::{DOMString, USVString};
+use crate::dom::document::{determine_policy_for_token, Document};
+use crate::dom::element::{cors_setting_for_element, referrer_policy_for_element};
+use crate::dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute};
+use crate::dom::element::{
+ AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
+};
+use crate::dom::event::Event;
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::globalscope::GlobalScope;
+use crate::dom::htmlareaelement::HTMLAreaElement;
+use crate::dom::htmlelement::HTMLElement;
+use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
+use crate::dom::htmlmapelement::HTMLMapElement;
+use crate::dom::htmlpictureelement::HTMLPictureElement;
+use crate::dom::htmlsourceelement::HTMLSourceElement;
+use crate::dom::mouseevent::MouseEvent;
+use crate::dom::node::UnbindContext;
+use crate::dom::node::{
+ document_from_node, window_from_node, BindContext, Node, NodeDamage, ShadowIncluding,
+};
+use crate::dom::performanceresourcetiming::InitiatorType;
+use crate::dom::values::UNSIGNED_LONG_MAX;
+use crate::dom::virtualmethods::VirtualMethods;
+use crate::dom::window::Window;
+use crate::fetch::create_a_potential_cors_request;
+use crate::image_listener::{generate_cache_listener_for_element, ImageCacheListener};
+use crate::microtask::{Microtask, MicrotaskRunnable};
+use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener};
+use crate::script_thread::ScriptThread;
+use crate::task_source::TaskSource;
use app_units::{Au, AU_PER_PX};
-use document_loader::{LoadType, LoadBlocker};
-use dom::activation::Activatable;
-use dom::attr::Attr;
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods;
-use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods;
-use dom::bindings::codegen::Bindings::HTMLImageElementBinding;
-use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
-use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
-use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
-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::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::values::UNSIGNED_LONG_MAX;
-use dom::virtualmethods::VirtualMethods;
-use dom::window::Window;
+use cssparser::{Parser, ParserInput};
use dom_struct::dom_struct;
-use euclid::point::Point2D;
-use html5ever_atoms::LocalName;
+use euclid::Point2D;
+use html5ever::{LocalName, Prefix, QualName};
use ipc_channel::ipc;
+use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
-use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
+use mime::{self, Mime};
+use msg::constellation_msg::PipelineId;
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 net_traits::image_cache::{
+ CorsStatus, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse,
+ PendingImageId, PendingImageResponse, UsePlaceholder,
+};
+use net_traits::request::{CorsSettings, Destination, Initiator, Referrer, RequestBuilder};
+use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError};
+use net_traits::{ReferrerPolicy, ResourceFetchTiming, ResourceTimingType};
use num_traits::ToPrimitive;
-use script_thread::Runnable;
+use servo_url::origin::ImmutableOrigin;
+use servo_url::origin::MutableOrigin;
use servo_url::ServoUrl;
use std::cell::Cell;
+use std::char;
+use std::collections::HashSet;
use std::default::Default;
use std::i32;
+use std::mem;
use std::sync::{Arc, Mutex};
-use style::attr::{AttrValue, LengthOrPercentageOrAuto};
-use task_source::TaskSource;
+use style::attr::{
+ parse_double, parse_length, parse_unsigned_integer, AttrValue, LengthOrPercentageOrAuto,
+};
+use style::context::QuirksMode;
+use style::media_queries::MediaList;
+use style::parser::ParserContext;
+use style::str::is_ascii_digit;
+use style::stylesheets::{CssRuleType, Origin};
+use style::values::specified::length::{Length, NoCalcLength};
+use style::values::specified::{source_size_list::SourceSizeList, AbsoluteLength};
+use style_traits::ParsingMode;
+
+enum ParseState {
+ InDescriptor,
+ InParens,
+ AfterDescriptor,
+}
+
+pub struct SourceSet {
+ image_sources: Vec<ImageSource>,
+ source_size: SourceSizeList,
+}
+
+impl SourceSet {
+ fn new() -> SourceSet {
+ SourceSet {
+ image_sources: Vec::new(),
+ source_size: SourceSizeList::empty(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct ImageSource {
+ pub url: String,
+ pub descriptor: Descriptor,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Descriptor {
+ pub wid: Option<u32>,
+ pub den: Option<f64>,
+}
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
#[allow(dead_code)]
enum State {
Unavailable,
@@ -63,57 +129,57 @@ enum State {
CompletelyAvailable,
Broken,
}
-#[derive(JSTraceable, HeapSizeOf)]
-#[must_root]
+
+#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
+enum ImageRequestPhase {
+ Pending,
+ Current,
+}
+#[derive(JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
struct ImageRequest {
state: State,
parsed_url: Option<ServoUrl>,
- source_url: Option<DOMString>,
+ source_url: Option<USVString>,
blocker: Option<LoadBlocker>,
- #[ignore_heap_size_of = "Arc"]
+ #[ignore_malloc_size_of = "Arc"]
image: Option<Arc<Image>>,
metadata: Option<ImageMetadata>,
+ final_url: Option<ServoUrl>,
+ current_pixel_density: Option<f64>,
}
#[dom_struct]
pub struct HTMLImageElement {
htmlelement: HTMLElement,
- current_request: DOMRefCell<ImageRequest>,
- pending_request: DOMRefCell<ImageRequest>,
- form_owner: MutNullableJS<HTMLFormElement>,
+ image_request: Cell<ImageRequestPhase>,
+ current_request: DomRefCell<ImageRequest>,
+ pending_request: DomRefCell<ImageRequest>,
+ form_owner: MutNullableDom<HTMLFormElement>,
generation: Cell<u32>,
+ #[ignore_malloc_size_of = "SourceSet"]
+ source_set: DomRefCell<SourceSet>,
+ last_selected_source: DomRefCell<Option<USVString>>,
}
impl HTMLImageElement {
pub fn get_url(&self) -> Option<ServoUrl> {
self.current_request.borrow().parsed_url.clone()
}
-}
-
-struct ImageResponseHandlerRunnable {
- element: Trusted<HTMLImageElement>,
- image: ImageResponse,
- generation: u32,
-}
-
-impl ImageResponseHandlerRunnable {
- fn new(element: Trusted<HTMLImageElement>, image: ImageResponse, generation: u32)
- -> ImageResponseHandlerRunnable {
- ImageResponseHandlerRunnable {
- element: element,
- image: image,
- generation: generation,
+ // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
+ pub fn is_usable(&self) -> Fallible<bool> {
+ // If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
+ if let Some(image) = &self.current_request.borrow().image {
+ if image.width == 0 || image.height == 0 {
+ return Ok(false);
+ }
}
- }
-}
-impl Runnable for ImageResponseHandlerRunnable {
- fn name(&self) -> &'static str { "ImageResponseHandlerRunnable" }
-
- fn handler(self: Box<Self>) {
- let element = self.element.root();
- // Ignore any image response for a previous request that has been discarded.
- if element.generation.get() == self.generation {
- element.process_image_response(self.image);
+ match self.current_request.borrow().state {
+ // If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
+ State::Broken => Err(Error::InvalidState),
+ State::CompletelyAvailable => Ok(true),
+ // If image is not fully decodable, then return bad.
+ State::PartiallyAvailable | State::Unavailable => Ok(false),
}
}
}
@@ -121,11 +187,18 @@ impl Runnable for ImageResponseHandlerRunnable {
/// The context required for asynchronously loading an external image.
struct ImageContext {
/// Reference to the script thread image cache.
- image_cache: Arc<ImageCache>,
+ image_cache: Arc<dyn ImageCache>,
/// Indicates whether the request failed, and why
status: Result<(), NetworkError>,
/// The cache ID for this request.
id: PendingImageId,
+ /// Used to mark abort
+ aborted: bool,
+ /// The document associated with this request
+ doc: Trusted<Document>,
+ /// timing data for this resource
+ resource_timing: ResourceFetchTiming,
+ url: ServoUrl,
}
impl FetchResponseListener for ImageContext {
@@ -133,112 +206,151 @@ impl FetchResponseListener for ImageContext {
fn process_request_eof(&mut self) {}
fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) {
- self.image_cache.notify_pending_response(
- self.id,
- FetchResponseMsg::ProcessResponse(metadata.clone()));
-
- let metadata = metadata.ok().map(|meta| {
- match meta {
- FetchMetadata::Unfiltered(m) => m,
- FetchMetadata::Filtered { unsafe_, .. } => unsafe_
- }
+ debug!("got {:?} for {:?}", metadata.as_ref().map(|_| ()), self.url);
+ self.image_cache
+ .notify_pending_response(self.id, FetchResponseMsg::ProcessResponse(metadata.clone()));
+
+ let metadata = metadata.ok().map(|meta| match meta {
+ FetchMetadata::Unfiltered(m) => m,
+ FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
});
- let status_code = metadata.as_ref().and_then(|m| {
- m.status.as_ref().map(|&(code, _)| code)
- }).unwrap_or(0);
+ // Step 14.5 of https://html.spec.whatwg.org/multipage/#img-environment-changes
+ if let Some(metadata) = metadata.as_ref() {
+ if let Some(ref content_type) = metadata.content_type {
+ let mime: Mime = content_type.clone().into_inner().into();
+ if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" {
+ self.aborted = true;
+ }
+ }
+ }
+
+ let status_code = metadata
+ .as_ref()
+ .and_then(|m| m.status.as_ref().map(|&(code, _)| code))
+ .unwrap_or(0);
self.status = match status_code {
- 0 => Err(NetworkError::Internal("No http status code received".to_owned())),
- 200...299 => Ok(()), // HTTP ok status codes
- _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code)))
+ 0 => Err(NetworkError::Internal(
+ "No http status code received".to_owned(),
+ )),
+ 200..=299 => Ok(()), // HTTP ok status codes
+ _ => Err(NetworkError::Internal(format!(
+ "HTTP error code {}",
+ status_code
+ ))),
};
}
fn process_response_chunk(&mut self, payload: Vec<u8>) {
if self.status.is_ok() {
- self.image_cache.notify_pending_response(
- self.id,
- FetchResponseMsg::ProcessResponseChunk(payload));
+ self.image_cache
+ .notify_pending_response(self.id, FetchResponseMsg::ProcessResponseChunk(payload));
}
}
- fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
- self.image_cache.notify_pending_response(
- self.id,
- FetchResponseMsg::ProcessResponseEOF(response));
+ fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) {
+ self.image_cache
+ .notify_pending_response(self.id, 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 resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
+ &mut self.resource_timing
+ }
- 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();
+ fn resource_timing(&self) -> &ResourceFetchTiming {
+ &self.resource_timing
+ }
- let window = window_from_node(elem);
- let task_source = window.networking_task_source();
- let wrapper = window.get_runnable_wrapper();
- let generation = elem.generation.get();
- ROUTER.add_route(responder_receiver.to_opaque(), box move |message| {
- debug!("Got image {:?}", message);
- // Return the image via a message to the script thread, which marks
- // the element as dirty and triggers a reflow.
- let runnable = ImageResponseHandlerRunnable::new(
- trusted_node.clone(), message.to().unwrap(), generation);
- let _ = task_source.queue_with_wrapper(box runnable, &wrapper);
- });
+ fn submit_resource_timing(&mut self) {
+ network_listener::submit_timing(self)
+ }
+}
- image_cache.add_listener(id, ImageResponder::new(responder_sender, id));
- }
+impl ResourceTimingListener for ImageContext {
+ fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
+ (
+ InitiatorType::LocalName("img".to_string()),
+ self.url.clone(),
+ )
+ }
- let window = window_from_node(self);
- let image_cache = window.image_cache();
- let response =
- image_cache.find_image_or_metadata(img_url.clone().into(),
- UsePlaceholder::Yes,
- CanRequestImages::Yes);
- match response {
- Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => {
- self.process_image_response(ImageResponse::Loaded(image));
- }
+ fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
+ self.doc.root().global()
+ }
+}
- Ok(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
- self.process_image_response(ImageResponse::MetadataLoaded(m));
- }
+impl PreInvoke for ImageContext {
+ fn should_invoke(&self) -> bool {
+ !self.aborted
+ }
+}
- Err(ImageState::Pending(id)) => {
- add_cache_listener_for_element(image_cache.clone(), id, self);
- }
+#[derive(PartialEq)]
+pub(crate) enum FromPictureOrSrcSet {
+ Yes,
+ No,
+}
- Err(ImageState::LoadError) => {
- self.process_image_response(ImageResponse::None);
- }
+// https://html.spec.whatwg.org/multipage/#update-the-image-data steps 17-20
+// This function is also used to prefetch an image in `script::dom::servoparser::prefetch`.
+pub(crate) fn image_fetch_request(
+ img_url: ServoUrl,
+ origin: ImmutableOrigin,
+ referrer: Referrer,
+ pipeline_id: PipelineId,
+ cors_setting: Option<CorsSettings>,
+ referrer_policy: Option<ReferrerPolicy>,
+ from_picture_or_srcset: FromPictureOrSrcSet,
+) -> RequestBuilder {
+ let mut request =
+ create_a_potential_cors_request(img_url, Destination::Image, cors_setting, None, referrer)
+ .origin(origin)
+ .pipeline_id(Some(pipeline_id))
+ .referrer_policy(referrer_policy);
+ if from_picture_or_srcset == FromPictureOrSrcSet::Yes {
+ request = request.initiator(Initiator::ImageSet);
+ }
+ request
+}
- Err(ImageState::NotRequested(id)) => {
- add_cache_listener_for_element(image_cache, id, self);
- self.request_image(img_url, id);
- }
- }
+#[allow(non_snake_case)]
+impl HTMLImageElement {
+ /// Update the current image with a valid URL.
+ fn fetch_image(&self, img_url: &ServoUrl) {
+ let window = window_from_node(self);
+ let image_cache = window.image_cache();
+ let sender = generate_cache_listener_for_element(self);
+ let cache_result = image_cache.track_image(
+ img_url.clone(),
+ window.origin().immutable().clone(),
+ cors_setting_for_element(self.upcast()),
+ sender,
+ UsePlaceholder::Yes,
+ );
+
+ match cache_result {
+ ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
+ image,
+ url,
+ is_placeholder,
+ }) => {
+ if is_placeholder {
+ self.process_image_response(ImageResponse::PlaceholderLoaded(image, url))
+ } else {
+ self.process_image_response(ImageResponse::Loaded(image, url))
+ }
+ },
+ ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
+ self.process_image_response(ImageResponse::MetadataLoaded(m))
+ },
+ ImageCacheResult::Pending(_) => (),
+ ImageCacheResult::ReadyForRequest(id) => self.fetch_request(img_url, id),
+ ImageCacheResult::LoadError => self.process_image_response(ImageResponse::None),
+ };
}
- 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);
@@ -246,62 +358,120 @@ impl HTMLImageElement {
image_cache: window.image_cache(),
status: Ok(()),
id: id,
+ aborted: false,
+ doc: Trusted::new(&document),
+ resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
+ url: img_url.clone(),
}));
let (action_sender, action_receiver) = ipc::channel().unwrap();
+ let (task_source, canceller) = document
+ .window()
+ .task_manager()
+ .networking_task_source_with_canceller();
let listener = NetworkListener {
- context: context,
- task_source: window.networking_task_source(),
- wrapper: Some(window.get_runnable_wrapper()),
- };
- ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
- listener.notify_fetch(message.to().unwrap());
- });
-
- let request = RequestInit {
- url: img_url.clone(),
- origin: document.url().clone(),
- type_: RequestType::Image,
- pipeline_id: Some(document.global().pipeline_id()),
- .. RequestInit::default()
+ context,
+ task_source,
+ canceller: Some(canceller),
};
+ ROUTER.add_route(
+ action_receiver.to_opaque(),
+ Box::new(move |message| {
+ listener.notify_fetch(message.to().unwrap());
+ }),
+ );
+
+ let request = image_fetch_request(
+ img_url.clone(),
+ document.origin().immutable().clone(),
+ document.global().get_referrer(),
+ document.global().pipeline_id(),
+ cors_setting_for_element(self.upcast()),
+ referrer_policy_for_element(self.upcast()),
+ if Self::uses_srcset_or_picture(self.upcast()) {
+ FromPictureOrSrcSet::Yes
+ } else {
+ FromPictureOrSrcSet::No
+ },
+ );
// 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);
+ document
+ .loader_mut()
+ .fetch_async_background(request, action_sender);
}
- fn process_image_response(&self, image: ImageResponse) {
- let (image, metadata, trigger_image_load, trigger_image_error) = match image {
- ImageResponse::Loaded(image) | ImageResponse::PlaceholderLoaded(image) => {
- (Some(image.clone()),
- Some(ImageMetadata { height: image.height, width: image.width }),
- true,
- false)
- }
- ImageResponse::MetadataLoaded(meta) => {
- (None, Some(meta), false, false)
- }
- ImageResponse::None => (None, None, false, true)
- };
- self.current_request.borrow_mut().image = image;
- self.current_request.borrow_mut().metadata = metadata;
-
+ // Steps common to when an image has been loaded.
+ fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl) {
+ 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);
+ }
- // Fire image.onload
+ /// Step 24 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+ fn process_image_response(&self, image: ImageResponse) {
+ // 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) => {
+ self.handle_loaded_image(image, url);
+ (true, false)
+ },
+ (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
+ self.handle_loaded_image(image, url);
+ (false, true)
+ },
+ (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
+ self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ self.image_request.set(ImageRequestPhase::Current);
+ self.handle_loaded_image(image, url);
+ (true, false)
+ },
+ (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
+ self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ self.image_request.set(ImageRequestPhase::Current);
+ self.handle_loaded_image(image, url);
+ (false, true)
+ },
+ (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)
+ },
+ };
+
+ // 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
@@ -309,103 +479,794 @@ 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);
+ fn process_image_response_for_environment_change(
+ &self,
+ image: ImageResponse,
+ src: USVString,
+ generation: u32,
+ selected_pixel_density: f64,
+ ) {
+ match image {
+ ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
+ self.pending_request.borrow_mut().metadata = Some(ImageMetadata {
+ height: image.height,
+ width: image.width,
+ });
+ self.pending_request.borrow_mut().final_url = Some(url);
+ self.pending_request.borrow_mut().image = Some(image);
+ self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
+ },
+ ImageResponse::MetadataLoaded(meta) => {
+ self.pending_request.borrow_mut().metadata = Some(meta);
+ },
+ ImageResponse::None => {
+ self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ },
+ };
+ }
+
+ /// <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;
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#update-the-source-set>
+ fn update_source_set(&self) {
+ // Step 1
+ *self.source_set.borrow_mut() = SourceSet::new();
+
+ // Step 2
+ let elem = self.upcast::<Element>();
+ let parent = elem.upcast::<Node>().GetParentElement();
+ let nodes;
+ let elements = match parent.as_ref() {
+ Some(p) => {
+ if p.is::<HTMLPictureElement>() {
+ nodes = p.upcast::<Node>().children();
+ nodes
+ .filter_map(DomRoot::downcast::<Element>)
+ .map(|n| DomRoot::from_ref(&*n))
+ .collect()
+ } else {
+ vec![DomRoot::from_ref(&*elem)]
+ }
+ },
+ None => vec![DomRoot::from_ref(&*elem)],
+ };
+
+ // Step 3
+ let width = match elem.get_attribute(&ns!(), &local_name!("width")) {
+ Some(x) => match parse_length(&x.value()) {
+ LengthOrPercentageOrAuto::Length(x) => {
+ let abs_length = AbsoluteLength::Px(x.to_f32_px());
+ Some(Length::NoCalc(NoCalcLength::Absolute(abs_length)))
+ },
+ _ => None,
+ },
+ None => None,
+ };
+
+ // Step 4
+ for element in &elements {
+ // Step 4.1
+ if *element == DomRoot::from_ref(&*elem) {
+ let mut source_set = SourceSet::new();
+ // Step 4.1.1
+ if let Some(x) = element.get_attribute(&ns!(), &local_name!("srcset")) {
+ source_set.image_sources = parse_a_srcset_attribute(&x.value());
+ }
+
+ // Step 4.1.2
+ if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) {
+ source_set.source_size =
+ parse_a_sizes_attribute(DOMString::from_string(x.value().to_string()));
+ }
+
+ // Step 4.1.3
+ let src_attribute = element.get_string_attribute(&local_name!("src"));
+ let is_src_empty = src_attribute.is_empty();
+ let no_density_source_of_1 = source_set
+ .image_sources
+ .iter()
+ .all(|source| source.descriptor.den != Some(1.));
+ let no_width_descriptor = source_set
+ .image_sources
+ .iter()
+ .all(|source| source.descriptor.wid.is_none());
+ if !is_src_empty && no_density_source_of_1 && no_width_descriptor {
+ source_set.image_sources.push(ImageSource {
+ url: src_attribute.to_string(),
+ descriptor: Descriptor {
+ wid: None,
+ den: None,
+ },
+ })
+ }
+
+ // Step 4.1.4
+ self.normalise_source_densities(&mut source_set, width);
+
+ // Step 4.1.5
+ *self.source_set.borrow_mut() = source_set;
+
+ // Step 4.1.6
+ return;
+ }
+ // Step 4.2
+ if !element.is::<HTMLSourceElement>() {
+ continue;
+ }
+
+ // Step 4.3 - 4.4
+ let mut source_set = SourceSet::new();
+ match element.get_attribute(&ns!(), &local_name!("srcset")) {
+ Some(x) => {
+ source_set.image_sources = parse_a_srcset_attribute(&x.value());
+ },
+ _ => continue,
+ }
+ // Step 4.5
+ if source_set.image_sources.is_empty() {
+ continue;
+ }
+
+ // Step 4.6
+ if let Some(x) = element.get_attribute(&ns!(), &local_name!("media")) {
+ if !self.matches_environment(x.value().to_string()) {
+ continue;
+ }
+ }
+
+ // Step 4.7
+ if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) {
+ source_set.source_size =
+ parse_a_sizes_attribute(DOMString::from_string(x.value().to_string()));
+ }
+
+ // Step 4.8
+ if let Some(x) = element.get_attribute(&ns!(), &local_name!("type")) {
+ // TODO Handle unsupported mime type
+ let mime = x.value().parse::<Mime>();
+ match mime {
+ Ok(m) => match m.type_() {
+ mime::IMAGE => (),
+ _ => continue,
+ },
+ _ => continue,
+ }
+ }
+
+ // Step 4.9
+ self.normalise_source_densities(&mut source_set, width);
+
+ // Step 4.10
+ *self.source_set.borrow_mut() = source_set;
+ return;
+ }
+ }
+
+ fn evaluate_source_size_list(
+ &self,
+ source_size_list: &mut SourceSizeList,
+ _width: Option<Length>,
+ ) -> Au {
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 device = document.device();
+ let quirks_mode = document.quirks_mode();
+ //FIXME https://github.com/whatwg/html/issues/3832
+ source_size_list.evaluate(&device, quirks_mode)
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#matches-the-environment
+ fn matches_environment(&self, media_query: String) -> bool {
+ let document = document_from_node(self);
+ let quirks_mode = document.quirks_mode();
+ let document_url = &document.url();
+ // FIXME(emilio): This should do the same that we do for other media
+ // lists regarding the rule type and such, though it doesn't really
+ // matter right now...
+ //
+ // Also, ParsingMode::all() is wrong, and should be DEFAULT.
+ let context = ParserContext::new(
+ Origin::Author,
+ document_url,
+ Some(CssRuleType::Style),
+ ParsingMode::all(),
+ quirks_mode,
+ None,
+ None,
+ );
+ let mut parserInput = ParserInput::new(&media_query);
+ let mut parser = Parser::new(&mut parserInput);
+ let media_list = MediaList::parse(&context, &mut parser);
+ media_list.evaluate(&document.device(), quirks_mode)
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#normalise-the-source-densities>
+ fn normalise_source_densities(&self, source_set: &mut SourceSet, width: Option<Length>) {
+ // Step 1
+ let mut source_size = &mut source_set.source_size;
+
+ // Find source_size_length for Step 2.2
+ let source_size_length = self.evaluate_source_size_list(&mut source_size, width);
+
+ // Step 2
+ for imgsource in &mut source_set.image_sources {
+ // Step 2.1
+ if imgsource.descriptor.den.is_some() {
+ continue;
}
- 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,
+ // Step 2.2
+ if imgsource.descriptor.wid.is_some() {
+ let wid = imgsource.descriptor.wid.unwrap();
+ imgsource.descriptor.den = Some(wid as f64 / source_size_length.to_f64_px());
+ } else {
+ //Step 2.3
+ imgsource.descriptor.den = Some(1 as f64);
+ }
+ }
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#select-an-image-source>
+ fn select_image_source(&self) -> Option<(USVString, f64)> {
+ // Step 1, 3
+ self.update_source_set();
+ let source_set = &*self.source_set.borrow_mut();
+ let len = source_set.image_sources.len();
+
+ // Step 2
+ if len == 0 {
+ return None;
+ }
+
+ // Step 4
+ let mut repeat_indices = HashSet::new();
+ for outer_index in 0..len {
+ if repeat_indices.contains(&outer_index) {
+ continue;
+ }
+ let imgsource = &source_set.image_sources[outer_index];
+ let pixel_density = imgsource.descriptor.den.unwrap();
+ for inner_index in (outer_index + 1)..len {
+ let imgsource2 = &source_set.image_sources[inner_index];
+ if pixel_density == imgsource2.descriptor.den.unwrap() {
+ repeat_indices.insert(inner_index);
+ }
+ }
+ }
+
+ let mut max = (0f64, 0);
+ let img_sources = &mut vec![];
+ for (index, image_source) in source_set.image_sources.iter().enumerate() {
+ if repeat_indices.contains(&index) {
+ continue;
+ }
+ let den = image_source.descriptor.den.unwrap();
+ if max.0 < den {
+ max = (den, img_sources.len());
+ }
+ img_sources.push(image_source);
+ }
+
+ // Step 5
+ let mut best_candidate = max;
+ let device = document_from_node(self).device();
+ let device_den = device.device_pixel_ratio().get() as f64;
+ for (index, image_source) in img_sources.iter().enumerate() {
+ let current_den = image_source.descriptor.den.unwrap();
+ if current_den < best_candidate.0 && current_den >= device_den {
+ best_candidate = (current_den, index);
+ }
+ }
+ let selected_source = img_sources.remove(best_candidate.1).clone();
+ Some((
+ USVString(selected_source.url),
+ selected_source.descriptor.den.unwrap() as f64,
+ ))
+ }
+
+ fn init_image_request(
+ &self,
+ request: &mut RefMut<ImageRequest>,
+ url: &ServoUrl,
+ src: &USVString,
+ ) {
+ request.parsed_url = Some(url.clone());
+ request.source_url = Some(src.clone());
+ request.image = None;
+ request.metadata = None;
+ let document = document_from_node(self);
+ LoadBlocker::terminate(&mut request.blocker);
+ request.blocker = Some(LoadBlocker::new(&*document, LoadType::Image(url.clone())));
+ }
+
+ /// Step 13-17 of html.spec.whatwg.org/multipage/#update-the-image-data
+ fn prepare_image_request(&self, url: &ServoUrl, src: &USVString, selected_pixel_density: f64) {
+ match self.image_request.get() {
+ ImageRequestPhase::Pending => {
+ if let Some(pending_url) = self.pending_request.borrow().parsed_url.clone() {
+ // Step 13
+ 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 16, create a new "image_request"
+ match (current_request.parsed_url.clone(), current_request.state) {
+ (Some(parsed_url), State::PartiallyAvailable) => {
+ // Step 14
+ if parsed_url == *url {
+ // Step 15 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;
}
- }
+ pending_request.current_pixel_density = Some(selected_pixel_density);
+ self.image_request.set(ImageRequestPhase::Pending);
+ self.init_image_request(&mut pending_request, &url, &src);
+ },
+ (_, State::Broken) | (_, State::Unavailable) => {
+ // Step 17
+ current_request.current_pixel_density = Some(selected_pixel_density);
+ self.init_image_request(&mut current_request, &url, &src);
+ },
+ (_, _) => {
+ // step 17
+ pending_request.current_pixel_density = Some(selected_pixel_density);
+ 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);
+ let window = document.window();
+ let task_source = window.task_manager().dom_manipulation_task_source();
+ let this = Trusted::new(self);
+ let (src, pixel_density) = match self.select_image_source() {
+ // Step 8
+ Some(data) => data,
+ None => {
+ self.abort_request(State::Broken, ImageRequestPhase::Current);
+ self.abort_request(State::Broken, ImageRequestPhase::Pending);
+ // Step 9.
+ // FIXME(nox): Why are errors silenced here?
+ let _ = task_source.queue(
+ task!(image_null_source_error: move || {
+ let this = this.root();
+ {
+ let mut current_request =
+ this.current_request.borrow_mut();
+ current_request.source_url = None;
+ current_request.parsed_url = None;
+ }
+ let elem = this.upcast::<Element>();
+ let src_present = elem.has_attribute(&local_name!("src"));
- let runnable = box ImgParseErrorRunnable {
- img: Trusted::new(self),
- src: src.into(),
+ if src_present || Self::uses_srcset_or_picture(elem) {
+ this.upcast::<EventTarget>().fire_event(atom!("error"));
+ }
+ }),
+ window.upcast(),
+ );
+ return;
+ },
+ };
+
+ // Step 11
+ let base_url = document.base_url();
+ let parsed_url = base_url.join(&src.0);
+ match parsed_url {
+ Ok(url) => {
+ // Step 13-17
+ self.prepare_image_request(&url, &src, pixel_density);
+ },
+ Err(_) => {
+ self.abort_request(State::Broken, ImageRequestPhase::Current);
+ self.abort_request(State::Broken, ImageRequestPhase::Pending);
+ // Step 12.1-12.5.
+ let src = src.0;
+ // FIXME(nox): Why are errors silenced here?
+ let _ = task_source.queue(
+ task!(image_selected_source_error: move || {
+ let this = this.root();
+ {
+ let mut current_request =
+ this.current_request.borrow_mut();
+ current_request.source_url = Some(USVString(src))
+ }
+ this.upcast::<EventTarget>().fire_event(atom!("error"));
+
+ }),
+ window.upcast(),
+ );
+ },
+ }
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
+ pub 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_url_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
+ let mut selected_source = None;
+ let mut pixel_density = None;
+ let src_set = elem.get_url_attribute(&local_name!("srcset"));
+ let is_parent_picture = elem
+ .upcast::<Node>()
+ .GetParentElement()
+ .map_or(false, |p| p.is::<HTMLPictureElement>());
+ if src_set.is_empty() && !is_parent_picture && !src.is_empty() {
+ selected_source = Some(src.clone());
+ pixel_density = Some(1 as f64);
+ };
+
+ // Step 5
+ *self.last_selected_source.borrow_mut() = selected_source.clone();
+
+ // Step 6, check the list of available images
+ if let Some(src) = selected_source {
+ if let Ok(img_url) = base_url.join(&src) {
+ let image_cache = window.image_cache();
+ let response = image_cache.get_image(
+ img_url.clone(),
+ window.origin().immutable().clone(),
+ cors_setting_for_element(self.upcast()),
+ );
+
+ if let Some(image) = response {
+ // Cancel any outstanding tasks that were queued before the src was
+ // set on this element.
+ self.generation.set(self.generation.get() + 1);
+ // Step 6.3
+ let metadata = ImageMetadata {
+ height: image.height,
+ width: image.width,
};
- let task = window.dom_manipulation_task_source();
- let _ = task.queue(runnable, window.upcast());
+ // Step 6.3.2 abort requests
+ self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current);
+ self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ let mut current_request = self.current_request.borrow_mut();
+ current_request.final_url = Some(img_url.clone());
+ current_request.image = Some(image.clone());
+ current_request.metadata = Some(metadata);
+ // Step 6.3.6
+ current_request.current_pixel_density = pixel_density;
+ let this = Trusted::new(self);
+ let src = src.0;
+ let _ = window.task_manager().dom_manipulation_task_source().queue(
+ task!(image_load_event: move || {
+ let this = this.root();
+ {
+ let mut current_request =
+ this.current_request.borrow_mut();
+ current_request.parsed_url = Some(img_url);
+ current_request.source_url = Some(USVString(src));
+ }
+ // TODO: restart animation, if set.
+ this.upcast::<EventTarget>().fire_event(atom!("load"));
+ }),
+ window.upcast(),
+ );
+ return;
}
}
}
+ // step 7, await a stable state.
+ self.generation.set(self.generation.get() + 1);
+ let task = ImageElementMicrotask::StableStateUpdateImageDataTask {
+ elem: DomRoot::from_ref(self),
+ generation: self.generation.get(),
+ };
+ ScriptThread::await_stable_state(Microtask::ImageElement(task));
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#img-environment-changes>
+ pub fn react_to_environment_changes(&self) {
+ // Step 1
+ let task = ImageElementMicrotask::EnvironmentChangesTask {
+ elem: DomRoot::from_ref(self),
+ generation: self.generation.get(),
+ };
+ ScriptThread::await_stable_state(Microtask::ImageElement(task));
+ }
+
+ /// Step 2-12 of https://html.spec.whatwg.org/multipage/#img-environment-changes
+ fn react_to_environment_changes_sync_steps(&self, generation: u32) {
+ // TODO reduce duplicacy of this code
+
+ fn generate_cache_listener_for_element(
+ elem: &HTMLImageElement,
+ selected_source: String,
+ selected_pixel_density: f64,
+ ) -> IpcSender<PendingImageResponse> {
+ let trusted_node = Trusted::new(elem);
+ let (responder_sender, responder_receiver) = ipc::channel().unwrap();
+
+ let window = window_from_node(elem);
+ let (task_source, canceller) = window
+ .task_manager()
+ .networking_task_source_with_canceller();
+ let generation = elem.generation.get();
+ ROUTER.add_route(
+ responder_receiver.to_opaque(),
+ Box::new(move |message| {
+ debug!("Got image {:?}", message);
+ // Return the image via a message to the script thread, which marks
+ // the element as dirty and triggers a reflow.
+ let element = trusted_node.clone();
+ let image = message.to().unwrap();
+ let selected_source_clone = selected_source.clone();
+ let _ = task_source.queue_with_canceller(
+ task!(process_image_response_for_environment_change: move || {
+ let element = element.root();
+ // Ignore any image response for a previous request that has been discarded.
+ if generation == element.generation.get() {
+ element.process_image_response_for_environment_change(image,
+ USVString::from(selected_source_clone), generation, selected_pixel_density);
+ }
+ }),
+ &canceller,
+ );
+ }),
+ );
+
+ responder_sender
+ }
+
+ let elem = self.upcast::<Element>();
+ let document = document_from_node(elem);
+ let has_pending_request = match self.image_request.get() {
+ ImageRequestPhase::Pending => true,
+ _ => false,
+ };
+
+ // Step 2
+ if !document.is_active() || !Self::uses_srcset_or_picture(elem) || has_pending_request {
+ return;
+ }
+
+ // Steps 3-4
+ let (selected_source, selected_pixel_density) = match self.select_image_source() {
+ Some(selected) => selected,
+ None => return,
+ };
+
+ // Step 5
+ let same_source = match *self.last_selected_source.borrow() {
+ Some(ref last_src) => *last_src == selected_source,
+ _ => false,
+ };
+
+ let same_selected_pixel_density = match self.current_request.borrow().current_pixel_density
+ {
+ Some(den) => selected_pixel_density == den,
+ _ => false,
+ };
+
+ if same_source && same_selected_pixel_density {
+ return;
+ }
+
+ let base_url = document.base_url();
+ // Step 6
+ let img_url = match base_url.join(&selected_source.0) {
+ Ok(url) => url,
+ Err(_) => return,
+ };
+
+ // Step 12
+ self.image_request.set(ImageRequestPhase::Pending);
+ self.init_image_request(
+ &mut self.pending_request.borrow_mut(),
+ &img_url,
+ &selected_source,
+ );
+
+ let window = window_from_node(self);
+ let image_cache = window.image_cache();
+
+ // Step 14
+ let sender = generate_cache_listener_for_element(
+ self,
+ selected_source.0.clone(),
+ selected_pixel_density,
+ );
+ let cache_result = image_cache.track_image(
+ img_url.clone(),
+ window.origin().immutable().clone(),
+ cors_setting_for_element(self.upcast()),
+ sender,
+ UsePlaceholder::No,
+ );
+
+ match cache_result {
+ ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
+ // Step 15
+ self.finish_reacting_to_environment_change(
+ selected_source,
+ generation,
+ selected_pixel_density,
+ )
+ },
+ ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
+ self.process_image_response_for_environment_change(
+ ImageResponse::MetadataLoaded(m),
+ selected_source,
+ generation,
+ selected_pixel_density,
+ );
+ },
+ ImageCacheResult::LoadError => {
+ self.process_image_response_for_environment_change(
+ ImageResponse::None,
+ selected_source,
+ generation,
+ selected_pixel_density,
+ );
+ },
+ ImageCacheResult::ReadyForRequest(id) => self.fetch_request(&img_url, id),
+ ImageCacheResult::Pending(_) => (),
+ }
+ }
+
+ /// Step 15 for <https://html.spec.whatwg.org/multipage/#img-environment-changes>
+ fn finish_reacting_to_environment_change(
+ &self,
+ src: USVString,
+ generation: u32,
+ selected_pixel_density: f64,
+ ) {
+ let this = Trusted::new(self);
+ let window = window_from_node(self);
+ let src = src.0;
+ let _ = window.task_manager().dom_manipulation_task_source().queue(
+ task!(image_load_event: move || {
+ let this = this.root();
+ let relevant_mutation = this.generation.get() != generation;
+ // Step 15.1
+ if relevant_mutation {
+ this.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ return;
+ }
+ // Step 15.2
+ *this.last_selected_source.borrow_mut() = Some(USVString(src));
+
+ {
+ let mut pending_request = this.pending_request.borrow_mut();
+ pending_request.current_pixel_density = Some(selected_pixel_density);
+
+ // Step 15.3
+ pending_request.state = State::CompletelyAvailable;
+
+ // Step 15.4
+ // Already a part of the list of available images due to Step 14
+
+ // Step 15.5
+ mem::swap(&mut this.current_request.borrow_mut(), &mut pending_request);
+ this.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+ }
+
+ // Step 15.6
+ this.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+
+ // Step 15.7
+ this.upcast::<EventTarget>().fire_event(atom!("load"));
+ }),
+ window.upcast(),
+ );
+ }
+
+ fn uses_srcset_or_picture(elem: &Element) -> bool {
+ let has_src = elem.has_attribute(&local_name!("srcset"));
+ let is_parent_picture = elem
+ .upcast::<Node>()
+ .GetParentElement()
+ .map_or(false, |p| p.is::<HTMLPictureElement>());
+ has_src || is_parent_picture
}
- fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLImageElement {
+ fn new_inherited(
+ local_name: LocalName,
+ prefix: Option<Prefix>,
+ document: &Document,
+ ) -> HTMLImageElement {
HTMLImageElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
- current_request: DOMRefCell::new(ImageRequest {
+ 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,
+ current_pixel_density: None,
}),
- pending_request: DOMRefCell::new(ImageRequest {
+ pending_request: DomRefCell::new(ImageRequest {
state: State::Unavailable,
parsed_url: None,
source_url: None,
image: None,
metadata: None,
blocker: None,
+ final_url: None,
+ current_pixel_density: None,
}),
form_owner: Default::default(),
generation: Default::default(),
+ source_set: DomRefCell::new(SourceSet::new()),
+ last_selected_source: DomRefCell::new(None),
}
}
#[allow(unrooted_must_root)]
- pub fn new(local_name: LocalName,
- prefix: Option<DOMString>,
- document: &Document) -> Root<HTMLImageElement> {
- Node::reflect_node(box HTMLImageElement::new_inherited(local_name, prefix, document),
- document,
- HTMLImageElementBinding::Wrap)
- }
-
- pub fn Image(window: &Window,
- width: Option<u32>,
- height: Option<u32>) -> Fallible<Root<HTMLImageElement>> {
- let document = window.Document();
- let image = HTMLImageElement::new(local_name!("img"), None, &document);
+ pub fn new(
+ local_name: LocalName,
+ prefix: Option<Prefix>,
+ document: &Document,
+ ) -> DomRoot<HTMLImageElement> {
+ Node::reflect_node(
+ Box::new(HTMLImageElement::new_inherited(
+ local_name, prefix, document,
+ )),
+ document,
+ )
+ }
+
+ pub fn Image(
+ window: &Window,
+ width: Option<u32>,
+ height: Option<u32>,
+ ) -> Fallible<DomRoot<HTMLImageElement>> {
+ let element = Element::create(
+ QualName::new(None, ns!(html), local_name!("img")),
+ None,
+ &window.Document(),
+ ElementCreator::ScriptCreated,
+ CustomElementCreationMode::Synchronous,
+ );
+
+ let image = DomRoot::downcast::<HTMLImageElement>(element).unwrap();
if let Some(w) = width {
image.SetWidth(w);
}
@@ -415,75 +1276,169 @@ impl HTMLImageElement {
Ok(image)
}
- pub fn areas(&self) -> Option<Vec<Root<HTMLAreaElement>>> {
+ pub fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> {
let elem = self.upcast::<Element>();
- let usemap_attr = match elem.get_attribute(&ns!(), &local_name!("usemap")) {
- Some(attr) => attr,
- None => return None,
- };
+ let usemap_attr = elem.get_attribute(&ns!(), &local_name!("usemap"))?;
let value = usemap_attr.value();
if value.len() == 0 || !value.is_char_boundary(1) {
- return None
+ return None;
}
let (first, last) = value.split_at(1);
if first != "#" || last.len() == 0 {
- return None
+ return None;
}
- let useMapElements = document_from_node(self).upcast::<Node>()
- .traverse_preorder()
- .filter_map(Root::downcast::<HTMLMapElement>)
- .find(|n| n.upcast::<Element>().get_string_attribute(&LocalName::from("name")) == last);
+ let useMapElements = document_from_node(self)
+ .upcast::<Node>()
+ .traverse_preorder(ShadowIncluding::No)
+ .filter_map(DomRoot::downcast::<HTMLMapElement>)
+ .find(|n| {
+ n.upcast::<Element>()
+ .get_name()
+ .map_or(false, |n| *n == *last)
+ });
useMapElements.map(|mapElem| mapElem.get_area_elements())
}
+
+ pub fn same_origin(&self, origin: &MutableOrigin) -> bool {
+ if let Some(ref image) = self.current_request.borrow().image {
+ return image.cors_status == CorsStatus::Safe;
+ }
+
+ self.current_request
+ .borrow()
+ .final_url
+ .as_ref()
+ .map_or(false, |url| {
+ url.scheme() == "data" || url.origin().same_origin(origin)
+ })
+ }
}
-pub trait LayoutHTMLImageElementHelpers {
- #[allow(unsafe_code)]
- unsafe fn image(&self) -> Option<Arc<Image>>;
+#[derive(JSTraceable, MallocSizeOf)]
+pub enum ImageElementMicrotask {
+ StableStateUpdateImageDataTask {
+ elem: DomRoot<HTMLImageElement>,
+ generation: u32,
+ },
+ EnvironmentChangesTask {
+ elem: DomRoot<HTMLImageElement>,
+ generation: u32,
+ },
+}
- #[allow(unsafe_code)]
- unsafe fn image_url(&self) -> Option<ServoUrl>;
+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();
+ }
+ },
+ &ImageElementMicrotask::EnvironmentChangesTask {
+ ref elem,
+ ref generation,
+ } => {
+ elem.react_to_environment_changes_sync_steps(*generation);
+ },
+ }
+ }
+}
- fn get_width(&self) -> LengthOrPercentageOrAuto;
- fn get_height(&self) -> LengthOrPercentageOrAuto;
+pub trait LayoutHTMLImageElementHelpers {
+ fn image(self) -> Option<Arc<Image>>;
+ fn image_url(self) -> Option<ServoUrl>;
+ fn image_density(self) -> Option<f64>;
+ fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>);
+ fn get_width(self) -> LengthOrPercentageOrAuto;
+ fn get_height(self) -> LengthOrPercentageOrAuto;
}
-impl LayoutHTMLImageElementHelpers for LayoutJS<HTMLImageElement> {
+impl<'dom> LayoutDom<'dom, HTMLImageElement> {
#[allow(unsafe_code)]
- unsafe fn image(&self) -> Option<Arc<Image>> {
- (*self.unsafe_get()).current_request.borrow_for_layout().image.clone()
+ fn current_request(self) -> &'dom ImageRequest {
+ unsafe { self.unsafe_get().current_request.borrow_for_layout() }
}
+}
- #[allow(unsafe_code)]
- unsafe fn image_url(&self) -> Option<ServoUrl> {
- (*self.unsafe_get()).current_request.borrow_for_layout().parsed_url.clone()
+impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
+ fn image(self) -> Option<Arc<Image>> {
+ self.current_request().image.clone()
}
- #[allow(unsafe_code)]
- fn get_width(&self) -> LengthOrPercentageOrAuto {
- unsafe {
- (*self.upcast::<Element>().unsafe_get())
- .get_attr_for_layout(&ns!(), &local_name!("width"))
- .map(AttrValue::as_dimension)
- .cloned()
- .unwrap_or(LengthOrPercentageOrAuto::Auto)
- }
+ fn image_url(self) -> Option<ServoUrl> {
+ self.current_request().parsed_url.clone()
}
- #[allow(unsafe_code)]
- fn get_height(&self) -> LengthOrPercentageOrAuto {
- unsafe {
- (*self.upcast::<Element>().unsafe_get())
- .get_attr_for_layout(&ns!(), &local_name!("height"))
- .map(AttrValue::as_dimension)
- .cloned()
- .unwrap_or(LengthOrPercentageOrAuto::Auto)
+ fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>) {
+ let current_request = self.current_request();
+ (
+ current_request.image.clone(),
+ current_request.metadata.clone(),
+ )
+ }
+
+ fn image_density(self) -> Option<f64> {
+ self.current_request().current_pixel_density.clone()
+ }
+
+ fn get_width(self) -> LengthOrPercentageOrAuto {
+ self.upcast::<Element>()
+ .get_attr_for_layout(&ns!(), &local_name!("width"))
+ .map(AttrValue::as_dimension)
+ .cloned()
+ .unwrap_or(LengthOrPercentageOrAuto::Auto)
+ }
+
+ fn get_height(self) -> LengthOrPercentageOrAuto {
+ self.upcast::<Element>()
+ .get_attr_for_layout(&ns!(), &local_name!("height"))
+ .map(AttrValue::as_dimension)
+ .cloned()
+ .unwrap_or(LengthOrPercentageOrAuto::Auto)
+ }
+}
+
+//https://html.spec.whatwg.org/multipage/#parse-a-sizes-attribute
+pub fn parse_a_sizes_attribute(value: DOMString) -> SourceSizeList {
+ let mut input = ParserInput::new(&value);
+ let mut parser = Parser::new(&mut input);
+ let url = ServoUrl::parse("about:blank").unwrap();
+ let context = ParserContext::new(
+ Origin::Author,
+ &url,
+ Some(CssRuleType::Style),
+ // FIXME(emilio): why ::empty() instead of ::DEFAULT? Also, what do
+ // browsers do regarding quirks-mode in a media list?
+ ParsingMode::empty(),
+ QuirksMode::NoQuirks,
+ None,
+ None,
+ );
+ SourceSizeList::parse(&context, &mut parser)
+}
+
+fn get_correct_referrerpolicy_from_raw_token(token: &DOMString) -> DOMString {
+ if token == "" {
+ // Empty token is treated as no-referrer inside determine_policy_for_token,
+ // while here it should be treated as the default value, so it should remain unchanged.
+ DOMString::new()
+ } else {
+ match determine_policy_for_token(token) {
+ Some(policy) => DOMString::from_string(policy.to_string()),
+ // If the policy is set to an incorrect value, then it should be
+ // treated as an invalid value default (empty string).
+ None => DOMString::new(),
}
}
}
@@ -496,8 +1451,14 @@ impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-src
make_url_getter!(Src, "src");
+
// https://html.spec.whatwg.org/multipage/#dom-img-src
- make_setter!(SetSrc, "src");
+ make_url_setter!(SetSrc, "src");
+
+ // https://html.spec.whatwg.org/multipage/#dom-img-srcset
+ make_url_getter!(Srcset, "srcset");
+ // https://html.spec.whatwg.org/multipage/#dom-img-src
+ make_url_setter!(SetSrcset, "srcset");
// https://html.spec.whatwg.org/multipage/#dom-img-crossOrigin
fn GetCrossOrigin(&self) -> Option<DOMString> {
@@ -549,36 +1510,80 @@ impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-naturalwidth
fn NaturalWidth(&self) -> u32 {
- let ref metadata = self.current_request.borrow().metadata;
+ let request = self.current_request.borrow();
+ let pixel_density = request.current_pixel_density.unwrap_or(1f64);
- match *metadata {
- Some(ref metadata) => metadata.width,
+ match request.metadata {
+ Some(ref metadata) => (metadata.width as f64 / pixel_density) as u32,
None => 0,
}
}
// https://html.spec.whatwg.org/multipage/#dom-img-naturalheight
fn NaturalHeight(&self) -> u32 {
- let ref metadata = self.current_request.borrow().metadata;
+ let request = self.current_request.borrow();
+ let pixel_density = request.current_pixel_density.unwrap_or(1f64);
- match *metadata {
- Some(ref metadata) => metadata.height,
+ match request.metadata {
+ Some(ref metadata) => (metadata.height as f64 / pixel_density) as u32,
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>();
+ let srcset_absent = !elem.has_attribute(&local_name!("srcset"));
+ if !elem.has_attribute(&local_name!("src")) && srcset_absent {
+ return true;
+ }
+ let src = elem.get_string_attribute(&local_name!("src"));
+ if srcset_absent && 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;
+ fn CurrentSrc(&self) -> USVString {
+ let current_request = self.current_request.borrow();
+ let ref url = current_request.parsed_url;
match *url {
- Some(ref url) => url.clone(),
- None => DOMString::from(""),
+ Some(ref url) => USVString(url.clone().into_string()),
+ None => {
+ let ref unparsed_url = current_request.source_url;
+ match *unparsed_url {
+ Some(ref url) => url.clone(),
+ None => USVString("".to_owned()),
+ }
+ },
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy
+ fn ReferrerPolicy(&self) -> DOMString {
+ let element = self.upcast::<Element>();
+ let current_policy_value = element.get_string_attribute(&local_name!("referrerpolicy"));
+ get_correct_referrerpolicy_from_raw_token(&current_policy_value)
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy
+ fn SetReferrerPolicy(&self, value: DOMString) {
+ let referrerpolicy_attr_name = local_name!("referrerpolicy");
+ let element = self.upcast::<Element>();
+ let previous_correct_attribute_value = get_correct_referrerpolicy_from_raw_token(
+ &element.get_string_attribute(&referrerpolicy_attr_name),
+ );
+ let correct_value_or_empty_string = get_correct_referrerpolicy_from_raw_token(&value);
+ if previous_correct_attribute_value != correct_value_or_empty_string {
+ // Setting the attribute to the same value will update the image.
+ // We don't want to start an update if referrerpolicy is set to the same value.
+ element.set_string_attribute(&referrerpolicy_attr_name, correct_value_or_empty_string);
}
}
@@ -620,82 +1625,112 @@ impl HTMLImageElementMethods for HTMLImageElement {
}
impl VirtualMethods for HTMLImageElement {
- fn super_type(&self) -> Option<&VirtualMethods> {
- Some(self.upcast::<HTMLElement>() as &VirtualMethods)
+ fn super_type(&self) -> Option<&dyn VirtualMethods> {
+ Some(self.upcast::<HTMLElement>() as &dyn 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") |
+ &local_name!("srcset") |
+ &local_name!("width") |
+ &local_name!("crossorigin") |
+ &local_name!("sizes") |
+ &local_name!("referrerpolicy") => 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()),
+ &local_name!("width") | &local_name!("height") => {
+ AttrValue::from_dimension(value.into())
+ },
&local_name!("hspace") | &local_name!("vspace") => AttrValue::from_u32(value.into(), 0),
- _ => self.super_type().unwrap().parse_plain_attribute(name, value),
+ _ => self
+ .super_type()
+ .unwrap()
+ .parse_plain_attribute(name, value),
}
}
fn handle_event(&self, event: &Event) {
- if event.type_() == atom!("click") {
- let area_elements = self.areas();
- let elements = if let Some(x) = area_elements {
- x
- } else {
- return
- };
-
- // Fetch click coordinates
- let mouse_event = if let Some(x) = event.downcast::<MouseEvent>() {
- x
- } else {
- return;
- };
-
- let point = Point2D::new(mouse_event.ClientX().to_f32().unwrap(),
- mouse_event.ClientY().to_f32().unwrap());
-
- // Walk HTMLAreaElements
- for element in elements {
- let shape = element.get_shape_from_coords();
- let p = Point2D::new(self.upcast::<Element>().GetBoundingClientRect().X() as f32,
- self.upcast::<Element>().GetBoundingClientRect().Y() as f32);
-
- let shp = if let Some(x) = shape {
- x.absolute_coords(p)
- } else {
- return
- };
- if shp.hit_test(point) {
- element.activation_behavior(event, self.upcast());
- return
- }
- }
- }
+ if event.type_() != atom!("click") {
+ return;
+ }
+
+ let area_elements = self.areas();
+ let elements = match area_elements {
+ Some(x) => x,
+ None => return,
+ };
+
+ // Fetch click coordinates
+ let mouse_event = match event.downcast::<MouseEvent>() {
+ Some(x) => x,
+ None => return,
+ };
+
+ let point = Point2D::new(
+ mouse_event.ClientX().to_f32().unwrap(),
+ mouse_event.ClientY().to_f32().unwrap(),
+ );
+ let bcr = self.upcast::<Element>().GetBoundingClientRect();
+ let bcr_p = Point2D::new(bcr.X() as f32, bcr.Y() as f32);
+
+ // Walk HTMLAreaElements
+ for element in elements {
+ let shape = element.get_shape_from_coords();
+ let shp = match shape {
+ Some(x) => x.absolute_coords(bcr_p),
+ None => return,
+ };
+ if shp.hit_test(&point) {
+ element.activation_behavior(event, self.upcast());
+ return;
+ }
+ }
+ }
+
+ fn bind_to_tree(&self, context: &BindContext) {
+ if let Some(ref s) = self.super_type() {
+ s.bind_to_tree(context);
+ }
+ let document = document_from_node(self);
+ if context.tree_connected {
+ document.register_responsive_image(self);
+ }
+
+ // The element is inserted into a picture parent element
+ // https://html.spec.whatwg.org/multipage/#relevant-mutations
+ if let Some(parent) = self.upcast::<Node>().GetParentElement() {
+ if parent.is::<HTMLPictureElement>() {
+ self.update_the_image_data();
+ }
+ }
+ }
+
+ fn unbind_from_tree(&self, context: &UnbindContext) {
+ self.super_type().unwrap().unbind_from_tree(context);
+ let document = document_from_node(self);
+ document.unregister_responsive_image(self);
+
+ // The element is removed from a picture parent element
+ // https://html.spec.whatwg.org/multipage/#relevant-mutations
+ if context.parent.is::<HTMLPictureElement>() {
+ self.update_the_image_data();
+ }
}
}
impl FormControl for HTMLImageElement {
- fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
+ fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
@@ -712,14 +1747,20 @@ impl FormControl for HTMLImageElement {
}
}
+impl ImageCacheListener for HTMLImageElement {
+ fn generation_id(&self) -> u32 {
+ self.generation.get()
+ }
+
+ fn process_image_response(&self, response: ImageResponse) {
+ self.process_image_response(response);
+ }
+}
+
fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) {
// This setter is a bit weird: the IDL type is unsigned long, but it's parsed as
// a dimension for rendering.
- let value = if value > UNSIGNED_LONG_MAX {
- 0
- } else {
- value
- };
+ let value = if value > UNSIGNED_LONG_MAX { 0 } else { value };
// FIXME: There are probably quite a few more cases of this. This is the
// only overflow that was hitting on automation, but we should consider what
@@ -736,3 +1777,183 @@ fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) {
let value = AttrValue::Dimension(value.to_string(), dim);
element.set_attribute(&attr, value);
}
+
+/// Collect sequence of code points
+pub fn collect_sequence_characters<F>(s: &str, predicate: F) -> (&str, &str)
+where
+ F: Fn(&char) -> bool,
+{
+ for (i, ch) in s.chars().enumerate() {
+ if !predicate(&ch) {
+ return (&s[0..i], &s[i..]);
+ }
+ }
+
+ return (s, "");
+}
+
+/// Parse an `srcset` attribute - https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute.
+pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
+ let mut url_len = 0;
+ let mut candidates: Vec<ImageSource> = vec![];
+ while url_len < input.len() {
+ let position = &input[url_len..];
+ let (spaces, position) =
+ collect_sequence_characters(position, |c| *c == ',' || char::is_whitespace(*c));
+ // add the length of the url that we parse to advance the start index
+ let space_len = spaces.char_indices().count();
+ url_len += space_len;
+ if position.is_empty() {
+ return candidates;
+ }
+ let (url, spaces) = collect_sequence_characters(position, |c| !char::is_whitespace(*c));
+ // add the counts of urls that we parse to advance the start index
+ url_len += url.chars().count();
+ let comma_count = url.chars().rev().take_while(|c| *c == ',').count();
+ let url: String = url
+ .chars()
+ .take(url.chars().count() - comma_count)
+ .collect();
+ // add 1 to start index, for the comma
+ url_len += comma_count + 1;
+ let (space, position) = collect_sequence_characters(spaces, |c| char::is_whitespace(*c));
+ let space_len = space.len();
+ url_len += space_len;
+ let mut descriptors = Vec::new();
+ let mut current_descriptor = String::new();
+ let mut state = ParseState::InDescriptor;
+ let mut char_stream = position.chars().enumerate();
+ let mut buffered: Option<(usize, char)> = None;
+ loop {
+ let next_char = buffered.take().or_else(|| char_stream.next());
+ if next_char.is_some() {
+ url_len += 1;
+ }
+ match state {
+ ParseState::InDescriptor => match next_char {
+ Some((_, ' ')) => {
+ if !current_descriptor.is_empty() {
+ descriptors.push(current_descriptor.clone());
+ current_descriptor = String::new();
+ state = ParseState::AfterDescriptor;
+ }
+ continue;
+ },
+ Some((_, ',')) => {
+ if !current_descriptor.is_empty() {
+ descriptors.push(current_descriptor.clone());
+ }
+ break;
+ },
+ Some((_, c @ '(')) => {
+ current_descriptor.push(c);
+ state = ParseState::InParens;
+ continue;
+ },
+ Some((_, c)) => {
+ current_descriptor.push(c);
+ },
+ None => {
+ if !current_descriptor.is_empty() {
+ descriptors.push(current_descriptor.clone());
+ }
+ break;
+ },
+ },
+ ParseState::InParens => match next_char {
+ Some((_, c @ ')')) => {
+ current_descriptor.push(c);
+ state = ParseState::InDescriptor;
+ continue;
+ },
+ Some((_, c)) => {
+ current_descriptor.push(c);
+ continue;
+ },
+ None => {
+ if !current_descriptor.is_empty() {
+ descriptors.push(current_descriptor.clone());
+ }
+ break;
+ },
+ },
+ ParseState::AfterDescriptor => match next_char {
+ Some((_, ' ')) => {
+ state = ParseState::AfterDescriptor;
+ continue;
+ },
+ Some((idx, c)) => {
+ state = ParseState::InDescriptor;
+ buffered = Some((idx, c));
+ continue;
+ },
+ None => {
+ if !current_descriptor.is_empty() {
+ descriptors.push(current_descriptor.clone());
+ }
+ break;
+ },
+ },
+ }
+ }
+
+ let mut error = false;
+ let mut width: Option<u32> = None;
+ let mut density: Option<f64> = None;
+ let mut future_compat_h: Option<u32> = None;
+ for descriptor in descriptors {
+ let (digits, remaining) =
+ collect_sequence_characters(&descriptor, |c| is_ascii_digit(c) || *c == '.');
+ let valid_non_negative_integer = parse_unsigned_integer(digits.chars());
+ let has_w = remaining == "w";
+ let valid_floating_point = parse_double(digits);
+ let has_x = remaining == "x";
+ let has_h = remaining == "h";
+ if valid_non_negative_integer.is_ok() && has_w {
+ let result = valid_non_negative_integer;
+ error = result.is_err();
+ if width.is_some() || density.is_some() {
+ error = true;
+ }
+ if let Ok(w) = result {
+ width = Some(w);
+ }
+ } else if valid_floating_point.is_ok() && has_x {
+ let result = valid_floating_point;
+ error = result.is_err();
+ if width.is_some() || density.is_some() || future_compat_h.is_some() {
+ error = true;
+ }
+ if let Ok(x) = result {
+ density = Some(x);
+ }
+ } else if valid_non_negative_integer.is_ok() && has_h {
+ let result = valid_non_negative_integer;
+ error = result.is_err();
+ if density.is_some() || future_compat_h.is_some() {
+ error = true;
+ }
+ if let Ok(h) = result {
+ future_compat_h = Some(h);
+ }
+ } else {
+ error = true;
+ }
+ }
+ if future_compat_h.is_some() && width.is_none() {
+ error = true;
+ }
+ if !error {
+ let descriptor = Descriptor {
+ wid: width,
+ den: density,
+ };
+ let image_source = ImageSource {
+ url: url,
+ descriptor: descriptor,
+ };
+ candidates.push(image_source);
+ }
+ }
+ candidates
+}