Skip to main content

script/dom/html/embedded_content/
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::sync::Arc;
7
8use atomic_refcell::{AtomicRef, AtomicRefCell};
9use bytes::Bytes;
10use dom_struct::dom_struct;
11use euclid::default::Point2D;
12use html5ever::{LocalName, Prefix, QualName, local_name, ns};
13use js::context::{JSContext, NoGC};
14use js::rust::HandleObject;
15use mime::{self, Mime};
16use net_traits::image_cache::{
17    Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
18    ImageResponse, PendingImageId,
19};
20use net_traits::request::{CorsSettings, Destination, Initiator, RequestId};
21use net_traits::{
22    FetchMetadata, FetchResponseMsg, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
23};
24use num_traits::ToPrimitive;
25use pixels::{CorsStatus, ImageMetadata, Snapshot};
26use script_bindings::cell::DomRefCell;
27use servo_base::cross_process_instant::CrossProcessInstant;
28use servo_url::ServoUrl;
29use servo_url::origin::MutableOrigin;
30use style::Atom;
31use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer};
32
33use crate::dom::activation::Activatable;
34use crate::dom::bindings::cell::AtomicSafeBorrowMut;
35use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRect_Binding::DOMRectMethods;
36use crate::dom::bindings::codegen::Bindings::ElementBinding::Element_Binding::ElementMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
38use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
39use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
40use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
41use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
42use crate::dom::bindings::error::{Error, Fallible};
43use crate::dom::bindings::inheritance::Castable;
44use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
45use crate::dom::bindings::reflector::DomGlobal;
46use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayoutOptional};
47use crate::dom::bindings::str::{DOMString, USVString};
48use crate::dom::csp::{GlobalCspReporting, Violation};
49use crate::dom::document::Document;
50use crate::dom::element::attributes::storage::AttrRef;
51use crate::dom::element::{
52    AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
53    cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute,
54    reflect_referrer_policy_attribute, set_cross_origin_attribute,
55};
56use crate::dom::event::Event;
57use crate::dom::eventtarget::EventTarget;
58use crate::dom::globalscope::GlobalScope;
59use crate::dom::html::htmlareaelement::HTMLAreaElement;
60use crate::dom::html::htmlelement::HTMLElement;
61use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
62use crate::dom::html::htmlmapelement::HTMLMapElement;
63use crate::dom::html::htmlpictureelement::HTMLPictureElement;
64use crate::dom::iterators::ShadowIncluding;
65use crate::dom::mouseevent::MouseEvent;
66use crate::dom::node::virtualmethods::VirtualMethods;
67use crate::dom::node::{BindContext, MoveContext, Node, NodeDamage, NodeTraits, UnbindContext};
68use crate::dom::performance::performanceresourcetiming::InitiatorType;
69use crate::dom::promise::Promise;
70use crate::dom::srcset::SourceSet;
71use crate::dom::window::Window;
72use crate::dom::{RootedPromise, TracedPromise};
73use crate::event_loop::document_loader::{LoadBlocker, LoadType};
74use crate::event_loop::script_thread::ScriptThread;
75use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
76use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
77use crate::realms::enter_auto_realm;
78use crate::runtime::job_queue::MicrotaskRunnable;
79
80/// <https://html.spec.whatwg.org/multipage/#img-req-state>
81#[derive(Clone, Copy, Default, JSTraceable, MallocSizeOf)]
82enum State {
83    #[default]
84    Unavailable,
85    PartiallyAvailable,
86    CompletelyAvailable,
87    Broken,
88}
89
90#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
91enum ImageRequestPhase {
92    Pending,
93    Current,
94}
95
96/// <https://html.spec.whatwg.org/multipage/#image-request>
97#[derive(Default, JSTraceable, MallocSizeOf)]
98#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
99struct ImageRequest {
100    state: State,
101    #[no_trace]
102    parsed_url: Option<ServoUrl>,
103    source_url: Option<USVString>,
104    blocker: DomRefCell<Option<LoadBlocker>>,
105    #[no_trace]
106    image: Option<Image>,
107    #[no_trace]
108    metadata: Option<ImageMetadata>,
109    #[no_trace]
110    final_url: Option<ServoUrl>,
111    /// The time the image became completely available, if it has.
112    #[no_trace]
113    load_time: Option<CrossProcessInstant>,
114    current_pixel_density: Option<f64>,
115}
116
117#[dom_struct]
118pub(crate) struct HTMLImageElement {
119    htmlelement: HTMLElement,
120    #[custom_trace]
121    current_request: AtomicRefCell<Box<ImageRequest>>,
122    pending_request: DomRefCell<Option<Box<ImageRequest>>>,
123    form_owner: MutNullableDom<HTMLFormElement>,
124    source_set: DomRefCell<SourceSet>,
125    /// <https://html.spec.whatwg.org/multipage/#concept-img-dimension-attribute-source>
126    /// Always non-null after construction.
127    dimension_attribute_source: MutNullableDom<Element>,
128    /// <https://html.spec.whatwg.org/multipage/#last-selected-source>
129    last_selected_source: DomRefCell<Option<USVString>>,
130    image_decode_promises: DomRefCell<Vec<TracedPromise>>,
131    /// Line number this element was created on
132    line_number: u64,
133    image_request: Cell<ImageRequestPhase>,
134    generation: Cell<u32>,
135}
136
137impl HTMLImageElement {
138    /// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
139    pub(crate) fn is_usable(&self) -> Fallible<bool> {
140        // If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
141        if let Some(image) = &self.current_request.borrow().image {
142            let intrinsic_size = image.metadata();
143            if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
144                return Ok(false);
145            }
146        }
147
148        match self.current_request.borrow().state {
149            // If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
150            State::Broken => Err(Error::InvalidState(Some(
151                "Image element's state is broken".into(),
152            ))),
153            State::CompletelyAvailable => Ok(true),
154            // If image is not fully decodable, then return bad.
155            State::PartiallyAvailable | State::Unavailable => Ok(false),
156        }
157    }
158
159    pub(crate) fn image_data(&self) -> Option<Image> {
160        self.current_request.borrow().image.clone()
161    }
162
163    /// Gets the copy of the raster image data.
164    pub(crate) fn get_raster_image_data(&self) -> Option<Snapshot> {
165        let Some(raster_image) = self.image_data()?.as_raster_image() else {
166            warn!("Vector image is not supported as raster image source");
167            return None;
168        };
169        Some(raster_image.as_snapshot())
170    }
171}
172
173/// The context required for asynchronously loading an external image.
174struct ImageContext {
175    /// Reference to the script thread image cache.
176    image_cache: Arc<dyn ImageCache>,
177    /// Indicates whether the request failed, and why
178    status: Result<(), NetworkError>,
179    /// The cache ID for this request.
180    id: PendingImageId,
181    /// Used to mark abort
182    aborted: bool,
183    url: ServoUrl,
184    element: Trusted<HTMLImageElement>,
185}
186
187impl FetchResponseListener for ImageContext {
188    fn should_invoke(&self) -> bool {
189        !self.aborted
190    }
191
192    fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
193
194    fn process_response(
195        &mut self,
196        _: &mut JSContext,
197        _: &GlobalScope,
198        request_id: RequestId,
199        metadata: Result<FetchMetadata, NetworkError>,
200    ) {
201        debug!("got {:?} for {:?}", metadata.as_ref().map(|_| ()), self.url);
202        self.image_cache.notify_pending_response(
203            self.id,
204            FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
205        );
206
207        let metadata: Option<Metadata> = metadata.ok().map(Into::into);
208
209        // Step 14.5 of https://html.spec.whatwg.org/multipage/#img-environment-changes
210        if let Some(metadata) = metadata.as_ref() &&
211            let Some(ref content_type) = metadata.content_type
212        {
213            let mime: Mime = content_type.clone().into_inner().into();
214            if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" {
215                self.aborted = true;
216            }
217        }
218
219        // The HTTP status code is ignored here. Ok NetworkError is treated
220        // as real error
221        self.status = match metadata.as_ref().map(|m| m.status.clone()) {
222            None => Err(NetworkError::ResourceLoadError(
223                "No http status code received".to_owned(),
224            )),
225            Some(_) => Ok(()),
226        };
227    }
228
229    fn process_response_chunk(
230        &mut self,
231        _: &mut JSContext,
232        _: &GlobalScope,
233        request_id: RequestId,
234        payload: Bytes,
235    ) {
236        if self.status.is_ok() {
237            self.image_cache.notify_pending_response(
238                self.id,
239                FetchResponseMsg::ProcessResponseChunk(request_id, payload),
240            );
241        }
242    }
243
244    fn process_response_eof(
245        self,
246        cx: &mut JSContext,
247        global: &GlobalScope,
248        request_id: RequestId,
249        response: Result<(), NetworkError>,
250        timing: ResourceFetchTiming,
251    ) {
252        self.image_cache.notify_pending_response(
253            self.id,
254            FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
255        );
256        network_listener::submit_timing(cx, global, &self, &response, &timing);
257    }
258
259    fn process_csp_violations(
260        &mut self,
261        cx: &mut JSContext,
262        global: &GlobalScope,
263        _request_id: RequestId,
264        violations: Vec<Violation>,
265    ) {
266        let elem = self.element.root();
267        let source_position = elem
268            .upcast::<Element>()
269            .compute_source_position(elem.line_number as u32);
270        global.report_csp_violations(cx, violations, None, Some(source_position));
271    }
272
273    fn process_content_length(&mut self, request_id: RequestId, size: usize) {
274        self.image_cache.notify_pending_response(
275            self.id,
276            FetchResponseMsg::ProcessContentLength(request_id, size),
277        );
278    }
279}
280
281impl ResourceTimingListener for ImageContext {
282    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
283        (InitiatorType::LocalName("img".into()), self.url.clone())
284    }
285}
286
287#[expect(non_snake_case)]
288impl HTMLImageElement {
289    /// Update the current image with a valid URL.
290    fn fetch_image(&self, cx: &mut JSContext, img_url: &ServoUrl) {
291        let window = self.owner_window_unrooted(cx.no_gc());
292
293        let cache_result = window.image_cache().get_cached_image_status(
294            img_url.clone(),
295            window.origin().immutable(),
296            cors_setting_for_element(self.upcast()),
297        );
298
299        match cache_result {
300            ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
301                image,
302                url,
303            }) => self.process_image_response(cx, ImageResponse::Loaded(image, url)),
304            ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
305                metadata,
306                id,
307            )) => {
308                self.process_image_response(cx, ImageResponse::MetadataLoaded(metadata));
309                self.register_image_cache_callback(id, ChangeType::Element);
310            },
311            ImageCacheResult::Pending(id) => {
312                self.register_image_cache_callback(id, ChangeType::Element);
313            },
314            ImageCacheResult::ReadyForRequest(id) => {
315                self.fetch_request(img_url, id);
316                self.register_image_cache_callback(id, ChangeType::Element);
317            },
318            ImageCacheResult::FailedToLoadOrDecode => {
319                self.process_image_response(cx, ImageResponse::FailedToLoadOrDecode)
320            },
321        };
322    }
323
324    fn register_image_cache_callback(&self, id: PendingImageId, change_type: ChangeType) {
325        let trusted_node = Trusted::new(self);
326        let generation = self.generation_id();
327        let window = self.owner_window();
328        let callback = window.register_image_cache_listener(id, move |response, _| {
329            let trusted_node = trusted_node.clone();
330            let window = trusted_node.root().owner_window();
331            let callback_type = change_type.clone();
332
333            window
334                .as_global_scope()
335                .task_manager()
336                .networking_task_source()
337                .queue(task!(process_image_response: move |cx| {
338                let element = trusted_node.root();
339
340                // Ignore any image response for a previous request that has been discarded.
341                if generation != element.generation_id() {
342                    return;
343                }
344
345                match callback_type {
346                    ChangeType::Element => {
347                        element.process_image_response(cx, response.response);
348                    }
349                    ChangeType::Environment { selected_source, selected_pixel_density } => {
350                        element.process_image_response_for_environment_change(
351                            cx, response.response, selected_source, generation, selected_pixel_density
352                        );
353                    }
354                }
355            }));
356        });
357
358        window.image_cache().add_listener(ImageLoadListener::new(
359            callback,
360            window.pipeline_id(),
361            id,
362        ));
363    }
364
365    fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
366        let document = self.owner_document();
367        let window = self.owner_window();
368
369        let context = ImageContext {
370            image_cache: window.image_cache(),
371            status: Ok(()),
372            id,
373            aborted: false,
374            element: Trusted::new(self),
375            url: img_url.clone(),
376        };
377
378        // https://html.spec.whatwg.org/multipage/#update-the-image-data steps 17-20
379        // This function is also used to prefetch an image in `script::dom::servoparser::prefetch`.
380        let global = document.global();
381        let mut request = create_a_potential_cors_request(
382            Some(window.webview_id()),
383            img_url.clone(),
384            Destination::Image,
385            cors_setting_for_element(self.upcast()),
386            None,
387            global.get_referrer(),
388        )
389        .with_global_scope(&global)
390        .referrer_policy(referrer_policy_for_element(self.upcast()));
391
392        if self.uses_srcset_or_picture() {
393            request = request.initiator(Initiator::ImageSet);
394        }
395
396        // This is a background load because the load blocker already fulfills the
397        // purpose of delaying the document's load event.
398        document.fetch_background(request, context);
399    }
400
401    // Steps common to when an image has been loaded.
402    fn handle_loaded_image(&self, cx: &mut JSContext, image: Image, url: ServoUrl) {
403        {
404            let mut current_request = self.current_request.safe_borrow_mut(cx.no_gc());
405            current_request.metadata = Some(image.metadata());
406            current_request.final_url = Some(url);
407            current_request.image = Some(image);
408            current_request.state = State::CompletelyAvailable;
409            current_request.load_time = Some(CrossProcessInstant::now());
410        }
411
412        self.pending_request.safe_borrow_mut(cx.no_gc()).take();
413
414        LoadBlocker::terminate(cx, &self.current_request.borrow().blocker);
415        // Mark the node dirty
416        self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
417        self.resolve_image_decode_promises(cx);
418    }
419
420    fn fire_event_including_host(&self, cx: &mut JSContext, event: Atom) {
421        // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)
422        // This is the case for an `<input type="image">` where in the shadow
423        // tree of that `<input>` element we use an `<img>`
424        let node = self.upcast::<Node>();
425        if node.is_in_ua_widget() &&
426            let Some(containing_shadow_root) = node.containing_shadow_root()
427        {
428            containing_shadow_root
429                .Host()
430                .upcast::<EventTarget>()
431                .fire_event(cx, event);
432        } else {
433            self.upcast::<EventTarget>().fire_event(cx, event);
434        }
435    }
436
437    /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
438    fn process_image_response(&self, cx: &mut JSContext, image: ImageResponse) {
439        // Step 27. As soon as possible, jump to the first applicable entry from the following list:
440
441        // TODO => "If the resource type is multipart/x-mixed-replace"
442
443        // => "If the resource type and data corresponds to a supported image format ...""
444        let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
445            (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) => {
446                self.handle_loaded_image(cx, image, url);
447                (true, false)
448            },
449            (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
450                self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
451                self.image_request.set(ImageRequestPhase::Current);
452                self.handle_loaded_image(cx, image, url);
453                (true, false)
454            },
455            (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
456                // Otherwise, if the user agent is able to determine image request's image's width
457                // and height, and image request is the current request, prepare image request for
458                // presentation given the img element and set image request's state to partially
459                // available.
460                self.current_request.safe_borrow_mut(cx.no_gc()).state = State::PartiallyAvailable;
461                self.current_request.safe_borrow_mut(cx.no_gc()).metadata = Some(meta);
462                (false, false)
463            },
464            (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
465                // If the user agent is able to determine image request's image's width and height,
466                // and image request is the pending request, set image request's state to partially
467                // available.
468                self.pending_request
469                    .safe_borrow_mut(cx.no_gc())
470                    .get_or_insert_default()
471                    .state = State::PartiallyAvailable;
472                (false, false)
473            },
474            (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Current) => {
475                // Otherwise, if the user agent is able to determine that image request's image is
476                // corrupted in some fatal way such that the image dimensions cannot be obtained,
477                // and image request is the current request:
478
479                // Step 1. Abort the image request for image request.
480                self.abort_request(cx, State::Broken, ImageRequestPhase::Current);
481
482                self.load_broken_image_icon(cx.no_gc());
483
484                // Step 2. If maybe omit events is not set or previousURL is not equal to urlString,
485                // then fire an event named error at the img element.
486                // TODO: Add missing `maybe omit events` flag and previousURL.
487                (false, true)
488            },
489            (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Pending) => {
490                // Otherwise, if the user agent is able to determine that image request's image is
491                // corrupted in some fatal way such that the image dimensions cannot be obtained,
492                // and image request is the pending request:
493
494                // Step 1. Abort the image request for the current request and the pending request.
495                self.abort_request(cx, State::Broken, ImageRequestPhase::Current);
496                self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
497
498                // Step 2. Upgrade the pending request to the current request.
499                // This is written this way as otherwise crown complains
500                if self.pending_request.borrow().is_some() {
501                    *self.current_request.safe_borrow_mut(cx.no_gc()) = self
502                        .pending_request
503                        .safe_borrow_mut(cx.no_gc())
504                        .take()
505                        .unwrap();
506                }
507                self.image_request.set(ImageRequestPhase::Current);
508
509                // Step 3. Set the current request's state to broken.
510                self.current_request.safe_borrow_mut(cx.no_gc()).state = State::Broken;
511
512                self.load_broken_image_icon(cx.no_gc());
513
514                // Step 4. Fire an event named error at the img element.
515                (false, true)
516            },
517        };
518
519        // Fire image.onload and loadend
520        if trigger_image_load {
521            self.fire_event_including_host(cx, atom!("load"));
522            // TODO: https://html.spec.whatwg.org/multipage/#fire-a-progress-event-or-event
523            self.upcast::<EventTarget>()
524                .fire_event(cx, atom!("loadend"));
525        }
526
527        // Fire image.onerror
528        if trigger_image_error {
529            self.fire_event_including_host(cx, atom!("error"));
530            self.upcast::<EventTarget>()
531                .fire_event(cx, atom!("loadend"));
532        }
533    }
534
535    /// The response part of
536    /// <https://html.spec.whatwg.org/multipage/#reacting-to-environment-changes>.
537    fn process_image_response_for_environment_change(
538        &self,
539        cx: &mut JSContext,
540        image: ImageResponse,
541        selected_source: USVString,
542        generation: u32,
543        selected_pixel_density: f64,
544    ) {
545        match image {
546            ImageResponse::Loaded(image, url) => {
547                if let Some(pending_request) =
548                    self.pending_request.safe_borrow_mut(cx.no_gc()).as_mut()
549                {
550                    pending_request.metadata = Some(image.metadata());
551                    pending_request.final_url = Some(url);
552                    pending_request.image = Some(image);
553                    pending_request.load_time = Some(CrossProcessInstant::now());
554                }
555                self.finish_reacting_to_environment_change(
556                    selected_source,
557                    generation,
558                    selected_pixel_density,
559                );
560            },
561            ImageResponse::FailedToLoadOrDecode => {
562                // > Step 15.6: If response's unsafe response is a network error or if the
563                // > image format is unsupported (as determined by applying the image
564                // > sniffing rules, again as mentioned earlier), or if the user agent is
565                // > able to determine that image request's image is corrupted in some fatal
566                // > way such that the image dimensions cannot be obtained, or if the
567                // > resource type is multipart/x-mixed-replace, then set the pending
568                // > request to null and abort these steps.
569                self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
570            },
571            ImageResponse::MetadataLoaded(meta) => {
572                self.pending_request
573                    .safe_borrow_mut(cx.no_gc())
574                    .get_or_insert_default()
575                    .metadata = Some(meta);
576            },
577        };
578    }
579
580    /// <https://html.spec.whatwg.org/multipage/#abort-the-image-request>
581    fn abort_request(&self, cx: &mut JSContext, state: State, request: ImageRequestPhase) {
582        match request {
583            ImageRequestPhase::Current => {
584                LoadBlocker::terminate(cx, &self.current_request.borrow().blocker);
585
586                let mut request = self.current_request.safe_borrow_mut(cx);
587                request.state = state;
588                request.image = None;
589                request.metadata = None;
590                request.current_pixel_density = None;
591            },
592            ImageRequestPhase::Pending => {
593                if let Some(pending_request) = &*self.pending_request.borrow() {
594                    LoadBlocker::terminate(cx, &pending_request.blocker);
595                }
596                self.pending_request.safe_borrow_mut(cx.no_gc()).take();
597            },
598        };
599
600        if matches!(state, State::Broken) {
601            self.reject_image_decode_promises(cx);
602        } else if matches!(state, State::CompletelyAvailable) {
603            self.resolve_image_decode_promises(cx);
604        }
605    }
606
607    fn init_image_request(
608        &self,
609        cx: &mut JSContext,
610        request: &AtomicRefCell<Box<ImageRequest>>,
611        url: &ServoUrl,
612        src: &USVString,
613    ) {
614        {
615            let mut request = request.safe_borrow_mut(cx.no_gc());
616            request.parsed_url = Some(url.clone());
617            request.source_url = Some(src.clone());
618            request.image = None;
619            request.metadata = None;
620        }
621        let document = self.owner_document();
622        LoadBlocker::terminate(cx, &request.borrow().blocker);
623        *request
624            .safe_borrow_mut(cx.no_gc())
625            .blocker
626            .safe_borrow_mut(cx.no_gc()) =
627            Some(LoadBlocker::new(&document, LoadType::Image(url.clone())));
628    }
629
630    fn init_pending_image_request(
631        &self,
632        cx: &mut JSContext,
633        request: &DomRefCell<Option<Box<ImageRequest>>>,
634        url: &ServoUrl,
635        src: &USVString,
636    ) {
637        {
638            let mut request = request.safe_borrow_mut(cx);
639            let request = request.get_or_insert_default();
640            request.parsed_url = Some(url.clone());
641            request.source_url = Some(src.clone());
642            request.image = None;
643            request.metadata = None;
644        }
645        let document = self.owner_document();
646        LoadBlocker::terminate(
647            cx,
648            &request
649                .borrow()
650                .as_ref()
651                .expect("Just created a request")
652                .blocker,
653        );
654        *request
655            .safe_borrow_mut(cx)
656            .as_mut()
657            .expect("Just created a request")
658            .blocker
659            .safe_borrow_mut(cx.no_gc()) =
660            Some(LoadBlocker::new(&document, LoadType::Image(url.clone())));
661    }
662
663    /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
664    fn prepare_image_request(
665        &self,
666        cx: &mut JSContext,
667        selected_source: &USVString,
668        selected_pixel_density: f64,
669        image_url: &ServoUrl,
670    ) {
671        match self.image_request.get() {
672            ImageRequestPhase::Pending => {
673                // Step 14. If the pending request is not null and urlString is the same as the
674                // pending request's current URL, then return.
675                if self
676                    .pending_request
677                    .borrow()
678                    .as_ref()
679                    .and_then(|pending_request| pending_request.parsed_url.as_ref())
680                    .is_some_and(|parsed_url| *parsed_url == *image_url)
681                {
682                    return;
683                }
684            },
685            ImageRequestPhase::Current => {
686                // Step 16. Abort the image request for the pending request.
687                self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
688
689                // Step 17. Set image request to a new image request whose current URL is urlString.
690                let (current_request_url, current_request_state) = {
691                    let current_request = self.current_request.borrow();
692                    (current_request.parsed_url.clone(), current_request.state)
693                };
694
695                match (current_request_url, current_request_state) {
696                    (Some(parsed_url), State::PartiallyAvailable) => {
697                        // Step 15. If urlString is the same as the current request's current URL
698                        // and the current request's state is partially available, then abort the
699                        // image request for the pending request, queue an element task on the DOM
700                        // manipulation task source given the img element to restart the animation
701                        // if restart animation is set, and return.
702                        if parsed_url == *image_url {
703                            // TODO: queue a task to restart animation, if restart-animation is set
704                            return;
705                        }
706
707                        // Step 18. If the current request's state is unavailable or broken, then
708                        // set the current request to image request. Otherwise, set the pending
709                        // request to image request.
710                        self.image_request.set(ImageRequestPhase::Pending);
711                        self.init_pending_image_request(
712                            cx,
713                            &self.pending_request,
714                            image_url,
715                            selected_source,
716                        );
717                        self.pending_request
718                            .safe_borrow_mut(cx.no_gc())
719                            .as_mut()
720                            .expect("Just created a pending request")
721                            .current_pixel_density = Some(selected_pixel_density);
722                    },
723                    (_, State::Broken) | (_, State::Unavailable) => {
724                        // Step 18. If the current request's state is unavailable or broken, then
725                        // set the current request to image request. Otherwise, set the pending
726                        // request to image request.
727                        self.init_image_request(
728                            cx,
729                            &self.current_request,
730                            image_url,
731                            selected_source,
732                        );
733                        self.current_request
734                            .safe_borrow_mut(cx.no_gc())
735                            .current_pixel_density = Some(selected_pixel_density);
736                        self.reject_image_decode_promises(cx);
737                    },
738                    (_, _) => {
739                        // Step 18. If the current request's state is unavailable or broken, then
740                        // set the current request to image request. Otherwise, set the pending
741                        // request to image request.
742                        self.image_request.set(ImageRequestPhase::Pending);
743                        self.init_pending_image_request(
744                            cx,
745                            &self.pending_request,
746                            image_url,
747                            selected_source,
748                        );
749                        self.pending_request
750                            .safe_borrow_mut(cx.no_gc())
751                            .as_mut()
752                            .expect("Just created a pending image request")
753                            .current_pixel_density = Some(selected_pixel_density);
754                    },
755                }
756            },
757        }
758
759        self.fetch_image(cx, image_url);
760    }
761
762    /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
763    fn update_the_image_data_sync_steps(&self, cx: &mut JSContext) {
764        // Step 10. Let selected source and selected pixel density be the URL and pixel density that
765        // results from selecting an image source, respectively.
766        let Some((selected_source, selected_pixel_density)) = self
767            .source_set
768            .safe_borrow_mut(cx.no_gc())
769            .select_image_source(cx.no_gc(), self.upcast::<Element>())
770        else {
771            // Step 11. If selected source is null, then:
772
773            // Step 11.1. Set the current request's state to broken, abort the image request for the
774            // current request and the pending request, and set the pending request to null.
775            self.abort_request(cx, State::Broken, ImageRequestPhase::Current);
776            self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
777            self.image_request.set(ImageRequestPhase::Current);
778
779            // Step 11.2. Queue an element task on the DOM manipulation task source given the img
780            // element and the following steps:
781            let this = Trusted::new(self);
782
783            self.owner_global().task_manager().dom_manipulation_task_source().queue(
784                task!(image_null_source_error: move |cx| {
785                    let this = this.root();
786
787                    // Step 11.2.1. Change the current request's current URL to the empty string.
788                    {
789                        let mut current_request =
790                            this.current_request.safe_borrow_mut(cx.no_gc());
791                        current_request.source_url = None;
792                        current_request.parsed_url = None;
793                    }
794
795                    // Step 11.2.2. If all of the following are true:
796                    // the element has a src attribute or it uses srcset or picture; and
797                    // maybe omit events is not set or previousURL is not the empty string,
798                    // then fire an event named error at the img element.
799                    // TODO: Add missing `maybe omit events` flag and previousURL.
800                    let has_src_attribute = this.upcast::<Element>().has_attribute(&local_name!("src"));
801
802                    if has_src_attribute || this.uses_srcset_or_picture() {
803                        this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
804                    }
805                }));
806
807            // Step 11.2.3. Return.
808            return;
809        };
810
811        // Step 12. Let urlString be the result of encoding-parsing-and-serializing a URL given
812        // selected source, relative to the element's node document.
813        let Ok(image_url) = self.owner_document().encoding_parse_a_url(&selected_source) else {
814            // Step 13. If urlString is failure, then:
815
816            // Step 13.1. Abort the image request for the current request and the pending request.
817            // Step 13.2. Set the current request's state to broken.
818            self.abort_request(cx, State::Broken, ImageRequestPhase::Current);
819            self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
820
821            // Step 13.3. Set the pending request to null.
822            self.image_request.set(ImageRequestPhase::Current);
823
824            // Step 13.4. Queue an element task on the DOM manipulation task source given the img
825            // element and the following steps:
826            let this = Trusted::new(self);
827
828            self.owner_global()
829                .task_manager()
830                .dom_manipulation_task_source()
831                .queue(task!(image_selected_source_error: move |cx| {
832                    let this = this.root();
833
834                    // Step 13.4.1. Change the current request's current URL to selected source.
835                    {
836                        let mut current_request =
837                            this.current_request.safe_borrow_mut(cx.no_gc());
838                        current_request.source_url = Some(selected_source);
839                        current_request.parsed_url = None;
840                    }
841
842                    // Step 13.4.2. If maybe omit events is not set or previousURL is not equal to
843                    // selected source, then fire an event named error at the img element.
844                    // TODO: Add missing `maybe omit events` flag and previousURL.
845                    this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
846                }));
847
848            // Step 13.5. Return.
849            return;
850        };
851
852        self.prepare_image_request(cx, &selected_source, selected_pixel_density, &image_url);
853    }
854
855    /// <https://html.spec.whatwg.org/multipage/#update-the-image-data>
856    pub(crate) fn update_the_image_data(&self, cx: &mut JSContext) {
857        // Cancel any outstanding tasks that were queued before.
858        self.generation.set(self.generation.get() + 1);
859
860        // Step 1. If the element's node document is not fully active, then:
861        if !self.owner_document().is_fully_active() {
862            // TODO Step 1.1. Continue running this algorithm in parallel.
863            // TODO Step 1.2. Wait until the element's node document is fully active.
864            // TODO Step 1.3. If another instance of this algorithm for this img element was started after
865            // this instance (even if it aborted and is no longer running), then return.
866            // TODO Step 1.4. Queue a microtask to continue this algorithm.
867        }
868
869        // TODO(https://github.com/whatwg/html/issues/8207): Add spec step once spec is aligned
870        // with implementations
871        self.source_set
872            .safe_borrow_mut(cx.no_gc())
873            .update_source_set(cx.no_gc(), self.upcast::<Element>());
874
875        // Step 2. If the user agent cannot support images, or its support for images has been
876        // disabled, then abort the image request for the current request and the pending request,
877        // set the current request's state to unavailable, set the pending request to null, and
878        // return.
879        // Nothing specific to be done here since the user agent supports image processing.
880
881        // Always first set the current request to unavailable, ensuring img.complete is false.
882        // <https://html.spec.whatwg.org/multipage/#when-to-obtain-images>
883        self.current_request.safe_borrow_mut(cx.no_gc()).state = State::Unavailable;
884
885        // TODO Step 3. Let previousURL be the current request's current URL.
886
887        // Step 4. Let selected source be null and selected pixel density be undefined.
888        let mut selected_source = None;
889        let mut selected_pixel_density = None;
890
891        // Step 5. If the element does not use srcset or picture and it has a src attribute
892        // specified whose value is not the empty string, then set selected source to the value of
893        // the element's src attribute and set selected pixel density to 1.0.
894        let src = self
895            .upcast::<Element>()
896            .get_string_attribute(&local_name!("src"));
897
898        if !self.uses_srcset_or_picture() && !src.is_empty() {
899            selected_source = Some(USVString(String::from(src)));
900            selected_pixel_density = Some(1_f64);
901        };
902
903        // Step 6. Set the element's last selected source to selected source.
904        self.last_selected_source
905            .safe_borrow_mut(cx.no_gc())
906            .clone_from(&selected_source);
907
908        // Step 7. If selected source is not null, then:
909        if let Some(selected_source) = selected_source {
910            // Step 7.1. Let urlString be the result of encoding-parsing-and-serializing a URL given
911            // selected source, relative to the element's node document.
912            // Step 7.2. If urlString is failure, then abort this inner set of steps.
913            if let Ok(image_url) = self.owner_document().encoding_parse_a_url(&selected_source) {
914                // Step 7.3. Let key be a tuple consisting of urlString, the img element's
915                // crossorigin attribute's mode, and, if that mode is not No CORS, the node
916                // document's origin.
917                let window = self.owner_window();
918                let response = window.image_cache().get_image(
919                    image_url.clone(),
920                    window.origin().immutable().clone(),
921                    cors_setting_for_element(self.upcast()),
922                );
923
924                // Step 7.4. If the list of available images contains an entry for key, then:
925                if let Some(image) = response {
926                    // TODO Step 7.4.1. Set the ignore higher-layer caching flag for that entry.
927
928                    // Step 7.4.2. Abort the image request for the current request and the pending
929                    // request.
930                    self.abort_request(cx, State::CompletelyAvailable, ImageRequestPhase::Current);
931                    self.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
932
933                    // Step 7.4.3. Set the pending request to null.
934                    self.image_request.set(ImageRequestPhase::Current);
935
936                    // Step 7.4.4. Set the current request to a new image request whose image data
937                    // is that of the entry and whose state is completely available.
938                    let mut current_request = self.current_request.safe_borrow_mut(cx.no_gc());
939                    current_request.metadata = Some(image.metadata());
940                    current_request.image = Some(image);
941                    current_request.final_url = Some(image_url.clone());
942
943                    // TODO Step 7.4.5. Prepare the current request for presentation given the img
944                    // element.
945                    self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
946
947                    // Step 7.4.6. Set the current request's current pixel density to selected pixel
948                    // density.
949                    current_request.current_pixel_density = selected_pixel_density;
950
951                    // Step 7.4.7. Queue an element task on the DOM manipulation task source given
952                    // the img element and the following steps:
953                    let this = Trusted::new(self);
954
955                    self.owner_global()
956                        .task_manager()
957                        .dom_manipulation_task_source()
958                        .queue(task!(image_load_event: move |cx| {
959                            let this = this.root();
960
961                            // TODO Step 7.4.7.1. If restart animation is set, then restart the
962                            // animation.
963
964                            // Step 7.4.7.2. Set the current request's current URL to urlString.
965                            {
966                                let mut current_request =
967                                    this.current_request.safe_borrow_mut(cx.no_gc());
968                                current_request.source_url = Some(selected_source);
969                                current_request.parsed_url = Some(image_url);
970                                current_request.load_time = Some(CrossProcessInstant::now());
971                            }
972
973                            // Step 7.4.7.3. If maybe omit events is not set or previousURL is not
974                            // equal to urlString, then fire an event named load at the img element.
975                            // TODO: Add missing `maybe omit events` flag and previousURL.
976                            this.fire_event_including_host(cx, atom!("load"));
977                        }));
978
979                    // Step 7.4.8. Abort the update the image data algorithm.
980                    return;
981                }
982            }
983        }
984
985        // Step 8. Queue a microtask to perform the rest of this algorithm, allowing the task that
986        // invoked this algorithm to continue.
987        let task = ImageElementMicrotask::UpdateImageData {
988            elem: Dom::from_ref(self),
989            generation: self.generation.get(),
990        };
991
992        ScriptThread::await_stable_state(cx, Box::new(task));
993    }
994
995    /// <https://html.spec.whatwg.org/multipage/#img-environment-changes>
996    pub(crate) fn react_to_environment_changes(&self, cx: &JSContext) {
997        // Step 1. Await a stable state.
998        let task = ImageElementMicrotask::EnvironmentChanges {
999            elem: Dom::from_ref(self),
1000            generation: self.generation.get(),
1001        };
1002
1003        ScriptThread::await_stable_state(cx, Box::new(task));
1004    }
1005
1006    /// <https://html.spec.whatwg.org/multipage/#img-environment-changes>
1007    fn react_to_environment_changes_sync_steps(&self, cx: &mut JSContext, generation: u32) {
1008        let document = self.owner_document();
1009        let has_pending_request = matches!(self.image_request.get(), ImageRequestPhase::Pending);
1010
1011        // Step 2. If the img element does not use srcset or picture, its node document is not fully
1012        // active, it has image data whose resource type is multipart/x-mixed-replace, or its
1013        // pending request is not null, then return.
1014        if !document.is_fully_active() || !self.uses_srcset_or_picture() || has_pending_request {
1015            return;
1016        }
1017
1018        // Step 3. Let selected source and selected pixel density be the URL and pixel density that
1019        // results from selecting an image source, respectively.
1020        let Some((selected_source, selected_pixel_density)) = self
1021            .source_set
1022            .safe_borrow_mut(cx.no_gc())
1023            .select_image_source(cx.no_gc(), self.upcast::<Element>())
1024        else {
1025            // Step 4. If selected source is null, then return.
1026            return;
1027        };
1028
1029        // Step 5. If selected source and selected pixel density are the same as the element's last
1030        // selected source and current pixel density, then return.
1031        let mut same_selected_source = self
1032            .last_selected_source
1033            .borrow()
1034            .as_ref()
1035            .is_some_and(|source| *source == selected_source);
1036
1037        // There are missing steps for the element's last selected source in specification so let's
1038        // check the current request's current URL as well.
1039        // <https://github.com/whatwg/html/issues/5060>
1040        same_selected_source = same_selected_source ||
1041            self.current_request
1042                .borrow()
1043                .source_url
1044                .as_ref()
1045                .is_some_and(|source| *source == selected_source);
1046
1047        let same_selected_pixel_density = self
1048            .current_request
1049            .borrow()
1050            .current_pixel_density
1051            .is_some_and(|pixel_density| pixel_density == selected_pixel_density);
1052
1053        if same_selected_source && same_selected_pixel_density {
1054            return;
1055        }
1056
1057        // Step 6. Let urlString be the result of encoding-parsing-and-serializing a URL given
1058        // selected source, relative to the element's node document.
1059        // Step 7. If urlString is failure, then return.
1060        let Ok(image_url) = document.encoding_parse_a_url(&selected_source) else {
1061            return;
1062        };
1063
1064        // Step 13. Set the element's pending request to image request.
1065        self.image_request.set(ImageRequestPhase::Pending);
1066        self.init_pending_image_request(cx, &self.pending_request, &image_url, &selected_source);
1067
1068        // Step 15. If the list of available images contains an entry for key, then set image
1069        // request's image data to that of the entry. Continue to the next step.
1070        let window = self.owner_window();
1071        let cache_result = window.image_cache().get_cached_image_status(
1072            image_url.clone(),
1073            window.origin().immutable(),
1074            cors_setting_for_element(self.upcast()),
1075        );
1076
1077        let change_type = ChangeType::Environment {
1078            selected_source: selected_source.clone(),
1079            selected_pixel_density,
1080        };
1081
1082        match cache_result {
1083            ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
1084                self.finish_reacting_to_environment_change(
1085                    selected_source,
1086                    generation,
1087                    selected_pixel_density,
1088                );
1089            },
1090            ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m, id)) => {
1091                self.process_image_response_for_environment_change(
1092                    cx,
1093                    ImageResponse::MetadataLoaded(m),
1094                    selected_source,
1095                    generation,
1096                    selected_pixel_density,
1097                );
1098                self.register_image_cache_callback(id, change_type);
1099            },
1100            ImageCacheResult::FailedToLoadOrDecode => {
1101                self.process_image_response_for_environment_change(
1102                    cx,
1103                    ImageResponse::FailedToLoadOrDecode,
1104                    selected_source,
1105                    generation,
1106                    selected_pixel_density,
1107                );
1108            },
1109            ImageCacheResult::ReadyForRequest(id) => {
1110                self.fetch_request(&image_url, id);
1111                self.register_image_cache_callback(id, change_type);
1112            },
1113            ImageCacheResult::Pending(id) => {
1114                self.register_image_cache_callback(id, change_type);
1115            },
1116        }
1117    }
1118
1119    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1120    fn react_to_decode_image_sync_steps(&self, cx: &mut JSContext, promise: &RootedPromise) {
1121        // Step 2.2. If any of the following are true: this's node document is not fully active; or
1122        // this's current request's state is broken, then reject promise with an "EncodingError"
1123        // DOMException.
1124        if !self.owner_document().is_fully_active() ||
1125            matches!(self.current_request.borrow().state, State::Broken)
1126        {
1127            promise.reject_error(cx, Error::Encoding(Some("Image element's owner document is not fully active or image element's request is broken".into())));
1128        } else if matches!(
1129            self.current_request.borrow().state,
1130            State::CompletelyAvailable
1131        ) {
1132            // this doesn't follow the spec, but it's been discussed in <https://github.com/whatwg/html/issues/4217>
1133            promise.resolve_native(cx, &());
1134        } else if matches!(self.current_request.borrow().state, State::Unavailable) &&
1135            self.current_request.borrow().source_url.is_none()
1136        {
1137            // Note: Despite being not explicitly stated in the specification but if current
1138            // request's state is unavailable and current URL is empty string (<img> without "src"
1139            // and "srcset" attributes) then reject promise with an "EncodingError" DOMException.
1140            // <https://github.com/whatwg/html/issues/11769>
1141            promise.reject_error(
1142                cx,
1143                Error::Encoding(Some(
1144                    "Image element does not provide `src` or `srcset` attributes".into(),
1145                )),
1146            );
1147        } else {
1148            self.image_decode_promises
1149                .safe_borrow_mut(cx.no_gc())
1150                .push(promise.to_traced());
1151        }
1152    }
1153
1154    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1155    fn resolve_image_decode_promises(&self, cx: &mut JSContext) {
1156        if self.image_decode_promises.borrow().is_empty() {
1157            return;
1158        }
1159
1160        // Step 2.3. If the decoding process completes successfully, then queue a global task on the
1161        // DOM manipulation task source with global to resolve promise with undefined.
1162        let trusted_image_decode_promises: Vec<TrustedPromise> = self
1163            .image_decode_promises
1164            .borrow()
1165            .iter()
1166            .map(|promise| TrustedPromise::from(&promise.root(cx)))
1167            .collect();
1168
1169        self.image_decode_promises
1170            .safe_borrow_mut(cx.no_gc())
1171            .clear();
1172
1173        self.owner_global()
1174            .task_manager()
1175            .dom_manipulation_task_source()
1176            .queue(task!(fulfill_image_decode_promises: move |cx| {
1177                for trusted_promise in trusted_image_decode_promises {
1178                    trusted_promise.root(cx).resolve_native(cx, &());
1179                }
1180            }));
1181    }
1182
1183    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1184    fn reject_image_decode_promises(&self, cx: &mut JSContext) {
1185        if self.image_decode_promises.borrow().is_empty() {
1186            return;
1187        }
1188
1189        // Step 2.3. Queue a global task on the DOM manipulation task source with global to reject
1190        // promise with an "EncodingError" DOMException.
1191        let trusted_image_decode_promises: Vec<TrustedPromise> = self
1192            .image_decode_promises
1193            .borrow()
1194            .iter()
1195            .map(|promise| TrustedPromise::from(&promise.root(cx)))
1196            .collect();
1197
1198        self.image_decode_promises
1199            .safe_borrow_mut(cx.no_gc())
1200            .clear();
1201
1202        self.owner_global()
1203            .task_manager()
1204            .dom_manipulation_task_source()
1205            .queue(task!(reject_image_decode_promises: move |cx| {
1206                for trusted_promise in trusted_image_decode_promises {
1207                    trusted_promise.root(cx).reject_error(cx, Error::Encoding(Some("Image could not be decoded".into())));
1208                }
1209            }));
1210    }
1211
1212    /// <https://html.spec.whatwg.org/multipage/#img-environment-changes>
1213    fn finish_reacting_to_environment_change(
1214        &self,
1215        selected_source: USVString,
1216        generation: u32,
1217        selected_pixel_density: f64,
1218    ) {
1219        // Step 16. Queue an element task on the DOM manipulation task source given the img element
1220        // and the following steps:
1221        let this = Trusted::new(self);
1222
1223        self.owner_global()
1224            .task_manager()
1225            .dom_manipulation_task_source()
1226            .queue(task!(image_load_event: move |cx| {
1227                let this = this.root();
1228
1229                // Step 16.1. If the img element has experienced relevant mutations since this
1230                // algorithm started, then set the pending request to null and abort these steps.
1231                if this.generation.get() != generation {
1232                    this.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
1233                    this.image_request.set(ImageRequestPhase::Current);
1234                    return;
1235                }
1236
1237                // Step 16.2. Set the img element's last selected source to selected source and the
1238                // img element's current pixel density to selected pixel density.
1239                *this.last_selected_source.safe_borrow_mut(cx.no_gc()) = Some(selected_source);
1240
1241
1242                if let Some(pending_request) = &mut *this.pending_request.safe_borrow_mut(cx.no_gc()) {
1243                    // Step 16.3. Set the image request's state to completely available.
1244                    pending_request.state = State::CompletelyAvailable;
1245
1246                    pending_request.current_pixel_density = Some(selected_pixel_density);
1247
1248                    // Step 16.4. Add the image to the list of available images using the key key,
1249                    // with the ignore higher-layer caching flag set.
1250                    // Already a part of the list of available images due to Step 15.
1251                    // Step 16.5. Upgrade the pending request to the current request.
1252                } else {
1253                    log::error!("Pending request was null");
1254                    return
1255                }
1256                *this.current_request.safe_borrow_mut(cx.no_gc()) = this.pending_request.safe_borrow_mut(cx.no_gc()).take().expect("Should have a pending request");
1257
1258
1259                this.abort_request(cx, State::Unavailable, ImageRequestPhase::Pending);
1260                this.image_request.set(ImageRequestPhase::Current);
1261
1262                // TODO Step 16.6. Prepare image request for presentation given the img element.
1263                this.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
1264
1265                // Step 16.7. Fire an event named load at the img element.
1266                this.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1267            }));
1268    }
1269
1270    /// <https://html.spec.whatwg.org/multipage/#use-srcset-or-picture>
1271    fn uses_srcset_or_picture(&self) -> bool {
1272        let element = self.upcast::<Element>();
1273
1274        let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1275        let has_parent_picture = element
1276            .upcast::<Node>()
1277            .GetParentElement()
1278            .is_some_and(|parent| parent.is::<HTMLPictureElement>());
1279        has_srcset_attribute || has_parent_picture
1280    }
1281
1282    fn new_inherited(
1283        local_name: LocalName,
1284        prefix: Option<Prefix>,
1285        document: &Document,
1286        creator: ElementCreator,
1287    ) -> HTMLImageElement {
1288        HTMLImageElement {
1289            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
1290            image_request: Cell::new(ImageRequestPhase::Current),
1291            current_request: AtomicRefCell::new(Box::new(ImageRequest {
1292                state: State::Unavailable,
1293                parsed_url: None,
1294                source_url: None,
1295                image: None,
1296                metadata: None,
1297                blocker: DomRefCell::new(None),
1298                final_url: None,
1299                load_time: None,
1300                current_pixel_density: None,
1301            })),
1302            pending_request: DomRefCell::new(None),
1303            form_owner: Default::default(),
1304            generation: Default::default(),
1305            source_set: DomRefCell::new(SourceSet::new()),
1306            dimension_attribute_source: Default::default(),
1307            last_selected_source: DomRefCell::new(None),
1308            image_decode_promises: DomRefCell::new(vec![]),
1309            line_number: creator.return_line_number(),
1310        }
1311    }
1312
1313    pub(crate) fn new(
1314        cx: &mut JSContext,
1315        local_name: LocalName,
1316        prefix: Option<Prefix>,
1317        document: &Document,
1318        proto: Option<HandleObject>,
1319        creator: ElementCreator,
1320    ) -> DomRoot<HTMLImageElement> {
1321        let image_element = Node::reflect_node_with_proto(
1322            cx,
1323            Box::new(HTMLImageElement::new_inherited(
1324                local_name, prefix, document, creator,
1325            )),
1326            document,
1327            proto,
1328        );
1329        image_element
1330            .dimension_attribute_source
1331            .set(Some(image_element.upcast()));
1332        image_element
1333    }
1334
1335    pub(crate) fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> {
1336        let elem = self.upcast::<Element>();
1337        let value = elem.get_attribute_string_value(&local_name!("usemap"))?;
1338
1339        if value.is_empty() || !value.is_char_boundary(1) {
1340            return None;
1341        }
1342
1343        let (first, last) = value.split_at(1);
1344
1345        if first != "#" || last.is_empty() {
1346            return None;
1347        }
1348
1349        let useMapElements = self
1350            .owner_document()
1351            .upcast::<Node>()
1352            .traverse_preorder(ShadowIncluding::No)
1353            .filter_map(DomRoot::downcast::<HTMLMapElement>)
1354            .find(|n| {
1355                n.upcast::<Element>()
1356                    .get_name()
1357                    .is_some_and(|n| *n == *last)
1358            });
1359
1360        useMapElements.map(|mapElem| mapElem.get_area_elements())
1361    }
1362
1363    pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
1364        if let Some(ref image) = self.current_request.borrow().image {
1365            return image.cors_status() == CorsStatus::Safe;
1366        }
1367
1368        self.current_request
1369            .borrow()
1370            .final_url
1371            .as_ref()
1372            .is_some_and(|url| url.scheme() == "data" || url.origin().same_origin(origin))
1373    }
1374
1375    fn generation_id(&self) -> u32 {
1376        self.generation.get()
1377    }
1378
1379    fn load_broken_image_icon(&self, no_gc: &NoGC) {
1380        let window = self.owner_window();
1381        let Some(broken_image_icon) = window.image_cache().get_broken_image_icon() else {
1382            return;
1383        };
1384
1385        self.current_request.safe_borrow_mut(no_gc).metadata = Some(broken_image_icon.metadata);
1386        self.current_request.safe_borrow_mut(no_gc).image = Some(Image::Raster(broken_image_icon));
1387        self.upcast::<Node>().dirty(no_gc, NodeDamage::Other);
1388    }
1389
1390    /// Get the full URL of the current image of this `<img>` element, returning `None` if the URL
1391    /// could not be joined with the `Document` URL.
1392    pub(crate) fn full_image_url_for_user_interface(&self) -> Option<ServoUrl> {
1393        self.owner_document()
1394            .base_url()
1395            .join(&self.CurrentSrc())
1396            .ok()
1397    }
1398
1399    pub(crate) fn set_dimension_attribute_source(&self, no_gc: &NoGC, value: Option<&Element>) {
1400        self.dimension_attribute_source.set(value);
1401        self.upcast::<Node>().dirty(no_gc, NodeDamage::Other);
1402    }
1403
1404    /// <https://html.spec.whatwg.org/multipage/#img-available>
1405    fn image_is_available(&self) -> bool {
1406        // > When an image request's state is either partially available
1407        // > or completely available, the image request is said to be available.
1408        matches!(
1409            self.current_request.borrow().state,
1410            State::PartiallyAvailable | State::CompletelyAvailable
1411        )
1412    }
1413
1414    /// <https://html.spec.whatwg.org/multipage/#img-dimensions>
1415    fn dimensions(&self, cx: &mut JSContext) -> (u32, u32) {
1416        // Step 2. If image is available, then set naturalDimensions to
1417        // image's density-corrected natural width and height.
1418        let natural_dimensions = if self.image_is_available() {
1419            Some(self.density_corrected_natural_width_and_height())
1420        } else {
1421            // Step 1. Let naturalDimensions be null.
1422            None
1423        };
1424        // Step 3. Return the result of determining the dimensions of image given naturalDimensions.
1425        Self::determine_dimensions(cx, self.upcast(), natural_dimensions)
1426    }
1427
1428    /// <https://html.spec.whatwg.org/multipage/#determine-dimensions>
1429    pub(crate) fn determine_dimensions(
1430        cx: &mut JSContext,
1431        element: &Element,
1432        natural_dimensions: Option<(u32, u32)>,
1433    ) -> (u32, u32) {
1434        // Step 1. If element is being rendered,
1435        // then return the width and height of element's content box, in CSS pixels. [CSS]
1436        if let Some(content_box) = element.upcast::<Node>().content_box(cx) {
1437            return (
1438                content_box.size.width.to_px() as u32,
1439                content_box.size.height.to_px() as u32,
1440            );
1441        }
1442        // Step 2. Let width and height be 0.
1443        // Step 3. If naturalDimensions is not null,
1444        // then set width to naturalDimensions's width and height to naturalDimensions's height.
1445        let (mut width, mut height) = natural_dimensions.unwrap_or_default();
1446        // Step 4. If element has a width attribute:
1447        if let Some(width_value) = element.get_attribute_string_ref(&local_name!("width")) {
1448            // Step 4.1. Let attributeWidth be the result of parsing that attribute's value.
1449            // Step 4.2. If attributeWidth is not an error, then set width to attributeWidth.
1450            if let Ok(attribute_width) =
1451                parse_unsigned_integer(width_value.as_attr_ref().value().as_ref().chars())
1452            {
1453                width = attribute_width;
1454            }
1455        }
1456        // Step 5. If element has a height attribute:
1457        if let Some(height_value) = element.get_attribute_string_ref(&local_name!("height")) {
1458            // Step 5.1. Let attributeHeight be the result of parsing that attribute's value.
1459            // Step 5.2. If attributeHeight is not an error, then set height to attributeHeight.
1460            if let Ok(attribute_height) =
1461                parse_unsigned_integer(height_value.as_attr_ref().value().as_ref().chars())
1462            {
1463                height = attribute_height;
1464            }
1465        }
1466        // Step 6. Return a width of width and a height of height, in CSS pixels.
1467        (width, height)
1468    }
1469
1470    /// <https://html.spec.whatwg.org/multipage/#density-corrected-intrinsic-width-and-height>
1471    fn density_corrected_natural_width_and_height(&self) -> (u32, u32) {
1472        // Step 1. Let density be img's current request's current pixel density.
1473        let current_request = self.current_request.borrow();
1474        let density = current_request.current_pixel_density.unwrap_or(1f64);
1475
1476        // Step 2. Let dimensions be img's current request's preferred density-corrected dimensions.
1477        // TODO
1478
1479        // Step 3. If dimensions is not null,
1480        // then set dimensions's width to dimensions's width divided by density,
1481        // set dimensions's height to dimensions's height divided by density,
1482        // and return dimensions.
1483        // TODO
1484
1485        // Step 4. Let intrinsicWidth, intrinsicHeight, and intrinsicRatio be img's intrinsic width,
1486        // intrinsic height, and intrinsic aspect ratio, if any, respectively.
1487        // Step 5. If intrinsicWidth is not absent, then set intrinsicWidth to intrinsicWidth divided by density.
1488        // Step 6. If intrinsicHeight is not absent, then set intrinsicHeight to intrinsicHeight divided by density.
1489        // Step 7. Return the result of applying the default sizing algorithm with intrinsicWidth,
1490        // intrinsicHeight, and intrinsicRatio, using a default object size of 300 by 150.
1491        let intrinsic_width = current_request
1492            .metadata
1493            .map(|metadata| (metadata.width as f64 / density) as u32)
1494            .unwrap_or(300);
1495        let intrinsic_height = current_request
1496            .metadata
1497            .map(|metadata| (metadata.height as f64 / density) as u32)
1498            .unwrap_or(150);
1499        (intrinsic_width, intrinsic_height)
1500    }
1501}
1502
1503#[derive(JSTraceable, MallocSizeOf)]
1504pub(crate) enum ImageElementMicrotask {
1505    UpdateImageData {
1506        elem: Dom<HTMLImageElement>,
1507        generation: u32,
1508    },
1509    EnvironmentChanges {
1510        elem: Dom<HTMLImageElement>,
1511        generation: u32,
1512    },
1513    Decode {
1514        elem: Dom<HTMLImageElement>,
1515        promise: TracedPromise,
1516    },
1517}
1518
1519impl MicrotaskRunnable for ImageElementMicrotask {
1520    fn handler(&self, cx: &mut JSContext) {
1521        let mut realm = match self {
1522            &ImageElementMicrotask::UpdateImageData { ref elem, .. } |
1523            &ImageElementMicrotask::EnvironmentChanges { ref elem, .. } |
1524            &ImageElementMicrotask::Decode { ref elem, .. } => enter_auto_realm(cx, &**elem),
1525        };
1526        let cx = &mut realm;
1527        match *self {
1528            ImageElementMicrotask::UpdateImageData {
1529                ref elem,
1530                ref generation,
1531            } => {
1532                // <https://html.spec.whatwg.org/multipage/#update-the-image-data>
1533                // Step 9. If another instance of this algorithm for this img element was started
1534                // after this instance (even if it aborted and is no longer running), then return.
1535                if elem.generation.get() == *generation {
1536                    elem.update_the_image_data_sync_steps(cx);
1537                }
1538            },
1539            ImageElementMicrotask::EnvironmentChanges {
1540                ref elem,
1541                ref generation,
1542            } => {
1543                elem.react_to_environment_changes_sync_steps(cx, *generation);
1544            },
1545            ImageElementMicrotask::Decode {
1546                ref elem,
1547                ref promise,
1548            } => {
1549                let promise = promise.root(cx);
1550                elem.react_to_decode_image_sync_steps(cx, &promise);
1551            },
1552        }
1553    }
1554}
1555
1556impl<'dom> LayoutDom<'dom, HTMLImageElement> {
1557    fn current_request(self) -> AtomicRef<'dom, ImageRequest> {
1558        AtomicRef::map(self.unsafe_get().current_request.borrow(), |request| {
1559            &**request
1560        })
1561    }
1562
1563    #[expect(unsafe_code)]
1564    fn dimension_attribute_source(self) -> LayoutDom<'dom, Element> {
1565        unsafe {
1566            self.unsafe_get()
1567                .dimension_attribute_source
1568                .to_layout()
1569                .expect("dimension attribute source should be always non-null")
1570        }
1571    }
1572
1573    pub(crate) fn image_url(self) -> Option<ServoUrl> {
1574        self.current_request().parsed_url.clone()
1575    }
1576
1577    pub(crate) fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
1578        let current_request = self.current_request();
1579        (current_request.image.clone(), current_request.metadata)
1580    }
1581
1582    pub(crate) fn load_time(self) -> Option<CrossProcessInstant> {
1583        self.current_request().load_time
1584    }
1585
1586    pub(crate) fn image_density(self) -> Option<f64> {
1587        self.current_request().current_pixel_density
1588    }
1589
1590    pub(crate) fn showing_broken_image_icon(self) -> bool {
1591        matches!(self.current_request().state, State::Broken)
1592    }
1593
1594    pub(crate) fn width(self) -> LengthOrPercentageOrAuto {
1595        self.dimension_attribute_source()
1596            .dimension_attr_value(local_name!("width"))
1597    }
1598
1599    pub(crate) fn height(self) -> LengthOrPercentageOrAuto {
1600        self.dimension_attribute_source()
1601            .dimension_attr_value(local_name!("height"))
1602    }
1603
1604    pub(crate) fn margin_right_left(self) -> LengthOrPercentageOrAuto {
1605        self.dimension_attribute_source()
1606            .dimension_attr_value(local_name!("hspace"))
1607    }
1608
1609    pub(crate) fn margin_top_bottom(self) -> LengthOrPercentageOrAuto {
1610        self.dimension_attribute_source()
1611            .dimension_attr_value(local_name!("vspace"))
1612    }
1613}
1614
1615impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
1616    /// <https://html.spec.whatwg.org/multipage/#dom-image>
1617    fn Image(
1618        cx: &mut JSContext,
1619        window: &Window,
1620        proto: Option<HandleObject>,
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            cx,
1631            QualName::new(None, ns!(html), local_name!("img")),
1632            None,
1633            &document,
1634            ElementCreator::ScriptCreated,
1635            CustomElementCreationMode::Synchronous,
1636            proto,
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(cx, 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(cx, 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, cx: &mut JSContext, value: Option<DOMString>) {
1685        set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
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, cx: &mut JSContext) -> u32 {
1700        // > The width getter steps are to return the width of this's dimensions.
1701        self.dimensions(cx).0
1702    }
1703
1704    // <https://html.spec.whatwg.org/multipage/#dom-img-width>
1705    make_dimension_uint_setter!(SetWidth, "width");
1706
1707    // <https://html.spec.whatwg.org/multipage/#dom-img-height>
1708    fn Height(&self, cx: &mut JSContext) -> u32 {
1709        // > The height getter steps are to return the height of this's dimensions.
1710        self.dimensions(cx).1
1711    }
1712
1713    // <https://html.spec.whatwg.org/multipage/#dom-img-height>
1714    make_dimension_uint_setter!(SetHeight, "height");
1715
1716    /// <https://html.spec.whatwg.org/multipage/#dom-img-naturalwidth>
1717    fn NaturalWidth(&self) -> u32 {
1718        // Step 1. If the image is not available, then return 0.
1719        if !self.image_is_available() {
1720            return 0;
1721        }
1722
1723        // Step 2. Return the respective component of the image's
1724        // density-corrected natural width and height, in CSS pixels. [CSS]
1725        self.density_corrected_natural_width_and_height().0
1726    }
1727
1728    /// <https://html.spec.whatwg.org/multipage/#dom-img-naturalheight>
1729    fn NaturalHeight(&self) -> u32 {
1730        // Step 1. If the image is not available, then return 0.
1731        if !self.image_is_available() {
1732            return 0;
1733        }
1734
1735        // Step 2. Return the respective component of the image's
1736        // density-corrected natural width and height, in CSS pixels. [CSS]
1737        self.density_corrected_natural_width_and_height().1
1738    }
1739
1740    /// <https://html.spec.whatwg.org/multipage/#dom-img-complete>
1741    fn Complete(&self) -> bool {
1742        let element = self.upcast::<Element>();
1743
1744        // Step 1. If any of the following are true:
1745        // both the src attribute and the srcset attribute are omitted;
1746        let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1747        if !element.has_attribute(&local_name!("src")) && !has_srcset_attribute {
1748            return true;
1749        }
1750
1751        // the srcset attribute is omitted and the src attribute's value is the empty string;
1752        let src = element.get_string_attribute(&local_name!("src"));
1753        if !has_srcset_attribute && src.is_empty() {
1754            return true;
1755        }
1756
1757        // the img element's current request's state is completely available and its pending request
1758        // is null; or the img element's current request's state is broken and its pending request
1759        // is null, then return true.
1760        if matches!(self.image_request.get(), ImageRequestPhase::Current) &&
1761            matches!(
1762                self.current_request.borrow().state,
1763                State::CompletelyAvailable | State::Broken
1764            )
1765        {
1766            return true;
1767        }
1768
1769        // Step 2. Return false.
1770        false
1771    }
1772
1773    /// <https://html.spec.whatwg.org/multipage/#dom-img-currentsrc>
1774    fn CurrentSrc(&self) -> USVString {
1775        let current_request = self.current_request.borrow();
1776        let url = &current_request.parsed_url;
1777        match *url {
1778            Some(ref url) => USVString(url.clone().into_string()),
1779            None => {
1780                let unparsed_url = &current_request.source_url;
1781                match *unparsed_url {
1782                    Some(ref url) => url.clone(),
1783                    None => USVString(String::new()),
1784                }
1785            },
1786        }
1787    }
1788
1789    /// <https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy>
1790    fn ReferrerPolicy(&self) -> DOMString {
1791        reflect_referrer_policy_attribute(self.upcast::<Element>())
1792    }
1793
1794    // <https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy>
1795    make_setter!(SetReferrerPolicy, "referrerpolicy");
1796
1797    /// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
1798    fn Decode(&self, cx: &mut JSContext) -> RootedPromise {
1799        // Step 1. Let promise be a new promise.
1800        let promise = Promise::new(cx, &self.global());
1801
1802        // Step 2. Queue a microtask to perform the following steps:
1803        let task = ImageElementMicrotask::Decode {
1804            elem: Dom::from_ref(self),
1805            promise: promise.to_traced(),
1806        };
1807
1808        ScriptThread::await_stable_state(cx, Box::new(task));
1809
1810        // Step 3. Return promise.
1811        promise
1812    }
1813
1814    // https://html.spec.whatwg.org/multipage/#dom-img-name
1815    make_getter!(Name, "name");
1816
1817    // https://html.spec.whatwg.org/multipage/#dom-img-name
1818    make_atomic_setter!(SetName, "name");
1819
1820    // https://html.spec.whatwg.org/multipage/#dom-img-align
1821    make_getter!(Align, "align");
1822
1823    // https://html.spec.whatwg.org/multipage/#dom-img-align
1824    make_setter!(SetAlign, "align");
1825
1826    // https://html.spec.whatwg.org/multipage/#dom-img-hspace
1827    make_dimension_uint_getter!(Hspace, "hspace");
1828
1829    // https://html.spec.whatwg.org/multipage/#dom-img-hspace
1830    make_dimension_uint_setter!(SetHspace, "hspace");
1831
1832    // https://html.spec.whatwg.org/multipage/#dom-img-vspace
1833    make_dimension_uint_getter!(Vspace, "vspace");
1834
1835    // https://html.spec.whatwg.org/multipage/#dom-img-vspace
1836    make_dimension_uint_setter!(SetVspace, "vspace");
1837
1838    // https://html.spec.whatwg.org/multipage/#dom-img-longdesc
1839    make_url_getter!(LongDesc, "longdesc");
1840
1841    // https://html.spec.whatwg.org/multipage/#dom-img-longdesc
1842    make_url_setter!(SetLongDesc, "longdesc");
1843
1844    // https://html.spec.whatwg.org/multipage/#dom-img-border
1845    make_getter!(Border, "border");
1846
1847    // https://html.spec.whatwg.org/multipage/#dom-img-border
1848    make_setter!(SetBorder, "border");
1849}
1850
1851impl VirtualMethods for HTMLImageElement {
1852    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1853        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1854    }
1855
1856    fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
1857        self.super_type().unwrap().adopting_steps(cx, old_doc);
1858        self.update_the_image_data(cx);
1859    }
1860
1861    fn attribute_mutated(
1862        &self,
1863        cx: &mut JSContext,
1864        attr: AttrRef<'_>,
1865        mutation: AttributeMutation,
1866    ) {
1867        self.super_type()
1868            .unwrap()
1869            .attribute_mutated(cx, attr, mutation);
1870
1871        match attr.local_name() {
1872            // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1873            // > The element's src, srcset, width, or sizes attributes are set, changed, or
1874            // > removed.
1875            //
1876            // The specification is wrong here. `width` should not be counted as a relevant
1877            // mutation and no other browser does this. It used to be on the list because
1878            // it was used as a fallback for `srcset`, but no browser implements that fallback.
1879            // See the following issues:
1880            // - The width attribute should not be a relevant mutation
1881            //   <https://github.com/whatwg/html/issues/9371>
1882            // - Srcset implementations do not take width as a fallback when sizes is not present
1883            //   <https://github.com/whatwg/html/pull/5900>
1884            &local_name!("src") | &local_name!("srcset") | &local_name!("sizes") => {
1885                self.update_the_image_data(cx);
1886            },
1887            &local_name!("crossorigin") => {
1888                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1889                // The element's crossorigin attribute's state is changed.
1890                let cross_origin_state_changed = match mutation {
1891                    AttributeMutation::Removed | AttributeMutation::Set(None) => true,
1892                    AttributeMutation::Set(Some(old_value)) => {
1893                        let new_cors_setting =
1894                            CorsSettings::from_enumerated_attribute(&attr.value());
1895                        let old_cors_setting = CorsSettings::from_enumerated_attribute(old_value);
1896
1897                        new_cors_setting != old_cors_setting
1898                    },
1899                };
1900
1901                if cross_origin_state_changed {
1902                    self.update_the_image_data(cx);
1903                }
1904            },
1905            &local_name!("referrerpolicy") => {
1906                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
1907                // The element's referrerpolicy attribute's state is changed.
1908                let referrer_policy_state_changed = match mutation {
1909                    AttributeMutation::Removed | AttributeMutation::Set(None) => {
1910                        ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::EmptyString
1911                    },
1912                    AttributeMutation::Set(Some(old_value)) => {
1913                        ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::from(&**old_value)
1914                    },
1915                };
1916
1917                if referrer_policy_state_changed {
1918                    self.update_the_image_data(cx);
1919                }
1920            },
1921            _ => {},
1922        }
1923    }
1924
1925    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1926        match attr.local_name() {
1927            &local_name!("width") |
1928            &local_name!("height") |
1929            &local_name!("hspace") |
1930            &local_name!("vspace") => true,
1931            _ => self
1932                .super_type()
1933                .unwrap()
1934                .attribute_affects_presentational_hints(attr),
1935        }
1936    }
1937
1938    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1939        match name {
1940            &local_name!("width") |
1941            &local_name!("height") |
1942            &local_name!("hspace") |
1943            &local_name!("vspace") => AttrValue::from_dimension(value.into()),
1944            _ => self
1945                .super_type()
1946                .unwrap()
1947                .parse_plain_attribute(name, value),
1948        }
1949    }
1950
1951    fn handle_event(&self, cx: &mut JSContext, event: &Event) {
1952        if event.type_() != atom!("click") {
1953            return;
1954        }
1955
1956        let Some(area_elements) = self.areas() else {
1957            return;
1958        };
1959
1960        // Fetch click coordinates
1961        let Some(mouse_event) = event.downcast::<MouseEvent>() else {
1962            return;
1963        };
1964
1965        let click_location = Point2D::new(
1966            mouse_event.ClientX().to_f32().unwrap(),
1967            mouse_event.ClientY().to_f32().unwrap(),
1968        );
1969        let bounding_rectangle = self.upcast::<Element>().GetBoundingClientRect(cx);
1970        let image_extents =
1971            Point2D::new(bounding_rectangle.X() as f32, bounding_rectangle.Y() as f32);
1972
1973        // Walk HTMLAreaElements
1974        for area_element in area_elements {
1975            if !area_element.is_instance_activatable() {
1976                continue;
1977            }
1978            let activatable_area = match area_element.get_shape_from_coords() {
1979                Some(shape) => shape.absolute_coords(image_extents),
1980                None => return,
1981            };
1982            if activatable_area.hit_test(&click_location) {
1983                area_element.activation_behavior(cx, event, self.upcast());
1984                return;
1985            }
1986        }
1987    }
1988
1989    /// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-insertion-steps>
1990    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1991        if let Some(s) = self.super_type() {
1992            s.bind_to_tree(cx, context);
1993        }
1994        let document = self.owner_document();
1995        if context.tree_connected {
1996            document.register_responsive_image(self);
1997        }
1998
1999        let parent = self.upcast::<Node>().GetParentNode().unwrap();
2000
2001        // Step 1. If insertedNode's parent is a picture element, then, count this as a relevant
2002        // mutation for insertedNode.
2003        if parent.is::<HTMLPictureElement>() && *parent == *context.parent {
2004            self.update_the_image_data(cx);
2005        }
2006    }
2007
2008    /// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-removing-steps>
2009    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
2010        self.super_type().unwrap().unbind_from_tree(cx, context);
2011        let document = self.owner_document();
2012        document.unregister_responsive_image(self);
2013
2014        // Step 1. If oldParent is a picture element, then, count this as a relevant mutation for
2015        // removedNode.
2016        if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
2017            self.update_the_image_data(cx);
2018        }
2019    }
2020
2021    /// <https://html.spec.whatwg.org/multipage#the-img-element:html-element-moving-steps>
2022    fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
2023        if let Some(super_type) = self.super_type() {
2024            super_type.moving_steps(cx, context);
2025        }
2026
2027        // Step 1. If isSubtreeRoot is true and oldAncestor is a picture element, then count this as
2028        // a relevant mutation for movedNode.
2029        if context.is_subtree_root && context.old_ancestor.is::<HTMLPictureElement>() {
2030            self.update_the_image_data(cx);
2031        }
2032    }
2033}
2034
2035impl FormControl for HTMLImageElement {
2036    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2037        self.form_owner.get()
2038    }
2039
2040    fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
2041        self.form_owner.set(form);
2042    }
2043
2044    fn to_html_element(&self) -> &HTMLElement {
2045        self.upcast::<HTMLElement>()
2046    }
2047}
2048
2049#[derive(Clone)]
2050enum ChangeType {
2051    Environment {
2052        selected_source: USVString,
2053        selected_pixel_density: f64,
2054    },
2055    Element,
2056}