1use std::rc::Rc;
6use std::sync::Arc;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use bytes::Bytes;
10use dom_struct::dom_struct;
11use embedder_traits::{
12 EmbedderMsg, Notification as EmbedderNotification,
13 NotificationAction as EmbedderNotificationAction,
14};
15use js::context::JSContext;
16use js::jsapi::Heap;
17use js::jsval::JSVal;
18use js::rust::{HandleObject, MutableHandleValue};
19use net_traits::http_status::HttpStatus;
20use net_traits::image_cache::{
21 ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
22 ImageOrMetadataAvailable, ImageResponse, PendingImageId,
23};
24use net_traits::request::{Destination, RequestBuilder, RequestId};
25use net_traits::{FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming};
26use pixels::RasterImage;
27use rustc_hash::FxHashSet;
28use script_bindings::cell::DomRefCell;
29use script_bindings::reflector::reflect_dom_object_with_proto;
30use servo_url::{ImmutableOrigin, ServoUrl};
31use uuid::Uuid;
32
33use crate::dom::bindings::callback::ExceptionHandling;
34use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
35 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
36 NotificationPermission, NotificationPermissionCallback,
37};
38use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
39use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
40 PermissionDescriptor, PermissionName, PermissionState,
41};
42use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
43use crate::dom::bindings::error::{Error, Fallible};
44use crate::dom::bindings::inheritance::Castable;
45use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
46use crate::dom::bindings::reflector::DomGlobal;
47use crate::dom::bindings::root::{Dom, DomRoot};
48use crate::dom::bindings::str::{DOMString, USVString};
49use crate::dom::bindings::trace::RootedTraceableBox;
50use crate::dom::bindings::utils::to_frozen_array;
51use crate::dom::csp::{GlobalCspReporting, Violation};
52use crate::dom::eventtarget::EventTarget;
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::performanceresourcetiming::InitiatorType;
55use crate::dom::permissions::{PermissionAlgorithm, Permissions, descriptor_permission_state};
56use crate::dom::permissionstatus::PermissionStatus;
57use crate::dom::promise::Promise;
58use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
59use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
60use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
61use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
62#[dom_struct]
67pub(crate) struct Notification {
68 eventtarget: EventTarget,
69 serviceworker_registration: Option<Dom<ServiceWorkerRegistration>>,
71 title: DOMString,
73 body: DOMString,
75 #[ignore_malloc_size_of = "mozjs"]
77 data: Heap<JSVal>,
78 dir: NotificationDirection,
80 image: Option<USVString>,
82 icon: Option<USVString>,
84 badge: Option<USVString>,
86 lang: DOMString,
88 silent: Option<bool>,
90 tag: DOMString,
92 #[no_trace] origin: ImmutableOrigin,
95 vibration_pattern: Vec<u32>,
97 timestamp: u64,
99 renotify: bool,
101 require_interaction: bool,
103 actions: Vec<Action>,
105 #[no_trace] pending_request_ids: DomRefCell<FxHashSet<RequestId>>,
108 #[ignore_malloc_size_of = "RasterImage"]
110 #[no_trace]
111 image_resource: DomRefCell<Option<Arc<RasterImage>>>,
112 #[ignore_malloc_size_of = "RasterImage"]
114 #[no_trace]
115 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
116 #[ignore_malloc_size_of = "RasterImage"]
118 #[no_trace]
119 badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
120}
121
122impl Notification {
123 #[expect(clippy::too_many_arguments)]
124 pub(crate) fn new(
125 cx: &mut JSContext,
126 global: &GlobalScope,
127 title: DOMString,
128 options: RootedTraceableBox<NotificationOptions>,
129 origin: ImmutableOrigin,
130 base_url: ServoUrl,
131 fallback_timestamp: u64,
132 proto: Option<HandleObject>,
133 ) -> DomRoot<Self> {
134 let notification = reflect_dom_object_with_proto(
135 cx,
136 Box::new(Notification::new_inherited(
137 global,
138 title,
139 &options,
140 origin,
141 base_url,
142 fallback_timestamp,
143 )),
144 global,
145 proto,
146 );
147
148 notification.data.set(options.data.get());
149
150 notification
151 }
152
153 fn new_inherited(
155 global: &GlobalScope,
156 title: DOMString,
157 options: &RootedTraceableBox<NotificationOptions>,
158 origin: ImmutableOrigin,
159 base_url: ServoUrl,
160 fallback_timestamp: u64,
161 ) -> Self {
162 let dir = options.dir;
166 let lang = options.lang.clone();
167 let body = options.body.clone();
168 let tag = options.tag.clone();
169
170 let image = options.image.as_ref().and_then(|image_url| {
173 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
174 .map(|url| USVString::from(url.to_string()))
175 .ok()
176 });
177 let icon = options.icon.as_ref().and_then(|icon_url| {
180 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
181 .map(|url| USVString::from(url.to_string()))
182 .ok()
183 });
184 let badge = options.badge.as_ref().and_then(|badge_url| {
187 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
188 .map(|url| USVString::from(url.to_string()))
189 .ok()
190 });
191 let vibration_pattern = match &options.vibrate {
194 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
195 None => Vec::new(),
196 };
197 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
200 let renotify = options.renotify;
201 let silent = options.silent;
202 let require_interaction = options.requireInteraction;
203
204 let mut actions: Vec<Action> = Vec::new();
207 let max_actions = Notification::MaxActions(global);
208 for action in options.actions.iter().take(max_actions as usize) {
209 actions.push(Action {
210 id: Uuid::new_v4().simple().to_string(),
211 name: action.action.clone(),
212 title: action.title.clone(),
213 icon_url: action.icon.as_ref().and_then(|icon_url| {
216 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
217 .map(|url| USVString::from(url.to_string()))
218 .ok()
219 }),
220 icon_resource: DomRefCell::new(None),
221 });
222 }
223
224 Self {
225 eventtarget: EventTarget::new_inherited(),
226 serviceworker_registration: None,
228 title,
229 body,
230 data: Heap::default(),
231 dir,
232 image,
233 icon,
234 badge,
235 lang,
236 silent,
237 origin,
238 vibration_pattern,
239 timestamp,
240 renotify,
241 tag,
242 require_interaction,
243 actions,
244 pending_request_ids: DomRefCell::new(Default::default()),
245 image_resource: DomRefCell::new(None),
246 icon_resource: DomRefCell::new(None),
247 badge_resource: DomRefCell::new(None),
248 }
249 }
250
251 fn show(&self) {
253 let shown = false;
255
256 if !shown {
270 self.global()
273 .send_to_embedder(EmbedderMsg::ShowNotification(
274 self.global().webview_id(),
275 self.to_embedder_notification(),
276 ));
277 }
278
279 if self.serviceworker_registration.is_none() {
287 self.global()
288 .task_manager()
289 .dom_manipulation_task_source()
290 .queue_simple_event(self.upcast(), atom!("show"));
291 }
292 }
293
294 fn to_embedder_notification(&self) -> EmbedderNotification {
296 let icon_resource = self
297 .icon_resource
298 .borrow()
299 .as_ref()
300 .map(|image| image.to_shared());
301 EmbedderNotification {
302 title: self.title.to_string(),
303 body: self.body.to_string(),
304 tag: self.tag.to_string(),
305 language: self.lang.to_string(),
306 require_interaction: self.require_interaction,
307 silent: self.silent,
308 icon_url: self
309 .icon
310 .as_ref()
311 .and_then(|icon| ServoUrl::parse(icon).ok()),
312 badge_url: self
313 .badge
314 .as_ref()
315 .and_then(|badge| ServoUrl::parse(badge).ok()),
316 image_url: self
317 .image
318 .as_ref()
319 .and_then(|image| ServoUrl::parse(image).ok()),
320 actions: self
321 .actions
322 .iter()
323 .map(|action| EmbedderNotificationAction {
324 name: action.name.to_string(),
325 title: action.title.to_string(),
326 icon_url: action
327 .icon_url
328 .as_ref()
329 .and_then(|icon| ServoUrl::parse(icon).ok()),
330 icon_resource: icon_resource.clone(),
331 })
332 .collect(),
333 icon_resource,
334 badge_resource: self
335 .badge_resource
336 .borrow()
337 .as_ref()
338 .map(|image| image.to_shared()),
339 image_resource: self
340 .image_resource
341 .borrow()
342 .as_ref()
343 .map(|image| image.to_shared()),
344 }
345 }
346}
347
348impl NotificationMethods<crate::DomTypeHolder> for Notification {
349 fn Constructor(
351 cx: &mut JSContext,
352 global: &GlobalScope,
353 proto: Option<HandleObject>,
354 title: DOMString,
355 options: RootedTraceableBox<NotificationOptions>,
356 ) -> Fallible<DomRoot<Notification>> {
357 if global.is::<ServiceWorkerGlobalScope>() {
359 return Err(Error::Type(
360 c"Notification constructor cannot be used in service worker.".to_owned(),
361 ));
362 }
363
364 if !options.actions.is_empty() {
366 return Err(Error::Type(
367 c"Actions are only supported for persistent notifications.".to_owned(),
368 ));
369 }
370
371 let notification =
373 create_notification_with_settings_object(cx, global, title, options, proto)?;
374
375 let permission_state = get_notifications_permission_state(global);
379 if permission_state != NotificationPermission::Granted {
380 global
381 .task_manager()
382 .dom_manipulation_task_source()
383 .queue_simple_event(notification.upcast(), atom!("error"));
384 } else {
386 notification.fetch_resources_and_show_when_ready();
390 }
391
392 Ok(notification)
393 }
394
395 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
397 Ok(get_notifications_permission_state(global))
398 }
399
400 fn RequestPermission(
402 cx: &mut JSContext,
403 global: &GlobalScope,
404 permission_callback: Option<Rc<NotificationPermissionCallback>>,
405 ) -> Rc<Promise> {
406 let promise = Promise::new(cx, global);
408
409 let notification_permission = request_notification_permission(cx, global);
412
413 let trusted_promise = TrustedPromise::new(promise.clone());
415 let uuid = Uuid::new_v4().simple().to_string();
416 let uuid_ = uuid.clone();
417
418 if let Some(callback) = permission_callback {
419 global.add_notification_permission_request_callback(uuid, callback);
420 }
421
422 global.task_manager().dom_manipulation_task_source().queue(
423 task!(request_permission: move |cx| {
424 let promise = trusted_promise.root();
425 let global = promise.global();
426
427 if let Some(callback) = global.remove_notification_permission_request_callback(uuid_) {
430 let _ = callback.Call__(cx, notification_permission, ExceptionHandling::Report);
431 }
432
433 promise.resolve_native(cx, ¬ification_permission);
435 }),
436 );
437
438 promise
439 }
440
441 event_handler!(click, GetOnclick, SetOnclick);
443 event_handler!(show, GetOnshow, SetOnshow);
445 event_handler!(error, GetOnerror, SetOnerror);
447 event_handler!(close, GetOnclose, SetOnclose);
449
450 fn MaxActions(_global: &GlobalScope) -> u32 {
452 2
454 }
455
456 fn Title(&self) -> DOMString {
458 self.title.clone()
459 }
460
461 fn Dir(&self) -> NotificationDirection {
463 self.dir
464 }
465
466 fn Lang(&self) -> DOMString {
468 self.lang.clone()
469 }
470
471 fn Body(&self) -> DOMString {
473 self.body.clone()
474 }
475
476 fn Tag(&self) -> DOMString {
478 self.tag.clone()
479 }
480
481 fn Image(&self) -> USVString {
483 self.image.clone().unwrap_or_default()
486 }
487
488 fn Icon(&self) -> USVString {
490 self.icon.clone().unwrap_or_default()
493 }
494
495 fn Badge(&self) -> USVString {
497 self.badge.clone().unwrap_or_default()
500 }
501
502 fn Renotify(&self) -> bool {
504 self.renotify
505 }
506
507 fn GetSilent(&self) -> Option<bool> {
509 self.silent
510 }
511
512 fn RequireInteraction(&self) -> bool {
514 self.require_interaction
515 }
516
517 fn Data(&self, mut retval: MutableHandleValue) {
519 retval.set(self.data.get());
520 }
521
522 fn Actions(&self, cx: &mut JSContext, retval: MutableHandleValue) {
524 let mut frozen_actions: Vec<NotificationAction> = Vec::new();
526
527 for action in self.actions.iter() {
529 let action = NotificationAction {
530 action: action.name.clone(),
531 title: action.title.clone(),
532 icon: action.icon_url.clone(),
535 };
536
537 frozen_actions.push(action);
540 }
541
542 to_frozen_array(cx, frozen_actions.as_slice(), retval);
544 }
545
546 fn Vibrate(&self, cx: &mut JSContext, retval: MutableHandleValue) {
548 to_frozen_array(cx, self.vibration_pattern.as_slice(), retval);
549 }
550
551 fn Timestamp(&self) -> u64 {
553 self.timestamp
554 }
555
556 fn Close(&self) {
558 if self.serviceworker_registration.is_none() {
564 self.global()
565 .task_manager()
566 .dom_manipulation_task_source()
567 .queue_simple_event(self.upcast(), atom!("close"));
568 }
569 }
570}
571
572#[derive(JSTraceable, MallocSizeOf)]
574struct Action {
575 id: String,
576 name: DOMString,
578 title: DOMString,
580 icon_url: Option<USVString>,
582 #[ignore_malloc_size_of = "RasterImage"]
584 #[no_trace]
585 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
586}
587
588fn create_notification_with_settings_object(
590 cx: &mut JSContext,
591 global: &GlobalScope,
592 title: DOMString,
593 options: RootedTraceableBox<NotificationOptions>,
594 proto: Option<HandleObject>,
595) -> Fallible<DomRoot<Notification>> {
596 let origin = global.origin().immutable().clone();
598 let base_url = global.api_base_url();
600 let fallback_timestamp = SystemTime::now()
603 .duration_since(UNIX_EPOCH)
604 .unwrap_or_default()
605 .as_millis() as u64;
606 create_notification(
609 cx,
610 global,
611 title,
612 options,
613 origin,
614 base_url,
615 fallback_timestamp,
616 proto,
617 )
618}
619
620#[expect(clippy::too_many_arguments)]
622fn create_notification(
623 cx: &mut JSContext,
624 global: &GlobalScope,
625 title: DOMString,
626 options: RootedTraceableBox<NotificationOptions>,
627 origin: ImmutableOrigin,
628 base_url: ServoUrl,
629 fallback_timestamp: u64,
630 proto: Option<HandleObject>,
631) -> Fallible<DomRoot<Notification>> {
632 if options.silent.is_some() && options.vibrate.is_some() {
634 return Err(Error::Type(
635 c"Can't specify vibration patterns when setting notification to silent.".to_owned(),
636 ));
637 }
638 if options.renotify && options.tag.is_empty() {
640 return Err(Error::Type(
641 c"tag must be set to renotify as an existing notification.".to_owned(),
642 ));
643 }
644
645 Ok(Notification::new(
646 cx,
647 global,
648 title,
649 options,
650 origin,
651 base_url,
652 fallback_timestamp,
653 proto,
654 ))
655}
656
657fn validate_and_normalize_vibration_pattern(
659 pattern: &UnsignedLongOrUnsignedLongSequence,
660) -> Vec<u32> {
661 let mut pattern: Vec<u32> = match pattern {
663 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
664 vec![*value]
667 },
668 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
669 };
670
671 pattern.truncate(10);
675
676 if pattern.len().is_multiple_of(2) && !pattern.is_empty() {
679 pattern.pop();
680 }
681
682 pattern.iter_mut().for_each(|entry| {
686 *entry = 10000.min(*entry);
687 });
688
689 pattern
691}
692
693fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
695 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
696 match permission_state {
697 PermissionState::Granted => NotificationPermission::Granted,
698 PermissionState::Denied => NotificationPermission::Denied,
699 PermissionState::Prompt => NotificationPermission::Default,
700 }
701}
702
703fn request_notification_permission(
704 cx: &mut JSContext,
705 global: &GlobalScope,
706) -> NotificationPermission {
707 let promise = &Promise::new(cx, global);
708 let descriptor = PermissionDescriptor {
709 name: PermissionName::Notifications,
710 };
711 let status = PermissionStatus::new(cx, global, &descriptor);
712
713 Permissions::permission_request(cx, promise, &descriptor, &status);
715
716 match status.State() {
717 PermissionState::Granted => NotificationPermission::Granted,
718 PermissionState::Denied => NotificationPermission::Denied,
719 PermissionState::Prompt => NotificationPermission::Default,
721 }
722}
723
724#[derive(Clone, Debug, Eq, Hash, PartialEq)]
725enum ResourceType {
726 Image,
727 Icon,
728 Badge,
729 ActionIcon(String), }
731
732struct ResourceFetchListener {
733 pending_image_id: PendingImageId,
735 image_cache: Arc<dyn ImageCache>,
737 notification: Trusted<Notification>,
739 status: Result<(), NetworkError>,
741 url: ServoUrl,
743}
744
745impl FetchResponseListener for ResourceFetchListener {
746 fn process_request_body(&mut self, _: RequestId) {}
747
748 fn process_response(
749 &mut self,
750 _: &mut js::context::JSContext,
751 request_id: RequestId,
752 metadata: Result<FetchMetadata, NetworkError>,
753 ) {
754 self.image_cache.notify_pending_response(
755 self.pending_image_id,
756 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
757 );
758
759 let metadata = metadata.ok().map(|meta| match meta {
760 FetchMetadata::Unfiltered(m) => m,
761 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
762 });
763
764 let status = metadata
765 .as_ref()
766 .map(|m| m.status.clone())
767 .unwrap_or_else(HttpStatus::new_error);
768
769 self.status = {
770 if status.is_success() {
771 Ok(())
772 } else if status.is_error() {
773 Err(NetworkError::ResourceLoadError(
774 "No http status code received".to_owned(),
775 ))
776 } else {
777 Err(NetworkError::ResourceLoadError(format!(
778 "HTTP error code {}",
779 status.code()
780 )))
781 }
782 };
783 }
784
785 fn process_response_chunk(
786 &mut self,
787 _: &mut js::context::JSContext,
788 request_id: RequestId,
789 payload: Bytes,
790 ) {
791 if self.status.is_ok() {
792 self.image_cache.notify_pending_response(
793 self.pending_image_id,
794 FetchResponseMsg::ProcessResponseChunk(request_id, payload),
795 );
796 }
797 }
798
799 fn process_response_eof(
800 self,
801 cx: &mut JSContext,
802 request_id: RequestId,
803 response: Result<(), NetworkError>,
804 timing: ResourceFetchTiming,
805 ) {
806 self.image_cache.notify_pending_response(
807 self.pending_image_id,
808 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
809 );
810 network_listener::submit_timing(cx, &self, &response, &timing);
811 }
812
813 fn process_csp_violations(
814 &mut self,
815 cx: &mut js::context::JSContext,
816 _request_id: RequestId,
817 violations: Vec<Violation>,
818 ) {
819 let global = &self.resource_timing_global();
820 global.report_csp_violations(cx, violations, None, None);
821 }
822
823 fn process_content_length(&mut self, request_id: RequestId, size: usize) {
824 self.image_cache.notify_pending_response(
825 self.pending_image_id,
826 FetchResponseMsg::ProcessContentLength(request_id, size),
827 );
828 }
829}
830
831impl ResourceTimingListener for ResourceFetchListener {
832 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
833 (InitiatorType::Other, self.url.clone())
834 }
835
836 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
837 self.notification.root().global()
838 }
839}
840
841impl Notification {
842 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
843 let global = &self.global();
844 create_a_potential_cors_request(
845 None,
846 url.clone(),
847 Destination::Image,
848 None, None,
850 global.get_referrer(),
851 )
852 .with_global_scope(global)
853 }
854
855 fn fetch_resources_and_show_when_ready(&self) {
857 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
858 if let Some(image_url) = &self.image &&
859 let Ok(url) = ServoUrl::parse(image_url)
860 {
861 let request = self.build_resource_request(&url);
862 self.pending_request_ids.borrow_mut().insert(request.id);
863 pending_requests.push((request, ResourceType::Image));
864 }
865 if let Some(icon_url) = &self.icon &&
866 let Ok(url) = ServoUrl::parse(icon_url)
867 {
868 let request = self.build_resource_request(&url);
869 self.pending_request_ids.borrow_mut().insert(request.id);
870 pending_requests.push((request, ResourceType::Icon));
871 }
872 if let Some(badge_url) = &self.badge &&
873 let Ok(url) = ServoUrl::parse(badge_url)
874 {
875 let request = self.build_resource_request(&url);
876 self.pending_request_ids.borrow_mut().insert(request.id);
877 pending_requests.push((request, ResourceType::Badge));
878 }
879 for action in self.actions.iter() {
880 if let Some(icon_url) = &action.icon_url &&
881 let Ok(url) = ServoUrl::parse(icon_url)
882 {
883 let request = self.build_resource_request(&url);
884 self.pending_request_ids.borrow_mut().insert(request.id);
885 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
886 }
887 }
888
889 for (request, resource_type) in pending_requests {
890 self.fetch_and_show_when_ready(request, resource_type);
891 }
892 }
893
894 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
895 let global: &GlobalScope = &self.global();
896 let request_id = request.id;
897
898 let cache_result = global.image_cache().get_cached_image_status(
899 request.url.url(),
900 global.origin().immutable().clone(),
901 None, );
903 match cache_result {
904 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
905 image, ..
906 }) => {
907 let image = image.as_raster_image();
908 if image.is_none() {
909 warn!("Vector images are not supported in notifications yet");
910 };
911 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
912 },
913 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
914 _,
915 pending_image_id,
916 )) => {
917 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
918 },
919 ImageCacheResult::Pending(pending_image_id) => {
920 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
921 },
922 ImageCacheResult::ReadyForRequest(pending_image_id) => {
923 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
924 self.fetch(pending_image_id, request, global);
925 },
926 ImageCacheResult::FailedToLoadOrDecode => {
927 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
928 },
929 };
930 }
931
932 fn register_image_cache_callback(
933 &self,
934 request_id: RequestId,
935 pending_image_id: PendingImageId,
936 resource_type: ResourceType,
937 ) {
938 let global: &GlobalScope = &self.global();
939 let trusted_this = Trusted::new(self);
940 let task_source = global.task_manager().networking_task_source().to_sendable();
941
942 let callback = Box::new(move |response| {
943 let trusted_this = trusted_this.clone();
944 let resource_type = resource_type.clone();
945 task_source.queue(task!(handle_response: move || {
946 let this = trusted_this.root();
947 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
948 warn!("Received unexpected message from image cache: {response:?}");
949 return;
950 };
951 this.handle_image_cache_response(request_id, status.response, resource_type);
952 }));
953 });
954
955 global.image_cache().add_listener(ImageLoadListener::new(
956 callback,
957 global.pipeline_id(),
958 pending_image_id,
959 ));
960 }
961
962 fn handle_image_cache_response(
963 &self,
964 request_id: RequestId,
965 response: ImageResponse,
966 resource_type: ResourceType,
967 ) {
968 match response {
969 ImageResponse::Loaded(image, _) => {
970 let image = image.as_raster_image();
971 if image.is_none() {
972 warn!("Vector images are not yet supported in notification attribute");
973 };
974 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
975 },
976 ImageResponse::FailedToLoadOrDecode => {
977 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
978 },
979 _ => (),
980 };
981 }
982
983 fn set_resource_and_show_when_ready(
984 &self,
985 request_id: RequestId,
986 resource_type: &ResourceType,
987 image: Option<Arc<RasterImage>>,
988 ) {
989 match resource_type {
990 ResourceType::Image => {
991 *self.image_resource.borrow_mut() = image;
992 },
993 ResourceType::Icon => {
994 *self.icon_resource.borrow_mut() = image;
995 },
996 ResourceType::Badge => {
997 *self.badge_resource.borrow_mut() = image;
998 },
999 ResourceType::ActionIcon(id) => {
1000 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
1001 *action.icon_resource.borrow_mut() = image;
1002 }
1003 },
1004 }
1005
1006 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1007 pending_requests_id.remove(&request_id);
1008
1009 if pending_requests_id.is_empty() {
1012 self.show();
1013 }
1014 }
1015
1016 fn fetch(
1017 &self,
1018 pending_image_id: PendingImageId,
1019 request: RequestBuilder,
1020 global: &GlobalScope,
1021 ) {
1022 let context = ResourceFetchListener {
1023 pending_image_id,
1024 image_cache: global.image_cache(),
1025 notification: Trusted::new(self),
1026 url: request.url.url(),
1027 status: Ok(()),
1028 };
1029
1030 global.fetch(
1031 request,
1032 context,
1033 global.task_manager().networking_task_source().into(),
1034 );
1035 }
1036}