script/dom/html/
htmlimageelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::default::Default;
7use std::rc::Rc;
8use std::sync::{Arc, LazyLock};
9use std::{char, mem};
10
11use app_units::Au;
12use cssparser::{Parser, ParserInput};
13use dom_struct::dom_struct;
14use euclid::default::Point2D;
15use html5ever::{LocalName, Prefix, QualName, local_name, ns};
16use js::jsapi::JSAutoRealm;
17use js::rust::HandleObject;
18use mime::{self, Mime};
19use net_traits::http_status::HttpStatus;
20use net_traits::image_cache::{
21    Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
22    ImageResponse, PendingImageId,
23};
24use net_traits::request::{CorsSettings, Destination, Initiator, RequestId};
25use net_traits::{
26    FetchMetadata, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming,
27};
28use num_traits::ToPrimitive;
29use pixels::{CorsStatus, ImageMetadata, Snapshot};
30use regex::Regex;
31use rustc_hash::FxHashSet;
32use servo_url::ServoUrl;
33use servo_url::origin::MutableOrigin;
34use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer};
35use style::context::QuirksMode;
36use style::parser::ParserContext;
37use style::stylesheets::{CssRuleType, Origin};
38use style::values::specified::source_size_list::SourceSizeList;
39use style_traits::ParsingMode;
40use url::Url;
41
42use crate::document_loader::{LoadBlocker, LoadType};
43use crate::dom::activation::Activatable;
44use crate::dom::attr::Attr;
45use crate::dom::bindings::cell::{DomRefCell, RefMut};
46use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRect_Binding::DOMRectMethods;
47use crate::dom::bindings::codegen::Bindings::ElementBinding::Element_Binding::ElementMethods;
48use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
49use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
50use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
51use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
52use crate::dom::bindings::error::{Error, Fallible};
53use crate::dom::bindings::inheritance::Castable;
54use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
55use crate::dom::bindings::reflector::DomGlobal;
56use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
57use crate::dom::bindings::str::{DOMString, USVString};
58use crate::dom::csp::{GlobalCspReporting, Violation};
59use crate::dom::document::Document;
60use crate::dom::element::{
61    AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
62    cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute,
63    reflect_referrer_policy_attribute, set_cross_origin_attribute,
64};
65use crate::dom::event::Event;
66use crate::dom::eventtarget::EventTarget;
67use crate::dom::globalscope::GlobalScope;
68use crate::dom::html::htmlareaelement::HTMLAreaElement;
69use crate::dom::html::htmlelement::HTMLElement;
70use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
71use crate::dom::html::htmlmapelement::HTMLMapElement;
72use crate::dom::html::htmlpictureelement::HTMLPictureElement;
73use crate::dom::html::htmlsourceelement::HTMLSourceElement;
74use crate::dom::medialist::MediaList;
75use crate::dom::mouseevent::MouseEvent;
76use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext};
77use crate::dom::performance::performanceresourcetiming::InitiatorType;
78use crate::dom::promise::Promise;
79use crate::dom::virtualmethods::VirtualMethods;
80use crate::dom::window::Window;
81use crate::fetch::create_a_potential_cors_request;
82use crate::microtask::{Microtask, MicrotaskRunnable};
83use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
84use crate::realms::enter_realm;
85use crate::script_runtime::CanGc;
86use crate::script_thread::ScriptThread;
87
88/// Supported image MIME types as defined by
89/// <https://mimesniff.spec.whatwg.org/#image-mime-type>.
90/// Keep this in sync with 'detect_image_format' from components/pixels/lib.rs
91const SUPPORTED_IMAGE_MIME_TYPES: &[&str] = &[
92    "image/bmp",
93    "image/gif",
94    "image/jpeg",
95    "image/jpg",
96    "image/pjpeg",
97    "image/png",
98    "image/apng",
99    "image/x-png",
100    "image/svg+xml",
101    "image/vnd.microsoft.icon",
102    "image/x-icon",
103    "image/webp",
104];
105
106#[derive(Clone, Copy, Debug)]
107enum ParseState {
108    InDescriptor,
109    InParens,
110    AfterDescriptor,
111}
112
113/// <https://html.spec.whatwg.org/multipage/#source-set>
114#[derive(MallocSizeOf)]
115pub(crate) struct SourceSet {
116    image_sources: Vec<ImageSource>,
117    source_size: SourceSizeList,
118}
119
120impl SourceSet {
121    fn new() -> SourceSet {
122        SourceSet {
123            image_sources: Vec::new(),
124            source_size: SourceSizeList::empty(),
125        }
126    }
127}
128
129#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
130pub struct ImageSource {
131    pub url: String,
132    pub descriptor: Descriptor,
133}
134
135#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
136pub struct Descriptor {
137    pub width: Option<u32>,
138    pub density: Option<f64>,
139}
140
141#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
142enum State {
143    Unavailable,
144    PartiallyAvailable,
145    CompletelyAvailable,
146    Broken,
147}
148
149#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
150enum ImageRequestPhase {
151    Pending,
152    Current,
153}
154#[derive(JSTraceable, MallocSizeOf)]
155#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
156struct ImageRequest {
157    state: State,
158    #[no_trace]
159    parsed_url: Option<ServoUrl>,
160    source_url: Option<USVString>,
161    blocker: DomRefCell<Option<LoadBlocker>>,
162    #[no_trace]
163    image: Option<Image>,
164    #[no_trace]
165    metadata: Option<ImageMetadata>,
166    #[no_trace]
167    final_url: Option<ServoUrl>,
168    current_pixel_density: Option<f64>,
169}
170#[dom_struct]
171pub(crate) struct HTMLImageElement {
172    htmlelement: HTMLElement,
173    image_request: Cell<ImageRequestPhase>,
174    current_request: DomRefCell<ImageRequest>,
175    pending_request: DomRefCell<ImageRequest>,
176    form_owner: MutNullableDom<HTMLFormElement>,
177    generation: Cell<u32>,
178    source_set: DomRefCell<SourceSet>,
179    /// <https://html.spec.whatwg.org/multipage/#concept-img-dimension-attribute-source>
180    /// Always non-null after construction.
181    dimension_attribute_source: MutNullableDom<Element>,
182    last_selected_source: DomRefCell<Option<USVString>>,
183    #[conditional_malloc_size_of]
184    image_decode_promises: DomRefCell<Vec<Rc<Promise>>>,
185    /// Line number this element was created on
186    line_number: u64,
187}
188
189impl HTMLImageElement {
190    // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
191    pub(crate) fn is_usable(&self) -> Fallible<bool> {
192        // If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
193        if let Some(image) = &self.current_request.borrow().image {
194            let intrinsic_size = image.metadata();
195            if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
196                return Ok(false);
197            }
198        }
199
200        match self.current_request.borrow().state {
201            // If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
202            State::Broken => Err(Error::InvalidState(None)),
203            State::CompletelyAvailable => Ok(true),
204            // If image is not fully decodable, then return bad.
205            State::PartiallyAvailable | State::Unavailable => Ok(false),
206        }
207    }
208
209    pub(crate) fn image_data(&self) -> Option<Image> {
210        self.current_request.borrow().image.clone()
211    }
212
213    /// Gets the copy of the raster image data.
214    pub(crate) fn get_raster_image_data(&self) -> Option<Snapshot> {
215        let Some(raster_image) = self.image_data()?.as_raster_image() else {
216            warn!("Vector image is not supported as raster image source");
217            return None;
218        };
219        Some(raster_image.as_snapshot())
220    }
221}
222
223/// The context required for asynchronously loading an external image.
224struct ImageContext {
225    /// Reference to the script thread image cache.
226    image_cache: Arc<dyn ImageCache>,
227    /// Indicates whether the request failed, and why
228    status: Result<(), NetworkError>,
229    /// The cache ID for this request.
230    id: PendingImageId,
231    /// Used to mark abort
232    aborted: bool,
233    /// The document associated with this request
234    doc: Trusted<Document>,
235    url: ServoUrl,
236    element: Trusted<HTMLImageElement>,
237}
238
239impl FetchResponseListener for ImageContext {
240    fn should_invoke(&self) -> bool {
241        !self.aborted
242    }
243
244    fn process_request_body(&mut self, _: RequestId) {}
245    fn process_request_eof(&mut self, _: RequestId) {}
246
247    fn process_response(
248        &mut self,
249        request_id: RequestId,
250        metadata: Result<FetchMetadata, NetworkError>,
251    ) {
252        debug!("got {:?} for {:?}", metadata.as_ref().map(|_| ()), self.url);
253        self.image_cache.notify_pending_response(
254            self.id,
255            FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
256        );
257
258        let metadata = metadata.ok().map(|meta| match meta {
259            FetchMetadata::Unfiltered(m) => m,
260            FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
261        });
262
263        // Step 14.5 of https://html.spec.whatwg.org/multipage/#img-environment-changes
264        if let Some(metadata) = metadata.as_ref() {
265            if let Some(ref content_type) = metadata.content_type {
266                let mime: Mime = content_type.clone().into_inner().into();
267                if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" {
268                    self.aborted = true;
269                }
270            }
271        }
272
273        let status = metadata
274            .as_ref()
275            .map(|m| m.status.clone())
276            .unwrap_or_else(HttpStatus::new_error);
277
278        self.status = {
279            if status.is_error() {
280                Err(NetworkError::Internal(
281                    "No http status code received".to_owned(),
282                ))
283            } else if status.is_success() {
284                Ok(())
285            } else {
286                Err(NetworkError::Internal(format!(
287                    "HTTP error code {}",
288                    status.code()
289                )))
290            }
291        };
292    }
293
294    fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
295        if self.status.is_ok() {
296            self.image_cache.notify_pending_response(
297                self.id,
298                FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
299            );
300        }
301    }
302
303    fn process_response_eof(
304        self,
305        request_id: RequestId,
306        response: Result<ResourceFetchTiming, NetworkError>,
307    ) {
308        self.image_cache.notify_pending_response(
309            self.id,
310            FetchResponseMsg::ProcessResponseEOF(request_id, response.clone()),
311        );
312        if let Ok(response) = response {
313            network_listener::submit_timing(&self, &response, CanGc::note());
314        }
315    }
316
317    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
318        let global = &self.resource_timing_global();
319        let elem = self.element.root();
320        let source_position = elem
321            .upcast::<Element>()
322            .compute_source_position(elem.line_number as u32);
323        global.report_csp_violations(violations, None, Some(source_position));
324    }
325}
326
327impl ResourceTimingListener for ImageContext {
328    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
329        (
330            InitiatorType::LocalName("img".to_string()),
331            self.url.clone(),
332        )
333    }
334
335    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
336        self.doc.root().global()
337    }
338}
339
340#[allow(non_snake_case)]
341impl HTMLImageElement {
342    /// Update the current image with a valid URL.
343    fn fetch_image(&self, img_url: &ServoUrl, can_gc: CanGc) {
344        let window = self.owner_window();
345
346        let cache_result = window.image_cache().get_cached_image_status(
347            img_url.clone(),
348            window.origin().immutable().clone(),
349            cors_setting_for_element(self.upcast()),
350        );
351
352        match cache_result {
353            ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
354                image,
355                url,
356            }) => self.process_image_response(ImageResponse::Loaded(image, url), can_gc),
357            ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
358                metadata,
359                id,
360            )) => {
361                self.process_image_response(ImageResponse::MetadataLoaded(metadata), can_gc);
362                self.register_image_cache_callback(id, ChangeType::Element);
363            },
364            ImageCacheResult::Pending(id) => {
365                self.register_image_cache_callback(id, ChangeType::Element);
366            },
367            ImageCacheResult::ReadyForRequest(id) => {
368                self.fetch_request(img_url, id);
369                self.register_image_cache_callback(id, ChangeType::Element);
370            },
371            ImageCacheResult::FailedToLoadOrDecode => {
372                self.process_image_response(ImageResponse::FailedToLoadOrDecode, can_gc)
373            },
374        };
375    }
376
377    fn register_image_cache_callback(&self, id: PendingImageId, change_type: ChangeType) {
378        let trusted_node = Trusted::new(self);
379        let generation = self.generation_id();
380        let window = self.owner_window();
381        let callback = window.register_image_cache_listener(id, move |response| {
382            let trusted_node = trusted_node.clone();
383            let window = trusted_node.root().owner_window();
384            let callback_type = change_type.clone();
385
386            window
387                .as_global_scope()
388                .task_manager()
389                .networking_task_source()
390                .queue(task!(process_image_response: move || {
391                let element = trusted_node.root();
392
393                // Ignore any image response for a previous request that has been discarded.
394                if generation != element.generation_id() {
395                    return;
396                }
397
398                match callback_type {
399                    ChangeType::Element => {
400                        element.process_image_response(response.response, CanGc::note());
401                    }
402                    ChangeType::Environment { selected_source, selected_pixel_density } => {
403                        element.process_image_response_for_environment_change(
404                            response.response, selected_source, generation, selected_pixel_density, CanGc::note()
405                        );
406                    }
407                }
408            }));
409        });
410
411        window.image_cache().add_listener(ImageLoadListener::new(
412            callback,
413            window.pipeline_id(),
414            id,
415        ));
416    }
417
418    fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
419        let document = self.owner_document();
420        let window = self.owner_window();
421
422        let context = ImageContext {
423            image_cache: window.image_cache(),
424            status: Ok(()),
425            id,
426            aborted: false,
427            doc: Trusted::new(&document),
428            element: Trusted::new(self),
429            url: img_url.clone(),
430        };
431
432        // https://html.spec.whatwg.org/multipage/#update-the-image-data steps 17-20
433        // This function is also used to prefetch an image in `script::dom::servoparser::prefetch`.
434        let global = document.global();
435        let mut request = create_a_potential_cors_request(
436            Some(window.webview_id()),
437            img_url.clone(),
438            Destination::Image,
439            cors_setting_for_element(self.upcast()),
440            None,
441            global.get_referrer(),
442            document.insecure_requests_policy(),
443            document.has_trustworthy_ancestor_or_current_origin(),
444            global.policy_container(),
445        )
446        .origin(document.origin().immutable().clone())
447        .pipeline_id(Some(document.global().pipeline_id()))
448        .referrer_policy(referrer_policy_for_element(self.upcast()));
449
450        if Self::uses_srcset_or_picture(self.upcast()) {
451            request = request.initiator(Initiator::ImageSet);
452        }
453
454        // This is a background load because the load blocker already fulfills the
455        // purpose of delaying the document's load event.
456        document.fetch_background(request, context);
457    }
458
459    // Steps common to when an image has been loaded.
460    fn handle_loaded_image(&self, image: Image, url: ServoUrl, can_gc: CanGc) {
461        self.current_request.borrow_mut().metadata = Some(image.metadata());
462        self.current_request.borrow_mut().final_url = Some(url);
463        self.current_request.borrow_mut().image = Some(image);
464        self.current_request.borrow_mut().state = State::CompletelyAvailable;
465        LoadBlocker::terminate(&self.current_request.borrow().blocker, can_gc);
466        // Mark the node dirty
467        self.upcast::<Node>().dirty(NodeDamage::Other);
468        self.resolve_image_decode_promises();
469    }
470
471    /// Step 24 of <https://html.spec.whatwg.org/multipage/#update-the-image-data>
472    fn process_image_response(&self, image: ImageResponse, can_gc: CanGc) {
473        // TODO: Handle multipart/x-mixed-replace
474        let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
475            (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) => {
476                self.handle_loaded_image(image, url, can_gc);
477                (true, false)
478            },
479            (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
480                self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
481                self.image_request.set(ImageRequestPhase::Current);
482                self.handle_loaded_image(image, url, can_gc);
483                (true, false)
484            },
485            (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
486                self.current_request.borrow_mut().state = State::PartiallyAvailable;
487                self.current_request.borrow_mut().metadata = Some(meta);
488                (false, false)
489            },
490            (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
491                self.pending_request.borrow_mut().state = State::PartiallyAvailable;
492                (false, false)
493            },
494            (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Current) => {
495                self.abort_request(State::Broken, ImageRequestPhase::Current, can_gc);
496
497                self.load_broken_image_icon();
498
499                (false, true)
500            },
501            (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Pending) => {
502                self.abort_request(State::Broken, ImageRequestPhase::Current, can_gc);
503                self.abort_request(State::Broken, ImageRequestPhase::Pending, can_gc);
504                self.image_request.set(ImageRequestPhase::Current);
505
506                self.load_broken_image_icon();
507
508                (false, true)
509            },
510        };
511
512        // Fire image.onload and loadend
513        if trigger_image_load {
514            // TODO: https://html.spec.whatwg.org/multipage/#fire-a-progress-event-or-event
515            self.upcast::<EventTarget>()
516                .fire_event(atom!("load"), can_gc);
517            self.upcast::<EventTarget>()
518                .fire_event(atom!("loadend"), can_gc);
519        }
520
521        // Fire image.onerror
522        if trigger_image_error {
523            self.upcast::<EventTarget>()
524                .fire_event(atom!("error"), can_gc);
525            self.upcast::<EventTarget>()
526                .fire_event(atom!("loadend"), can_gc);
527        }
528    }
529
530    /// The response part of
531    /// <https://html.spec.whatwg.org/multipage/#reacting-to-environment-changes>.
532    fn process_image_response_for_environment_change(
533        &self,
534        image: ImageResponse,
535        src: USVString,
536        generation: u32,
537        selected_pixel_density: f64,
538        can_gc: CanGc,
539    ) {
540        match image {
541            ImageResponse::Loaded(image, url) => {
542                self.pending_request.borrow_mut().metadata = Some(image.metadata());
543                self.pending_request.borrow_mut().final_url = Some(url);
544                self.pending_request.borrow_mut().image = Some(image);
545                self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
546            },
547            ImageResponse::FailedToLoadOrDecode => {
548                // > Step 15.6: If response's unsafe response is a network error or if the
549                // > image format is unsupported (as determined by applying the image
550                // > sniffing rules, again as mentioned earlier), or if the user agent is
551                // > able to determine that image request's image is corrupted in some fatal
552                // > way such that the image dimensions cannot be obtained, or if the
553                // > resource type is multipart/x-mixed-replace, then set the pending
554                // > request to null and abort these steps.
555                self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
556            },
557            ImageResponse::MetadataLoaded(meta) => {
558                self.pending_request.borrow_mut().metadata = Some(meta);
559            },
560        };
561    }
562
563    /// <https://html.spec.whatwg.org/multipage/#abort-the-image-request>
564    fn abort_request(&self, state: State, request: ImageRequestPhase, can_gc: CanGc) {
565        let mut request = match request {
566            ImageRequestPhase::Current => self.current_request.borrow_mut(),
567            ImageRequestPhase::Pending => self.pending_request.borrow_mut(),
568        };
569        LoadBlocker::terminate(&request.blocker, can_gc);
570        request.state = state;
571        request.image = None;
572        request.metadata = None;
573
574        if matches!(state, State::Broken) {
575            self.reject_image_decode_promises();
576        } else if matches!(state, State::CompletelyAvailable) {
577            self.resolve_image_decode_promises();
578        }
579    }
580
581    /// <https://html.spec.whatwg.org/multipage/#create-a-source-set>
582    fn create_source_set(&self) -> SourceSet {
583        let element = self.upcast::<Element>();
584
585        // Step 1. Let source set be an empty source set.
586        let mut source_set = SourceSet::new();
587
588        // Step 2. If srcset is not an empty string, then set source set to the result of parsing
589        // srcset.
590        if let Some(srcset) = element.get_attribute(&ns!(), &local_name!("srcset")) {
591            source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
592        }
593
594        // Step 3. Set source set's source size to the result of parsing sizes with img.
595        if let Some(sizes) = element.get_attribute(&ns!(), &local_name!("sizes")) {
596            source_set.source_size = parse_a_sizes_attribute(&sizes.value());
597        }
598
599        // Step 4. If default source is not the empty string and source set does not contain an
600        // image source with a pixel density descriptor value of 1, and no image source with a width
601        // descriptor, append default source to source set.
602        let src_attribute = element.get_string_attribute(&local_name!("src"));
603        let is_src_empty = src_attribute.is_empty();
604        let no_density_source_of_1 = source_set
605            .image_sources
606            .iter()
607            .all(|source| source.descriptor.density != Some(1.));
608        let no_width_descriptor = source_set
609            .image_sources
610            .iter()
611            .all(|source| source.descriptor.width.is_none());
612        if !is_src_empty && no_density_source_of_1 && no_width_descriptor {
613            source_set.image_sources.push(ImageSource {
614                url: src_attribute.to_string(),
615                descriptor: Descriptor {
616                    width: None,
617                    density: None,
618                },
619            })
620        }
621
622        // Step 5. Normalize the source densities of source set.
623        self.normalise_source_densities(&mut source_set);
624
625        // Step 6. Return source set.
626        source_set
627    }
628
629    /// <https://html.spec.whatwg.org/multipage/#update-the-source-set>
630    fn update_source_set(&self) {
631        // Step 1. Set el's source set to an empty source set.
632        *self.source_set.borrow_mut() = SourceSet::new();
633
634        // Step 2. Let elements be « el ».
635        // Step 3. If el is an img element whose parent node is a picture element, then replace the
636        // contents of elements with el's parent node's child elements, retaining relative order.
637        // Step 4. Let img be el if el is an img element, otherwise null.
638        let elem = self.upcast::<Element>();
639        let parent = elem.upcast::<Node>().GetParentElement();
640        let elements = match parent.as_ref() {
641            Some(p) => {
642                if p.is::<HTMLPictureElement>() {
643                    p.upcast::<Node>()
644                        .children()
645                        .filter_map(DomRoot::downcast::<Element>)
646                        .map(|n| DomRoot::from_ref(&*n))
647                        .collect()
648                } else {
649                    vec![DomRoot::from_ref(elem)]
650                }
651            },
652            None => vec![DomRoot::from_ref(elem)],
653        };
654
655        // Step 5. For each child in elements:
656        for element in &elements {
657            // Step 5.1. If child is el:
658            if *element == DomRoot::from_ref(elem) {
659                // Step 5.1.10. Set el's source set to the result of creating a source set given
660                // default source, srcset, sizes, and img.
661                *self.source_set.borrow_mut() = self.create_source_set();
662
663                // Step 5.1.11. Return.
664                return;
665            }
666
667            // Step 5.2. If child is not a source element, then continue.
668            if !element.is::<HTMLSourceElement>() {
669                continue;
670            }
671
672            let mut source_set = SourceSet::new();
673
674            // Step 5.3. If child does not have a srcset attribute, continue to the next child.
675            // Step 5.4. Parse child's srcset attribute and let source set be the returned source
676            // set.
677            match element.get_attribute(&ns!(), &local_name!("srcset")) {
678                Some(srcset) => {
679                    source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
680                },
681                _ => continue,
682            }
683
684            // Step 5.5. If source set has zero image sources, continue to the next child.
685            if source_set.image_sources.is_empty() {
686                continue;
687            }
688
689            // Step 5.6. If child has a media attribute, and its value does not match the
690            // environment, continue to the next child.
691            if let Some(media) = element.get_attribute(&ns!(), &local_name!("media")) {
692                if !MediaList::matches_environment(&element.owner_document(), &media.value()) {
693                    continue;
694                }
695            }
696
697            // Step 5.7. Parse child's sizes attribute with img, and let source set's source size be
698            // the returned value.
699            if let Some(sizes) = element.get_attribute(&ns!(), &local_name!("sizes")) {
700                source_set.source_size = parse_a_sizes_attribute(&sizes.value());
701            }
702
703            // Step 5.8. If child has a type attribute, and its value is an unknown or unsupported
704            // MIME type, continue to the next child.
705            if let Some(type_) = element.get_attribute(&ns!(), &local_name!("type")) {
706                if !is_supported_image_mime_type(&type_.value()) {
707                    continue;
708                }
709            }
710
711            // Step 5.9. If child has width or height attributes, set el's dimension attribute
712            // source to child. Otherwise, set el's dimension attribute source to el.
713            if element
714                .get_attribute(&ns!(), &local_name!("width"))
715                .is_some() ||
716                element
717                    .get_attribute(&ns!(), &local_name!("height"))
718                    .is_some()
719            {
720                self.dimension_attribute_source.set(Some(element));
721            } else {
722                self.dimension_attribute_source.set(Some(elem));
723            }
724
725            // Step 5.10. Normalize the source densities of source set.
726            self.normalise_source_densities(&mut source_set);
727
728            // Step 5.11. Set el's source set to source set.
729            *self.source_set.borrow_mut() = source_set;
730
731            // Step 5.12. Return.
732            return;
733        }
734    }
735
736    fn evaluate_source_size_list(&self, source_size_list: &SourceSizeList) -> Au {
737        let document = self.owner_document();
738        let quirks_mode = document.quirks_mode();
739        source_size_list.evaluate(document.window().layout().device(), quirks_mode)
740    }
741
742    /// <https://html.spec.whatwg.org/multipage/#normalise-the-source-densities>
743    fn normalise_source_densities(&self, source_set: &mut SourceSet) {
744        // Step 1. Let source size be source set's source size.
745        let source_size = self.evaluate_source_size_list(&source_set.source_size);
746
747        // Step 2. For each image source in source set:
748        for image_source in &mut source_set.image_sources {
749            // Step 2.1. If the image source has a pixel density descriptor, continue to the next
750            // image source.
751            if image_source.descriptor.density.is_some() {
752                continue;
753            }
754
755            // Step 2.2. Otherwise, if the image source has a width descriptor, replace the width
756            // descriptor with a pixel density descriptor with a value of the width descriptor value
757            // divided by source size and a unit of x.
758            if image_source.descriptor.width.is_some() {
759                let width = image_source.descriptor.width.unwrap();
760                image_source.descriptor.density = Some(width as f64 / source_size.to_f64_px());
761            } else {
762                // Step 2.3. Otherwise, give the image source a pixel density descriptor of 1x.
763                image_source.descriptor.density = Some(1_f64);
764            }
765        }
766    }
767
768    /// <https://html.spec.whatwg.org/multipage/#select-an-image-source>
769    fn select_image_source(&self) -> Option<(USVString, f64)> {
770        // Step 1. Update the source set for el.
771        self.update_source_set();
772
773        // Step 2. If el's source set is empty, return null as the URL and undefined as the pixel
774        // density.
775        if self.source_set.borrow().image_sources.is_empty() {
776            return None;
777        }
778
779        // Step 3. Return the result of selecting an image from el's source set.
780        self.select_image_source_from_source_set()
781    }
782
783    /// <https://html.spec.whatwg.org/multipage/#select-an-image-source-from-a-source-set>
784    fn select_image_source_from_source_set(&self) -> Option<(USVString, f64)> {
785        // Step 1. If an entry b in sourceSet has the same associated pixel density descriptor as an
786        // earlier entry a in sourceSet, then remove entry b. Repeat this step until none of the
787        // entries in sourceSet have the same associated pixel density descriptor as an earlier
788        // entry.
789        let source_set = self.source_set.borrow();
790        let len = source_set.image_sources.len();
791
792        // Using FxHash is ok here as the indices are just 0..len
793        let mut repeat_indices = FxHashSet::default();
794        for outer_index in 0..len {
795            if repeat_indices.contains(&outer_index) {
796                continue;
797            }
798            let imgsource = &source_set.image_sources[outer_index];
799            let pixel_density = imgsource.descriptor.density.unwrap();
800            for inner_index in (outer_index + 1)..len {
801                let imgsource2 = &source_set.image_sources[inner_index];
802                if pixel_density == imgsource2.descriptor.density.unwrap() {
803                    repeat_indices.insert(inner_index);
804                }
805            }
806        }
807
808        let mut max = (0f64, 0);
809        let img_sources = &mut vec![];
810        for (index, image_source) in source_set.image_sources.iter().enumerate() {
811            if repeat_indices.contains(&index) {
812                continue;
813            }
814            let den = image_source.descriptor.density.unwrap();
815            if max.0 < den {
816                max = (den, img_sources.len());
817            }
818            img_sources.push(image_source);
819        }
820
821        // Step 2. In an implementation-defined manner, choose one image source from sourceSet. Let
822        // selectedSource be this choice.
823        let mut best_candidate = max;
824        let device_pixel_ratio = self
825            .owner_document()
826            .window()
827            .viewport_details()
828            .hidpi_scale_factor
829            .get() as f64;
830        for (index, image_source) in img_sources.iter().enumerate() {
831            let current_den = image_source.descriptor.density.unwrap();
832            if current_den < best_candidate.0 && current_den >= device_pixel_ratio {
833                best_candidate = (current_den, index);
834            }
835        }
836        let selected_source = img_sources.remove(best_candidate.1).clone();
837
838        // Step 3. Return selectedSource and its associated pixel density.
839        Some((
840            USVString(selected_source.url),
841            selected_source.descriptor.density.unwrap(),
842        ))
843    }
844
845    fn init_image_request(
846        &self,
847        request: &mut RefMut<'_, ImageRequest>,
848        url: &ServoUrl,
849        src: &USVString,
850        can_gc: CanGc,
851    ) {
852        request.parsed_url = Some(url.clone());
853        request.source_url = Some(src.clone());
854        request.image = None;
855        request.metadata = None;
856        let document = self.owner_document();
857        LoadBlocker::terminate(&request.blocker, can_gc);
858        *request.blocker.borrow_mut() =
859            Some(LoadBlocker::new(&document, LoadType::Image(url.clone())));
860    }
861
862    /// Step 13-17 of <https://html.spec.whatwg.org/multipage/#update-the-image-data>.
863    fn prepare_image_request(
864        &self,
865        url: &ServoUrl,
866        src: &USVString,
867        selected_pixel_density: f64,
868        can_gc: CanGc,
869    ) {
870        match self.image_request.get() {
871            ImageRequestPhase::Pending => {
872                if let Some(pending_url) = self.pending_request.borrow().parsed_url.clone() {
873                    // Step 13
874                    if pending_url == *url {
875                        return;
876                    }
877                }
878            },
879            ImageRequestPhase::Current => {
880                let mut current_request = self.current_request.borrow_mut();
881                let mut pending_request = self.pending_request.borrow_mut();
882                // step 16, create a new "image_request"
883                match (current_request.parsed_url.clone(), current_request.state) {
884                    (Some(parsed_url), State::PartiallyAvailable) => {
885                        // Step 14
886                        if parsed_url == *url {
887                            // Step 15 abort pending request
888                            pending_request.image = None;
889                            pending_request.parsed_url = None;
890                            LoadBlocker::terminate(&pending_request.blocker, can_gc);
891                            // TODO: queue a task to restart animation, if restart-animation is set
892                            return;
893                        }
894                        pending_request.current_pixel_density = Some(selected_pixel_density);
895                        self.image_request.set(ImageRequestPhase::Pending);
896                        self.init_image_request(&mut pending_request, url, src, can_gc);
897                    },
898                    (_, State::Broken) | (_, State::Unavailable) => {
899                        // Step 17
900                        current_request.current_pixel_density = Some(selected_pixel_density);
901                        self.init_image_request(&mut current_request, url, src, can_gc);
902                        self.reject_image_decode_promises();
903                    },
904                    (_, _) => {
905                        // step 17
906                        pending_request.current_pixel_density = Some(selected_pixel_density);
907                        self.image_request.set(ImageRequestPhase::Pending);
908                        self.init_image_request(&mut pending_request, url, src, can_gc);
909                    },
910                }
911            },
912        }
913        self.fetch_image(url, can_gc);
914    }
915
916    /// Step 8-12 of html.spec.whatwg.org/multipage/#update-the-image-data
917    fn update_the_image_data_sync_steps(&self, can_gc: CanGc) {
918        let document = self.owner_document();
919        let global = self.owner_global();
920        let task_manager = global.task_manager();
921        let task_source = task_manager.dom_manipulation_task_source();
922        let this = Trusted::new(self);
923        let (src, pixel_density) = match self.select_image_source() {
924            // Step 8
925            Some(data) => data,
926            None => {
927                self.abort_request(State::Broken, ImageRequestPhase::Current, can_gc);
928                self.abort_request(State::Broken, ImageRequestPhase::Pending, can_gc);
929                // Step 9.
930                task_source.queue(task!(image_null_source_error: move || {
931                    let this = this.root();
932                    {
933                        let mut current_request =
934                            this.current_request.borrow_mut();
935                        current_request.source_url = None;
936                        current_request.parsed_url = None;
937                    }
938                    let elem = this.upcast::<Element>();
939                    let src_present = elem.has_attribute(&local_name!("src"));
940
941                    if src_present || Self::uses_srcset_or_picture(elem) {
942                        this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::note());
943                    }
944                }));
945                return;
946            },
947        };
948
949        // Step 11
950        let base_url = document.base_url();
951        let parsed_url = base_url.join(&src.0);
952        match parsed_url {
953            Ok(url) => {
954                // Step 13-17
955                self.prepare_image_request(&url, &src, pixel_density, can_gc);
956            },
957            Err(_) => {
958                self.abort_request(State::Broken, ImageRequestPhase::Current, can_gc);
959                self.abort_request(State::Broken, ImageRequestPhase::Pending, can_gc);
960                // Step 12.1-12.5.
961                let src = src.0;
962                task_source.queue(task!(image_selected_source_error: move || {
963                    let this = this.root();
964                    {
965                        let mut current_request =
966                            this.current_request.borrow_mut();
967                        current_request.source_url = Some(USVString(src))
968                    }
969                    this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::note());
970
971                }));
972            },
973        }
974    }
975
976    /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
977    pub(crate) fn update_the_image_data(&self, can_gc: CanGc) {
978        let document = self.owner_document();
979        let window = document.window();
980        let elem = self.upcast::<Element>();
981        let src = elem.get_url_attribute(&local_name!("src"));
982        let base_url = document.base_url();
983
984        // https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations
985        // Always first set the current request to unavailable,
986        // ensuring img.complete is false.
987        {
988            let mut current_request = self.current_request.borrow_mut();
989            current_request.state = State::Unavailable;
990        }
991
992        if !document.is_active() {
993            // Step 1 (if the document is inactive)
994            // TODO: use GlobalScope::enqueue_microtask,
995            // to queue micro task to come back to this algorithm
996        }
997        // Step 2 abort if user-agent does not supports images
998        // NOTE: Servo only supports images, skipping this step
999
1000        // Step 3, 4
1001        let mut selected_source = None;
1002        let mut pixel_density = None;
1003        let src_set = elem.get_url_attribute(&local_name!("srcset"));
1004        let is_parent_picture = elem
1005            .upcast::<Node>()
1006            .GetParentElement()
1007            .is_some_and(|p| p.is::<HTMLPictureElement>());
1008        if src_set.is_empty() && !is_parent_picture && !src.is_empty() {
1009            selected_source = Some(src.clone());
1010            pixel_density = Some(1_f64);
1011        };
1012
1013        // Step 5
1014        self.last_selected_source
1015            .borrow_mut()
1016            .clone_from(&selected_source);
1017
1018        // Step 6, check the list of available images
1019        if let Some(src) = selected_source {
1020            if let Ok(img_url) = base_url.join(&src) {
1021                let image_cache = window.image_cache();
1022                let response = image_cache.get_image(
1023                    img_url.clone(),
1024                    window.origin().immutable().clone(),
1025                    cors_setting_for_element(self.upcast()),
1026                );
1027
1028                if let Some(image) = response {
1029                    // Cancel any outstanding tasks that were queued before the src was
1030                    // set on this element.
1031                    self.generation.set(self.generation.get() + 1);
1032                    // Step 6.3
1033                    let metadata = image.metadata();
1034                    // Step 6.3.2 abort requests
1035                    self.abort_request(
1036                        State::CompletelyAvailable,
1037                        ImageRequestPhase::Current,
1038                        can_gc,
1039                    );
1040                    self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
1041                    let mut current_request = self.current_request.borrow_mut();
1042                    current_request.final_url = Some(img_url.clone());
1043                    current_request.image = Some(image);
1044                    current_request.metadata = Some(metadata);
1045                    // Step 6.3.6
1046                    current_request.current_pixel_density = pixel_density;
1047                    let this = Trusted::new(self);
1048                    let src = src.0;
1049
1050                    self.owner_global()
1051                        .task_manager()
1052                        .dom_manipulation_task_source()
1053                        .queue(task!(image_load_event: move || {
1054                            let this = this.root();
1055                            {
1056                                let mut current_request =
1057                                    this.current_request.borrow_mut();
1058                                current_request.parsed_url = Some(img_url);
1059                                current_request.source_url = Some(USVString(src));
1060                            }
1061                            // TODO: restart animation, if set.
1062                            this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::note());
1063                        }));
1064                    return;
1065                }
1066            }
1067        }
1068        // step 7, await a stable state.
1069        self.generation.set(self.generation.get() + 1);
1070        let task = ImageElementMicrotask::StableStateUpdateImageData {
1071            elem: DomRoot::from_ref(self),
1072            generation: self.generation.get(),
1073        };
1074        ScriptThread::await_stable_state(Microtask::ImageElement(task));
1075    }
1076
1077    /// <https://html.spec.whatwg.org/multipage/#img-environment-changes>
1078    pub(crate) fn react_to_environment_changes(&self) {
1079        // Step 1
1080        let task = ImageElementMicrotask::EnvironmentChanges {
1081            elem: DomRoot::from_ref(self),
1082            generation: self.generation.get(),
1083        };
1084        ScriptThread::await_stable_state(Microtask::ImageElement(task));
1085    }
1086
1087    /// Step 2-12 of <https://html.spec.whatwg.org/multipage/#img-environment-changes>
1088    fn react_to_environment_changes_sync_steps(&self, generation: u32, can_gc: CanGc) {
1089        let elem = self.upcast::<Element>();
1090        let document = elem.owner_document();
1091        let has_pending_request = matches!(self.image_request.get(), ImageRequestPhase::Pending);
1092
1093        // Step 2
1094        if !document.is_active() || !Self::uses_srcset_or_picture(elem) || has_pending_request {
1095            return;
1096        }
1097
1098        // Steps 3-4
1099        let (selected_source, selected_pixel_density) = match self.select_image_source() {
1100            Some(selected) => selected,
1101            None => return,
1102        };
1103
1104        // Step 5
1105        let same_source = match *self.last_selected_source.borrow() {
1106            Some(ref last_src) => *last_src == selected_source,
1107            _ => false,
1108        };
1109
1110        let same_selected_pixel_density = match self.current_request.borrow().current_pixel_density
1111        {
1112            Some(den) => selected_pixel_density == den,
1113            _ => false,
1114        };
1115
1116        if same_source && same_selected_pixel_density {
1117            return;
1118        }
1119
1120        let base_url = document.base_url();
1121        // Step 6
1122        let img_url = match base_url.join(&selected_source.0) {
1123            Ok(url) => url,
1124            Err(_) => return,
1125        };
1126
1127        // Step 12
1128        self.image_request.set(ImageRequestPhase::Pending);
1129        self.init_image_request(
1130            &mut self.pending_request.borrow_mut(),
1131            &img_url,
1132            &selected_source,
1133            can_gc,
1134        );
1135
1136        // Step 14
1137        let window = self.owner_window();
1138        let cache_result = window.image_cache().get_cached_image_status(
1139            img_url.clone(),
1140            window.origin().immutable().clone(),
1141            cors_setting_for_element(self.upcast()),
1142        );
1143
1144        let change_type = ChangeType::Environment {
1145            selected_source: selected_source.clone(),
1146            selected_pixel_density,
1147        };
1148
1149        match cache_result {
1150            ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
1151                // Step 15
1152                self.finish_reacting_to_environment_change(
1153                    selected_source,
1154                    generation,
1155                    selected_pixel_density,
1156                )
1157            },
1158            ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m, id)) => {
1159                self.process_image_response_for_environment_change(
1160                    ImageResponse::MetadataLoaded(m),
1161                    selected_source,
1162                    generation,
1163                    selected_pixel_density,
1164                    can_gc,
1165                );
1166                self.register_image_cache_callback(id, change_type);
1167            },
1168            ImageCacheResult::FailedToLoadOrDecode => {
1169                self.process_image_response_for_environment_change(
1170                    ImageResponse::FailedToLoadOrDecode,
1171                    selected_source,
1172                    generation,
1173                    selected_pixel_density,
1174                    can_gc,
1175                );
1176            },
1177            ImageCacheResult::ReadyForRequest(id) => {
1178                self.fetch_request(&img_url, id);
1179                self.register_image_cache_callback(id, change_type);
1180            },
1181            ImageCacheResult::Pending(id) => {
1182                self.register_image_cache_callback(id, change_type);
1183            },
1184        }
1185    }
1186
1187    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1188    fn react_to_decode_image_sync_steps(&self, promise: Rc<Promise>, can_gc: CanGc) {
1189        // Step 2.2. If any of the following are true: this's node document is not fully active; or
1190        // this's current request's state is broken, then reject promise with an "EncodingError"
1191        // DOMException.
1192        if !self.owner_document().is_fully_active() ||
1193            matches!(self.current_request.borrow().state, State::Broken)
1194        {
1195            promise.reject_error(Error::Encoding, can_gc);
1196        } else if matches!(
1197            self.current_request.borrow().state,
1198            State::CompletelyAvailable
1199        ) {
1200            // this doesn't follow the spec, but it's been discussed in <https://github.com/whatwg/html/issues/4217>
1201            promise.resolve_native(&(), can_gc);
1202        } else if matches!(self.current_request.borrow().state, State::Unavailable) &&
1203            self.current_request.borrow().source_url.is_none()
1204        {
1205            // Note: Despite being not explicitly stated in the specification but if current
1206            // request's state is unavailable and current URL is empty string (<img> without "src"
1207            // and "srcset" attributes) then reject promise with an "EncodingError" DOMException.
1208            promise.reject_error(Error::Encoding, can_gc);
1209        } else {
1210            self.image_decode_promises.borrow_mut().push(promise);
1211        }
1212    }
1213
1214    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1215    fn resolve_image_decode_promises(&self) {
1216        if self.image_decode_promises.borrow().is_empty() {
1217            return;
1218        }
1219
1220        // Step 3. If the decoding process completes successfully, then queue a
1221        // global task on the DOM manipulation task source with global to
1222        // resolve promise with undefined.
1223        let trusted_image_decode_promises: Vec<TrustedPromise> = self
1224            .image_decode_promises
1225            .borrow()
1226            .iter()
1227            .map(|promise| TrustedPromise::new(promise.clone()))
1228            .collect();
1229
1230        self.image_decode_promises.borrow_mut().clear();
1231
1232        self.owner_global()
1233            .task_manager()
1234            .dom_manipulation_task_source()
1235            .queue(task!(fulfill_image_decode_promises: move || {
1236                for trusted_promise in trusted_image_decode_promises {
1237                    trusted_promise.root().resolve_native(&(), CanGc::note());
1238                }
1239            }));
1240    }
1241
1242    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1243    fn reject_image_decode_promises(&self) {
1244        if self.image_decode_promises.borrow().is_empty() {
1245            return;
1246        }
1247
1248        // Step 3. Queue a global task on the DOM manipulation task source with
1249        // global to reject promise with an "EncodingError" DOMException.
1250        let trusted_image_decode_promises: Vec<TrustedPromise> = self
1251            .image_decode_promises
1252            .borrow()
1253            .iter()
1254            .map(|promise| TrustedPromise::new(promise.clone()))
1255            .collect();
1256
1257        self.image_decode_promises.borrow_mut().clear();
1258
1259        self.owner_global()
1260            .task_manager()
1261            .dom_manipulation_task_source()
1262            .queue(task!(reject_image_decode_promises: move || {
1263                for trusted_promise in trusted_image_decode_promises {
1264                    trusted_promise.root().reject_error(Error::Encoding, CanGc::note());
1265                }
1266            }));
1267    }
1268
1269    /// Step 15 for <https://html.spec.whatwg.org/multipage/#img-environment-changes>
1270    fn finish_reacting_to_environment_change(
1271        &self,
1272        src: USVString,
1273        generation: u32,
1274        selected_pixel_density: f64,
1275    ) {
1276        let this = Trusted::new(self);
1277        let src = src.0;
1278        self.owner_global().task_manager().dom_manipulation_task_source().queue(
1279            task!(image_load_event: move || {
1280                let this = this.root();
1281                let relevant_mutation = this.generation.get() != generation;
1282                // Step 15.1
1283                if relevant_mutation {
1284                    this.abort_request(State::Unavailable, ImageRequestPhase::Pending, CanGc::note());
1285                    return;
1286                }
1287                // Step 15.2
1288                *this.last_selected_source.borrow_mut() = Some(USVString(src));
1289
1290                {
1291                    let mut pending_request = this.pending_request.borrow_mut();
1292                    pending_request.current_pixel_density = Some(selected_pixel_density);
1293
1294                    // Step 15.3
1295                    pending_request.state = State::CompletelyAvailable;
1296
1297                    // Step 15.4
1298                    // Already a part of the list of available images due to Step 14
1299
1300                    // Step 15.5
1301                    #[allow(clippy::swap_with_temporary)]
1302                    mem::swap(&mut this.current_request.borrow_mut(), &mut pending_request);
1303                }
1304                this.abort_request(State::Unavailable, ImageRequestPhase::Pending, CanGc::note());
1305
1306                // Step 15.6
1307                this.upcast::<Node>().dirty(NodeDamage::Other);
1308
1309                // Step 15.7
1310                this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::note());
1311            })
1312        );
1313    }
1314
1315    fn uses_srcset_or_picture(elem: &Element) -> bool {
1316        let has_src = elem.has_attribute(&local_name!("srcset"));
1317        let is_parent_picture = elem
1318            .upcast::<Node>()
1319            .GetParentElement()
1320            .is_some_and(|p| p.is::<HTMLPictureElement>());
1321        has_src || is_parent_picture
1322    }
1323
1324    fn new_inherited(
1325        local_name: LocalName,
1326        prefix: Option<Prefix>,
1327        document: &Document,
1328        creator: ElementCreator,
1329    ) -> HTMLImageElement {
1330        HTMLImageElement {
1331            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
1332            image_request: Cell::new(ImageRequestPhase::Current),
1333            current_request: DomRefCell::new(ImageRequest {
1334                state: State::Unavailable,
1335                parsed_url: None,
1336                source_url: None,
1337                image: None,
1338                metadata: None,
1339                blocker: DomRefCell::new(None),
1340                final_url: None,
1341                current_pixel_density: None,
1342            }),
1343            pending_request: DomRefCell::new(ImageRequest {
1344                state: State::Unavailable,
1345                parsed_url: None,
1346                source_url: None,
1347                image: None,
1348                metadata: None,
1349                blocker: DomRefCell::new(None),
1350                final_url: None,
1351                current_pixel_density: None,
1352            }),
1353            form_owner: Default::default(),
1354            generation: Default::default(),
1355            source_set: DomRefCell::new(SourceSet::new()),
1356            dimension_attribute_source: Default::default(),
1357            last_selected_source: DomRefCell::new(None),
1358            image_decode_promises: DomRefCell::new(vec![]),
1359            line_number: creator.return_line_number(),
1360        }
1361    }
1362
1363    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
1364    pub(crate) fn new(
1365        local_name: LocalName,
1366        prefix: Option<Prefix>,
1367        document: &Document,
1368        proto: Option<HandleObject>,
1369        creator: ElementCreator,
1370        can_gc: CanGc,
1371    ) -> DomRoot<HTMLImageElement> {
1372        let image_element = Node::reflect_node_with_proto(
1373            Box::new(HTMLImageElement::new_inherited(
1374                local_name, prefix, document, creator,
1375            )),
1376            document,
1377            proto,
1378            can_gc,
1379        );
1380        image_element
1381            .dimension_attribute_source
1382            .set(Some(image_element.upcast()));
1383        image_element
1384    }
1385
1386    pub(crate) fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> {
1387        let elem = self.upcast::<Element>();
1388        let usemap_attr = elem.get_attribute(&ns!(), &local_name!("usemap"))?;
1389
1390        let value = usemap_attr.value();
1391
1392        if value.is_empty() || !value.is_char_boundary(1) {
1393            return None;
1394        }
1395
1396        let (first, last) = value.split_at(1);
1397
1398        if first != "#" || last.is_empty() {
1399            return None;
1400        }
1401
1402        let useMapElements = self
1403            .owner_document()
1404            .upcast::<Node>()
1405            .traverse_preorder(ShadowIncluding::No)
1406            .filter_map(DomRoot::downcast::<HTMLMapElement>)
1407            .find(|n| {
1408                n.upcast::<Element>()
1409                    .get_name()
1410                    .is_some_and(|n| *n == *last)
1411            });
1412
1413        useMapElements.map(|mapElem| mapElem.get_area_elements())
1414    }
1415
1416    pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
1417        if let Some(ref image) = self.current_request.borrow().image {
1418            return image.cors_status() == CorsStatus::Safe;
1419        }
1420
1421        self.current_request
1422            .borrow()
1423            .final_url
1424            .as_ref()
1425            .is_some_and(|url| url.scheme() == "data" || url.origin().same_origin(origin))
1426    }
1427
1428    fn generation_id(&self) -> u32 {
1429        self.generation.get()
1430    }
1431
1432    fn load_broken_image_icon(&self) {
1433        let window = self.owner_window();
1434        let Some(broken_image_icon) = window.image_cache().get_broken_image_icon() else {
1435            return;
1436        };
1437
1438        self.current_request.borrow_mut().metadata = Some(broken_image_icon.metadata);
1439        self.current_request.borrow_mut().image = Some(Image::Raster(broken_image_icon));
1440        self.upcast::<Node>().dirty(NodeDamage::Other);
1441    }
1442}
1443
1444#[derive(JSTraceable, MallocSizeOf)]
1445pub(crate) enum ImageElementMicrotask {
1446    StableStateUpdateImageData {
1447        elem: DomRoot<HTMLImageElement>,
1448        generation: u32,
1449    },
1450    EnvironmentChanges {
1451        elem: DomRoot<HTMLImageElement>,
1452        generation: u32,
1453    },
1454    Decode {
1455        elem: DomRoot<HTMLImageElement>,
1456        #[conditional_malloc_size_of]
1457        promise: Rc<Promise>,
1458    },
1459}
1460
1461impl MicrotaskRunnable for ImageElementMicrotask {
1462    fn handler(&self, can_gc: CanGc) {
1463        match *self {
1464            ImageElementMicrotask::StableStateUpdateImageData {
1465                ref elem,
1466                ref generation,
1467            } => {
1468                // Step 7 of https://html.spec.whatwg.org/multipage/#update-the-image-data,
1469                // stop here if other instances of this algorithm have been scheduled
1470                if elem.generation.get() == *generation {
1471                    elem.update_the_image_data_sync_steps(can_gc);
1472                }
1473            },
1474            ImageElementMicrotask::EnvironmentChanges {
1475                ref elem,
1476                ref generation,
1477            } => {
1478                elem.react_to_environment_changes_sync_steps(*generation, can_gc);
1479            },
1480            ImageElementMicrotask::Decode {
1481                ref elem,
1482                ref promise,
1483            } => {
1484                elem.react_to_decode_image_sync_steps(promise.clone(), can_gc);
1485            },
1486        }
1487    }
1488
1489    fn enter_realm(&self) -> JSAutoRealm {
1490        match self {
1491            &ImageElementMicrotask::StableStateUpdateImageData { ref elem, .. } |
1492            &ImageElementMicrotask::EnvironmentChanges { ref elem, .. } |
1493            &ImageElementMicrotask::Decode { ref elem, .. } => enter_realm(&**elem),
1494        }
1495    }
1496}
1497
1498pub(crate) trait LayoutHTMLImageElementHelpers {
1499    fn image_url(self) -> Option<ServoUrl>;
1500    fn image_density(self) -> Option<f64>;
1501    fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
1502    fn get_width(self) -> LengthOrPercentageOrAuto;
1503    fn get_height(self) -> LengthOrPercentageOrAuto;
1504    fn showing_broken_image_icon(self) -> bool;
1505}
1506
1507impl<'dom> LayoutDom<'dom, HTMLImageElement> {
1508    #[expect(unsafe_code)]
1509    fn current_request(self) -> &'dom ImageRequest {
1510        unsafe { self.unsafe_get().current_request.borrow_for_layout() }
1511    }
1512
1513    #[expect(unsafe_code)]
1514    fn dimension_attribute_source(self) -> LayoutDom<'dom, Element> {
1515        unsafe {
1516            self.unsafe_get()
1517                .dimension_attribute_source
1518                .get_inner_as_layout()
1519                .expect("dimension attribute source should be always non-null")
1520        }
1521    }
1522}
1523
1524impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
1525    fn image_url(self) -> Option<ServoUrl> {
1526        self.current_request().parsed_url.clone()
1527    }
1528
1529    fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
1530        let current_request = self.current_request();
1531        (current_request.image.clone(), current_request.metadata)
1532    }
1533
1534    fn image_density(self) -> Option<f64> {
1535        self.current_request().current_pixel_density
1536    }
1537
1538    fn showing_broken_image_icon(self) -> bool {
1539        matches!(self.current_request().state, State::Broken)
1540    }
1541
1542    fn get_width(self) -> LengthOrPercentageOrAuto {
1543        self.dimension_attribute_source()
1544            .get_attr_for_layout(&ns!(), &local_name!("width"))
1545            .map(AttrValue::as_dimension)
1546            .cloned()
1547            .unwrap_or(LengthOrPercentageOrAuto::Auto)
1548    }
1549
1550    fn get_height(self) -> LengthOrPercentageOrAuto {
1551        self.dimension_attribute_source()
1552            .get_attr_for_layout(&ns!(), &local_name!("height"))
1553            .map(AttrValue::as_dimension)
1554            .cloned()
1555            .unwrap_or(LengthOrPercentageOrAuto::Auto)
1556    }
1557}
1558
1559/// <https://html.spec.whatwg.org/multipage/#parse-a-sizes-attribute>
1560fn parse_a_sizes_attribute(value: &str) -> SourceSizeList {
1561    let mut input = ParserInput::new(value);
1562    let mut parser = Parser::new(&mut input);
1563    let url_data = Url::parse("about:blank").unwrap().into();
1564    let context = ParserContext::new(
1565        Origin::Author,
1566        &url_data,
1567        Some(CssRuleType::Style),
1568        // FIXME(emilio): why ::empty() instead of ::DEFAULT? Also, what do
1569        // browsers do regarding quirks-mode in a media list?
1570        ParsingMode::empty(),
1571        QuirksMode::NoQuirks,
1572        /* namespaces = */ Default::default(),
1573        None,
1574        None,
1575    );
1576    SourceSizeList::parse(&context, &mut parser)
1577}
1578
1579#[allow(non_snake_case)]
1580impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
1581    /// <https://html.spec.whatwg.org/multipage/#dom-image>
1582    fn Image(
1583        window: &Window,
1584        proto: Option<HandleObject>,
1585        can_gc: CanGc,
1586        width: Option<u32>,
1587        height: Option<u32>,
1588    ) -> Fallible<DomRoot<HTMLImageElement>> {
1589        // Step 1. Let document be the current global object's associated Document.
1590        let document = window.Document();
1591
1592        // Step 2. Let img be the result of creating an element given document, "img", and the HTML
1593        // namespace.
1594        let element = Element::create(
1595            QualName::new(None, ns!(html), local_name!("img")),
1596            None,
1597            &document,
1598            ElementCreator::ScriptCreated,
1599            CustomElementCreationMode::Synchronous,
1600            proto,
1601            can_gc,
1602        );
1603
1604        let image = DomRoot::downcast::<HTMLImageElement>(element).unwrap();
1605
1606        // Step 3. If width is given, then set an attribute value for img using "width" and width.
1607        if let Some(w) = width {
1608            image.SetWidth(w);
1609        }
1610
1611        // Step 4. If height is given, then set an attribute value for img using "height" and
1612        // height.
1613        if let Some(h) = height {
1614            image.SetHeight(h);
1615        }
1616
1617        // Step 5. Return img.
1618        Ok(image)
1619    }
1620
1621    // https://html.spec.whatwg.org/multipage/#dom-img-alt
1622    make_getter!(Alt, "alt");
1623    // https://html.spec.whatwg.org/multipage/#dom-img-alt
1624    make_setter!(SetAlt, "alt");
1625
1626    // https://html.spec.whatwg.org/multipage/#dom-img-src
1627    make_url_getter!(Src, "src");
1628
1629    // https://html.spec.whatwg.org/multipage/#dom-img-src
1630    make_url_setter!(SetSrc, "src");
1631
1632    // https://html.spec.whatwg.org/multipage/#dom-img-srcset
1633    make_url_getter!(Srcset, "srcset");
1634    // https://html.spec.whatwg.org/multipage/#dom-img-src
1635    make_url_setter!(SetSrcset, "srcset");
1636
1637    // <https://html.spec.whatwg.org/multipage/#dom-img-sizes>
1638    make_getter!(Sizes, "sizes");
1639
1640    // <https://html.spec.whatwg.org/multipage/#dom-img-sizes>
1641    make_setter!(SetSizes, "sizes");
1642
1643    /// <https://html.spec.whatwg.org/multipage/#dom-img-crossOrigin>
1644    fn GetCrossOrigin(&self) -> Option<DOMString> {
1645        reflect_cross_origin_attribute(self.upcast::<Element>())
1646    }
1647
1648    /// <https://html.spec.whatwg.org/multipage/#dom-img-crossOrigin>
1649    fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1650        set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1651    }
1652
1653    // https://html.spec.whatwg.org/multipage/#dom-img-usemap
1654    make_getter!(UseMap, "usemap");
1655    // https://html.spec.whatwg.org/multipage/#dom-img-usemap
1656    make_setter!(SetUseMap, "usemap");
1657
1658    // https://html.spec.whatwg.org/multipage/#dom-img-ismap
1659    make_bool_getter!(IsMap, "ismap");
1660    // https://html.spec.whatwg.org/multipage/#dom-img-ismap
1661    make_bool_setter!(SetIsMap, "ismap");
1662
1663    // <https://html.spec.whatwg.org/multipage/#dom-img-width>
1664    fn Width(&self) -> u32 {
1665        let node = self.upcast::<Node>();
1666        node.content_box()
1667            .map(|rect| rect.size.width.to_px() as u32)
1668            .unwrap_or_else(|| self.NaturalWidth())
1669    }
1670
1671    // <https://html.spec.whatwg.org/multipage/#dom-img-width>
1672    make_dimension_uint_setter!(SetWidth, "width");
1673
1674    // <https://html.spec.whatwg.org/multipage/#dom-img-height>
1675    fn Height(&self) -> u32 {
1676        let node = self.upcast::<Node>();
1677        node.content_box()
1678            .map(|rect| rect.size.height.to_px() as u32)
1679            .unwrap_or_else(|| self.NaturalHeight())
1680    }
1681
1682    // <https://html.spec.whatwg.org/multipage/#dom-img-height>
1683    make_dimension_uint_setter!(SetHeight, "height");
1684
1685    /// <https://html.spec.whatwg.org/multipage/#dom-img-naturalwidth>
1686    fn NaturalWidth(&self) -> u32 {
1687        let request = self.current_request.borrow();
1688        if matches!(request.state, State::Broken) {
1689            return 0;
1690        }
1691
1692        let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1693        match request.metadata {
1694            Some(ref metadata) => (metadata.width as f64 / pixel_density) as u32,
1695            None => 0,
1696        }
1697    }
1698
1699    /// <https://html.spec.whatwg.org/multipage/#dom-img-naturalheight>
1700    fn NaturalHeight(&self) -> u32 {
1701        let request = self.current_request.borrow();
1702        if matches!(request.state, State::Broken) {
1703            return 0;
1704        }
1705
1706        let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1707        match request.metadata {
1708            Some(ref metadata) => (metadata.height as f64 / pixel_density) as u32,
1709            None => 0,
1710        }
1711    }
1712
1713    /// <https://html.spec.whatwg.org/multipage/#dom-img-complete>
1714    fn Complete(&self) -> bool {
1715        let elem = self.upcast::<Element>();
1716        let srcset_absent = !elem.has_attribute(&local_name!("srcset"));
1717        if !elem.has_attribute(&local_name!("src")) && srcset_absent {
1718            return true;
1719        }
1720        let src = elem.get_string_attribute(&local_name!("src"));
1721        if srcset_absent && src.is_empty() {
1722            return true;
1723        }
1724        let request = self.current_request.borrow();
1725        let request_state = request.state;
1726        match request_state {
1727            State::CompletelyAvailable | State::Broken => true,
1728            State::PartiallyAvailable | State::Unavailable => false,
1729        }
1730    }
1731
1732    /// <https://html.spec.whatwg.org/multipage/#dom-img-currentsrc>
1733    fn CurrentSrc(&self) -> USVString {
1734        let current_request = self.current_request.borrow();
1735        let url = &current_request.parsed_url;
1736        match *url {
1737            Some(ref url) => USVString(url.clone().into_string()),
1738            None => {
1739                let unparsed_url = &current_request.source_url;
1740                match *unparsed_url {
1741                    Some(ref url) => url.clone(),
1742                    None => USVString("".to_owned()),
1743                }
1744            },
1745        }
1746    }
1747
1748    /// <https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy>
1749    fn ReferrerPolicy(&self) -> DOMString {
1750        reflect_referrer_policy_attribute(self.upcast::<Element>())
1751    }
1752
1753    // <https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy>
1754    make_setter!(SetReferrerPolicy, "referrerpolicy");
1755
1756    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1757    fn Decode(&self, can_gc: CanGc) -> Rc<Promise> {
1758        // Step 1. Let promise be a new promise.
1759        let promise = Promise::new(&self.global(), can_gc);
1760
1761        // Step 2. Queue a microtask to perform the following steps:
1762        let task = ImageElementMicrotask::Decode {
1763            elem: DomRoot::from_ref(self),
1764            promise: promise.clone(),
1765        };
1766        ScriptThread::await_stable_state(Microtask::ImageElement(task));
1767
1768        // Step 3. Return promise.
1769        promise
1770    }
1771
1772    // https://html.spec.whatwg.org/multipage/#dom-img-name
1773    make_getter!(Name, "name");
1774
1775    // https://html.spec.whatwg.org/multipage/#dom-img-name
1776    make_atomic_setter!(SetName, "name");
1777
1778    // https://html.spec.whatwg.org/multipage/#dom-img-align
1779    make_getter!(Align, "align");
1780
1781    // https://html.spec.whatwg.org/multipage/#dom-img-align
1782    make_setter!(SetAlign, "align");
1783
1784    // https://html.spec.whatwg.org/multipage/#dom-img-hspace
1785    make_uint_getter!(Hspace, "hspace");
1786
1787    // https://html.spec.whatwg.org/multipage/#dom-img-hspace
1788    make_uint_setter!(SetHspace, "hspace");
1789
1790    // https://html.spec.whatwg.org/multipage/#dom-img-vspace
1791    make_uint_getter!(Vspace, "vspace");
1792
1793    // https://html.spec.whatwg.org/multipage/#dom-img-vspace
1794    make_uint_setter!(SetVspace, "vspace");
1795
1796    // https://html.spec.whatwg.org/multipage/#dom-img-longdesc
1797    make_getter!(LongDesc, "longdesc");
1798
1799    // https://html.spec.whatwg.org/multipage/#dom-img-longdesc
1800    make_setter!(SetLongDesc, "longdesc");
1801
1802    // https://html.spec.whatwg.org/multipage/#dom-img-border
1803    make_getter!(Border, "border");
1804
1805    // https://html.spec.whatwg.org/multipage/#dom-img-border
1806    make_setter!(SetBorder, "border");
1807}
1808
1809impl VirtualMethods for HTMLImageElement {
1810    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1811        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1812    }
1813
1814    fn adopting_steps(&self, old_doc: &Document, can_gc: CanGc) {
1815        self.super_type().unwrap().adopting_steps(old_doc, can_gc);
1816        self.update_the_image_data(can_gc);
1817    }
1818
1819    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1820        self.super_type()
1821            .unwrap()
1822            .attribute_mutated(attr, mutation, can_gc);
1823        match attr.local_name() {
1824            &local_name!("src") |
1825            &local_name!("srcset") |
1826            &local_name!("width") |
1827            &local_name!("sizes") => {
1828                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1829                // The element's src, srcset, width, or sizes attributes are set, changed, or
1830                // removed.
1831                self.update_the_image_data(can_gc);
1832            },
1833            &local_name!("crossorigin") => {
1834                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1835                // The element's crossorigin attribute's state is changed.
1836                let cross_origin_state_changed = match mutation {
1837                    AttributeMutation::Removed | AttributeMutation::Set(None, _) => true,
1838                    AttributeMutation::Set(Some(old_value), _) => {
1839                        let new_cors_setting =
1840                            CorsSettings::from_enumerated_attribute(&attr.value());
1841                        let old_cors_setting = CorsSettings::from_enumerated_attribute(old_value);
1842
1843                        new_cors_setting != old_cors_setting
1844                    },
1845                };
1846
1847                if cross_origin_state_changed {
1848                    self.update_the_image_data(can_gc);
1849                }
1850            },
1851            &local_name!("referrerpolicy") => {
1852                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1853                // The element's referrerpolicy attribute's state is changed.
1854                let referrer_policy_state_changed = match mutation {
1855                    AttributeMutation::Removed | AttributeMutation::Set(None, _) => {
1856                        ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::EmptyString
1857                    },
1858                    AttributeMutation::Set(Some(old_value), _) => {
1859                        ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::from(&**old_value)
1860                    },
1861                };
1862
1863                if referrer_policy_state_changed {
1864                    self.update_the_image_data(can_gc);
1865                }
1866            },
1867            _ => {},
1868        }
1869    }
1870
1871    fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1872        match attr.local_name() {
1873            &local_name!("width") | &local_name!("height") => true,
1874            _ => self
1875                .super_type()
1876                .unwrap()
1877                .attribute_affects_presentational_hints(attr),
1878        }
1879    }
1880
1881    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1882        match name {
1883            &local_name!("width") | &local_name!("height") => {
1884                AttrValue::from_dimension(value.into())
1885            },
1886            &local_name!("hspace") | &local_name!("vspace") => AttrValue::from_u32(value.into(), 0),
1887            _ => self
1888                .super_type()
1889                .unwrap()
1890                .parse_plain_attribute(name, value),
1891        }
1892    }
1893
1894    fn handle_event(&self, event: &Event, can_gc: CanGc) {
1895        if event.type_() != atom!("click") {
1896            return;
1897        }
1898
1899        let area_elements = self.areas();
1900        let elements = match area_elements {
1901            Some(x) => x,
1902            None => return,
1903        };
1904
1905        // Fetch click coordinates
1906        let mouse_event = match event.downcast::<MouseEvent>() {
1907            Some(x) => x,
1908            None => return,
1909        };
1910
1911        let point = Point2D::new(
1912            mouse_event.ClientX().to_f32().unwrap(),
1913            mouse_event.ClientY().to_f32().unwrap(),
1914        );
1915        let bcr = self.upcast::<Element>().GetBoundingClientRect(can_gc);
1916        let bcr_p = Point2D::new(bcr.X() as f32, bcr.Y() as f32);
1917
1918        // Walk HTMLAreaElements
1919        for element in elements {
1920            let shape = element.get_shape_from_coords();
1921            let shp = match shape {
1922                Some(x) => x.absolute_coords(bcr_p),
1923                None => return,
1924            };
1925            if shp.hit_test(&point) {
1926                element.activation_behavior(event, self.upcast(), can_gc);
1927                return;
1928            }
1929        }
1930    }
1931
1932    /// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-insertion-steps>
1933    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1934        if let Some(s) = self.super_type() {
1935            s.bind_to_tree(context, can_gc);
1936        }
1937        let document = self.owner_document();
1938        if context.tree_connected {
1939            document.register_responsive_image(self);
1940        }
1941
1942        let parent = self.upcast::<Node>().GetParentNode().unwrap();
1943
1944        // Step 1. If insertedNode's parent is a picture element, then, count this as a relevant
1945        // mutation for insertedNode.
1946        if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
1947            self.update_the_image_data(can_gc);
1948        }
1949    }
1950
1951    /// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-removing-steps>
1952    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1953        self.super_type().unwrap().unbind_from_tree(context, can_gc);
1954        let document = self.owner_document();
1955        document.unregister_responsive_image(self);
1956
1957        // Step 1. If oldParent is a picture element, then, count this as a relevant mutation for
1958        // removedNode.
1959        if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
1960            self.update_the_image_data(can_gc);
1961        }
1962    }
1963}
1964
1965impl FormControl for HTMLImageElement {
1966    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1967        self.form_owner.get()
1968    }
1969
1970    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1971        self.form_owner.set(form);
1972    }
1973
1974    fn to_element(&self) -> &Element {
1975        self.upcast::<Element>()
1976    }
1977
1978    fn is_listed(&self) -> bool {
1979        false
1980    }
1981}
1982
1983/// Collect sequence of code points
1984/// <https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points>
1985pub(crate) fn collect_sequence_characters(
1986    s: &str,
1987    mut predicate: impl FnMut(&char) -> bool,
1988) -> (&str, &str) {
1989    let i = s.find(|ch| !predicate(&ch)).unwrap_or(s.len());
1990    (&s[0..i], &s[i..])
1991}
1992
1993/// <https://html.spec.whatwg.org/multipage/#valid-non-negative-integer>
1994/// TODO(#39315): Use the validation rule from Stylo
1995fn is_valid_non_negative_integer_string(s: &str) -> bool {
1996    s.chars().all(|c| c.is_ascii_digit())
1997}
1998
1999/// <https://html.spec.whatwg.org/multipage/#valid-floating-point-number>
2000/// TODO(#39315): Use the validation rule from Stylo
2001fn is_valid_floating_point_number_string(s: &str) -> bool {
2002    static RE: LazyLock<Regex> =
2003        LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
2004
2005    RE.is_match(s)
2006}
2007
2008/// Parse an `srcset` attribute:
2009/// <https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute>.
2010pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
2011    // > 1. Let input be the value passed to this algorithm.
2012    // > 2. Let position be a pointer into input, initially pointing at the start of the string.
2013    let mut current_index = 0;
2014
2015    // > 3. Let candidates be an initially empty source set.
2016    let mut candidates = vec![];
2017    while current_index < input.len() {
2018        let remaining_string = &input[current_index..];
2019
2020        // > 4. Splitting loop: Collect a sequence of code points that are ASCII whitespace or
2021        // > U+002C COMMA characters from input given position. If any U+002C COMMA
2022        // > characters were collected, that is a parse error.
2023        // NOTE: A parse error indicating a non-fatal mismatch between the input and the
2024        // requirements will be silently ignored to match the behavior of other browsers.
2025        // <https://html.spec.whatwg.org/multipage/#concept-microsyntax-parse-error>
2026        let (collected_characters, string_after_whitespace) =
2027            collect_sequence_characters(remaining_string, |character| {
2028                *character == ',' || character.is_ascii_whitespace()
2029            });
2030
2031        // Add the length of collected whitespace, to find the start of the URL we are going
2032        // to parse.
2033        current_index += collected_characters.len();
2034
2035        // > 5. If position is past the end of input, return candidates.
2036        if string_after_whitespace.is_empty() {
2037            return candidates;
2038        }
2039
2040        // 6. Collect a sequence of code points that are not ASCII whitespace from input
2041        // given position, and let that be url.
2042        let (url, _) =
2043            collect_sequence_characters(string_after_whitespace, |c| !char::is_ascii_whitespace(c));
2044
2045        // Add the length of `url` that we will parse to advance the index of the next part
2046        // of the string to prase.
2047        current_index += url.len();
2048
2049        // 7. Let descriptors be a new empty list.
2050        let mut descriptors = Vec::new();
2051
2052        // > 8. If url ends with U+002C (,), then:
2053        // >    1. Remove all trailing U+002C COMMA characters from url. If this removed
2054        // >       more than one character, that is a parse error.
2055        if url.ends_with(',') {
2056            let image_source = ImageSource {
2057                url: url.trim_end_matches(',').into(),
2058                descriptor: Descriptor {
2059                    width: None,
2060                    density: None,
2061                },
2062            };
2063            candidates.push(image_source);
2064            continue;
2065        }
2066
2067        // Otherwise:
2068        // > 8.1. Descriptor tokenizer: Skip ASCII whitespace within input given position.
2069        let descriptors_string = &input[current_index..];
2070        let (spaces, descriptors_string) =
2071            collect_sequence_characters(descriptors_string, |character| {
2072                character.is_ascii_whitespace()
2073            });
2074        current_index += spaces.len();
2075
2076        // > 8.2. Let current descriptor be the empty string.
2077        let mut current_descriptor = String::new();
2078
2079        // > 8.3. Let state be "in descriptor".
2080        let mut state = ParseState::InDescriptor;
2081
2082        // > 8.4. Let c be the character at position. Do the following depending on the value of
2083        // > state. For the purpose of this step, "EOF" is a special character representing
2084        // > that position is past the end of input.
2085        let mut characters = descriptors_string.chars();
2086        let mut character = characters.next();
2087        if let Some(character) = character {
2088            current_index += character.len_utf8();
2089        }
2090
2091        loop {
2092            match (state, character) {
2093                (ParseState::InDescriptor, Some(character)) if character.is_ascii_whitespace() => {
2094                    // > If current descriptor is not empty, append current descriptor to
2095                    // > descriptors and let current descriptor be the empty string. Set
2096                    // > state to after descriptor.
2097                    if !current_descriptor.is_empty() {
2098                        descriptors.push(current_descriptor);
2099                        current_descriptor = String::new();
2100                        state = ParseState::AfterDescriptor;
2101                    }
2102                },
2103                (ParseState::InDescriptor, Some(',')) => {
2104                    // > Advance position to the next character in input. If current descriptor
2105                    // > is not empty, append current descriptor to descriptors. Jump to the
2106                    // > step labeled descriptor parser.
2107                    if !current_descriptor.is_empty() {
2108                        descriptors.push(current_descriptor);
2109                    }
2110                    break;
2111                },
2112                (ParseState::InDescriptor, Some('(')) => {
2113                    // > Append c to current descriptor. Set state to in parens.
2114                    current_descriptor.push('(');
2115                    state = ParseState::InParens;
2116                },
2117                (ParseState::InDescriptor, Some(character)) => {
2118                    // > Append c to current descriptor.
2119                    current_descriptor.push(character);
2120                },
2121                (ParseState::InDescriptor, None) => {
2122                    // > If current descriptor is not empty, append current descriptor to
2123                    // > descriptors. Jump to the step labeled descriptor parser.
2124                    if !current_descriptor.is_empty() {
2125                        descriptors.push(current_descriptor);
2126                    }
2127                    break;
2128                },
2129                (ParseState::InParens, Some(')')) => {
2130                    // > Append c to current descriptor. Set state to in descriptor.
2131                    current_descriptor.push(')');
2132                    state = ParseState::InDescriptor;
2133                },
2134                (ParseState::InParens, Some(character)) => {
2135                    // Append c to current descriptor.
2136                    current_descriptor.push(character);
2137                },
2138                (ParseState::InParens, None) => {
2139                    // > Append current descriptor to descriptors. Jump to the step
2140                    // > labeled descriptor parser.
2141                    descriptors.push(current_descriptor);
2142                    break;
2143                },
2144                (ParseState::AfterDescriptor, Some(character))
2145                    if character.is_ascii_whitespace() =>
2146                {
2147                    // > Stay in this state.
2148                },
2149                (ParseState::AfterDescriptor, Some(_)) => {
2150                    // > Set state to in descriptor. Set position to the previous
2151                    // > character in input.
2152                    state = ParseState::InDescriptor;
2153                    continue;
2154                },
2155                (ParseState::AfterDescriptor, None) => {
2156                    // > Jump to the step labeled descriptor parser.
2157                    break;
2158                },
2159            }
2160
2161            character = characters.next();
2162            if let Some(character) = character {
2163                current_index += character.len_utf8();
2164            }
2165        }
2166
2167        // > 9. Descriptor parser: Let error be no.
2168        let mut error = false;
2169        // > 10. Let width be absent.
2170        let mut width: Option<u32> = None;
2171        // > 11. Let density be absent.
2172        let mut density: Option<f64> = None;
2173        // > 12. Let future-compat-h be absent.
2174        let mut future_compat_h: Option<u32> = None;
2175
2176        // > 13. For each descriptor in descriptors, run the appropriate set of steps from
2177        // > the following list:
2178        for descriptor in descriptors.into_iter() {
2179            let Some(last_character) = descriptor.chars().last() else {
2180                break;
2181            };
2182
2183            let first_part_of_string = &descriptor[0..descriptor.len() - last_character.len_utf8()];
2184            match last_character {
2185                // > If the descriptor consists of a valid non-negative integer followed by a
2186                // > U+0077 LATIN SMALL LETTER W character
2187                // > 1. If the user agent does not support the sizes attribute, let error be yes.
2188                // > 2. If width and density are not both absent, then let error be yes.
2189                // > 3. Apply the rules for parsing non-negative integers to the descriptor.
2190                // >    If the result is 0, let error be yes. Otherwise, let width be the result.
2191                'w' if is_valid_non_negative_integer_string(first_part_of_string) &&
2192                    density.is_none() &&
2193                    width.is_none() =>
2194                {
2195                    match parse_unsigned_integer(first_part_of_string.chars()) {
2196                        Ok(number) if number > 0 => {
2197                            width = Some(number);
2198                            continue;
2199                        },
2200                        _ => error = true,
2201                    }
2202                },
2203
2204                // > If the descriptor consists of a valid floating-point number followed by a
2205                // > U+0078 LATIN SMALL LETTER X character
2206                // > 1. If width, density and future-compat-h are not all absent, then let
2207                // >    error be yes.
2208                // > 2. Apply the rules for parsing floating-point number values to the
2209                // >    descriptor. If the result is less than 0, let error be yes. Otherwise, let
2210                // >    density be the result.
2211                //
2212                // The HTML specification has a procedure for parsing floats that is different enough from
2213                // the one that stylo uses, that it's better to use Rust's float parser here. This is
2214                // what Gecko does, but it also checks to see if the number is a valid HTML-spec compliant
2215                // number first. Not doing that means that we might be parsing numbers that otherwise
2216                // wouldn't parse.
2217                'x' if is_valid_floating_point_number_string(first_part_of_string) &&
2218                    width.is_none() &&
2219                    density.is_none() &&
2220                    future_compat_h.is_none() =>
2221                {
2222                    match first_part_of_string.parse::<f64>() {
2223                        Ok(number) if number.is_finite() && number >= 0. => {
2224                            density = Some(number);
2225                            continue;
2226                        },
2227                        _ => error = true,
2228                    }
2229                },
2230
2231                // > If the descriptor consists of a valid non-negative integer followed by a
2232                // > U+0068 LATIN SMALL LETTER H character
2233                // >   This is a parse error.
2234                // > 1. If future-compat-h and density are not both absent, then let error be
2235                // >    yes.
2236                // > 2. Apply the rules for parsing non-negative integers to the descriptor.
2237                // >    If the result is 0, let error be yes. Otherwise, let future-compat-h be the
2238                // >    result.
2239                'h' if is_valid_non_negative_integer_string(first_part_of_string) &&
2240                    future_compat_h.is_none() &&
2241                    density.is_none() =>
2242                {
2243                    match parse_unsigned_integer(first_part_of_string.chars()) {
2244                        Ok(number) if number > 0 => {
2245                            future_compat_h = Some(number);
2246                            continue;
2247                        },
2248                        _ => error = true,
2249                    }
2250                },
2251
2252                // > Anything else
2253                // >  Let error be yes.
2254                _ => error = true,
2255            }
2256
2257            if error {
2258                break;
2259            }
2260        }
2261
2262        // > 14. If future-compat-h is not absent and width is absent, let error be yes.
2263        if future_compat_h.is_some() && width.is_none() {
2264            error = true;
2265        }
2266
2267        // Step 15. If error is still no, then append a new image source to candidates whose URL is
2268        // url, associated with a width width if not absent and a pixel density density if not
2269        // absent. Otherwise, there is a parse error.
2270        if !error {
2271            let image_source = ImageSource {
2272                url: url.into(),
2273                descriptor: Descriptor { width, density },
2274            };
2275            candidates.push(image_source);
2276        }
2277
2278        // Step 16. Return to the step labeled splitting loop.
2279    }
2280    candidates
2281}
2282
2283#[derive(Clone)]
2284enum ChangeType {
2285    Environment {
2286        selected_source: USVString,
2287        selected_pixel_density: f64,
2288    },
2289    Element,
2290}
2291
2292/// Returns true if the given image MIME type is supported.
2293fn is_supported_image_mime_type(input: &str) -> bool {
2294    // Remove any leading and trailing HTTP whitespace from input.
2295    let mime_type = input.trim();
2296
2297    // <https://mimesniff.spec.whatwg.org/#mime-type-essence>
2298    let mime_type_essence = match mime_type.find(';') {
2299        Some(semi) => &mime_type[..semi],
2300        _ => mime_type,
2301    };
2302
2303    // The HTML specification says the type attribute may be present and if present, the value
2304    // must be a valid MIME type string. However an empty type attribute is implicitly supported
2305    // to match the behavior of other browsers.
2306    // <https://html.spec.whatwg.org/multipage/#attr-source-type>
2307    if mime_type_essence.is_empty() {
2308        return true;
2309    }
2310
2311    SUPPORTED_IMAGE_MIME_TYPES.contains(&mime_type_essence)
2312}