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