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