1use std::cell::Cell;
6use std::default::Default;
7use std::rc::Rc;
8use std::sync::{Arc, LazyLock};
9use std::{char, mem};
10
11use app_units::Au;
12use cssparser::{Parser, ParserInput};
13use dom_struct::dom_struct;
14use euclid::default::Point2D;
15use html5ever::{LocalName, Prefix, QualName, local_name, ns};
16use js::context::JSContext;
17use js::realm::AutoRealm;
18use js::rust::HandleObject;
19use mime::{self, Mime};
20use net_traits::http_status::HttpStatus;
21use net_traits::image_cache::{
22 Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
23 ImageResponse, PendingImageId,
24};
25use net_traits::request::{CorsSettings, Destination, Initiator, RequestId};
26use net_traits::{
27 FetchMetadata, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming,
28};
29use num_traits::ToPrimitive;
30use pixels::{CorsStatus, ImageMetadata, Snapshot};
31use regex::Regex;
32use rustc_hash::FxHashSet;
33use script_bindings::script_runtime::temp_cx;
34use servo_url::ServoUrl;
35use servo_url::origin::MutableOrigin;
36use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer};
37use style::stylesheets::CssRuleType;
38use style::values::specified::source_size_list::SourceSizeList;
39use style_traits::ParsingMode;
40use url::Url;
41
42use crate::css::parser_context_for_anonymous_content;
43use crate::document_loader::{LoadBlocker, LoadType};
44use crate::dom::activation::Activatable;
45use crate::dom::attr::Attr;
46use crate::dom::bindings::cell::{DomRefCell, RefMut};
47use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRect_Binding::DOMRectMethods;
48use crate::dom::bindings::codegen::Bindings::ElementBinding::Element_Binding::ElementMethods;
49use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
50use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
51use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
52use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
53use crate::dom::bindings::error::{Error, Fallible};
54use crate::dom::bindings::inheritance::Castable;
55use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
56use crate::dom::bindings::reflector::DomGlobal;
57use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
58use crate::dom::bindings::str::{DOMString, USVString};
59use crate::dom::csp::{GlobalCspReporting, Violation};
60use crate::dom::document::Document;
61use crate::dom::element::{
62 AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
63 cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute,
64 reflect_referrer_policy_attribute, set_cross_origin_attribute,
65};
66use crate::dom::event::Event;
67use crate::dom::eventtarget::EventTarget;
68use crate::dom::globalscope::GlobalScope;
69use crate::dom::html::htmlareaelement::HTMLAreaElement;
70use crate::dom::html::htmlelement::HTMLElement;
71use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
72use crate::dom::html::htmlmapelement::HTMLMapElement;
73use crate::dom::html::htmlpictureelement::HTMLPictureElement;
74use crate::dom::html::htmlsourceelement::HTMLSourceElement;
75use crate::dom::medialist::MediaList;
76use crate::dom::mouseevent::MouseEvent;
77use crate::dom::node::{
78 BindContext, MoveContext, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
79};
80use crate::dom::performance::performanceresourcetiming::InitiatorType;
81use crate::dom::promise::Promise;
82use crate::dom::virtualmethods::VirtualMethods;
83use crate::dom::window::Window;
84use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
85use crate::microtask::{Microtask, MicrotaskRunnable};
86use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
87use crate::realms::enter_auto_realm;
88use crate::script_runtime::CanGc;
89use crate::script_thread::ScriptThread;
90
91const SUPPORTED_IMAGE_MIME_TYPES: &[&str] = &[
95 "image/bmp",
96 "image/gif",
97 "image/jpeg",
98 "image/jpg",
99 "image/pjpeg",
100 "image/png",
101 "image/apng",
102 "image/x-png",
103 "image/svg+xml",
104 "image/vnd.microsoft.icon",
105 "image/x-icon",
106 "image/webp",
107];
108
109#[derive(Clone, Copy, Debug)]
110enum ParseState {
111 InDescriptor,
112 InParens,
113 AfterDescriptor,
114}
115
116#[derive(MallocSizeOf)]
118pub(crate) struct SourceSet {
119 image_sources: Vec<ImageSource>,
120 source_size: SourceSizeList,
121}
122
123impl SourceSet {
124 fn new() -> SourceSet {
125 SourceSet {
126 image_sources: Vec::new(),
127 source_size: SourceSizeList::empty(),
128 }
129 }
130}
131
132#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
133pub struct ImageSource {
134 pub url: String,
135 pub descriptor: Descriptor,
136}
137
138#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
139pub struct Descriptor {
140 pub width: Option<u32>,
141 pub density: Option<f64>,
142}
143
144#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
146enum State {
147 Unavailable,
148 PartiallyAvailable,
149 CompletelyAvailable,
150 Broken,
151}
152
153#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
154enum ImageRequestPhase {
155 Pending,
156 Current,
157}
158
159#[derive(JSTraceable, MallocSizeOf)]
161#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
162struct ImageRequest {
163 state: State,
164 #[no_trace]
165 parsed_url: Option<ServoUrl>,
166 source_url: Option<USVString>,
167 blocker: DomRefCell<Option<LoadBlocker>>,
168 #[no_trace]
169 image: Option<Image>,
170 #[no_trace]
171 metadata: Option<ImageMetadata>,
172 #[no_trace]
173 final_url: Option<ServoUrl>,
174 current_pixel_density: Option<f64>,
175}
176
177#[dom_struct]
178pub(crate) struct HTMLImageElement {
179 htmlelement: HTMLElement,
180 image_request: Cell<ImageRequestPhase>,
181 current_request: DomRefCell<ImageRequest>,
182 pending_request: DomRefCell<ImageRequest>,
183 form_owner: MutNullableDom<HTMLFormElement>,
184 generation: Cell<u32>,
185 source_set: DomRefCell<SourceSet>,
186 dimension_attribute_source: MutNullableDom<Element>,
189 last_selected_source: DomRefCell<Option<USVString>>,
191 #[conditional_malloc_size_of]
192 image_decode_promises: DomRefCell<Vec<Rc<Promise>>>,
193 line_number: u64,
195}
196
197impl HTMLImageElement {
198 pub(crate) fn is_usable(&self) -> Fallible<bool> {
200 if let Some(image) = &self.current_request.borrow().image {
202 let intrinsic_size = image.metadata();
203 if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
204 return Ok(false);
205 }
206 }
207
208 match self.current_request.borrow().state {
209 State::Broken => Err(Error::InvalidState(None)),
211 State::CompletelyAvailable => Ok(true),
212 State::PartiallyAvailable | State::Unavailable => Ok(false),
214 }
215 }
216
217 pub(crate) fn image_data(&self) -> Option<Image> {
218 self.current_request.borrow().image.clone()
219 }
220
221 pub(crate) fn get_raster_image_data(&self) -> Option<Snapshot> {
223 let Some(raster_image) = self.image_data()?.as_raster_image() else {
224 warn!("Vector image is not supported as raster image source");
225 return None;
226 };
227 Some(raster_image.as_snapshot())
228 }
229}
230
231struct ImageContext {
233 image_cache: Arc<dyn ImageCache>,
235 status: Result<(), NetworkError>,
237 id: PendingImageId,
239 aborted: bool,
241 doc: Trusted<Document>,
243 url: ServoUrl,
244 element: Trusted<HTMLImageElement>,
245}
246
247impl FetchResponseListener for ImageContext {
248 fn should_invoke(&self) -> bool {
249 !self.aborted
250 }
251
252 fn process_request_body(&mut self, _: RequestId) {}
253
254 fn process_response(
255 &mut self,
256 _: &mut js::context::JSContext,
257 request_id: RequestId,
258 metadata: Result<FetchMetadata, NetworkError>,
259 ) {
260 debug!("got {:?} for {:?}", metadata.as_ref().map(|_| ()), self.url);
261 self.image_cache.notify_pending_response(
262 self.id,
263 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
264 );
265
266 let metadata = metadata.ok().map(|meta| match meta {
267 FetchMetadata::Unfiltered(m) => m,
268 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
269 });
270
271 if let Some(metadata) = metadata.as_ref() {
273 if let Some(ref content_type) = metadata.content_type {
274 let mime: Mime = content_type.clone().into_inner().into();
275 if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" {
276 self.aborted = true;
277 }
278 }
279 }
280
281 let status = metadata
282 .as_ref()
283 .map(|m| m.status.clone())
284 .unwrap_or_else(HttpStatus::new_error);
285
286 self.status = {
287 if status.is_error() {
288 Err(NetworkError::ResourceLoadError(
289 "No http status code received".to_owned(),
290 ))
291 } else if status.is_success() {
292 Ok(())
293 } else {
294 Err(NetworkError::ResourceLoadError(format!(
295 "HTTP error code {}",
296 status.code()
297 )))
298 }
299 };
300 }
301
302 fn process_response_chunk(
303 &mut self,
304 _: &mut js::context::JSContext,
305 request_id: RequestId,
306 payload: Vec<u8>,
307 ) {
308 if self.status.is_ok() {
309 self.image_cache.notify_pending_response(
310 self.id,
311 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
312 );
313 }
314 }
315
316 fn process_response_eof(
317 self,
318 cx: &mut js::context::JSContext,
319 request_id: RequestId,
320 response: Result<(), NetworkError>,
321 timing: ResourceFetchTiming,
322 ) {
323 self.image_cache.notify_pending_response(
324 self.id,
325 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
326 );
327 network_listener::submit_timing(cx, &self, &response, &timing);
328 }
329
330 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
331 let global = &self.resource_timing_global();
332 let elem = self.element.root();
333 let source_position = elem
334 .upcast::<Element>()
335 .compute_source_position(elem.line_number as u32);
336 global.report_csp_violations(violations, None, Some(source_position));
337 }
338}
339
340impl ResourceTimingListener for ImageContext {
341 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
342 (
343 InitiatorType::LocalName("img".to_string()),
344 self.url.clone(),
345 )
346 }
347
348 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
349 self.doc.root().global()
350 }
351}
352
353#[expect(non_snake_case)]
354impl HTMLImageElement {
355 fn fetch_image(&self, img_url: &ServoUrl, cx: &mut js::context::JSContext) {
357 let window = self.owner_window();
358
359 let cache_result = window.image_cache().get_cached_image_status(
360 img_url.clone(),
361 window.origin().immutable().clone(),
362 cors_setting_for_element(self.upcast()),
363 );
364
365 match cache_result {
366 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
367 image,
368 url,
369 }) => self.process_image_response(ImageResponse::Loaded(image, url), cx),
370 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
371 metadata,
372 id,
373 )) => {
374 self.process_image_response(ImageResponse::MetadataLoaded(metadata), cx);
375 self.register_image_cache_callback(id, ChangeType::Element);
376 },
377 ImageCacheResult::Pending(id) => {
378 self.register_image_cache_callback(id, ChangeType::Element);
379 },
380 ImageCacheResult::ReadyForRequest(id) => {
381 self.fetch_request(img_url, id);
382 self.register_image_cache_callback(id, ChangeType::Element);
383 },
384 ImageCacheResult::FailedToLoadOrDecode => {
385 self.process_image_response(ImageResponse::FailedToLoadOrDecode, cx)
386 },
387 };
388 }
389
390 fn register_image_cache_callback(&self, id: PendingImageId, change_type: ChangeType) {
391 let trusted_node = Trusted::new(self);
392 let generation = self.generation_id();
393 let window = self.owner_window();
394 let callback = window.register_image_cache_listener(id, move |response, _| {
395 let trusted_node = trusted_node.clone();
396 let window = trusted_node.root().owner_window();
397 let callback_type = change_type.clone();
398
399 window
400 .as_global_scope()
401 .task_manager()
402 .networking_task_source()
403 .queue(task!(process_image_response: move |cx| {
404 let element = trusted_node.root();
405
406 if generation != element.generation_id() {
408 return;
409 }
410
411 match callback_type {
412 ChangeType::Element => {
413 element.process_image_response(response.response, cx);
414 }
415 ChangeType::Environment { selected_source, selected_pixel_density } => {
416 element.process_image_response_for_environment_change(
417 response.response, selected_source, generation, selected_pixel_density, cx
418 );
419 }
420 }
421 }));
422 });
423
424 window.image_cache().add_listener(ImageLoadListener::new(
425 callback,
426 window.pipeline_id(),
427 id,
428 ));
429 }
430
431 fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
432 let document = self.owner_document();
433 let window = self.owner_window();
434
435 let context = ImageContext {
436 image_cache: window.image_cache(),
437 status: Ok(()),
438 id,
439 aborted: false,
440 doc: Trusted::new(&document),
441 element: Trusted::new(self),
442 url: img_url.clone(),
443 };
444
445 let global = document.global();
448 let mut request = create_a_potential_cors_request(
449 Some(window.webview_id()),
450 img_url.clone(),
451 Destination::Image,
452 cors_setting_for_element(self.upcast()),
453 None,
454 global.get_referrer(),
455 )
456 .with_global_scope(&global)
457 .referrer_policy(referrer_policy_for_element(self.upcast()));
458
459 if self.uses_srcset_or_picture() {
460 request = request.initiator(Initiator::ImageSet);
461 }
462
463 document.fetch_background(request, context);
466 }
467
468 fn handle_loaded_image(&self, image: Image, url: ServoUrl, cx: &mut js::context::JSContext) {
470 self.current_request.borrow_mut().metadata = Some(image.metadata());
471 self.current_request.borrow_mut().final_url = Some(url);
472 self.current_request.borrow_mut().image = Some(image);
473 self.current_request.borrow_mut().state = State::CompletelyAvailable;
474 LoadBlocker::terminate(&self.current_request.borrow().blocker, cx);
475 self.upcast::<Node>().dirty(NodeDamage::Other);
477 self.resolve_image_decode_promises();
478 }
479
480 fn process_image_response(&self, image: ImageResponse, cx: &mut js::context::JSContext) {
482 let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
488 (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) => {
489 self.handle_loaded_image(image, url, cx);
490 (true, false)
491 },
492 (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
493 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
494 self.image_request.set(ImageRequestPhase::Current);
495 self.handle_loaded_image(image, url, cx);
496 (true, false)
497 },
498 (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
499 self.current_request.borrow_mut().state = State::PartiallyAvailable;
504 self.current_request.borrow_mut().metadata = Some(meta);
505 (false, false)
506 },
507 (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
508 self.pending_request.borrow_mut().state = State::PartiallyAvailable;
512 (false, false)
513 },
514 (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Current) => {
515 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
521
522 self.load_broken_image_icon();
523
524 (false, true)
528 },
529 (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Pending) => {
530 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
536 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
537
538 mem::swap(
540 &mut *self.current_request.borrow_mut(),
541 &mut *self.pending_request.borrow_mut(),
542 );
543 self.image_request.set(ImageRequestPhase::Current);
544
545 self.current_request.borrow_mut().state = State::Broken;
547
548 self.load_broken_image_icon();
549
550 (false, true)
552 },
553 };
554
555 if trigger_image_load {
557 self.upcast::<EventTarget>()
559 .fire_event(atom!("load"), CanGc::from_cx(cx));
560 self.upcast::<EventTarget>()
561 .fire_event(atom!("loadend"), CanGc::from_cx(cx));
562 }
563
564 if trigger_image_error {
566 self.upcast::<EventTarget>()
567 .fire_event(atom!("error"), CanGc::from_cx(cx));
568 self.upcast::<EventTarget>()
569 .fire_event(atom!("loadend"), CanGc::from_cx(cx));
570 }
571 }
572
573 fn process_image_response_for_environment_change(
576 &self,
577 image: ImageResponse,
578 selected_source: USVString,
579 generation: u32,
580 selected_pixel_density: f64,
581 cx: &mut js::context::JSContext,
582 ) {
583 match image {
584 ImageResponse::Loaded(image, url) => {
585 self.pending_request.borrow_mut().metadata = Some(image.metadata());
586 self.pending_request.borrow_mut().final_url = Some(url);
587 self.pending_request.borrow_mut().image = Some(image);
588 self.finish_reacting_to_environment_change(
589 selected_source,
590 generation,
591 selected_pixel_density,
592 );
593 },
594 ImageResponse::FailedToLoadOrDecode => {
595 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
603 },
604 ImageResponse::MetadataLoaded(meta) => {
605 self.pending_request.borrow_mut().metadata = Some(meta);
606 },
607 };
608 }
609
610 fn abort_request(
612 &self,
613 state: State,
614 request: ImageRequestPhase,
615 cx: &mut js::context::JSContext,
616 ) {
617 let mut request = match request {
618 ImageRequestPhase::Current => self.current_request.borrow_mut(),
619 ImageRequestPhase::Pending => self.pending_request.borrow_mut(),
620 };
621 LoadBlocker::terminate(&request.blocker, cx);
622 request.state = state;
623 request.image = None;
624 request.metadata = None;
625 request.current_pixel_density = None;
626
627 if matches!(state, State::Broken) {
628 self.reject_image_decode_promises();
629 } else if matches!(state, State::CompletelyAvailable) {
630 self.resolve_image_decode_promises();
631 }
632 }
633
634 fn create_source_set(&self) -> SourceSet {
636 let element = self.upcast::<Element>();
637
638 let mut source_set = SourceSet::new();
640
641 if let Some(srcset) = element.get_attribute(&local_name!("srcset")) {
644 source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
645 }
646
647 if let Some(sizes) = element.get_attribute(&local_name!("sizes")) {
649 source_set.source_size = parse_a_sizes_attribute(&sizes.value());
650 }
651
652 let src = element.get_string_attribute(&local_name!("src"));
656 let no_density_source_of_1 = source_set
657 .image_sources
658 .iter()
659 .all(|source| source.descriptor.density != Some(1.));
660 let no_width_descriptor = source_set
661 .image_sources
662 .iter()
663 .all(|source| source.descriptor.width.is_none());
664 if !src.is_empty() && no_density_source_of_1 && no_width_descriptor {
665 source_set.image_sources.push(ImageSource {
666 url: src.to_string(),
667 descriptor: Descriptor {
668 width: None,
669 density: None,
670 },
671 })
672 }
673
674 self.normalise_source_densities(&mut source_set);
676
677 source_set
679 }
680
681 fn update_source_set(&self) {
683 *self.source_set.borrow_mut() = SourceSet::new();
685
686 let elem = self.upcast::<Element>();
691 let parent = elem.upcast::<Node>().GetParentElement();
692 let elements = match parent.as_ref() {
693 Some(p) => {
694 if p.is::<HTMLPictureElement>() {
695 p.upcast::<Node>()
696 .children()
697 .filter_map(DomRoot::downcast::<Element>)
698 .map(|n| DomRoot::from_ref(&*n))
699 .collect()
700 } else {
701 vec![DomRoot::from_ref(elem)]
702 }
703 },
704 None => vec![DomRoot::from_ref(elem)],
705 };
706
707 for element in &elements {
709 if *element == DomRoot::from_ref(elem) {
711 *self.source_set.borrow_mut() = self.create_source_set();
714
715 return;
717 }
718 if !element.is::<HTMLSourceElement>() {
720 continue;
721 }
722
723 let mut source_set = SourceSet::new();
724
725 match element.get_attribute(&local_name!("srcset")) {
729 Some(srcset) => {
730 source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
731 },
732 _ => continue,
733 }
734
735 if source_set.image_sources.is_empty() {
737 continue;
738 }
739
740 if let Some(media) = element.get_attribute(&local_name!("media")) {
743 if !MediaList::matches_environment(&element.owner_document(), &media.value()) {
744 continue;
745 }
746 }
747
748 if let Some(sizes) = element.get_attribute(&local_name!("sizes")) {
751 source_set.source_size = parse_a_sizes_attribute(&sizes.value());
752 }
753
754 if let Some(type_) = element.get_attribute(&local_name!("type")) {
757 if !is_supported_image_mime_type(&type_.value()) {
758 continue;
759 }
760 }
761
762 if element.get_attribute(&local_name!("width")).is_some() ||
765 element.get_attribute(&local_name!("height")).is_some()
766 {
767 self.dimension_attribute_source.set(Some(element));
768 } else {
769 self.dimension_attribute_source.set(Some(elem));
770 }
771
772 self.normalise_source_densities(&mut source_set);
774
775 *self.source_set.borrow_mut() = source_set;
777
778 return;
780 }
781 }
782
783 fn evaluate_source_size_list(&self, source_size_list: &SourceSizeList) -> Au {
784 let document = self.owner_document();
785 let quirks_mode = document.quirks_mode();
786 source_size_list.evaluate(document.window().layout().device(), quirks_mode)
787 }
788
789 fn normalise_source_densities(&self, source_set: &mut SourceSet) {
791 let source_size = self.evaluate_source_size_list(&source_set.source_size);
793
794 for image_source in &mut source_set.image_sources {
796 if image_source.descriptor.density.is_some() {
799 continue;
800 }
801
802 if image_source.descriptor.width.is_some() {
806 let width = image_source.descriptor.width.unwrap();
807 image_source.descriptor.density = Some(width as f64 / source_size.to_f64_px());
808 } else {
809 image_source.descriptor.density = Some(1_f64);
811 }
812 }
813 }
814
815 fn select_image_source(&self) -> Option<(USVString, f64)> {
817 self.update_source_set();
819
820 if self.source_set.borrow().image_sources.is_empty() {
823 return None;
824 }
825
826 self.select_image_source_from_source_set()
828 }
829
830 fn select_image_source_from_source_set(&self) -> Option<(USVString, f64)> {
832 let source_set = self.source_set.borrow();
837 let len = source_set.image_sources.len();
838
839 let mut repeat_indices = FxHashSet::default();
841 for outer_index in 0..len {
842 if repeat_indices.contains(&outer_index) {
843 continue;
844 }
845 let imgsource = &source_set.image_sources[outer_index];
846 let pixel_density = imgsource.descriptor.density.unwrap();
847 for inner_index in (outer_index + 1)..len {
848 let imgsource2 = &source_set.image_sources[inner_index];
849 if pixel_density == imgsource2.descriptor.density.unwrap() {
850 repeat_indices.insert(inner_index);
851 }
852 }
853 }
854
855 let mut max = (0f64, 0);
856 let img_sources = &mut vec![];
857 for (index, image_source) in source_set.image_sources.iter().enumerate() {
858 if repeat_indices.contains(&index) {
859 continue;
860 }
861 let den = image_source.descriptor.density.unwrap();
862 if max.0 < den {
863 max = (den, img_sources.len());
864 }
865 img_sources.push(image_source);
866 }
867
868 let mut best_candidate = max;
871 let device_pixel_ratio = self
872 .owner_document()
873 .window()
874 .viewport_details()
875 .hidpi_scale_factor
876 .get() as f64;
877 for (index, image_source) in img_sources.iter().enumerate() {
878 let current_den = image_source.descriptor.density.unwrap();
879 if current_den < best_candidate.0 && current_den >= device_pixel_ratio {
880 best_candidate = (current_den, index);
881 }
882 }
883 let selected_source = img_sources.remove(best_candidate.1).clone();
884
885 Some((
887 USVString(selected_source.url),
888 selected_source.descriptor.density.unwrap(),
889 ))
890 }
891
892 fn init_image_request(
893 &self,
894 request: &mut RefMut<'_, ImageRequest>,
895 url: &ServoUrl,
896 src: &USVString,
897 cx: &mut js::context::JSContext,
898 ) {
899 request.parsed_url = Some(url.clone());
900 request.source_url = Some(src.clone());
901 request.image = None;
902 request.metadata = None;
903 let document = self.owner_document();
904 LoadBlocker::terminate(&request.blocker, cx);
905 *request.blocker.borrow_mut() =
906 Some(LoadBlocker::new(&document, LoadType::Image(url.clone())));
907 }
908
909 fn prepare_image_request(
911 &self,
912 selected_source: &USVString,
913 selected_pixel_density: f64,
914 image_url: &ServoUrl,
915 cx: &mut js::context::JSContext,
916 ) {
917 match self.image_request.get() {
918 ImageRequestPhase::Pending => {
919 if self
922 .pending_request
923 .borrow()
924 .parsed_url
925 .as_ref()
926 .is_some_and(|parsed_url| *parsed_url == *image_url)
927 {
928 return;
929 }
930 },
931 ImageRequestPhase::Current => {
932 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
934
935 let mut current_request = self.current_request.borrow_mut();
938 let mut pending_request = self.pending_request.borrow_mut();
939
940 match (current_request.parsed_url.as_ref(), current_request.state) {
941 (Some(parsed_url), State::PartiallyAvailable) => {
942 if *parsed_url == *image_url {
948 return;
950 }
951
952 self.image_request.set(ImageRequestPhase::Pending);
956 self.init_image_request(
957 &mut pending_request,
958 image_url,
959 selected_source,
960 cx,
961 );
962 pending_request.current_pixel_density = Some(selected_pixel_density);
963 },
964 (_, State::Broken) | (_, State::Unavailable) => {
965 self.init_image_request(
969 &mut current_request,
970 image_url,
971 selected_source,
972 cx,
973 );
974 current_request.current_pixel_density = Some(selected_pixel_density);
975 self.reject_image_decode_promises();
976 },
977 (_, _) => {
978 self.image_request.set(ImageRequestPhase::Pending);
982 self.init_image_request(
983 &mut pending_request,
984 image_url,
985 selected_source,
986 cx,
987 );
988 pending_request.current_pixel_density = Some(selected_pixel_density);
989 },
990 }
991 },
992 }
993
994 self.fetch_image(image_url, cx);
995 }
996
997 fn update_the_image_data_sync_steps(&self, cx: &mut js::context::JSContext) {
999 let Some((selected_source, selected_pixel_density)) = self.select_image_source() else {
1002 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
1007 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1008 self.image_request.set(ImageRequestPhase::Current);
1009
1010 let this = Trusted::new(self);
1013
1014 self.owner_global().task_manager().dom_manipulation_task_source().queue(
1015 task!(image_null_source_error: move |cx| {
1016 let this = this.root();
1017
1018 {
1020 let mut current_request =
1021 this.current_request.borrow_mut();
1022 current_request.source_url = None;
1023 current_request.parsed_url = None;
1024 }
1025
1026 let has_src_attribute = this.upcast::<Element>().has_attribute(&local_name!("src"));
1032
1033 if has_src_attribute || this.uses_srcset_or_picture() {
1034 this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::from_cx(cx));
1035 }
1036 }));
1037
1038 return;
1040 };
1041
1042 let Ok(image_url) = self.owner_document().base_url().join(&selected_source) else {
1045 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
1050 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1051
1052 self.image_request.set(ImageRequestPhase::Current);
1054
1055 let this = Trusted::new(self);
1058
1059 self.owner_global()
1060 .task_manager()
1061 .dom_manipulation_task_source()
1062 .queue(task!(image_selected_source_error: move |cx| {
1063 let this = this.root();
1064
1065 {
1067 let mut current_request =
1068 this.current_request.borrow_mut();
1069 current_request.source_url = Some(selected_source);
1070 current_request.parsed_url = None;
1071 }
1072
1073 this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::from_cx(cx));
1077 }));
1078
1079 return;
1081 };
1082
1083 self.prepare_image_request(&selected_source, selected_pixel_density, &image_url, cx);
1084 }
1085
1086 pub(crate) fn update_the_image_data(&self, cx: &mut js::context::JSContext) {
1088 self.generation.set(self.generation.get() + 1);
1090
1091 if !self.owner_document().is_active() {
1093 }
1099
1100 self.current_request.borrow_mut().state = State::Unavailable;
1109
1110 let mut selected_source = None;
1114 let mut selected_pixel_density = None;
1115
1116 let src = self
1120 .upcast::<Element>()
1121 .get_string_attribute(&local_name!("src"));
1122
1123 if !self.uses_srcset_or_picture() && !src.is_empty() {
1124 selected_source = Some(USVString(src.to_string()));
1125 selected_pixel_density = Some(1_f64);
1126 };
1127
1128 self.last_selected_source
1130 .borrow_mut()
1131 .clone_from(&selected_source);
1132
1133 if let Some(selected_source) = selected_source {
1135 if let Ok(image_url) = self.owner_document().base_url().join(&selected_source) {
1139 let window = self.owner_window();
1143 let response = window.image_cache().get_image(
1144 image_url.clone(),
1145 window.origin().immutable().clone(),
1146 cors_setting_for_element(self.upcast()),
1147 );
1148
1149 if let Some(image) = response {
1151 self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current, cx);
1156 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1157
1158 self.image_request.set(ImageRequestPhase::Current);
1160
1161 let mut current_request = self.current_request.borrow_mut();
1164 current_request.metadata = Some(image.metadata());
1165 current_request.image = Some(image);
1166 current_request.final_url = Some(image_url.clone());
1167
1168 self.upcast::<Node>().dirty(NodeDamage::Other);
1171
1172 current_request.current_pixel_density = selected_pixel_density;
1175
1176 let this = Trusted::new(self);
1179
1180 self.owner_global()
1181 .task_manager()
1182 .dom_manipulation_task_source()
1183 .queue(task!(image_load_event: move |cx| {
1184 let this = this.root();
1185
1186 {
1191 let mut current_request =
1192 this.current_request.borrow_mut();
1193 current_request.source_url = Some(selected_source);
1194 current_request.parsed_url = Some(image_url);
1195 }
1196
1197 this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::from_cx(cx));
1201 }));
1202
1203 return;
1205 }
1206 }
1207 }
1208
1209 let task = ImageElementMicrotask::UpdateImageData {
1212 elem: DomRoot::from_ref(self),
1213 generation: self.generation.get(),
1214 };
1215
1216 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1217 }
1218
1219 pub(crate) fn react_to_environment_changes(&self) {
1221 let task = ImageElementMicrotask::EnvironmentChanges {
1223 elem: DomRoot::from_ref(self),
1224 generation: self.generation.get(),
1225 };
1226
1227 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1228 }
1229
1230 fn react_to_environment_changes_sync_steps(
1232 &self,
1233 generation: u32,
1234 cx: &mut js::context::JSContext,
1235 ) {
1236 let document = self.owner_document();
1237 let has_pending_request = matches!(self.image_request.get(), ImageRequestPhase::Pending);
1238
1239 if !document.is_active() || !self.uses_srcset_or_picture() || has_pending_request {
1243 return;
1244 }
1245
1246 let Some((selected_source, selected_pixel_density)) = self.select_image_source() else {
1249 return;
1251 };
1252
1253 let mut same_selected_source = self
1256 .last_selected_source
1257 .borrow()
1258 .as_ref()
1259 .is_some_and(|source| *source == selected_source);
1260
1261 same_selected_source = same_selected_source ||
1265 self.current_request
1266 .borrow()
1267 .source_url
1268 .as_ref()
1269 .is_some_and(|source| *source == selected_source);
1270
1271 let same_selected_pixel_density = self
1272 .current_request
1273 .borrow()
1274 .current_pixel_density
1275 .is_some_and(|pixel_density| pixel_density == selected_pixel_density);
1276
1277 if same_selected_source && same_selected_pixel_density {
1278 return;
1279 }
1280
1281 let Ok(image_url) = document.base_url().join(&selected_source) else {
1285 return;
1286 };
1287
1288 self.image_request.set(ImageRequestPhase::Pending);
1290 self.init_image_request(
1291 &mut self.pending_request.borrow_mut(),
1292 &image_url,
1293 &selected_source,
1294 cx,
1295 );
1296
1297 let window = self.owner_window();
1300 let cache_result = window.image_cache().get_cached_image_status(
1301 image_url.clone(),
1302 window.origin().immutable().clone(),
1303 cors_setting_for_element(self.upcast()),
1304 );
1305
1306 let change_type = ChangeType::Environment {
1307 selected_source: selected_source.clone(),
1308 selected_pixel_density,
1309 };
1310
1311 match cache_result {
1312 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
1313 self.finish_reacting_to_environment_change(
1314 selected_source,
1315 generation,
1316 selected_pixel_density,
1317 );
1318 },
1319 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m, id)) => {
1320 self.process_image_response_for_environment_change(
1321 ImageResponse::MetadataLoaded(m),
1322 selected_source,
1323 generation,
1324 selected_pixel_density,
1325 cx,
1326 );
1327 self.register_image_cache_callback(id, change_type);
1328 },
1329 ImageCacheResult::FailedToLoadOrDecode => {
1330 self.process_image_response_for_environment_change(
1331 ImageResponse::FailedToLoadOrDecode,
1332 selected_source,
1333 generation,
1334 selected_pixel_density,
1335 cx,
1336 );
1337 },
1338 ImageCacheResult::ReadyForRequest(id) => {
1339 self.fetch_request(&image_url, id);
1340 self.register_image_cache_callback(id, change_type);
1341 },
1342 ImageCacheResult::Pending(id) => {
1343 self.register_image_cache_callback(id, change_type);
1344 },
1345 }
1346 }
1347
1348 fn react_to_decode_image_sync_steps(&self, promise: Rc<Promise>, can_gc: CanGc) {
1350 if !self.owner_document().is_fully_active() ||
1354 matches!(self.current_request.borrow().state, State::Broken)
1355 {
1356 promise.reject_error(Error::Encoding(None), can_gc);
1357 } else if matches!(
1358 self.current_request.borrow().state,
1359 State::CompletelyAvailable
1360 ) {
1361 promise.resolve_native(&(), can_gc);
1363 } else if matches!(self.current_request.borrow().state, State::Unavailable) &&
1364 self.current_request.borrow().source_url.is_none()
1365 {
1366 promise.reject_error(Error::Encoding(None), can_gc);
1371 } else {
1372 self.image_decode_promises.borrow_mut().push(promise);
1373 }
1374 }
1375
1376 fn resolve_image_decode_promises(&self) {
1378 if self.image_decode_promises.borrow().is_empty() {
1379 return;
1380 }
1381
1382 let trusted_image_decode_promises: Vec<TrustedPromise> = self
1385 .image_decode_promises
1386 .borrow()
1387 .iter()
1388 .map(|promise| TrustedPromise::new(promise.clone()))
1389 .collect();
1390
1391 self.image_decode_promises.borrow_mut().clear();
1392
1393 self.owner_global()
1394 .task_manager()
1395 .dom_manipulation_task_source()
1396 .queue(task!(fulfill_image_decode_promises: move || {
1397 for trusted_promise in trusted_image_decode_promises {
1398 trusted_promise.root().resolve_native(&(), CanGc::note());
1399 }
1400 }));
1401 }
1402
1403 fn reject_image_decode_promises(&self) {
1405 if self.image_decode_promises.borrow().is_empty() {
1406 return;
1407 }
1408
1409 let trusted_image_decode_promises: Vec<TrustedPromise> = self
1412 .image_decode_promises
1413 .borrow()
1414 .iter()
1415 .map(|promise| TrustedPromise::new(promise.clone()))
1416 .collect();
1417
1418 self.image_decode_promises.borrow_mut().clear();
1419
1420 self.owner_global()
1421 .task_manager()
1422 .dom_manipulation_task_source()
1423 .queue(task!(reject_image_decode_promises: move || {
1424 for trusted_promise in trusted_image_decode_promises {
1425 trusted_promise.root().reject_error(Error::Encoding(None), CanGc::note());
1426 }
1427 }));
1428 }
1429
1430 fn finish_reacting_to_environment_change(
1432 &self,
1433 selected_source: USVString,
1434 generation: u32,
1435 selected_pixel_density: f64,
1436 ) {
1437 let this = Trusted::new(self);
1440
1441 self.owner_global()
1442 .task_manager()
1443 .dom_manipulation_task_source()
1444 .queue(task!(image_load_event: move |cx| {
1445 let this = this.root();
1446
1447 if this.generation.get() != generation {
1450 this.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1451 this.image_request.set(ImageRequestPhase::Current);
1452 return;
1453 }
1454
1455 *this.last_selected_source.borrow_mut() = Some(selected_source);
1458
1459 {
1460 let mut pending_request = this.pending_request.borrow_mut();
1461
1462 pending_request.state = State::CompletelyAvailable;
1464
1465 pending_request.current_pixel_density = Some(selected_pixel_density);
1466
1467 mem::swap(&mut *this.current_request.borrow_mut(), &mut *pending_request);
1473 }
1474
1475 this.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1476 this.image_request.set(ImageRequestPhase::Current);
1477
1478 this.upcast::<Node>().dirty(NodeDamage::Other);
1480
1481 this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::from_cx(cx));
1483 }));
1484 }
1485
1486 fn uses_srcset_or_picture(&self) -> bool {
1488 let element = self.upcast::<Element>();
1489
1490 let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1491 let has_parent_picture = element
1492 .upcast::<Node>()
1493 .GetParentElement()
1494 .is_some_and(|parent| parent.is::<HTMLPictureElement>());
1495 has_srcset_attribute || has_parent_picture
1496 }
1497
1498 fn new_inherited(
1499 local_name: LocalName,
1500 prefix: Option<Prefix>,
1501 document: &Document,
1502 creator: ElementCreator,
1503 ) -> HTMLImageElement {
1504 HTMLImageElement {
1505 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
1506 image_request: Cell::new(ImageRequestPhase::Current),
1507 current_request: DomRefCell::new(ImageRequest {
1508 state: State::Unavailable,
1509 parsed_url: None,
1510 source_url: None,
1511 image: None,
1512 metadata: None,
1513 blocker: DomRefCell::new(None),
1514 final_url: None,
1515 current_pixel_density: None,
1516 }),
1517 pending_request: DomRefCell::new(ImageRequest {
1518 state: State::Unavailable,
1519 parsed_url: None,
1520 source_url: None,
1521 image: None,
1522 metadata: None,
1523 blocker: DomRefCell::new(None),
1524 final_url: None,
1525 current_pixel_density: None,
1526 }),
1527 form_owner: Default::default(),
1528 generation: Default::default(),
1529 source_set: DomRefCell::new(SourceSet::new()),
1530 dimension_attribute_source: Default::default(),
1531 last_selected_source: DomRefCell::new(None),
1532 image_decode_promises: DomRefCell::new(vec![]),
1533 line_number: creator.return_line_number(),
1534 }
1535 }
1536
1537 pub(crate) fn new(
1538 cx: &mut js::context::JSContext,
1539 local_name: LocalName,
1540 prefix: Option<Prefix>,
1541 document: &Document,
1542 proto: Option<HandleObject>,
1543 creator: ElementCreator,
1544 ) -> DomRoot<HTMLImageElement> {
1545 let image_element = Node::reflect_node_with_proto(
1546 cx,
1547 Box::new(HTMLImageElement::new_inherited(
1548 local_name, prefix, document, creator,
1549 )),
1550 document,
1551 proto,
1552 );
1553 image_element
1554 .dimension_attribute_source
1555 .set(Some(image_element.upcast()));
1556 image_element
1557 }
1558
1559 pub(crate) fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> {
1560 let elem = self.upcast::<Element>();
1561 let usemap_attr = elem.get_attribute(&local_name!("usemap"))?;
1562
1563 let value = usemap_attr.value();
1564
1565 if value.is_empty() || !value.is_char_boundary(1) {
1566 return None;
1567 }
1568
1569 let (first, last) = value.split_at(1);
1570
1571 if first != "#" || last.is_empty() {
1572 return None;
1573 }
1574
1575 let useMapElements = self
1576 .owner_document()
1577 .upcast::<Node>()
1578 .traverse_preorder(ShadowIncluding::No)
1579 .filter_map(DomRoot::downcast::<HTMLMapElement>)
1580 .find(|n| {
1581 n.upcast::<Element>()
1582 .get_name()
1583 .is_some_and(|n| *n == *last)
1584 });
1585
1586 useMapElements.map(|mapElem| mapElem.get_area_elements())
1587 }
1588
1589 pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
1590 if let Some(ref image) = self.current_request.borrow().image {
1591 return image.cors_status() == CorsStatus::Safe;
1592 }
1593
1594 self.current_request
1595 .borrow()
1596 .final_url
1597 .as_ref()
1598 .is_some_and(|url| url.scheme() == "data" || url.origin().same_origin(origin))
1599 }
1600
1601 fn generation_id(&self) -> u32 {
1602 self.generation.get()
1603 }
1604
1605 fn load_broken_image_icon(&self) {
1606 let window = self.owner_window();
1607 let Some(broken_image_icon) = window.image_cache().get_broken_image_icon() else {
1608 return;
1609 };
1610
1611 self.current_request.borrow_mut().metadata = Some(broken_image_icon.metadata);
1612 self.current_request.borrow_mut().image = Some(Image::Raster(broken_image_icon));
1613 self.upcast::<Node>().dirty(NodeDamage::Other);
1614 }
1615
1616 pub(crate) fn full_image_url_for_user_interface(&self) -> Option<ServoUrl> {
1619 self.owner_document()
1620 .base_url()
1621 .join(&self.CurrentSrc())
1622 .ok()
1623 }
1624}
1625
1626#[derive(JSTraceable, MallocSizeOf)]
1627pub(crate) enum ImageElementMicrotask {
1628 UpdateImageData {
1629 elem: DomRoot<HTMLImageElement>,
1630 generation: u32,
1631 },
1632 EnvironmentChanges {
1633 elem: DomRoot<HTMLImageElement>,
1634 generation: u32,
1635 },
1636 Decode {
1637 elem: DomRoot<HTMLImageElement>,
1638 #[conditional_malloc_size_of]
1639 promise: Rc<Promise>,
1640 },
1641}
1642
1643impl MicrotaskRunnable for ImageElementMicrotask {
1644 fn handler(&self, cx: &mut js::context::JSContext) {
1645 match *self {
1646 ImageElementMicrotask::UpdateImageData {
1647 ref elem,
1648 ref generation,
1649 } => {
1650 if elem.generation.get() == *generation {
1654 elem.update_the_image_data_sync_steps(cx);
1655 }
1656 },
1657 ImageElementMicrotask::EnvironmentChanges {
1658 ref elem,
1659 ref generation,
1660 } => {
1661 elem.react_to_environment_changes_sync_steps(*generation, cx);
1662 },
1663 ImageElementMicrotask::Decode {
1664 ref elem,
1665 ref promise,
1666 } => {
1667 elem.react_to_decode_image_sync_steps(promise.clone(), CanGc::from_cx(cx));
1668 },
1669 }
1670 }
1671
1672 fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
1673 match self {
1674 &ImageElementMicrotask::UpdateImageData { ref elem, .. } |
1675 &ImageElementMicrotask::EnvironmentChanges { ref elem, .. } |
1676 &ImageElementMicrotask::Decode { ref elem, .. } => enter_auto_realm(cx, &**elem),
1677 }
1678 }
1679}
1680
1681pub(crate) trait LayoutHTMLImageElementHelpers {
1682 fn image_url(self) -> Option<ServoUrl>;
1683 fn image_density(self) -> Option<f64>;
1684 fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
1685 fn get_width(self) -> LengthOrPercentageOrAuto;
1686 fn get_height(self) -> LengthOrPercentageOrAuto;
1687 fn showing_broken_image_icon(self) -> bool;
1688}
1689
1690impl<'dom> LayoutDom<'dom, HTMLImageElement> {
1691 #[expect(unsafe_code)]
1692 fn current_request(self) -> &'dom ImageRequest {
1693 unsafe { self.unsafe_get().current_request.borrow_for_layout() }
1694 }
1695
1696 #[expect(unsafe_code)]
1697 fn dimension_attribute_source(self) -> LayoutDom<'dom, Element> {
1698 unsafe {
1699 self.unsafe_get()
1700 .dimension_attribute_source
1701 .get_inner_as_layout()
1702 .expect("dimension attribute source should be always non-null")
1703 }
1704 }
1705}
1706
1707impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
1708 fn image_url(self) -> Option<ServoUrl> {
1709 self.current_request().parsed_url.clone()
1710 }
1711
1712 fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
1713 let current_request = self.current_request();
1714 (current_request.image.clone(), current_request.metadata)
1715 }
1716
1717 fn image_density(self) -> Option<f64> {
1718 self.current_request().current_pixel_density
1719 }
1720
1721 fn showing_broken_image_icon(self) -> bool {
1722 matches!(self.current_request().state, State::Broken)
1723 }
1724
1725 fn get_width(self) -> LengthOrPercentageOrAuto {
1726 self.dimension_attribute_source()
1727 .get_attr_for_layout(&ns!(), &local_name!("width"))
1728 .map(AttrValue::as_dimension)
1729 .cloned()
1730 .unwrap_or(LengthOrPercentageOrAuto::Auto)
1731 }
1732
1733 fn get_height(self) -> LengthOrPercentageOrAuto {
1734 self.dimension_attribute_source()
1735 .get_attr_for_layout(&ns!(), &local_name!("height"))
1736 .map(AttrValue::as_dimension)
1737 .cloned()
1738 .unwrap_or(LengthOrPercentageOrAuto::Auto)
1739 }
1740}
1741
1742fn parse_a_sizes_attribute(value: &str) -> SourceSizeList {
1744 let mut input = ParserInput::new(value);
1745 let mut parser = Parser::new(&mut input);
1746 let url_data = Url::parse("about:blank").unwrap().into();
1747 let context =
1750 parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::empty(), &url_data);
1751 SourceSizeList::parse(&context, &mut parser)
1752}
1753
1754impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
1755 fn Image(
1757 cx: &mut JSContext,
1758 window: &Window,
1759 proto: Option<HandleObject>,
1760 width: Option<u32>,
1761 height: Option<u32>,
1762 ) -> Fallible<DomRoot<HTMLImageElement>> {
1763 let document = window.Document();
1765
1766 let element = Element::create(
1769 cx,
1770 QualName::new(None, ns!(html), local_name!("img")),
1771 None,
1772 &document,
1773 ElementCreator::ScriptCreated,
1774 CustomElementCreationMode::Synchronous,
1775 proto,
1776 );
1777
1778 let image = DomRoot::downcast::<HTMLImageElement>(element).unwrap();
1779
1780 if let Some(w) = width {
1782 image.SetWidth(w);
1783 }
1784
1785 if let Some(h) = height {
1788 image.SetHeight(h);
1789 }
1790
1791 Ok(image)
1793 }
1794
1795 make_getter!(Alt, "alt");
1797 make_setter!(SetAlt, "alt");
1799
1800 make_url_getter!(Src, "src");
1802
1803 make_url_setter!(SetSrc, "src");
1805
1806 make_url_getter!(Srcset, "srcset");
1808 make_url_setter!(SetSrcset, "srcset");
1810
1811 make_getter!(Sizes, "sizes");
1813
1814 make_setter!(SetSizes, "sizes");
1816
1817 fn GetCrossOrigin(&self) -> Option<DOMString> {
1819 reflect_cross_origin_attribute(self.upcast::<Element>())
1820 }
1821
1822 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1824 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1825 }
1826
1827 make_getter!(UseMap, "usemap");
1829 make_setter!(SetUseMap, "usemap");
1831
1832 make_bool_getter!(IsMap, "ismap");
1834 make_bool_setter!(SetIsMap, "ismap");
1836
1837 fn Width(&self) -> u32 {
1839 let node = self.upcast::<Node>();
1840 node.content_box()
1841 .map(|rect| rect.size.width.to_px() as u32)
1842 .unwrap_or_else(|| self.NaturalWidth())
1843 }
1844
1845 make_dimension_uint_setter!(SetWidth, "width");
1847
1848 fn Height(&self) -> u32 {
1850 let node = self.upcast::<Node>();
1851 node.content_box()
1852 .map(|rect| rect.size.height.to_px() as u32)
1853 .unwrap_or_else(|| self.NaturalHeight())
1854 }
1855
1856 make_dimension_uint_setter!(SetHeight, "height");
1858
1859 fn NaturalWidth(&self) -> u32 {
1861 let request = self.current_request.borrow();
1862 if matches!(request.state, State::Broken) {
1863 return 0;
1864 }
1865
1866 let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1867 match request.metadata {
1868 Some(ref metadata) => (metadata.width as f64 / pixel_density) as u32,
1869 None => 0,
1870 }
1871 }
1872
1873 fn NaturalHeight(&self) -> u32 {
1875 let request = self.current_request.borrow();
1876 if matches!(request.state, State::Broken) {
1877 return 0;
1878 }
1879
1880 let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1881 match request.metadata {
1882 Some(ref metadata) => (metadata.height as f64 / pixel_density) as u32,
1883 None => 0,
1884 }
1885 }
1886
1887 fn Complete(&self) -> bool {
1889 let element = self.upcast::<Element>();
1890
1891 let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1894 if !element.has_attribute(&local_name!("src")) && !has_srcset_attribute {
1895 return true;
1896 }
1897
1898 let src = element.get_string_attribute(&local_name!("src"));
1900 if !has_srcset_attribute && src.is_empty() {
1901 return true;
1902 }
1903
1904 if matches!(self.image_request.get(), ImageRequestPhase::Current) &&
1908 matches!(
1909 self.current_request.borrow().state,
1910 State::CompletelyAvailable | State::Broken
1911 )
1912 {
1913 return true;
1914 }
1915
1916 false
1918 }
1919
1920 fn CurrentSrc(&self) -> USVString {
1922 let current_request = self.current_request.borrow();
1923 let url = ¤t_request.parsed_url;
1924 match *url {
1925 Some(ref url) => USVString(url.clone().into_string()),
1926 None => {
1927 let unparsed_url = ¤t_request.source_url;
1928 match *unparsed_url {
1929 Some(ref url) => url.clone(),
1930 None => USVString("".to_owned()),
1931 }
1932 },
1933 }
1934 }
1935
1936 fn ReferrerPolicy(&self) -> DOMString {
1938 reflect_referrer_policy_attribute(self.upcast::<Element>())
1939 }
1940
1941 make_setter!(SetReferrerPolicy, "referrerpolicy");
1943
1944 fn Decode(&self, cx: &mut JSContext) -> Rc<Promise> {
1946 let promise = Promise::new2(cx, &self.global());
1948
1949 let task = ImageElementMicrotask::Decode {
1951 elem: DomRoot::from_ref(self),
1952 promise: promise.clone(),
1953 };
1954
1955 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1956
1957 promise
1959 }
1960
1961 make_getter!(Name, "name");
1963
1964 make_atomic_setter!(SetName, "name");
1966
1967 make_getter!(Align, "align");
1969
1970 make_setter!(SetAlign, "align");
1972
1973 make_uint_getter!(Hspace, "hspace");
1975
1976 make_uint_setter!(SetHspace, "hspace");
1978
1979 make_uint_getter!(Vspace, "vspace");
1981
1982 make_uint_setter!(SetVspace, "vspace");
1984
1985 make_url_getter!(LongDesc, "longdesc");
1987
1988 make_url_setter!(SetLongDesc, "longdesc");
1990
1991 make_getter!(Border, "border");
1993
1994 make_setter!(SetBorder, "border");
1996}
1997
1998impl VirtualMethods for HTMLImageElement {
1999 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2000 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2001 }
2002
2003 fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
2004 self.super_type().unwrap().adopting_steps(cx, old_doc);
2005 self.update_the_image_data(cx);
2006 }
2007
2008 fn attribute_mutated(
2009 &self,
2010 cx: &mut js::context::JSContext,
2011 attr: &Attr,
2012 mutation: AttributeMutation,
2013 ) {
2014 self.super_type()
2015 .unwrap()
2016 .attribute_mutated(cx, attr, mutation);
2017 match attr.local_name() {
2018 &local_name!("src") |
2019 &local_name!("srcset") |
2020 &local_name!("width") |
2021 &local_name!("sizes") => {
2022 self.update_the_image_data(cx);
2026 },
2027 &local_name!("crossorigin") => {
2028 let cross_origin_state_changed = match mutation {
2031 AttributeMutation::Removed | AttributeMutation::Set(None, _) => true,
2032 AttributeMutation::Set(Some(old_value), _) => {
2033 let new_cors_setting =
2034 CorsSettings::from_enumerated_attribute(&attr.value());
2035 let old_cors_setting = CorsSettings::from_enumerated_attribute(old_value);
2036
2037 new_cors_setting != old_cors_setting
2038 },
2039 };
2040
2041 if cross_origin_state_changed {
2042 self.update_the_image_data(cx);
2043 }
2044 },
2045 &local_name!("referrerpolicy") => {
2046 let referrer_policy_state_changed = match mutation {
2049 AttributeMutation::Removed | AttributeMutation::Set(None, _) => {
2050 ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::EmptyString
2051 },
2052 AttributeMutation::Set(Some(old_value), _) => {
2053 ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::from(&**old_value)
2054 },
2055 };
2056
2057 if referrer_policy_state_changed {
2058 self.update_the_image_data(cx);
2059 }
2060 },
2061 _ => {},
2062 }
2063 }
2064
2065 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
2066 match attr.local_name() {
2067 &local_name!("width") | &local_name!("height") => true,
2068 _ => self
2069 .super_type()
2070 .unwrap()
2071 .attribute_affects_presentational_hints(attr),
2072 }
2073 }
2074
2075 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2076 match name {
2077 &local_name!("width") | &local_name!("height") => {
2078 AttrValue::from_dimension(value.into())
2079 },
2080 &local_name!("hspace") | &local_name!("vspace") => AttrValue::from_u32(value.into(), 0),
2081 _ => self
2082 .super_type()
2083 .unwrap()
2084 .parse_plain_attribute(name, value),
2085 }
2086 }
2087
2088 fn handle_event(&self, event: &Event, can_gc: CanGc) {
2089 if event.type_() != atom!("click") {
2090 return;
2091 }
2092
2093 let area_elements = self.areas();
2094 let elements = match area_elements {
2095 Some(x) => x,
2096 None => return,
2097 };
2098
2099 let mouse_event = match event.downcast::<MouseEvent>() {
2101 Some(x) => x,
2102 None => return,
2103 };
2104
2105 let point = Point2D::new(
2106 mouse_event.ClientX().to_f32().unwrap(),
2107 mouse_event.ClientY().to_f32().unwrap(),
2108 );
2109 let bcr = self.upcast::<Element>().GetBoundingClientRect(can_gc);
2110 let bcr_p = Point2D::new(bcr.X() as f32, bcr.Y() as f32);
2111
2112 for element in elements {
2114 let shape = element.get_shape_from_coords();
2115 let shp = match shape {
2116 Some(x) => x.absolute_coords(bcr_p),
2117 None => return,
2118 };
2119 if shp.hit_test(&point) {
2120 element.activation_behavior(event, self.upcast(), can_gc);
2121 return;
2122 }
2123 }
2124 }
2125
2126 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2128 if let Some(s) = self.super_type() {
2129 s.bind_to_tree(cx, context);
2130 }
2131 let document = self.owner_document();
2132 if context.tree_connected {
2133 document.register_responsive_image(self);
2134 }
2135
2136 let parent = self.upcast::<Node>().GetParentNode().unwrap();
2137
2138 if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
2141 self.update_the_image_data(cx);
2142 }
2143 }
2144
2145 #[expect(unsafe_code)]
2146 fn unbind_from_tree(&self, context: &UnbindContext, _can_gc: CanGc) {
2148 let mut cx = unsafe { temp_cx() };
2150 let cx = &mut cx;
2151 self.super_type()
2152 .unwrap()
2153 .unbind_from_tree(context, CanGc::from_cx(cx));
2154 let document = self.owner_document();
2155 document.unregister_responsive_image(self);
2156
2157 if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
2160 self.update_the_image_data(cx);
2161 }
2162 }
2163
2164 #[expect(unsafe_code)]
2166 fn moving_steps(&self, context: &MoveContext, can_gc: CanGc) {
2167 if let Some(super_type) = self.super_type() {
2168 super_type.moving_steps(context, can_gc);
2169 }
2170 let mut cx = unsafe { temp_cx() };
2172 let cx = &mut cx;
2173
2174 if let Some(old_parent) = context.old_parent {
2176 if old_parent.is::<HTMLPictureElement>() {
2177 self.update_the_image_data(cx);
2178 }
2179 }
2180 }
2181}
2182
2183impl FormControl for HTMLImageElement {
2184 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2185 self.form_owner.get()
2186 }
2187
2188 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2189 self.form_owner.set(form);
2190 }
2191
2192 fn to_element(&self) -> &Element {
2193 self.upcast::<Element>()
2194 }
2195
2196 fn is_listed(&self) -> bool {
2197 false
2198 }
2199}
2200
2201pub(crate) fn collect_sequence_characters(
2204 s: &str,
2205 mut predicate: impl FnMut(&char) -> bool,
2206) -> (&str, &str) {
2207 let i = s.find(|ch| !predicate(&ch)).unwrap_or(s.len());
2208 (&s[0..i], &s[i..])
2209}
2210
2211fn is_valid_non_negative_integer_string(s: &str) -> bool {
2214 s.chars().all(|c| c.is_ascii_digit())
2215}
2216
2217fn is_valid_floating_point_number_string(s: &str) -> bool {
2220 static RE: LazyLock<Regex> =
2221 LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
2222
2223 RE.is_match(s)
2224}
2225
2226pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
2229 let mut current_index = 0;
2232
2233 let mut candidates = vec![];
2235 while current_index < input.len() {
2236 let remaining_string = &input[current_index..];
2237
2238 let (collected_characters, string_after_whitespace) =
2245 collect_sequence_characters(remaining_string, |character| {
2246 *character == ',' || character.is_ascii_whitespace()
2247 });
2248
2249 current_index += collected_characters.len();
2252
2253 if string_after_whitespace.is_empty() {
2255 return candidates;
2256 }
2257
2258 let (url, _) =
2261 collect_sequence_characters(string_after_whitespace, |c| !char::is_ascii_whitespace(c));
2262
2263 current_index += url.len();
2266
2267 let mut descriptors = Vec::new();
2269
2270 if url.ends_with(',') {
2274 let image_source = ImageSource {
2275 url: url.trim_end_matches(',').into(),
2276 descriptor: Descriptor {
2277 width: None,
2278 density: None,
2279 },
2280 };
2281 candidates.push(image_source);
2282 continue;
2283 }
2284
2285 let descriptors_string = &input[current_index..];
2288 let (spaces, descriptors_string) =
2289 collect_sequence_characters(descriptors_string, |character| {
2290 character.is_ascii_whitespace()
2291 });
2292 current_index += spaces.len();
2293
2294 let mut current_descriptor = String::new();
2296
2297 let mut state = ParseState::InDescriptor;
2299
2300 let mut characters = descriptors_string.chars();
2304 let mut character = characters.next();
2305 if let Some(character) = character {
2306 current_index += character.len_utf8();
2307 }
2308
2309 loop {
2310 match (state, character) {
2311 (ParseState::InDescriptor, Some(character)) if character.is_ascii_whitespace() => {
2312 if !current_descriptor.is_empty() {
2316 descriptors.push(current_descriptor);
2317 current_descriptor = String::new();
2318 state = ParseState::AfterDescriptor;
2319 }
2320 },
2321 (ParseState::InDescriptor, Some(',')) => {
2322 if !current_descriptor.is_empty() {
2326 descriptors.push(current_descriptor);
2327 }
2328 break;
2329 },
2330 (ParseState::InDescriptor, Some('(')) => {
2331 current_descriptor.push('(');
2333 state = ParseState::InParens;
2334 },
2335 (ParseState::InDescriptor, Some(character)) => {
2336 current_descriptor.push(character);
2338 },
2339 (ParseState::InDescriptor, None) => {
2340 if !current_descriptor.is_empty() {
2343 descriptors.push(current_descriptor);
2344 }
2345 break;
2346 },
2347 (ParseState::InParens, Some(')')) => {
2348 current_descriptor.push(')');
2350 state = ParseState::InDescriptor;
2351 },
2352 (ParseState::InParens, Some(character)) => {
2353 current_descriptor.push(character);
2355 },
2356 (ParseState::InParens, None) => {
2357 descriptors.push(current_descriptor);
2360 break;
2361 },
2362 (ParseState::AfterDescriptor, Some(character))
2363 if character.is_ascii_whitespace() =>
2364 {
2365 },
2367 (ParseState::AfterDescriptor, Some(_)) => {
2368 state = ParseState::InDescriptor;
2371 continue;
2372 },
2373 (ParseState::AfterDescriptor, None) => {
2374 break;
2376 },
2377 }
2378
2379 character = characters.next();
2380 if let Some(character) = character {
2381 current_index += character.len_utf8();
2382 }
2383 }
2384
2385 let mut error = false;
2387 let mut width: Option<u32> = None;
2389 let mut density: Option<f64> = None;
2391 let mut future_compat_h: Option<u32> = None;
2393
2394 for descriptor in descriptors.into_iter() {
2397 let Some(last_character) = descriptor.chars().last() else {
2398 break;
2399 };
2400
2401 let first_part_of_string = &descriptor[0..descriptor.len() - last_character.len_utf8()];
2402 match last_character {
2403 'w' if is_valid_non_negative_integer_string(first_part_of_string) &&
2410 density.is_none() &&
2411 width.is_none() =>
2412 {
2413 match parse_unsigned_integer(first_part_of_string.chars()) {
2414 Ok(number) if number > 0 => {
2415 width = Some(number);
2416 continue;
2417 },
2418 _ => error = true,
2419 }
2420 },
2421
2422 'x' if is_valid_floating_point_number_string(first_part_of_string) &&
2436 width.is_none() &&
2437 density.is_none() &&
2438 future_compat_h.is_none() =>
2439 {
2440 match first_part_of_string.parse::<f64>() {
2441 Ok(number) if number.is_finite() && number >= 0. => {
2442 density = Some(number);
2443 continue;
2444 },
2445 _ => error = true,
2446 }
2447 },
2448
2449 'h' if is_valid_non_negative_integer_string(first_part_of_string) &&
2458 future_compat_h.is_none() &&
2459 density.is_none() =>
2460 {
2461 match parse_unsigned_integer(first_part_of_string.chars()) {
2462 Ok(number) if number > 0 => {
2463 future_compat_h = Some(number);
2464 continue;
2465 },
2466 _ => error = true,
2467 }
2468 },
2469
2470 _ => error = true,
2473 }
2474
2475 if error {
2476 break;
2477 }
2478 }
2479
2480 if future_compat_h.is_some() && width.is_none() {
2482 error = true;
2483 }
2484
2485 if !error {
2489 let image_source = ImageSource {
2490 url: url.into(),
2491 descriptor: Descriptor { width, density },
2492 };
2493 candidates.push(image_source);
2494 }
2495
2496 }
2498 candidates
2499}
2500
2501#[derive(Clone)]
2502enum ChangeType {
2503 Environment {
2504 selected_source: USVString,
2505 selected_pixel_density: f64,
2506 },
2507 Element,
2508}
2509
2510fn is_supported_image_mime_type(input: &str) -> bool {
2512 let mime_type = input.trim();
2514
2515 let mime_type_essence = match mime_type.find(';') {
2517 Some(semi) => &mime_type[..semi],
2518 _ => mime_type,
2519 };
2520
2521 if mime_type_essence.is_empty() {
2526 return true;
2527 }
2528
2529 SUPPORTED_IMAGE_MIME_TYPES.contains(&mime_type_essence)
2530}