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