Auto merge of #21280 - paavininanda:Update_the_image_data, r=jdm

Reacting to environment changes

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix a subset of #11416

<!-- Either: -->
- [x] These changes do not require tests because they will be added when the complete responsive images is complete

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21280)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-09-12 10:47:13 -04:00 committed by GitHub
commit 9a83ab6297
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 308 additions and 1935 deletions

View file

@ -392,6 +392,8 @@ pub struct Document {
salvageable: Cell<bool>,
/// Whether the unload event has already been fired.
fired_unload: Cell<bool>,
/// List of responsive images
responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>,
}
#[derive(JSTraceable, MallocSizeOf)]
@ -2221,6 +2223,23 @@ impl Document {
let counter = self.throw_on_dynamic_markup_insertion_counter.get();
self.throw_on_dynamic_markup_insertion_counter.set(counter - 1);
}
pub fn react_to_environment_changes(&self) {
for image in self.responsive_images.borrow().iter() {
image.react_to_environment_changes();
}
}
pub fn register_responsive_image(&self, img: &HTMLImageElement) {
self.responsive_images.borrow_mut().push(Dom::from_ref(img));
}
pub fn unregister_responsive_image(&self, img: &HTMLImageElement) {
let index = self.responsive_images.borrow().iter().position(|x| **x == *img);
if let Some(i) = index {
self.responsive_images.borrow_mut().remove(i);
}
}
}
#[derive(MallocSizeOf, PartialEq)]
@ -2465,7 +2484,8 @@ impl Document {
throw_on_dynamic_markup_insertion_counter: Cell::new(0),
page_showing: Cell::new(false),
salvageable: Cell::new(true),
fired_unload: Cell::new(false)
fired_unload: Cell::new(false),
responsive_images: Default::default()
}
}

View file

