1use std::collections::HashSet;
6use std::rc::Rc;
7use std::sync::Arc;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use dom_struct::dom_struct;
11use embedder_traits::{
12 EmbedderMsg, Notification as EmbedderNotification,
13 NotificationAction as EmbedderNotificationAction,
14};
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 servo_url::{ImmutableOrigin, ServoUrl};
27use uuid::Uuid;
28
29use super::bindings::cell::DomRefCell;
30use super::bindings::refcounted::{Trusted, TrustedPromise};
31use super::bindings::reflector::DomGlobal;
32use super::performanceresourcetiming::InitiatorType;
33use super::permissionstatus::PermissionStatus;
34use crate::dom::bindings::callback::ExceptionHandling;
35use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
36 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
37 NotificationPermission, NotificationPermissionCallback,
38};
39use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
40use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
41 PermissionDescriptor, PermissionName, PermissionState,
42};
43use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
44use crate::dom::bindings::error::{Error, Fallible};
45use crate::dom::bindings::inheritance::Castable;
46use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
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::permissions::{PermissionAlgorithm, Permissions, descriptor_permission_state};
55use crate::dom::promise::Promise;
56use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
57use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
58use crate::fetch::create_a_potential_cors_request;
59use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
60use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
61
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<HashSet<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 global: &GlobalScope,
126 title: DOMString,
127 options: RootedTraceableBox<NotificationOptions>,
128 origin: ImmutableOrigin,
129 base_url: ServoUrl,
130 fallback_timestamp: u64,
131 proto: Option<HandleObject>,
132 can_gc: CanGc,
133 ) -> DomRoot<Self> {
134 let notification = reflect_dom_object_with_proto(
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 can_gc,
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 title = title.clone();
166 let dir = options.dir;
167 let lang = options.lang.clone();
168 let body = options.body.clone();
169 let tag = options.tag.clone();
170
171 let image = options.image.as_ref().and_then(|image_url| {
174 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
175 .map(|url| USVString::from(url.to_string()))
176 .ok()
177 });
178 let icon = options.icon.as_ref().and_then(|icon_url| {
181 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
182 .map(|url| USVString::from(url.to_string()))
183 .ok()
184 });
185 let badge = options.badge.as_ref().and_then(|badge_url| {
188 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
189 .map(|url| USVString::from(url.to_string()))
190 .ok()
191 });
192 let vibration_pattern = match &options.vibrate {
195 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
196 None => Vec::new(),
197 };
198 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
201 let renotify = options.renotify;
202 let silent = options.silent;
203 let require_interaction = options.requireInteraction;
204
205 let mut actions: Vec<Action> = Vec::new();
208 let max_actions = Notification::MaxActions(global);
209 for action in options.actions.iter().take(max_actions as usize) {
210 actions.push(Action {
211 id: Uuid::new_v4().simple().to_string(),
212 name: action.action.clone(),
213 title: action.title.clone(),
214 icon_url: action.icon.as_ref().and_then(|icon_url| {
217 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
218 .map(|url| USVString::from(url.to_string()))
219 .ok()
220 }),
221 icon_resource: DomRefCell::new(None),
222 });
223 }
224
225 Self {
226 eventtarget: EventTarget::new_inherited(),
227 serviceworker_registration: None,
229 title,
230 body,
231 data: Heap::default(),
232 dir,
233 image,
234 icon,
235 badge,
236 lang,
237 silent,
238 origin,
239 vibration_pattern,
240 timestamp,
241 renotify,
242 tag,
243 require_interaction,
244 actions,
245 pending_request_ids: DomRefCell::new(HashSet::new()),
246 image_resource: DomRefCell::new(None),
247 icon_resource: DomRefCell::new(None),
248 badge_resource: DomRefCell::new(None),
249 }
250 }
251
252 fn show(&self) {
254 let shown = false;
256
257 if !shown {
271 self.global()
274 .send_to_embedder(EmbedderMsg::ShowNotification(
275 self.global().webview_id(),
276 self.to_embedder_notification(),
277 ));
278 }
279
280 if self.serviceworker_registration.is_none() {
288 self.global()
289 .task_manager()
290 .dom_manipulation_task_source()
291 .queue_simple_event(self.upcast(), atom!("show"));
292 }
293 }
294
295 fn to_embedder_notification(&self) -> EmbedderNotification {
297 let icon_resource = self
298 .icon_resource
299 .borrow()
300 .as_ref()
301 .map(|image| image.to_shared());
302 EmbedderNotification {
303 title: self.title.to_string(),
304 body: self.body.to_string(),
305 tag: self.tag.to_string(),
306 language: self.lang.to_string(),
307 require_interaction: self.require_interaction,
308 silent: self.silent,
309 icon_url: self
310 .icon
311 .as_ref()
312 .and_then(|icon| ServoUrl::parse(icon).ok()),
313 badge_url: self
314 .badge
315 .as_ref()
316 .and_then(|badge| ServoUrl::parse(badge).ok()),
317 image_url: self
318 .image
319 .as_ref()
320 .and_then(|image| ServoUrl::parse(image).ok()),
321 actions: self
322 .actions
323 .iter()
324 .map(|action| EmbedderNotificationAction {
325 name: action.name.to_string(),
326 title: action.title.to_string(),
327 icon_url: action
328 .icon_url
329 .as_ref()
330 .and_then(|icon| ServoUrl::parse(icon).ok()),
331 icon_resource: icon_resource.clone(),
332 })
333 .collect(),
334 icon_resource: icon_resource.clone(),
335 badge_resource: self
336 .badge_resource
337 .borrow()
338 .as_ref()
339 .map(|image| image.to_shared()),
340 image_resource: self
341 .image_resource
342 .borrow()
343 .as_ref()
344 .map(|image| image.to_shared()),
345 }
346 }
347}
348
349impl NotificationMethods<crate::DomTypeHolder> for Notification {
350 fn Constructor(
352 global: &GlobalScope,
353 proto: Option<HandleObject>,
354 can_gc: CanGc,
355 title: DOMString,
356 options: RootedTraceableBox<NotificationOptions>,
357 ) -> Fallible<DomRoot<Notification>> {
358 if global.is::<ServiceWorkerGlobalScope>() {
360 return Err(Error::Type(
361 "Notification constructor cannot be used in service worker.".to_string(),
362 ));
363 }
364
365 if !options.actions.is_empty() {
367 return Err(Error::Type(
368 "Actions are only supported for persistent notifications.".to_string(),
369 ));
370 }
371
372 let notification =
374 create_notification_with_settings_object(global, title, options, proto, can_gc)?;
375
376 let permission_state = get_notifications_permission_state(global);
380 if permission_state != NotificationPermission::Granted {
381 global
382 .task_manager()
383 .dom_manipulation_task_source()
384 .queue_simple_event(notification.upcast(), atom!("error"));
385 } else {
387 notification.fetch_resources_and_show_when_ready();
391 }
392
393 Ok(notification)
394 }
395
396 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
398 Ok(get_notifications_permission_state(global))
399 }
400
401 fn RequestPermission(
403 global: &GlobalScope,
404 permission_callback: Option<Rc<NotificationPermissionCallback>>,
405 can_gc: CanGc,
406 ) -> Rc<Promise> {
407 let promise = Promise::new(global, can_gc);
409
410 let notification_permission = request_notification_permission(global, can_gc);
413
414 let trusted_promise = TrustedPromise::new(promise.clone());
416 let uuid = Uuid::new_v4().simple().to_string();
417 let uuid_ = uuid.clone();
418
419 if let Some(callback) = permission_callback {
420 global.add_notification_permission_request_callback(uuid.clone(), callback.clone());
421 }
422
423 global.task_manager().dom_manipulation_task_source().queue(
424 task!(request_permission: move || {
425 let promise = trusted_promise.root();
426 let global = promise.global();
427
428 if let Some(callback) = global.remove_notification_permission_request_callback(uuid_) {
431 let _ = callback.Call__(notification_permission, ExceptionHandling::Report, CanGc::note());
432 }
433
434 promise.resolve_native(¬ification_permission, CanGc::note());
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 fn Title(&self) -> DOMString {
458 self.title.clone()
459 }
460 fn Dir(&self) -> NotificationDirection {
462 self.dir
463 }
464 fn Lang(&self) -> DOMString {
466 self.lang.clone()
467 }
468 fn Body(&self) -> DOMString {
470 self.body.clone()
471 }
472 fn Tag(&self) -> DOMString {
474 self.tag.clone()
475 }
476 fn Image(&self) -> USVString {
478 self.image.clone().unwrap_or_default()
481 }
482 fn Icon(&self) -> USVString {
484 self.icon.clone().unwrap_or_default()
487 }
488 fn Badge(&self) -> USVString {
490 self.badge.clone().unwrap_or_default()
493 }
494 fn Renotify(&self) -> bool {
496 self.renotify
497 }
498 fn GetSilent(&self) -> Option<bool> {
500 self.silent
501 }
502 fn RequireInteraction(&self) -> bool {
504 self.require_interaction
505 }
506 fn Data(&self, _cx: SafeJSContext, mut retval: MutableHandleValue) {
508 retval.set(self.data.get());
509 }
510 fn Actions(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
512 let mut frozen_actions: Vec<NotificationAction> = Vec::new();
514
515 for action in self.actions.iter() {
517 let action = NotificationAction {
518 action: action.name.clone(),
519 title: action.title.clone(),
520 icon: action.icon_url.clone(),
523 };
524
525 frozen_actions.push(action);
528 }
529
530 to_frozen_array(frozen_actions.as_slice(), cx, retval, can_gc);
532 }
533 fn Vibrate(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
535 to_frozen_array(self.vibration_pattern.as_slice(), cx, retval, can_gc);
536 }
537 fn Timestamp(&self) -> u64 {
539 self.timestamp
540 }
541 fn Close(&self) {
543 if self.serviceworker_registration.is_none() {
549 self.global()
550 .task_manager()
551 .dom_manipulation_task_source()
552 .queue_simple_event(self.upcast(), atom!("close"));
553 }
554 }
555}
556
557#[derive(JSTraceable, MallocSizeOf)]
559struct Action {
560 id: String,
561 name: DOMString,
563 title: DOMString,
565 icon_url: Option<USVString>,
567 #[ignore_malloc_size_of = "RasterImage"]
569 #[no_trace]
570 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
571}
572
573fn create_notification_with_settings_object(
575 global: &GlobalScope,
576 title: DOMString,
577 options: RootedTraceableBox<NotificationOptions>,
578 proto: Option<HandleObject>,
579 can_gc: CanGc,
580) -> Fallible<DomRoot<Notification>> {
581 let origin = global.origin().immutable().clone();
583 let base_url = global.api_base_url();
585 let fallback_timestamp = SystemTime::now()
588 .duration_since(UNIX_EPOCH)
589 .unwrap_or_default()
590 .as_millis() as u64;
591 create_notification(
594 global,
595 title,
596 options,
597 origin,
598 base_url,
599 fallback_timestamp,
600 proto,
601 can_gc,
602 )
603}
604
605#[expect(clippy::too_many_arguments)]
607fn create_notification(
608 global: &GlobalScope,
609 title: DOMString,
610 options: RootedTraceableBox<NotificationOptions>,
611 origin: ImmutableOrigin,
612 base_url: ServoUrl,
613 fallback_timestamp: u64,
614 proto: Option<HandleObject>,
615 can_gc: CanGc,
616) -> Fallible<DomRoot<Notification>> {
617 if options.silent.is_some() && options.vibrate.is_some() {
619 return Err(Error::Type(
620 "Can't specify vibration patterns when setting notification to silent.".to_string(),
621 ));
622 }
623 if options.renotify && options.tag.is_empty() {
625 return Err(Error::Type(
626 "tag must be set to renotify as an existing notification.".to_string(),
627 ));
628 }
629
630 Ok(Notification::new(
631 global,
632 title,
633 options,
634 origin,
635 base_url,
636 fallback_timestamp,
637 proto,
638 can_gc,
639 ))
640}
641
642fn validate_and_normalize_vibration_pattern(
644 pattern: &UnsignedLongOrUnsignedLongSequence,
645) -> Vec<u32> {
646 let mut pattern: Vec<u32> = match pattern {
648 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
649 vec![*value]
652 },
653 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
654 };
655
656 pattern.truncate(10);
660
661 if pattern.len() % 2 == 0 && !pattern.is_empty() {
664 pattern.pop();
665 }
666
667 pattern.iter_mut().for_each(|entry| {
671 *entry = 10000.min(*entry);
672 });
673
674 pattern
676}
677
678fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
680 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
681 match permission_state {
682 PermissionState::Granted => NotificationPermission::Granted,
683 PermissionState::Denied => NotificationPermission::Denied,
684 PermissionState::Prompt => NotificationPermission::Default,
685 }
686}
687
688fn request_notification_permission(global: &GlobalScope, can_gc: CanGc) -> NotificationPermission {
689 let cx = GlobalScope::get_cx();
690 let promise = &Promise::new(global, can_gc);
691 let descriptor = PermissionDescriptor {
692 name: PermissionName::Notifications,
693 };
694 let status = PermissionStatus::new(global, &descriptor, can_gc);
695
696 Permissions::permission_request(cx, promise, &descriptor, &status);
698
699 match status.State() {
700 PermissionState::Granted => NotificationPermission::Granted,
701 PermissionState::Denied => NotificationPermission::Denied,
702 PermissionState::Prompt => NotificationPermission::Default,
704 }
705}
706
707#[derive(Clone, Debug, Eq, Hash, PartialEq)]
708enum ResourceType {
709 Image,
710 Icon,
711 Badge,
712 ActionIcon(String), }
714
715struct ResourceFetchListener {
716 pending_image_id: PendingImageId,
718 image_cache: Arc<dyn ImageCache>,
720 notification: Trusted<Notification>,
722 status: Result<(), NetworkError>,
724 url: ServoUrl,
726}
727
728impl FetchResponseListener for ResourceFetchListener {
729 fn process_request_body(&mut self, _: RequestId) {}
730 fn process_request_eof(&mut self, _: RequestId) {}
731
732 fn process_response(
733 &mut self,
734 request_id: RequestId,
735 metadata: Result<FetchMetadata, NetworkError>,
736 ) {
737 self.image_cache.notify_pending_response(
738 self.pending_image_id,
739 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
740 );
741
742 let metadata = metadata.ok().map(|meta| match meta {
743 FetchMetadata::Unfiltered(m) => m,
744 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
745 });
746
747 let status = metadata
748 .as_ref()
749 .map(|m| m.status.clone())
750 .unwrap_or_else(HttpStatus::new_error);
751
752 self.status = {
753 if status.is_success() {
754 Ok(())
755 } else if status.is_error() {
756 Err(NetworkError::Internal(
757 "No http status code received".to_owned(),
758 ))
759 } else {
760 Err(NetworkError::Internal(format!(
761 "HTTP error code {}",
762 status.code()
763 )))
764 }
765 };
766 }
767
768 fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
769 if self.status.is_ok() {
770 self.image_cache.notify_pending_response(
771 self.pending_image_id,
772 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
773 );
774 }
775 }
776
777 fn process_response_eof(
778 self,
779 request_id: RequestId,
780 response: Result<ResourceFetchTiming, NetworkError>,
781 ) {
782 self.image_cache.notify_pending_response(
783 self.pending_image_id,
784 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone()),
785 );
786 if let Ok(response) = response {
787 network_listener::submit_timing(&self, &response, CanGc::note());
788 }
789 }
790
791 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
792 let global = &self.resource_timing_global();
793 global.report_csp_violations(violations, None, None);
794 }
795}
796
797impl ResourceTimingListener for ResourceFetchListener {
798 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
799 (InitiatorType::Other, self.url.clone())
800 }
801
802 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
803 self.notification.root().global()
804 }
805}
806
807impl Notification {
808 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
809 let global = &self.global();
810 create_a_potential_cors_request(
811 None,
812 url.clone(),
813 Destination::Image,
814 None, None,
816 global.get_referrer(),
817 global.insecure_requests_policy(),
818 global.has_trustworthy_ancestor_or_current_origin(),
819 global.policy_container(),
820 )
821 .origin(global.origin().immutable().clone())
822 .pipeline_id(Some(global.pipeline_id()))
823 }
824
825 fn fetch_resources_and_show_when_ready(&self) {
827 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
828 if let Some(image_url) = &self.image {
829 if let Ok(url) = ServoUrl::parse(image_url) {
830 let request = self.build_resource_request(&url);
831 self.pending_request_ids.borrow_mut().insert(request.id);
832 pending_requests.push((request, ResourceType::Image));
833 }
834 }
835 if let Some(icon_url) = &self.icon {
836 if let Ok(url) = ServoUrl::parse(icon_url) {
837 let request = self.build_resource_request(&url);
838 self.pending_request_ids.borrow_mut().insert(request.id);
839 pending_requests.push((request, ResourceType::Icon));
840 }
841 }
842 if let Some(badge_url) = &self.badge {
843 if let Ok(url) = ServoUrl::parse(badge_url) {
844 let request = self.build_resource_request(&url);
845 self.pending_request_ids.borrow_mut().insert(request.id);
846 pending_requests.push((request, ResourceType::Badge));
847 }
848 }
849 for action in self.actions.iter() {
850 if let Some(icon_url) = &action.icon_url {
851 if let Ok(url) = ServoUrl::parse(icon_url) {
852 let request = self.build_resource_request(&url);
853 self.pending_request_ids.borrow_mut().insert(request.id);
854 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
855 }
856 }
857 }
858
859 for (request, resource_type) in pending_requests {
860 self.fetch_and_show_when_ready(request, resource_type);
861 }
862 }
863
864 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
865 let global: &GlobalScope = &self.global();
866 let request_id = request.id;
867
868 let cache_result = global.image_cache().get_cached_image_status(
869 request.url.clone(),
870 global.origin().immutable().clone(),
871 None, );
873 match cache_result {
874 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
875 image, ..
876 }) => {
877 let image = image.as_raster_image();
878 if image.is_none() {
879 warn!("Vector images are not supported in notifications yet");
880 };
881 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
882 },
883 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
884 _,
885 pending_image_id,
886 )) => {
887 self.register_image_cache_callback(
888 request_id,
889 pending_image_id,
890 resource_type.clone(),
891 );
892 },
893 ImageCacheResult::Pending(pending_image_id) => {
894 self.register_image_cache_callback(
895 request_id,
896 pending_image_id,
897 resource_type.clone(),
898 );
899 },
900 ImageCacheResult::ReadyForRequest(pending_image_id) => {
901 self.register_image_cache_callback(
902 request_id,
903 pending_image_id,
904 resource_type.clone(),
905 );
906 self.fetch(pending_image_id, request, global);
907 },
908 ImageCacheResult::FailedToLoadOrDecode => {
909 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
910 },
911 };
912 }
913
914 fn register_image_cache_callback(
915 &self,
916 request_id: RequestId,
917 pending_image_id: PendingImageId,
918 resource_type: ResourceType,
919 ) {
920 let global: &GlobalScope = &self.global();
921 let trusted_this = Trusted::new(self);
922 let task_source = global.task_manager().networking_task_source().to_sendable();
923
924 let callback = Box::new(move |response| {
925 let trusted_this = trusted_this.clone();
926 let resource_type = resource_type.clone();
927 task_source.queue(task!(handle_response: move || {
928 let this = trusted_this.root();
929 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
930 warn!("Received unexpected message from image cache: {response:?}");
931 return;
932 };
933 this.handle_image_cache_response(request_id, status.response, resource_type);
934 }));
935 });
936
937 global.image_cache().add_listener(ImageLoadListener::new(
938 callback,
939 global.pipeline_id(),
940 pending_image_id,
941 ));
942 }
943
944 fn handle_image_cache_response(
945 &self,
946 request_id: RequestId,
947 response: ImageResponse,
948 resource_type: ResourceType,
949 ) {
950 match response {
951 ImageResponse::Loaded(image, _) => {
952 let image = image.as_raster_image();
953 if image.is_none() {
954 warn!("Vector images are not yet supported in notification attribute");
955 };
956 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
957 },
958 ImageResponse::FailedToLoadOrDecode => {
959 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
960 },
961 _ => (),
962 };
963 }
964
965 fn set_resource_and_show_when_ready(
966 &self,
967 request_id: RequestId,
968 resource_type: &ResourceType,
969 image: Option<Arc<RasterImage>>,
970 ) {
971 match resource_type {
972 ResourceType::Image => {
973 *self.image_resource.borrow_mut() = image;
974 },
975 ResourceType::Icon => {
976 *self.icon_resource.borrow_mut() = image;
977 },
978 ResourceType::Badge => {
979 *self.badge_resource.borrow_mut() = image;
980 },
981 ResourceType::ActionIcon(id) => {
982 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
983 *action.icon_resource.borrow_mut() = image;
984 }
985 },
986 }
987
988 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
989 pending_requests_id.remove(&request_id);
990
991 if pending_requests_id.is_empty() {
994 self.show();
995 }
996 }
997
998 fn fetch(
999 &self,
1000 pending_image_id: PendingImageId,
1001 request: RequestBuilder,
1002 global: &GlobalScope,
1003 ) {
1004 let context = ResourceFetchListener {
1005 pending_image_id,
1006 image_cache: global.image_cache(),
1007 notification: Trusted::new(self),
1008 url: request.url.clone(),
1009 status: Ok(()),
1010 };
1011
1012 global.fetch(
1013 request,
1014 context,
1015 global.task_manager().networking_task_source().into(),
1016 );
1017 }
1018}