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