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 fn process_request_eof(&mut self, _: RequestId) {}
254
255 fn process_response(
256 &mut self,
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(&mut self, request_id: RequestId, payload: Vec<u8>) {
303 if self.status.is_ok() {
304 self.image_cache.notify_pending_response(
305 self.id,
306 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
307 );
308 }
309 }
310
311 fn process_response_eof(
312 self,
313 cx: &mut js::context::JSContext,
314 request_id: RequestId,
315 response: Result<(), NetworkError>,
316 timing: ResourceFetchTiming,
317 ) {
318 self.image_cache.notify_pending_response(
319 self.id,
320 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
321 );
322 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
323 }
324
325 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
326 let global = &self.resource_timing_global();
327 let elem = self.element.root();
328 let source_position = elem
329 .upcast::<Element>()
330 .compute_source_position(elem.line_number as u32);
331 global.report_csp_violations(violations, None, Some(source_position));
332 }
333}
334
335impl ResourceTimingListener for ImageContext {
336 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
337 (
338 InitiatorType::LocalName("img".to_string()),
339 self.url.clone(),
340 )
341 }
342
343 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
344 self.doc.root().global()
345 }
346}
347
348#[expect(non_snake_case)]
349impl HTMLImageElement {
350 fn fetch_image(&self, img_url: &ServoUrl, cx: &mut js::context::JSContext) {
352 let window = self.owner_window();
353
354 let cache_result = window.image_cache().get_cached_image_status(
355 img_url.clone(),
356 window.origin().immutable().clone(),
357 cors_setting_for_element(self.upcast()),
358 );
359
360 match cache_result {
361 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
362 image,
363 url,
364 }) => self.process_image_response(ImageResponse::Loaded(image, url), cx),
365 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
366 metadata,
367 id,
368 )) => {
369 self.process_image_response(ImageResponse::MetadataLoaded(metadata), cx);
370 self.register_image_cache_callback(id, ChangeType::Element);
371 },
372 ImageCacheResult::Pending(id) => {
373 self.register_image_cache_callback(id, ChangeType::Element);
374 },
375 ImageCacheResult::ReadyForRequest(id) => {
376 self.fetch_request(img_url, id);
377 self.register_image_cache_callback(id, ChangeType::Element);
378 },
379 ImageCacheResult::FailedToLoadOrDecode => {
380 self.process_image_response(ImageResponse::FailedToLoadOrDecode, cx)
381 },
382 };
383 }
384
385 fn register_image_cache_callback(&self, id: PendingImageId, change_type: ChangeType) {
386 let trusted_node = Trusted::new(self);
387 let generation = self.generation_id();
388 let window = self.owner_window();
389 let callback = window.register_image_cache_listener(id, move |response, _| {
390 let trusted_node = trusted_node.clone();
391 let window = trusted_node.root().owner_window();
392 let callback_type = change_type.clone();
393
394 window
395 .as_global_scope()
396 .task_manager()
397 .networking_task_source()
398 .queue(task!(process_image_response: move |cx| {
399 let element = trusted_node.root();
400
401 if generation != element.generation_id() {
403 return;
404 }
405
406 match callback_type {
407 ChangeType::Element => {
408 element.process_image_response(response.response, cx);
409 }
410 ChangeType::Environment { selected_source, selected_pixel_density } => {
411 element.process_image_response_for_environment_change(
412 response.response, selected_source, generation, selected_pixel_density, cx
413 );
414 }
415 }
416 }));
417 });
418
419 window.image_cache().add_listener(ImageLoadListener::new(
420 callback,
421 window.pipeline_id(),
422 id,
423 ));
424 }
425
426 fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
427 let document = self.owner_document();
428 let window = self.owner_window();
429
430 let context = ImageContext {
431 image_cache: window.image_cache(),
432 status: Ok(()),
433 id,
434 aborted: false,
435 doc: Trusted::new(&document),
436 element: Trusted::new(self),
437 url: img_url.clone(),
438 };
439
440 let global = document.global();
443 let mut request = create_a_potential_cors_request(
444 Some(window.webview_id()),
445 img_url.clone(),
446 Destination::Image,
447 cors_setting_for_element(self.upcast()),
448 None,
449 global.get_referrer(),
450 )
451 .with_global_scope(&global)
452 .referrer_policy(referrer_policy_for_element(self.upcast()));
453
454 if self.uses_srcset_or_picture() {
455 request = request.initiator(Initiator::ImageSet);
456 }
457
458 document.fetch_background(request, context);
461 }
462
463 fn handle_loaded_image(&self, image: Image, url: ServoUrl, cx: &mut js::context::JSContext) {
465 self.current_request.borrow_mut().metadata = Some(image.metadata());
466 self.current_request.borrow_mut().final_url = Some(url);
467 self.current_request.borrow_mut().image = Some(image);
468 self.current_request.borrow_mut().state = State::CompletelyAvailable;
469 LoadBlocker::terminate(&self.current_request.borrow().blocker, cx);
470 self.upcast::<Node>().dirty(NodeDamage::Other);
472 self.resolve_image_decode_promises();
473 }
474
475 fn process_image_response(&self, image: ImageResponse, cx: &mut js::context::JSContext) {
477 let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
483 (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) => {
484 self.handle_loaded_image(image, url, cx);
485 (true, false)
486 },
487 (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
488 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
489 self.image_request.set(ImageRequestPhase::Current);
490 self.handle_loaded_image(image, url, cx);
491 (true, false)
492 },
493 (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
494 self.current_request.borrow_mut().state = State::PartiallyAvailable;
499 self.current_request.borrow_mut().metadata = Some(meta);
500 (false, false)
501 },
502 (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
503 self.pending_request.borrow_mut().state = State::PartiallyAvailable;
507 (false, false)
508 },
509 (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Current) => {
510 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
516
517 self.load_broken_image_icon();
518
519 (false, true)
523 },
524 (ImageResponse::FailedToLoadOrDecode, ImageRequestPhase::Pending) => {
525 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
531 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
532
533 mem::swap(
535 &mut *self.current_request.borrow_mut(),
536 &mut *self.pending_request.borrow_mut(),
537 );
538 self.image_request.set(ImageRequestPhase::Current);
539
540 self.current_request.borrow_mut().state = State::Broken;
542
543 self.load_broken_image_icon();
544
545 (false, true)
547 },
548 };
549
550 if trigger_image_load {
552 self.upcast::<EventTarget>()
554 .fire_event(atom!("load"), CanGc::from_cx(cx));
555 self.upcast::<EventTarget>()
556 .fire_event(atom!("loadend"), CanGc::from_cx(cx));
557 }
558
559 if trigger_image_error {
561 self.upcast::<EventTarget>()
562 .fire_event(atom!("error"), CanGc::from_cx(cx));
563 self.upcast::<EventTarget>()
564 .fire_event(atom!("loadend"), CanGc::from_cx(cx));
565 }
566 }
567
568 fn process_image_response_for_environment_change(
571 &self,
572 image: ImageResponse,
573 selected_source: USVString,
574 generation: u32,
575 selected_pixel_density: f64,
576 cx: &mut js::context::JSContext,
577 ) {
578 match image {
579 ImageResponse::Loaded(image, url) => {
580 self.pending_request.borrow_mut().metadata = Some(image.metadata());
581 self.pending_request.borrow_mut().final_url = Some(url);
582 self.pending_request.borrow_mut().image = Some(image);
583 self.finish_reacting_to_environment_change(
584 selected_source,
585 generation,
586 selected_pixel_density,
587 );
588 },
589 ImageResponse::FailedToLoadOrDecode => {
590 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
598 },
599 ImageResponse::MetadataLoaded(meta) => {
600 self.pending_request.borrow_mut().metadata = Some(meta);
601 },
602 };
603 }
604
605 fn abort_request(
607 &self,
608 state: State,
609 request: ImageRequestPhase,
610 cx: &mut js::context::JSContext,
611 ) {
612 let mut request = match request {
613 ImageRequestPhase::Current => self.current_request.borrow_mut(),
614 ImageRequestPhase::Pending => self.pending_request.borrow_mut(),
615 };
616 LoadBlocker::terminate(&request.blocker, cx);
617 request.state = state;
618 request.image = None;
619 request.metadata = None;
620 request.current_pixel_density = None;
621
622 if matches!(state, State::Broken) {
623 self.reject_image_decode_promises();
624 } else if matches!(state, State::CompletelyAvailable) {
625 self.resolve_image_decode_promises();
626 }
627 }
628
629 fn create_source_set(&self) -> SourceSet {
631 let element = self.upcast::<Element>();
632
633 let mut source_set = SourceSet::new();
635
636 if let Some(srcset) = element.get_attribute(&local_name!("srcset")) {
639 source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
640 }
641
642 if let Some(sizes) = element.get_attribute(&local_name!("sizes")) {
644 source_set.source_size = parse_a_sizes_attribute(&sizes.value());
645 }
646
647 let src = element.get_string_attribute(&local_name!("src"));
651 let no_density_source_of_1 = source_set
652 .image_sources
653 .iter()
654 .all(|source| source.descriptor.density != Some(1.));
655 let no_width_descriptor = source_set
656 .image_sources
657 .iter()
658 .all(|source| source.descriptor.width.is_none());
659 if !src.is_empty() && no_density_source_of_1 && no_width_descriptor {
660 source_set.image_sources.push(ImageSource {
661 url: src.to_string(),
662 descriptor: Descriptor {
663 width: None,
664 density: None,
665 },
666 })
667 }
668
669 self.normalise_source_densities(&mut source_set);
671
672 source_set
674 }
675
676 fn update_source_set(&self) {
678 *self.source_set.borrow_mut() = SourceSet::new();
680
681 let elem = self.upcast::<Element>();
686 let parent = elem.upcast::<Node>().GetParentElement();
687 let elements = match parent.as_ref() {
688 Some(p) => {
689 if p.is::<HTMLPictureElement>() {
690 p.upcast::<Node>()
691 .children()
692 .filter_map(DomRoot::downcast::<Element>)
693 .map(|n| DomRoot::from_ref(&*n))
694 .collect()
695 } else {
696 vec![DomRoot::from_ref(elem)]
697 }
698 },
699 None => vec![DomRoot::from_ref(elem)],
700 };
701
702 for element in &elements {
704 if *element == DomRoot::from_ref(elem) {
706 *self.source_set.borrow_mut() = self.create_source_set();
709
710 return;
712 }
713 if !element.is::<HTMLSourceElement>() {
715 continue;
716 }
717
718 let mut source_set = SourceSet::new();
719
720 match element.get_attribute(&local_name!("srcset")) {
724 Some(srcset) => {
725 source_set.image_sources = parse_a_srcset_attribute(&srcset.value());
726 },
727 _ => continue,
728 }
729
730 if source_set.image_sources.is_empty() {
732 continue;
733 }
734
735 if let Some(media) = element.get_attribute(&local_name!("media")) {
738 if !MediaList::matches_environment(&element.owner_document(), &media.value()) {
739 continue;
740 }
741 }
742
743 if let Some(sizes) = element.get_attribute(&local_name!("sizes")) {
746 source_set.source_size = parse_a_sizes_attribute(&sizes.value());
747 }
748
749 if let Some(type_) = element.get_attribute(&local_name!("type")) {
752 if !is_supported_image_mime_type(&type_.value()) {
753 continue;
754 }
755 }
756
757 if element.get_attribute(&local_name!("width")).is_some() ||
760 element.get_attribute(&local_name!("height")).is_some()
761 {
762 self.dimension_attribute_source.set(Some(element));
763 } else {
764 self.dimension_attribute_source.set(Some(elem));
765 }
766
767 self.normalise_source_densities(&mut source_set);
769
770 *self.source_set.borrow_mut() = source_set;
772
773 return;
775 }
776 }
777
778 fn evaluate_source_size_list(&self, source_size_list: &SourceSizeList) -> Au {
779 let document = self.owner_document();
780 let quirks_mode = document.quirks_mode();
781 source_size_list.evaluate(document.window().layout().device(), quirks_mode)
782 }
783
784 fn normalise_source_densities(&self, source_set: &mut SourceSet) {
786 let source_size = self.evaluate_source_size_list(&source_set.source_size);
788
789 for image_source in &mut source_set.image_sources {
791 if image_source.descriptor.density.is_some() {
794 continue;
795 }
796
797 if image_source.descriptor.width.is_some() {
801 let width = image_source.descriptor.width.unwrap();
802 image_source.descriptor.density = Some(width as f64 / source_size.to_f64_px());
803 } else {
804 image_source.descriptor.density = Some(1_f64);
806 }
807 }
808 }
809
810 fn select_image_source(&self) -> Option<(USVString, f64)> {
812 self.update_source_set();
814
815 if self.source_set.borrow().image_sources.is_empty() {
818 return None;
819 }
820
821 self.select_image_source_from_source_set()
823 }
824
825 fn select_image_source_from_source_set(&self) -> Option<(USVString, f64)> {
827 let source_set = self.source_set.borrow();
832 let len = source_set.image_sources.len();
833
834 let mut repeat_indices = FxHashSet::default();
836 for outer_index in 0..len {
837 if repeat_indices.contains(&outer_index) {
838 continue;
839 }
840 let imgsource = &source_set.image_sources[outer_index];
841 let pixel_density = imgsource.descriptor.density.unwrap();
842 for inner_index in (outer_index + 1)..len {
843 let imgsource2 = &source_set.image_sources[inner_index];
844 if pixel_density == imgsource2.descriptor.density.unwrap() {
845 repeat_indices.insert(inner_index);
846 }
847 }
848 }
849
850 let mut max = (0f64, 0);
851 let img_sources = &mut vec![];
852 for (index, image_source) in source_set.image_sources.iter().enumerate() {
853 if repeat_indices.contains(&index) {
854 continue;
855 }
856 let den = image_source.descriptor.density.unwrap();
857 if max.0 < den {
858 max = (den, img_sources.len());
859 }
860 img_sources.push(image_source);
861 }
862
863 let mut best_candidate = max;
866 let device_pixel_ratio = self
867 .owner_document()
868 .window()
869 .viewport_details()
870 .hidpi_scale_factor
871 .get() as f64;
872 for (index, image_source) in img_sources.iter().enumerate() {
873 let current_den = image_source.descriptor.density.unwrap();
874 if current_den < best_candidate.0 && current_den >= device_pixel_ratio {
875 best_candidate = (current_den, index);
876 }
877 }
878 let selected_source = img_sources.remove(best_candidate.1).clone();
879
880 Some((
882 USVString(selected_source.url),
883 selected_source.descriptor.density.unwrap(),
884 ))
885 }
886
887 fn init_image_request(
888 &self,
889 request: &mut RefMut<'_, ImageRequest>,
890 url: &ServoUrl,
891 src: &USVString,
892 cx: &mut js::context::JSContext,
893 ) {
894 request.parsed_url = Some(url.clone());
895 request.source_url = Some(src.clone());
896 request.image = None;
897 request.metadata = None;
898 let document = self.owner_document();
899 LoadBlocker::terminate(&request.blocker, cx);
900 *request.blocker.borrow_mut() =
901 Some(LoadBlocker::new(&document, LoadType::Image(url.clone())));
902 }
903
904 fn prepare_image_request(
906 &self,
907 selected_source: &USVString,
908 selected_pixel_density: f64,
909 image_url: &ServoUrl,
910 cx: &mut js::context::JSContext,
911 ) {
912 match self.image_request.get() {
913 ImageRequestPhase::Pending => {
914 if self
917 .pending_request
918 .borrow()
919 .parsed_url
920 .as_ref()
921 .is_some_and(|parsed_url| *parsed_url == *image_url)
922 {
923 return;
924 }
925 },
926 ImageRequestPhase::Current => {
927 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
929
930 let mut current_request = self.current_request.borrow_mut();
933 let mut pending_request = self.pending_request.borrow_mut();
934
935 match (current_request.parsed_url.as_ref(), current_request.state) {
936 (Some(parsed_url), State::PartiallyAvailable) => {
937 if *parsed_url == *image_url {
943 return;
945 }
946
947 self.image_request.set(ImageRequestPhase::Pending);
951 self.init_image_request(
952 &mut pending_request,
953 image_url,
954 selected_source,
955 cx,
956 );
957 pending_request.current_pixel_density = Some(selected_pixel_density);
958 },
959 (_, State::Broken) | (_, State::Unavailable) => {
960 self.init_image_request(
964 &mut current_request,
965 image_url,
966 selected_source,
967 cx,
968 );
969 current_request.current_pixel_density = Some(selected_pixel_density);
970 self.reject_image_decode_promises();
971 },
972 (_, _) => {
973 self.image_request.set(ImageRequestPhase::Pending);
977 self.init_image_request(
978 &mut pending_request,
979 image_url,
980 selected_source,
981 cx,
982 );
983 pending_request.current_pixel_density = Some(selected_pixel_density);
984 },
985 }
986 },
987 }
988
989 self.fetch_image(image_url, cx);
990 }
991
992 fn update_the_image_data_sync_steps(&self, cx: &mut js::context::JSContext) {
994 let Some((selected_source, selected_pixel_density)) = self.select_image_source() else {
997 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
1002 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1003 self.image_request.set(ImageRequestPhase::Current);
1004
1005 let this = Trusted::new(self);
1008
1009 self.owner_global().task_manager().dom_manipulation_task_source().queue(
1010 task!(image_null_source_error: move |cx| {
1011 let this = this.root();
1012
1013 {
1015 let mut current_request =
1016 this.current_request.borrow_mut();
1017 current_request.source_url = None;
1018 current_request.parsed_url = None;
1019 }
1020
1021 let has_src_attribute = this.upcast::<Element>().has_attribute(&local_name!("src"));
1027
1028 if has_src_attribute || this.uses_srcset_or_picture() {
1029 this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::from_cx(cx));
1030 }
1031 }));
1032
1033 return;
1035 };
1036
1037 let Ok(image_url) = self.owner_document().base_url().join(&selected_source) else {
1040 self.abort_request(State::Broken, ImageRequestPhase::Current, cx);
1045 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1046
1047 self.image_request.set(ImageRequestPhase::Current);
1049
1050 let this = Trusted::new(self);
1053
1054 self.owner_global()
1055 .task_manager()
1056 .dom_manipulation_task_source()
1057 .queue(task!(image_selected_source_error: move |cx| {
1058 let this = this.root();
1059
1060 {
1062 let mut current_request =
1063 this.current_request.borrow_mut();
1064 current_request.source_url = Some(selected_source);
1065 current_request.parsed_url = None;
1066 }
1067
1068 this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::from_cx(cx));
1072 }));
1073
1074 return;
1076 };
1077
1078 self.prepare_image_request(&selected_source, selected_pixel_density, &image_url, cx);
1079 }
1080
1081 pub(crate) fn update_the_image_data(&self, cx: &mut js::context::JSContext) {
1083 self.generation.set(self.generation.get() + 1);
1085
1086 if !self.owner_document().is_active() {
1088 }
1094
1095 self.current_request.borrow_mut().state = State::Unavailable;
1104
1105 let mut selected_source = None;
1109 let mut selected_pixel_density = None;
1110
1111 let src = self
1115 .upcast::<Element>()
1116 .get_string_attribute(&local_name!("src"));
1117
1118 if !self.uses_srcset_or_picture() && !src.is_empty() {
1119 selected_source = Some(USVString(src.to_string()));
1120 selected_pixel_density = Some(1_f64);
1121 };
1122
1123 self.last_selected_source
1125 .borrow_mut()
1126 .clone_from(&selected_source);
1127
1128 if let Some(selected_source) = selected_source {
1130 if let Ok(image_url) = self.owner_document().base_url().join(&selected_source) {
1134 let window = self.owner_window();
1138 let response = window.image_cache().get_image(
1139 image_url.clone(),
1140 window.origin().immutable().clone(),
1141 cors_setting_for_element(self.upcast()),
1142 );
1143
1144 if let Some(image) = response {
1146 self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current, cx);
1151 self.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1152
1153 self.image_request.set(ImageRequestPhase::Current);
1155
1156 let mut current_request = self.current_request.borrow_mut();
1159 current_request.metadata = Some(image.metadata());
1160 current_request.image = Some(image);
1161 current_request.final_url = Some(image_url.clone());
1162
1163 self.upcast::<Node>().dirty(NodeDamage::Other);
1166
1167 current_request.current_pixel_density = selected_pixel_density;
1170
1171 let this = Trusted::new(self);
1174
1175 self.owner_global()
1176 .task_manager()
1177 .dom_manipulation_task_source()
1178 .queue(task!(image_load_event: move |cx| {
1179 let this = this.root();
1180
1181 {
1186 let mut current_request =
1187 this.current_request.borrow_mut();
1188 current_request.source_url = Some(selected_source);
1189 current_request.parsed_url = Some(image_url);
1190 }
1191
1192 this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::from_cx(cx));
1196 }));
1197
1198 return;
1200 }
1201 }
1202 }
1203
1204 let task = ImageElementMicrotask::UpdateImageData {
1207 elem: DomRoot::from_ref(self),
1208 generation: self.generation.get(),
1209 };
1210
1211 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1212 }
1213
1214 pub(crate) fn react_to_environment_changes(&self) {
1216 let task = ImageElementMicrotask::EnvironmentChanges {
1218 elem: DomRoot::from_ref(self),
1219 generation: self.generation.get(),
1220 };
1221
1222 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1223 }
1224
1225 fn react_to_environment_changes_sync_steps(
1227 &self,
1228 generation: u32,
1229 cx: &mut js::context::JSContext,
1230 ) {
1231 let document = self.owner_document();
1232 let has_pending_request = matches!(self.image_request.get(), ImageRequestPhase::Pending);
1233
1234 if !document.is_active() || !self.uses_srcset_or_picture() || has_pending_request {
1238 return;
1239 }
1240
1241 let Some((selected_source, selected_pixel_density)) = self.select_image_source() else {
1244 return;
1246 };
1247
1248 let mut same_selected_source = self
1251 .last_selected_source
1252 .borrow()
1253 .as_ref()
1254 .is_some_and(|source| *source == selected_source);
1255
1256 same_selected_source = same_selected_source ||
1260 self.current_request
1261 .borrow()
1262 .source_url
1263 .as_ref()
1264 .is_some_and(|source| *source == selected_source);
1265
1266 let same_selected_pixel_density = self
1267 .current_request
1268 .borrow()
1269 .current_pixel_density
1270 .is_some_and(|pixel_density| pixel_density == selected_pixel_density);
1271
1272 if same_selected_source && same_selected_pixel_density {
1273 return;
1274 }
1275
1276 let Ok(image_url) = document.base_url().join(&selected_source) else {
1280 return;
1281 };
1282
1283 self.image_request.set(ImageRequestPhase::Pending);
1285 self.init_image_request(
1286 &mut self.pending_request.borrow_mut(),
1287 &image_url,
1288 &selected_source,
1289 cx,
1290 );
1291
1292 let window = self.owner_window();
1295 let cache_result = window.image_cache().get_cached_image_status(
1296 image_url.clone(),
1297 window.origin().immutable().clone(),
1298 cors_setting_for_element(self.upcast()),
1299 );
1300
1301 let change_type = ChangeType::Environment {
1302 selected_source: selected_source.clone(),
1303 selected_pixel_density,
1304 };
1305
1306 match cache_result {
1307 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
1308 self.finish_reacting_to_environment_change(
1309 selected_source,
1310 generation,
1311 selected_pixel_density,
1312 );
1313 },
1314 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m, id)) => {
1315 self.process_image_response_for_environment_change(
1316 ImageResponse::MetadataLoaded(m),
1317 selected_source,
1318 generation,
1319 selected_pixel_density,
1320 cx,
1321 );
1322 self.register_image_cache_callback(id, change_type);
1323 },
1324 ImageCacheResult::FailedToLoadOrDecode => {
1325 self.process_image_response_for_environment_change(
1326 ImageResponse::FailedToLoadOrDecode,
1327 selected_source,
1328 generation,
1329 selected_pixel_density,
1330 cx,
1331 );
1332 },
1333 ImageCacheResult::ReadyForRequest(id) => {
1334 self.fetch_request(&image_url, id);
1335 self.register_image_cache_callback(id, change_type);
1336 },
1337 ImageCacheResult::Pending(id) => {
1338 self.register_image_cache_callback(id, change_type);
1339 },
1340 }
1341 }
1342
1343 fn react_to_decode_image_sync_steps(&self, promise: Rc<Promise>, can_gc: CanGc) {
1345 if !self.owner_document().is_fully_active() ||
1349 matches!(self.current_request.borrow().state, State::Broken)
1350 {
1351 promise.reject_error(Error::Encoding(None), can_gc);
1352 } else if matches!(
1353 self.current_request.borrow().state,
1354 State::CompletelyAvailable
1355 ) {
1356 promise.resolve_native(&(), can_gc);
1358 } else if matches!(self.current_request.borrow().state, State::Unavailable) &&
1359 self.current_request.borrow().source_url.is_none()
1360 {
1361 promise.reject_error(Error::Encoding(None), can_gc);
1366 } else {
1367 self.image_decode_promises.borrow_mut().push(promise);
1368 }
1369 }
1370
1371 fn resolve_image_decode_promises(&self) {
1373 if self.image_decode_promises.borrow().is_empty() {
1374 return;
1375 }
1376
1377 let trusted_image_decode_promises: Vec<TrustedPromise> = self
1380 .image_decode_promises
1381 .borrow()
1382 .iter()
1383 .map(|promise| TrustedPromise::new(promise.clone()))
1384 .collect();
1385
1386 self.image_decode_promises.borrow_mut().clear();
1387
1388 self.owner_global()
1389 .task_manager()
1390 .dom_manipulation_task_source()
1391 .queue(task!(fulfill_image_decode_promises: move || {
1392 for trusted_promise in trusted_image_decode_promises {
1393 trusted_promise.root().resolve_native(&(), CanGc::note());
1394 }
1395 }));
1396 }
1397
1398 fn reject_image_decode_promises(&self) {
1400 if self.image_decode_promises.borrow().is_empty() {
1401 return;
1402 }
1403
1404 let trusted_image_decode_promises: Vec<TrustedPromise> = self
1407 .image_decode_promises
1408 .borrow()
1409 .iter()
1410 .map(|promise| TrustedPromise::new(promise.clone()))
1411 .collect();
1412
1413 self.image_decode_promises.borrow_mut().clear();
1414
1415 self.owner_global()
1416 .task_manager()
1417 .dom_manipulation_task_source()
1418 .queue(task!(reject_image_decode_promises: move || {
1419 for trusted_promise in trusted_image_decode_promises {
1420 trusted_promise.root().reject_error(Error::Encoding(None), CanGc::note());
1421 }
1422 }));
1423 }
1424
1425 fn finish_reacting_to_environment_change(
1427 &self,
1428 selected_source: USVString,
1429 generation: u32,
1430 selected_pixel_density: f64,
1431 ) {
1432 let this = Trusted::new(self);
1435
1436 self.owner_global()
1437 .task_manager()
1438 .dom_manipulation_task_source()
1439 .queue(task!(image_load_event: move |cx| {
1440 let this = this.root();
1441
1442 if this.generation.get() != generation {
1445 this.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1446 this.image_request.set(ImageRequestPhase::Current);
1447 return;
1448 }
1449
1450 *this.last_selected_source.borrow_mut() = Some(selected_source);
1453
1454 {
1455 let mut pending_request = this.pending_request.borrow_mut();
1456
1457 pending_request.state = State::CompletelyAvailable;
1459
1460 pending_request.current_pixel_density = Some(selected_pixel_density);
1461
1462 mem::swap(&mut *this.current_request.borrow_mut(), &mut *pending_request);
1468 }
1469
1470 this.abort_request(State::Unavailable, ImageRequestPhase::Pending, cx);
1471 this.image_request.set(ImageRequestPhase::Current);
1472
1473 this.upcast::<Node>().dirty(NodeDamage::Other);
1475
1476 this.upcast::<EventTarget>().fire_event(atom!("load"), CanGc::from_cx(cx));
1478 }));
1479 }
1480
1481 fn uses_srcset_or_picture(&self) -> bool {
1483 let element = self.upcast::<Element>();
1484
1485 let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1486 let has_parent_picture = element
1487 .upcast::<Node>()
1488 .GetParentElement()
1489 .is_some_and(|parent| parent.is::<HTMLPictureElement>());
1490 has_srcset_attribute || has_parent_picture
1491 }
1492
1493 fn new_inherited(
1494 local_name: LocalName,
1495 prefix: Option<Prefix>,
1496 document: &Document,
1497 creator: ElementCreator,
1498 ) -> HTMLImageElement {
1499 HTMLImageElement {
1500 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
1501 image_request: Cell::new(ImageRequestPhase::Current),
1502 current_request: DomRefCell::new(ImageRequest {
1503 state: State::Unavailable,
1504 parsed_url: None,
1505 source_url: None,
1506 image: None,
1507 metadata: None,
1508 blocker: DomRefCell::new(None),
1509 final_url: None,
1510 current_pixel_density: None,
1511 }),
1512 pending_request: DomRefCell::new(ImageRequest {
1513 state: State::Unavailable,
1514 parsed_url: None,
1515 source_url: None,
1516 image: None,
1517 metadata: None,
1518 blocker: DomRefCell::new(None),
1519 final_url: None,
1520 current_pixel_density: None,
1521 }),
1522 form_owner: Default::default(),
1523 generation: Default::default(),
1524 source_set: DomRefCell::new(SourceSet::new()),
1525 dimension_attribute_source: Default::default(),
1526 last_selected_source: DomRefCell::new(None),
1527 image_decode_promises: DomRefCell::new(vec![]),
1528 line_number: creator.return_line_number(),
1529 }
1530 }
1531
1532 pub(crate) fn new(
1533 local_name: LocalName,
1534 prefix: Option<Prefix>,
1535 document: &Document,
1536 proto: Option<HandleObject>,
1537 creator: ElementCreator,
1538 can_gc: CanGc,
1539 ) -> DomRoot<HTMLImageElement> {
1540 let image_element = Node::reflect_node_with_proto(
1541 Box::new(HTMLImageElement::new_inherited(
1542 local_name, prefix, document, creator,
1543 )),
1544 document,
1545 proto,
1546 can_gc,
1547 );
1548 image_element
1549 .dimension_attribute_source
1550 .set(Some(image_element.upcast()));
1551 image_element
1552 }
1553
1554 pub(crate) fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> {
1555 let elem = self.upcast::<Element>();
1556 let usemap_attr = elem.get_attribute(&local_name!("usemap"))?;
1557
1558 let value = usemap_attr.value();
1559
1560 if value.is_empty() || !value.is_char_boundary(1) {
1561 return None;
1562 }
1563
1564 let (first, last) = value.split_at(1);
1565
1566 if first != "#" || last.is_empty() {
1567 return None;
1568 }
1569
1570 let useMapElements = self
1571 .owner_document()
1572 .upcast::<Node>()
1573 .traverse_preorder(ShadowIncluding::No)
1574 .filter_map(DomRoot::downcast::<HTMLMapElement>)
1575 .find(|n| {
1576 n.upcast::<Element>()
1577 .get_name()
1578 .is_some_and(|n| *n == *last)
1579 });
1580
1581 useMapElements.map(|mapElem| mapElem.get_area_elements())
1582 }
1583
1584 pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
1585 if let Some(ref image) = self.current_request.borrow().image {
1586 return image.cors_status() == CorsStatus::Safe;
1587 }
1588
1589 self.current_request
1590 .borrow()
1591 .final_url
1592 .as_ref()
1593 .is_some_and(|url| url.scheme() == "data" || url.origin().same_origin(origin))
1594 }
1595
1596 fn generation_id(&self) -> u32 {
1597 self.generation.get()
1598 }
1599
1600 fn load_broken_image_icon(&self) {
1601 let window = self.owner_window();
1602 let Some(broken_image_icon) = window.image_cache().get_broken_image_icon() else {
1603 return;
1604 };
1605
1606 self.current_request.borrow_mut().metadata = Some(broken_image_icon.metadata);
1607 self.current_request.borrow_mut().image = Some(Image::Raster(broken_image_icon));
1608 self.upcast::<Node>().dirty(NodeDamage::Other);
1609 }
1610
1611 pub(crate) fn full_image_url_for_user_interface(&self) -> Option<ServoUrl> {
1614 self.owner_document()
1615 .base_url()
1616 .join(&self.CurrentSrc())
1617 .ok()
1618 }
1619}
1620
1621#[derive(JSTraceable, MallocSizeOf)]
1622pub(crate) enum ImageElementMicrotask {
1623 UpdateImageData {
1624 elem: DomRoot<HTMLImageElement>,
1625 generation: u32,
1626 },
1627 EnvironmentChanges {
1628 elem: DomRoot<HTMLImageElement>,
1629 generation: u32,
1630 },
1631 Decode {
1632 elem: DomRoot<HTMLImageElement>,
1633 #[conditional_malloc_size_of]
1634 promise: Rc<Promise>,
1635 },
1636}
1637
1638impl MicrotaskRunnable for ImageElementMicrotask {
1639 fn handler(&self, cx: &mut js::context::JSContext) {
1640 match *self {
1641 ImageElementMicrotask::UpdateImageData {
1642 ref elem,
1643 ref generation,
1644 } => {
1645 if elem.generation.get() == *generation {
1649 elem.update_the_image_data_sync_steps(cx);
1650 }
1651 },
1652 ImageElementMicrotask::EnvironmentChanges {
1653 ref elem,
1654 ref generation,
1655 } => {
1656 elem.react_to_environment_changes_sync_steps(*generation, cx);
1657 },
1658 ImageElementMicrotask::Decode {
1659 ref elem,
1660 ref promise,
1661 } => {
1662 elem.react_to_decode_image_sync_steps(promise.clone(), CanGc::from_cx(cx));
1663 },
1664 }
1665 }
1666
1667 fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
1668 match self {
1669 &ImageElementMicrotask::UpdateImageData { ref elem, .. } |
1670 &ImageElementMicrotask::EnvironmentChanges { ref elem, .. } |
1671 &ImageElementMicrotask::Decode { ref elem, .. } => enter_auto_realm(cx, &**elem),
1672 }
1673 }
1674}
1675
1676pub(crate) trait LayoutHTMLImageElementHelpers {
1677 fn image_url(self) -> Option<ServoUrl>;
1678 fn image_density(self) -> Option<f64>;
1679 fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
1680 fn get_width(self) -> LengthOrPercentageOrAuto;
1681 fn get_height(self) -> LengthOrPercentageOrAuto;
1682 fn showing_broken_image_icon(self) -> bool;
1683}
1684
1685impl<'dom> LayoutDom<'dom, HTMLImageElement> {
1686 #[expect(unsafe_code)]
1687 fn current_request(self) -> &'dom ImageRequest {
1688 unsafe { self.unsafe_get().current_request.borrow_for_layout() }
1689 }
1690
1691 #[expect(unsafe_code)]
1692 fn dimension_attribute_source(self) -> LayoutDom<'dom, Element> {
1693 unsafe {
1694 self.unsafe_get()
1695 .dimension_attribute_source
1696 .get_inner_as_layout()
1697 .expect("dimension attribute source should be always non-null")
1698 }
1699 }
1700}
1701
1702impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
1703 fn image_url(self) -> Option<ServoUrl> {
1704 self.current_request().parsed_url.clone()
1705 }
1706
1707 fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
1708 let current_request = self.current_request();
1709 (current_request.image.clone(), current_request.metadata)
1710 }
1711
1712 fn image_density(self) -> Option<f64> {
1713 self.current_request().current_pixel_density
1714 }
1715
1716 fn showing_broken_image_icon(self) -> bool {
1717 matches!(self.current_request().state, State::Broken)
1718 }
1719
1720 fn get_width(self) -> LengthOrPercentageOrAuto {
1721 self.dimension_attribute_source()
1722 .get_attr_for_layout(&ns!(), &local_name!("width"))
1723 .map(AttrValue::as_dimension)
1724 .cloned()
1725 .unwrap_or(LengthOrPercentageOrAuto::Auto)
1726 }
1727
1728 fn get_height(self) -> LengthOrPercentageOrAuto {
1729 self.dimension_attribute_source()
1730 .get_attr_for_layout(&ns!(), &local_name!("height"))
1731 .map(AttrValue::as_dimension)
1732 .cloned()
1733 .unwrap_or(LengthOrPercentageOrAuto::Auto)
1734 }
1735}
1736
1737fn parse_a_sizes_attribute(value: &str) -> SourceSizeList {
1739 let mut input = ParserInput::new(value);
1740 let mut parser = Parser::new(&mut input);
1741 let url_data = Url::parse("about:blank").unwrap().into();
1742 let context =
1745 parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::empty(), &url_data);
1746 SourceSizeList::parse(&context, &mut parser)
1747}
1748
1749impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
1750 fn Image(
1752 cx: &mut JSContext,
1753 window: &Window,
1754 proto: Option<HandleObject>,
1755 width: Option<u32>,
1756 height: Option<u32>,
1757 ) -> Fallible<DomRoot<HTMLImageElement>> {
1758 let document = window.Document();
1760
1761 let element = Element::create(
1764 QualName::new(None, ns!(html), local_name!("img")),
1765 None,
1766 &document,
1767 ElementCreator::ScriptCreated,
1768 CustomElementCreationMode::Synchronous,
1769 proto,
1770 CanGc::from_cx(cx),
1771 );
1772
1773 let image = DomRoot::downcast::<HTMLImageElement>(element).unwrap();
1774
1775 if let Some(w) = width {
1777 image.SetWidth(w);
1778 }
1779
1780 if let Some(h) = height {
1783 image.SetHeight(h);
1784 }
1785
1786 Ok(image)
1788 }
1789
1790 make_getter!(Alt, "alt");
1792 make_setter!(SetAlt, "alt");
1794
1795 make_url_getter!(Src, "src");
1797
1798 make_url_setter!(SetSrc, "src");
1800
1801 make_url_getter!(Srcset, "srcset");
1803 make_url_setter!(SetSrcset, "srcset");
1805
1806 make_getter!(Sizes, "sizes");
1808
1809 make_setter!(SetSizes, "sizes");
1811
1812 fn GetCrossOrigin(&self) -> Option<DOMString> {
1814 reflect_cross_origin_attribute(self.upcast::<Element>())
1815 }
1816
1817 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1819 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1820 }
1821
1822 make_getter!(UseMap, "usemap");
1824 make_setter!(SetUseMap, "usemap");
1826
1827 make_bool_getter!(IsMap, "ismap");
1829 make_bool_setter!(SetIsMap, "ismap");
1831
1832 fn Width(&self) -> u32 {
1834 let node = self.upcast::<Node>();
1835 node.content_box()
1836 .map(|rect| rect.size.width.to_px() as u32)
1837 .unwrap_or_else(|| self.NaturalWidth())
1838 }
1839
1840 make_dimension_uint_setter!(SetWidth, "width");
1842
1843 fn Height(&self) -> u32 {
1845 let node = self.upcast::<Node>();
1846 node.content_box()
1847 .map(|rect| rect.size.height.to_px() as u32)
1848 .unwrap_or_else(|| self.NaturalHeight())
1849 }
1850
1851 make_dimension_uint_setter!(SetHeight, "height");
1853
1854 fn NaturalWidth(&self) -> u32 {
1856 let request = self.current_request.borrow();
1857 if matches!(request.state, State::Broken) {
1858 return 0;
1859 }
1860
1861 let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1862 match request.metadata {
1863 Some(ref metadata) => (metadata.width as f64 / pixel_density) as u32,
1864 None => 0,
1865 }
1866 }
1867
1868 fn NaturalHeight(&self) -> u32 {
1870 let request = self.current_request.borrow();
1871 if matches!(request.state, State::Broken) {
1872 return 0;
1873 }
1874
1875 let pixel_density = request.current_pixel_density.unwrap_or(1f64);
1876 match request.metadata {
1877 Some(ref metadata) => (metadata.height as f64 / pixel_density) as u32,
1878 None => 0,
1879 }
1880 }
1881
1882 fn Complete(&self) -> bool {
1884 let element = self.upcast::<Element>();
1885
1886 let has_srcset_attribute = element.has_attribute(&local_name!("srcset"));
1889 if !element.has_attribute(&local_name!("src")) && !has_srcset_attribute {
1890 return true;
1891 }
1892
1893 let src = element.get_string_attribute(&local_name!("src"));
1895 if !has_srcset_attribute && src.is_empty() {
1896 return true;
1897 }
1898
1899 if matches!(self.image_request.get(), ImageRequestPhase::Current) &&
1903 matches!(
1904 self.current_request.borrow().state,
1905 State::CompletelyAvailable | State::Broken
1906 )
1907 {
1908 return true;
1909 }
1910
1911 false
1913 }
1914
1915 fn CurrentSrc(&self) -> USVString {
1917 let current_request = self.current_request.borrow();
1918 let url = ¤t_request.parsed_url;
1919 match *url {
1920 Some(ref url) => USVString(url.clone().into_string()),
1921 None => {
1922 let unparsed_url = ¤t_request.source_url;
1923 match *unparsed_url {
1924 Some(ref url) => url.clone(),
1925 None => USVString("".to_owned()),
1926 }
1927 },
1928 }
1929 }
1930
1931 fn ReferrerPolicy(&self) -> DOMString {
1933 reflect_referrer_policy_attribute(self.upcast::<Element>())
1934 }
1935
1936 make_setter!(SetReferrerPolicy, "referrerpolicy");
1938
1939 fn Decode(&self, can_gc: CanGc) -> Rc<Promise> {
1941 let promise = Promise::new(&self.global(), can_gc);
1943
1944 let task = ImageElementMicrotask::Decode {
1946 elem: DomRoot::from_ref(self),
1947 promise: promise.clone(),
1948 };
1949
1950 ScriptThread::await_stable_state(Microtask::ImageElement(task));
1951
1952 promise
1954 }
1955
1956 make_getter!(Name, "name");
1958
1959 make_atomic_setter!(SetName, "name");
1961
1962 make_getter!(Align, "align");
1964
1965 make_setter!(SetAlign, "align");
1967
1968 make_uint_getter!(Hspace, "hspace");
1970
1971 make_uint_setter!(SetHspace, "hspace");
1973
1974 make_uint_getter!(Vspace, "vspace");
1976
1977 make_uint_setter!(SetVspace, "vspace");
1979
1980 make_getter!(LongDesc, "longdesc");
1982
1983 make_setter!(SetLongDesc, "longdesc");
1985
1986 make_getter!(Border, "border");
1988
1989 make_setter!(SetBorder, "border");
1991}
1992
1993impl VirtualMethods for HTMLImageElement {
1994 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1995 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1996 }
1997
1998 #[expect(unsafe_code)]
1999 fn adopting_steps(&self, old_doc: &Document, _can_gc: CanGc) {
2000 let mut cx = unsafe { temp_cx() };
2001 let cx = &mut cx;
2002 self.super_type()
2003 .unwrap()
2004 .adopting_steps(old_doc, CanGc::from_cx(cx));
2005 self.update_the_image_data(cx);
2006 }
2007
2008 #[expect(unsafe_code)]
2009 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, _can_gc: CanGc) {
2010 let mut cx = unsafe { temp_cx() };
2012 let cx = &mut cx;
2013 self.super_type()
2014 .unwrap()
2015 .attribute_mutated(attr, mutation, CanGc::from_cx(cx));
2016 match attr.local_name() {
2017 &local_name!("src") |
2018 &local_name!("srcset") |
2019 &local_name!("width") |
2020 &local_name!("sizes") => {
2021 self.update_the_image_data(cx);
2025 },
2026 &local_name!("crossorigin") => {
2027 let cross_origin_state_changed = match mutation {
2030 AttributeMutation::Removed | AttributeMutation::Set(None, _) => true,
2031 AttributeMutation::Set(Some(old_value), _) => {
2032 let new_cors_setting =
2033 CorsSettings::from_enumerated_attribute(&attr.value());
2034 let old_cors_setting = CorsSettings::from_enumerated_attribute(old_value);
2035
2036 new_cors_setting != old_cors_setting
2037 },
2038 };
2039
2040 if cross_origin_state_changed {
2041 self.update_the_image_data(cx);
2042 }
2043 },
2044 &local_name!("referrerpolicy") => {
2045 let referrer_policy_state_changed = match mutation {
2048 AttributeMutation::Removed | AttributeMutation::Set(None, _) => {
2049 ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::EmptyString
2050 },
2051 AttributeMutation::Set(Some(old_value), _) => {
2052 ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::from(&**old_value)
2053 },
2054 };
2055
2056 if referrer_policy_state_changed {
2057 self.update_the_image_data(cx);
2058 }
2059 },
2060 _ => {},
2061 }
2062 }
2063
2064 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
2065 match attr.local_name() {
2066 &local_name!("width") | &local_name!("height") => true,
2067 _ => self
2068 .super_type()
2069 .unwrap()
2070 .attribute_affects_presentational_hints(attr),
2071 }
2072 }
2073
2074 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2075 match name {
2076 &local_name!("width") | &local_name!("height") => {
2077 AttrValue::from_dimension(value.into())
2078 },
2079 &local_name!("hspace") | &local_name!("vspace") => AttrValue::from_u32(value.into(), 0),
2080 _ => self
2081 .super_type()
2082 .unwrap()
2083 .parse_plain_attribute(name, value),
2084 }
2085 }
2086
2087 fn handle_event(&self, event: &Event, can_gc: CanGc) {
2088 if event.type_() != atom!("click") {
2089 return;
2090 }
2091
2092 let area_elements = self.areas();
2093 let elements = match area_elements {
2094 Some(x) => x,
2095 None => return,
2096 };
2097
2098 let mouse_event = match event.downcast::<MouseEvent>() {
2100 Some(x) => x,
2101 None => return,
2102 };
2103
2104 let point = Point2D::new(
2105 mouse_event.ClientX().to_f32().unwrap(),
2106 mouse_event.ClientY().to_f32().unwrap(),
2107 );
2108 let bcr = self.upcast::<Element>().GetBoundingClientRect(can_gc);
2109 let bcr_p = Point2D::new(bcr.X() as f32, bcr.Y() as f32);
2110
2111 for element in elements {
2113 let shape = element.get_shape_from_coords();
2114 let shp = match shape {
2115 Some(x) => x.absolute_coords(bcr_p),
2116 None => return,
2117 };
2118 if shp.hit_test(&point) {
2119 element.activation_behavior(event, self.upcast(), can_gc);
2120 return;
2121 }
2122 }
2123 }
2124
2125 #[expect(unsafe_code)]
2126 fn bind_to_tree(&self, context: &BindContext, _can_gc: CanGc) {
2128 let mut cx = unsafe { temp_cx() };
2130 let cx = &mut cx;
2131 if let Some(s) = self.super_type() {
2132 s.bind_to_tree(context, CanGc::from_cx(cx));
2133 }
2134 let document = self.owner_document();
2135 if context.tree_connected {
2136 document.register_responsive_image(self);
2137 }
2138
2139 let parent = self.upcast::<Node>().GetParentNode().unwrap();
2140
2141 if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
2144 self.update_the_image_data(cx);
2145 }
2146 }
2147
2148 #[expect(unsafe_code)]
2149 fn unbind_from_tree(&self, context: &UnbindContext, _can_gc: CanGc) {
2151 let mut cx = unsafe { temp_cx() };
2153 let cx = &mut cx;
2154 self.super_type()
2155 .unwrap()
2156 .unbind_from_tree(context, CanGc::from_cx(cx));
2157 let document = self.owner_document();
2158 document.unregister_responsive_image(self);
2159
2160 if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
2163 self.update_the_image_data(cx);
2164 }
2165 }
2166
2167 #[expect(unsafe_code)]
2169 fn moving_steps(&self, context: &MoveContext, can_gc: CanGc) {
2170 if let Some(super_type) = self.super_type() {
2171 super_type.moving_steps(context, can_gc);
2172 }
2173 let mut cx = unsafe { temp_cx() };
2175 let cx = &mut cx;
2176
2177 if let Some(old_parent) = context.old_parent {
2179 if old_parent.is::<HTMLPictureElement>() {
2180 self.update_the_image_data(cx);
2181 }
2182 }
2183 }
2184}
2185
2186impl FormControl for HTMLImageElement {
2187 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2188 self.form_owner.get()
2189 }
2190
2191 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2192 self.form_owner.set(form);
2193 }
2194
2195 fn to_element(&self) -> &Element {
2196 self.upcast::<Element>()
2197 }
2198
2199 fn is_listed(&self) -> bool {
2200 false
2201 }
2202}
2203
2204pub(crate) fn collect_sequence_characters(
2207 s: &str,
2208 mut predicate: impl FnMut(&char) -> bool,
2209) -> (&str, &str) {
2210 let i = s.find(|ch| !predicate(&ch)).unwrap_or(s.len());
2211 (&s[0..i], &s[i..])
2212}
2213
2214fn is_valid_non_negative_integer_string(s: &str) -> bool {
2217 s.chars().all(|c| c.is_ascii_digit())
2218}
2219
2220fn is_valid_floating_point_number_string(s: &str) -> bool {
2223 static RE: LazyLock<Regex> =
2224 LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
2225
2226 RE.is_match(s)
2227}
2228
2229pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
2232 let mut current_index = 0;
2235
2236 let mut candidates = vec![];
2238 while current_index < input.len() {
2239 let remaining_string = &input[current_index..];
2240
2241 let (collected_characters, string_after_whitespace) =
2248 collect_sequence_characters(remaining_string, |character| {
2249 *character == ',' || character.is_ascii_whitespace()
2250 });
2251
2252 current_index += collected_characters.len();
2255
2256 if string_after_whitespace.is_empty() {
2258 return candidates;
2259 }
2260
2261 let (url, _) =
2264 collect_sequence_characters(string_after_whitespace, |c| !char::is_ascii_whitespace(c));
2265
2266 current_index += url.len();
2269
2270 let mut descriptors = Vec::new();
2272
2273 if url.ends_with(',') {
2277 let image_source = ImageSource {
2278 url: url.trim_end_matches(',').into(),
2279 descriptor: Descriptor {
2280 width: None,
2281 density: None,
2282 },
2283 };
2284 candidates.push(image_source);
2285 continue;
2286 }
2287
2288 let descriptors_string = &input[current_index..];
2291 let (spaces, descriptors_string) =
2292 collect_sequence_characters(descriptors_string, |character| {
2293 character.is_ascii_whitespace()
2294 });
2295 current_index += spaces.len();
2296
2297 let mut current_descriptor = String::new();
2299
2300 let mut state = ParseState::InDescriptor;
2302
2303 let mut characters = descriptors_string.chars();
2307 let mut character = characters.next();
2308 if let Some(character) = character {
2309 current_index += character.len_utf8();
2310 }
2311
2312 loop {
2313 match (state, character) {
2314 (ParseState::InDescriptor, Some(character)) if character.is_ascii_whitespace() => {
2315 if !current_descriptor.is_empty() {
2319 descriptors.push(current_descriptor);
2320 current_descriptor = String::new();
2321 state = ParseState::AfterDescriptor;
2322 }
2323 },
2324 (ParseState::InDescriptor, Some(',')) => {
2325 if !current_descriptor.is_empty() {
2329 descriptors.push(current_descriptor);
2330 }
2331 break;
2332 },
2333 (ParseState::InDescriptor, Some('(')) => {
2334 current_descriptor.push('(');
2336 state = ParseState::InParens;
2337 },
2338 (ParseState::InDescriptor, Some(character)) => {
2339 current_descriptor.push(character);
2341 },
2342 (ParseState::InDescriptor, None) => {
2343 if !current_descriptor.is_empty() {
2346 descriptors.push(current_descriptor);
2347 }
2348 break;
2349 },
2350 (ParseState::InParens, Some(')')) => {
2351 current_descriptor.push(')');
2353 state = ParseState::InDescriptor;
2354 },
2355 (ParseState::InParens, Some(character)) => {
2356 current_descriptor.push(character);
2358 },
2359 (ParseState::InParens, None) => {
2360 descriptors.push(current_descriptor);
2363 break;
2364 },
2365 (ParseState::AfterDescriptor, Some(character))
2366 if character.is_ascii_whitespace() =>
2367 {
2368 },
2370 (ParseState::AfterDescriptor, Some(_)) => {
2371 state = ParseState::InDescriptor;
2374 continue;
2375 },
2376 (ParseState::AfterDescriptor, None) => {
2377 break;
2379 },
2380 }
2381
2382 character = characters.next();
2383 if let Some(character) = character {
2384 current_index += character.len_utf8();
2385 }
2386 }
2387
2388 let mut error = false;
2390 let mut width: Option<u32> = None;
2392 let mut density: Option<f64> = None;
2394 let mut future_compat_h: Option<u32> = None;
2396
2397 for descriptor in descriptors.into_iter() {
2400 let Some(last_character) = descriptor.chars().last() else {
2401 break;
2402 };
2403
2404 let first_part_of_string = &descriptor[0..descriptor.len() - last_character.len_utf8()];
2405 match last_character {
2406 'w' if is_valid_non_negative_integer_string(first_part_of_string) &&
2413 density.is_none() &&
2414 width.is_none() =>
2415 {
2416 match parse_unsigned_integer(first_part_of_string.chars()) {
2417 Ok(number) if number > 0 => {
2418 width = Some(number);
2419 continue;
2420 },
2421 _ => error = true,
2422 }
2423 },
2424
2425 'x' if is_valid_floating_point_number_string(first_part_of_string) &&
2439 width.is_none() &&
2440 density.is_none() &&
2441 future_compat_h.is_none() =>
2442 {
2443 match first_part_of_string.parse::<f64>() {
2444 Ok(number) if number.is_finite() && number >= 0. => {
2445 density = Some(number);
2446 continue;
2447 },
2448 _ => error = true,
2449 }
2450 },
2451
2452 'h' if is_valid_non_negative_integer_string(first_part_of_string) &&
2461 future_compat_h.is_none() &&
2462 density.is_none() =>
2463 {
2464 match parse_unsigned_integer(first_part_of_string.chars()) {
2465 Ok(number) if number > 0 => {
2466 future_compat_h = Some(number);
2467 continue;
2468 },
2469 _ => error = true,
2470 }
2471 },
2472
2473 _ => error = true,
2476 }
2477
2478 if error {
2479 break;
2480 }
2481 }
2482
2483 if future_compat_h.is_some() && width.is_none() {
2485 error = true;
2486 }
2487
2488 if !error {
2492 let image_source = ImageSource {
2493 url: url.into(),
2494 descriptor: Descriptor { width, density },
2495 };
2496 candidates.push(image_source);
2497 }
2498
2499 }
2501 candidates
2502}
2503
2504#[derive(Clone)]
2505enum ChangeType {
2506 Environment {
2507 selected_source: USVString,
2508 selected_pixel_density: f64,
2509 },
2510 Element,
2511}
2512
2513fn is_supported_image_mime_type(input: &str) -> bool {
2515 let mime_type = input.trim();
2517
2518 let mime_type_essence = match mime_type.find(';') {
2520 Some(semi) => &mime_type[..semi],
2521 _ => mime_type,
2522 };
2523
2524 if mime_type_essence.is_empty() {
2529 return true;
2530 }
2531
2532 SUPPORTED_IMAGE_MIME_TYPES.contains(&mime_type_essence)
2533}