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