@ -33,7 +33,7 @@ use dom::htmlmapelement::HTMLMapElement;
use dom::htmlpictureelement::HTMLPictureElement;
use dom::htmlsourceelement::HTMLSourceElement;
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, UnbindContext};
use dom::progressevent::ProgressEvent;
use dom::values::UNSIGNED_LONG_MAX;
use dom::virtualmethods::VirtualMethods;
@ -44,7 +44,7 @@ use html5ever::{LocalName, Prefix};
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use microtask::{Microtask, MicrotaskRunnable};
use mime::{Mime, TopLevel};
use mime::{Mime, TopLevel, SubLevel};
use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable};
@ -61,6 +61,7 @@ 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, parse_double, parse_length, parse_unsigned_integer};
use style::context::QuirksMode;
@ -160,6 +161,8 @@ struct ImageContext {
status: Result<(), NetworkError>,
/// The cache ID for this request.
id: PendingImageId,
/// Used to mark abort
aborted: Cell<bool>,
}
impl FetchResponseListener for ImageContext {
@ -178,6 +181,20 @@ impl FetchResponseListener for ImageContext {
}
});
// 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 {
match content_type.clone().into_inner().0 {
Mime(TopLevel::Multipart, SubLevel::Ext(s), _) => {
if s == "x-mixed-replace" {
self.aborted.set(true);
}
},
_ => ()
}
}
}
let status_code = metadata.as_ref().and_then(|m| {
m.status.as_ref().map(|&(code, _)| code)
}).unwrap_or(0);
@ -204,7 +221,11 @@ impl FetchResponseListener for ImageContext {
}
}
impl PreInvoke for ImageContext {}
impl PreInvoke for ImageContext {
fn should_invoke(&self) -> bool {
!self.aborted.get()
}
}
impl HTMLImageElement {
/// Update the current image with a valid URL.
@ -279,6 +300,7 @@ impl HTMLImageElement {
image_cache: window.image_cache(),
status: Ok(()),
id: id,
aborted: Cell::new(false),
}));
let (action_sender, action_receiver) = ipc::channel().unwrap();
@ -380,6 +402,30 @@ impl HTMLImageElement {
window.add_pending_reflow();
}
fn process_image_response_for_environment_change(&self,
image: ImageResponse,
src: DOMString,
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 {
@ -590,7 +636,7 @@ impl HTMLImageElement {
}
/// <https://html.spec.whatwg.org/multipage/#select-an-image-source>
fn select_image_source(&self) -> Option<(DOMString, f32)> {
fn select_image_source(&self) -> Option<(DOMString, f64)> {
// Step 1, 3
self.update_source_set();
let source_set = &*self.source_set.borrow_mut();
@ -643,7 +689,7 @@ impl HTMLImageElement {
}
}
let selected_source = img_sources.remove(best_candidate.1).clone();
Some((DOMString::from_string(selected_source.url), selected_source.descriptor.den.unwrap() as f32))
Some((DOMString::from_string(selected_source.url), selected_source.descriptor.den.unwrap() as f64))
}
fn init_image_request(&self,
@ -659,12 +705,12 @@ impl HTMLImageElement {
request.blocker = Some(LoadBlocker::new(&*document, LoadType::Image(url.clone())));
}
/// Step 12 of html.spec.whatwg.org/multipage/#update-the-image-data
/// Step 13-17 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
// Step 13
if pending_url == *url {
return
}
@ -673,12 +719,12 @@ impl HTMLImageElement {
ImageRequestPhase::Current => {
let mut current_request = self.current_request.borrow_mut();
let mut pending_request = self.pending_request.borrow_mut();
// step 12.4, create a new "image_request"
// step 16, create a new "image_request"
match (current_request.parsed_url.clone(), current_request.state) {
(Some(parsed_url), State::PartiallyAvailable) => {
// Step 12.2
// Step 14
if parsed_url == *url {
// 12.3 abort pending request
// Step 15 abort pending request
pending_request.image = None;
pending_request.parsed_url = None;
LoadBlocker::terminate(&mut pending_request.blocker);
@ -689,11 +735,11 @@ impl HTMLImageElement {
self.init_image_request(&mut pending_request, &url, &src);
},
(_, State::Broken) | (_, State::Unavailable) => {
// Step 12.5
// Step 17
self.init_image_request(&mut current_request, &url, &src);
},
(_, _) => {
// step 12.6
// step 17
self.image_request.set(ImageRequestPhase::Pending);
self.init_image_request(&mut pending_request, &url, &src);
},
@ -711,7 +757,7 @@ impl HTMLImageElement {
let this = Trusted::new(self);
let src = match self.select_image_source() {
Some(src) => {
// Step 8.
// Step 8
// TODO: Handle pixel density.
src.0
},
@ -727,7 +773,10 @@ impl HTMLImageElement {
current_request.source_url = None;
current_request.parsed_url = None;
}
if this.upcast::<Element>().has_attribute(&local_name!("src")) {
let elem = this.upcast::<Element>();
let src_present = elem.has_attribute(&local_name!("src"));
if src_present || Self::uses_srcset_or_picture(elem) {
this.upcast::<EventTarget>().fire_event(atom!("error"));
}
// FIXME(nox): According to the spec, setting the current
@ -766,11 +815,11 @@ impl HTMLImageElement {
let parsed_url = base_url.join(&src);
match parsed_url {
Ok(url) => {
// Step 12
// Step 13-17
self.prepare_image_request(&url, &src);
},
Err(_) => {
// Step 11.1-11.5.
// Step 12.1-12.5.
let src = String::from(src);
// FIXME(nox): Why are errors silenced here?
let _ = task_source.queue(
@ -882,6 +931,183 @@ impl HTMLImageElement {
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 add_cache_listener_for_element(image_cache: Arc<ImageCache>,
id: PendingImageId,
elem: &HTMLImageElement,
selected_source: String,
selected_pixel_density: f64) {
let trusted_node = Trusted::new(elem);
let (responder_sender, responder_receiver) = ipc::channel().unwrap();
let window = window_from_node(elem);
let task_source = window.networking_task_source();
let task_canceller = window.task_canceller(TaskSourceName::Networking);
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,
DOMString::from_string(selected_source_clone), generation, selected_pixel_density);
}
}),
&task_canceller,
);
}));
image_cache.add_listener(id, ImageResponder::new(responder_sender, id));
}
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) {
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 response =
image_cache.find_image_or_metadata(img_url.clone().into(),
UsePlaceholder::No,
CanRequestImages::Yes);
match response {
Ok(ImageOrMetadataAvailable::ImageAvailable(_image, _url)) => {
// Step 15
self.finish_reacting_to_environment_change(selected_source, generation, selected_pixel_density);
}
Ok(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
self.process_image_response_for_environment_change(ImageResponse::MetadataLoaded(m),
selected_source, generation, selected_pixel_density);
}
Err(ImageState::Pending(id)) => {
add_cache_listener_for_element(image_cache.clone(), id, self,
selected_source.to_string(), selected_pixel_density);
}
Err(ImageState::LoadError) => {
self.process_image_response_for_environment_change(ImageResponse::None,
selected_source, generation, selected_pixel_density);
}
Err(ImageState::NotRequested(id)) => {
add_cache_listener_for_element(image_cache, id, self, selected_source.to_string(),
selected_pixel_density);
self.fetch_request(&img_url, id);
}
}
}
/// Step 15 for <https://html.spec.whatwg.org/multipage/#img-environment-changes>
fn finish_reacting_to_environment_change(&self, src: DOMString, generation: u32, selected_pixel_density: f64) {
let this = Trusted::new(self);
let window = window_from_node(self);
let src = src.to_string();
let _ = window.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(DOMString::from_string(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<Prefix>, document: &Document) -> HTMLImageElement {
HTMLImageElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
@ -974,6 +1200,10 @@ pub enum ImageElementMicrotask {
StableStateUpdateImageDataTask {
elem: DomRoot<HTMLImageElement>,
generation: u32,
},
EnvironmentChangesTask {
elem: DomRoot<HTMLImageElement>,
generation: u32,
}
}
@ -987,6 +1217,9 @@ impl MicrotaskRunnable for ImageElementMicrotask {
elem.update_the_image_data_sync_steps();
}
},
&ImageElementMicrotask::EnvironmentChangesTask { ref elem, ref generation } => {
elem.react_to_environment_changes_sync_steps(*generation);
},
}
}
}
@ -1156,11 +1389,12 @@ impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-currentsrc
fn CurrentSrc(&self) -> DOMString {
let ref url = self.current_request.borrow().parsed_url;
let current_request = self.current_request.borrow();
let ref url = current_request.parsed_url;
match *url {
Some(ref url) => DOMString::from_string(url.clone().into_string()),
None => {
let ref unparsed_url = self.current_request.borrow().source_url;
let ref unparsed_url = current_request.source_url;
match *unparsed_url {
Some(ref url) => url.clone(),
None => DOMString::from("")
@ -1220,8 +1454,14 @@ impl VirtualMethods for HTMLImageElement {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match attr.local_name() {
&local_name!("src") => self.update_the_image_data(),
&local_name!("srcset") => self.update_the_image_data(),
_ => {},
}
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
if parent.is::<HTMLPictureElement>() {
self.update_the_image_data();
}
}
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
@ -1268,6 +1508,27 @@ impl VirtualMethods for HTMLImageElement {
}
}
}
fn bind_to_tree(&self, tree_in_doc: bool) {
if let Some(ref s) = self.super_type() {
s.bind_to_tree(tree_in_doc);
}
let document = document_from_node(self);
if tree_in_doc {
document.register_responsive_image(self);
}
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);
}
}
impl FormControl for HTMLImageElement {

View file

@ -1861,6 +1861,7 @@ impl Window {
mql.Matches());
event.upcast::<Event>().fire(mql.upcast::<EventTarget>());
}
self.Document().react_to_environment_changes();
}
/// Slow down/speed up timers based on visibility.