1use std::rc::Rc;
6use std::sync::Arc;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use dom_struct::dom_struct;
10use embedder_traits::{
11 EmbedderMsg, Notification as EmbedderNotification,
12 NotificationAction as EmbedderNotificationAction,
13};
14use js::context::JSContext;
15use js::jsapi::Heap;
16use js::jsval::JSVal;
17use js::rust::{HandleObject, MutableHandleValue};
18use net_traits::http_status::HttpStatus;
19use net_traits::image_cache::{
20 ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
21 ImageOrMetadataAvailable, ImageResponse, PendingImageId,
22};
23use net_traits::request::{Destination, RequestBuilder, RequestId};
24use net_traits::{FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming};
25use pixels::RasterImage;
26use rustc_hash::FxHashSet;
27use servo_url::{ImmutableOrigin, ServoUrl};
28use uuid::Uuid;
29
30use super::bindings::cell::DomRefCell;
31use super::bindings::refcounted::{Trusted, TrustedPromise};
32use super::bindings::reflector::DomGlobal;
33use super::performanceresourcetiming::InitiatorType;
34use super::permissionstatus::PermissionStatus;
35use crate::dom::bindings::callback::ExceptionHandling;
36use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
37 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
38 NotificationPermission, NotificationPermissionCallback,
39};
40use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
41use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
42 PermissionDescriptor, PermissionName, PermissionState,
43};
44use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
45use crate::dom::bindings::error::{Error, Fallible};
46use crate::dom::bindings::inheritance::Castable;
47use crate::dom::bindings::reflector::reflect_dom_object_with_proto_and_cx;
48use crate::dom::bindings::root::{Dom, DomRoot};
49use crate::dom::bindings::str::{DOMString, USVString};
50use crate::dom::bindings::trace::RootedTraceableBox;
51use crate::dom::bindings::utils::to_frozen_array;
52use crate::dom::csp::{GlobalCspReporting, Violation};
53use crate::dom::eventtarget::EventTarget;
54use crate::dom::globalscope::GlobalScope;
55use crate::dom::permissions::{PermissionAlgorithm, Permissions, descriptor_permission_state};
56use crate::dom::promise::Promise;
57use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
58use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
59use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
60use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
61use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
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_and_cx(
135 Box::new(Notification::new_inherited(
136 global,
137 title,
138 &options,
139 origin,
140 base_url,
141 fallback_timestamp,
142 )),
143 global,
144 proto,
145 cx,
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(global, CanGc::from_cx(cx));
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 || {
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__(notification_permission, ExceptionHandling::Report, CanGc::note());
431 }
432
433 promise.resolve_native(¬ification_permission, CanGc::note());
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, _cx: SafeJSContext, 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(
544 frozen_actions.as_slice(),
545 cx.into(),
546 retval,
547 CanGc::from_cx(cx),
548 );
549 }
550
551 fn Vibrate(&self, cx: &mut JSContext, retval: MutableHandleValue) {
553 to_frozen_array(
554 self.vibration_pattern.as_slice(),
555 cx.into(),
556 retval,
557 CanGc::from_cx(cx),
558 );
559 }
560
561 fn Timestamp(&self) -> u64 {
563 self.timestamp
564 }
565
566 fn Close(&self) {
568 if self.serviceworker_registration.is_none() {
574 self.global()
575 .task_manager()
576 .dom_manipulation_task_source()
577 .queue_simple_event(self.upcast(), atom!("close"));
578 }
579 }
580}
581
582#[derive(JSTraceable, MallocSizeOf)]
584struct Action {
585 id: String,
586 name: DOMString,
588 title: DOMString,
590 icon_url: Option<USVString>,
592 #[ignore_malloc_size_of = "RasterImage"]
594 #[no_trace]
595 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
596}
597
598fn create_notification_with_settings_object(
600 cx: &mut JSContext,
601 global: &GlobalScope,
602 title: DOMString,
603 options: RootedTraceableBox<NotificationOptions>,
604 proto: Option<HandleObject>,
605) -> Fallible<DomRoot<Notification>> {
606 let origin = global.origin().immutable().clone();
608 let base_url = global.api_base_url();
610 let fallback_timestamp = SystemTime::now()
613 .duration_since(UNIX_EPOCH)
614 .unwrap_or_default()
615 .as_millis() as u64;
616 create_notification(
619 cx,
620 global,
621 title,
622 options,
623 origin,
624 base_url,
625 fallback_timestamp,
626 proto,
627 )
628}
629
630#[expect(clippy::too_many_arguments)]
632fn create_notification(
633 cx: &mut JSContext,
634 global: &GlobalScope,
635 title: DOMString,
636 options: RootedTraceableBox<NotificationOptions>,
637 origin: ImmutableOrigin,
638 base_url: ServoUrl,
639 fallback_timestamp: u64,
640 proto: Option<HandleObject>,
641) -> Fallible<DomRoot<Notification>> {
642 if options.silent.is_some() && options.vibrate.is_some() {
644 return Err(Error::Type(
645 c"Can't specify vibration patterns when setting notification to silent.".to_owned(),
646 ));
647 }
648 if options.renotify && options.tag.is_empty() {
650 return Err(Error::Type(
651 c"tag must be set to renotify as an existing notification.".to_owned(),
652 ));
653 }
654
655 Ok(Notification::new(
656 cx,
657 global,
658 title,
659 options,
660 origin,
661 base_url,
662 fallback_timestamp,
663 proto,
664 ))
665}
666
667fn validate_and_normalize_vibration_pattern(
669 pattern: &UnsignedLongOrUnsignedLongSequence,
670) -> Vec<u32> {
671 let mut pattern: Vec<u32> = match pattern {
673 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
674 vec![*value]
677 },
678 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
679 };
680
681 pattern.truncate(10);
685
686 if pattern.len() % 2 == 0 && !pattern.is_empty() {
689 pattern.pop();
690 }
691
692 pattern.iter_mut().for_each(|entry| {
696 *entry = 10000.min(*entry);
697 });
698
699 pattern
701}
702
703fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
705 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
706 match permission_state {
707 PermissionState::Granted => NotificationPermission::Granted,
708 PermissionState::Denied => NotificationPermission::Denied,
709 PermissionState::Prompt => NotificationPermission::Default,
710 }
711}
712
713fn request_notification_permission(
714 cx: &mut JSContext,
715 global: &GlobalScope,
716) -> NotificationPermission {
717 let promise = &Promise::new(global, CanGc::from_cx(cx));
718 let descriptor = PermissionDescriptor {
719 name: PermissionName::Notifications,
720 };
721 let status = PermissionStatus::new(global, &descriptor, CanGc::from_cx(cx));
722
723 Permissions::permission_request(cx, promise, &descriptor, &status);
725
726 match status.State() {
727 PermissionState::Granted => NotificationPermission::Granted,
728 PermissionState::Denied => NotificationPermission::Denied,
729 PermissionState::Prompt => NotificationPermission::Default,
731 }
732}
733
734#[derive(Clone, Debug, Eq, Hash, PartialEq)]
735enum ResourceType {
736 Image,
737 Icon,
738 Badge,
739 ActionIcon(String), }
741
742struct ResourceFetchListener {
743 pending_image_id: PendingImageId,
745 image_cache: Arc<dyn ImageCache>,
747 notification: Trusted<Notification>,
749 status: Result<(), NetworkError>,
751 url: ServoUrl,
753}
754
755impl FetchResponseListener for ResourceFetchListener {
756 fn process_request_body(&mut self, _: RequestId) {}
757
758 fn process_response(
759 &mut self,
760 _: &mut js::context::JSContext,
761 request_id: RequestId,
762 metadata: Result<FetchMetadata, NetworkError>,
763 ) {
764 self.image_cache.notify_pending_response(
765 self.pending_image_id,
766 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
767 );
768
769 let metadata = metadata.ok().map(|meta| match meta {
770 FetchMetadata::Unfiltered(m) => m,
771 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
772 });
773
774 let status = metadata
775 .as_ref()
776 .map(|m| m.status.clone())
777 .unwrap_or_else(HttpStatus::new_error);
778
779 self.status = {
780 if status.is_success() {
781 Ok(())
782 } else if status.is_error() {
783 Err(NetworkError::ResourceLoadError(
784 "No http status code received".to_owned(),
785 ))
786 } else {
787 Err(NetworkError::ResourceLoadError(format!(
788 "HTTP error code {}",
789 status.code()
790 )))
791 }
792 };
793 }
794
795 fn process_response_chunk(
796 &mut self,
797 _: &mut js::context::JSContext,
798 request_id: RequestId,
799 payload: Vec<u8>,
800 ) {
801 if self.status.is_ok() {
802 self.image_cache.notify_pending_response(
803 self.pending_image_id,
804 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
805 );
806 }
807 }
808
809 fn process_response_eof(
810 self,
811 cx: &mut JSContext,
812 request_id: RequestId,
813 response: Result<(), NetworkError>,
814 timing: ResourceFetchTiming,
815 ) {
816 self.image_cache.notify_pending_response(
817 self.pending_image_id,
818 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
819 );
820 network_listener::submit_timing(cx, &self, &response, &timing);
821 }
822
823 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
824 let global = &self.resource_timing_global();
825 global.report_csp_violations(violations, None, None);
826 }
827}
828
829impl ResourceTimingListener for ResourceFetchListener {
830 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
831 (InitiatorType::Other, self.url.clone())
832 }
833
834 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
835 self.notification.root().global()
836 }
837}
838
839impl Notification {
840 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
841 let global = &self.global();
842 create_a_potential_cors_request(
843 None,
844 url.clone(),
845 Destination::Image,
846 None, None,
848 global.get_referrer(),
849 )
850 .with_global_scope(global)
851 }
852
853 fn fetch_resources_and_show_when_ready(&self) {
855 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
856 if let Some(image_url) = &self.image {
857 if let Ok(url) = ServoUrl::parse(image_url) {
858 let request = self.build_resource_request(&url);
859 self.pending_request_ids.borrow_mut().insert(request.id);
860 pending_requests.push((request, ResourceType::Image));
861 }
862 }
863 if let Some(icon_url) = &self.icon {
864 if let Ok(url) = ServoUrl::parse(icon_url) {
865 let request = self.build_resource_request(&url);
866 self.pending_request_ids.borrow_mut().insert(request.id);
867 pending_requests.push((request, ResourceType::Icon));
868 }
869 }
870 if let Some(badge_url) = &self.badge {
871 if let Ok(url) = ServoUrl::parse(badge_url) {
872 let request = self.build_resource_request(&url);
873 self.pending_request_ids.borrow_mut().insert(request.id);
874 pending_requests.push((request, ResourceType::Badge));
875 }
876 }
877 for action in self.actions.iter() {
878 if let Some(icon_url) = &action.icon_url {
879 if let Ok(url) = ServoUrl::parse(icon_url) {
880 let request = self.build_resource_request(&url);
881 self.pending_request_ids.borrow_mut().insert(request.id);
882 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
883 }
884 }
885 }
886
887 for (request, resource_type) in pending_requests {
888 self.fetch_and_show_when_ready(request, resource_type);
889 }
890 }
891
892 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
893 let global: &GlobalScope = &self.global();
894 let request_id = request.id;
895
896 let cache_result = global.image_cache().get_cached_image_status(
897 request.url.url(),
898 global.origin().immutable().clone(),
899 None, );
901 match cache_result {
902 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
903 image, ..
904 }) => {
905 let image = image.as_raster_image();
906 if image.is_none() {
907 warn!("Vector images are not supported in notifications yet");
908 };
909 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
910 },
911 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
912 _,
913 pending_image_id,
914 )) => {
915 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
916 },
917 ImageCacheResult::Pending(pending_image_id) => {
918 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
919 },
920 ImageCacheResult::ReadyForRequest(pending_image_id) => {
921 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
922 self.fetch(pending_image_id, request, global);
923 },
924 ImageCacheResult::FailedToLoadOrDecode => {
925 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
926 },
927 };
928 }
929
930 fn register_image_cache_callback(
931 &self,
932 request_id: RequestId,
933 pending_image_id: PendingImageId,
934 resource_type: ResourceType,
935 ) {
936 let global: &GlobalScope = &self.global();
937 let trusted_this = Trusted::new(self);
938 let task_source = global.task_manager().networking_task_source().to_sendable();
939
940 let callback = Box::new(move |response| {
941 let trusted_this = trusted_this.clone();
942 let resource_type = resource_type.clone();
943 task_source.queue(task!(handle_response: move || {
944 let this = trusted_this.root();
945 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
946 warn!("Received unexpected message from image cache: {response:?}");
947 return;
948 };
949 this.handle_image_cache_response(request_id, status.response, resource_type);
950 }));
951 });
952
953 global.image_cache().add_listener(ImageLoadListener::new(
954 callback,
955 global.pipeline_id(),
956 pending_image_id,
957 ));
958 }
959
960 fn handle_image_cache_response(
961 &self,
962 request_id: RequestId,
963 response: ImageResponse,
964 resource_type: ResourceType,
965 ) {
966 match response {
967 ImageResponse::Loaded(image, _) => {
968 let image = image.as_raster_image();
969 if image.is_none() {
970 warn!("Vector images are not yet supported in notification attribute");
971 };
972 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
973 },
974 ImageResponse::FailedToLoadOrDecode => {
975 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
976 },
977 _ => (),
978 };
979 }
980
981 fn set_resource_and_show_when_ready(
982 &self,
983 request_id: RequestId,
984 resource_type: &ResourceType,
985 image: Option<Arc<RasterImage>>,
986 ) {
987 match resource_type {
988 ResourceType::Image => {
989 *self.image_resource.borrow_mut() = image;
990 },
991 ResourceType::Icon => {
992 *self.icon_resource.borrow_mut() = image;
993 },
994 ResourceType::Badge => {
995 *self.badge_resource.borrow_mut() = image;
996 },
997 ResourceType::ActionIcon(id) => {
998 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
999 *action.icon_resource.borrow_mut() = image;
1000 }
1001 },
1002 }
1003
1004 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1005 pending_requests_id.remove(&request_id);
1006
1007 if pending_requests_id.is_empty() {
1010 self.show();
1011 }
1012 }
1013
1014 fn fetch(
1015 &self,
1016 pending_image_id: PendingImageId,
1017 request: RequestBuilder,
1018 global: &GlobalScope,
1019 ) {
1020 let context = ResourceFetchListener {
1021 pending_image_id,
1022 image_cache: global.image_cache(),
1023 notification: Trusted::new(self),
1024 url: request.url.url(),
1025 status: Ok(()),
1026 };
1027
1028 global.fetch(
1029 request,
1030 context,
1031 global.task_manager().networking_task_source().into(),
1032 );
1033 }
1034}