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::reflector::reflect_dom_object_with_proto;
29use servo_url::{ImmutableOrigin, ServoUrl};
30use uuid::Uuid;
31
32use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback};
33use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
34 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
35 NotificationPermission, NotificationPermissionCallback,
36};
37use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
38 PermissionDescriptor, PermissionName, PermissionState, PermissionStatusMethods,
39};
40use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
41use crate::dom::bindings::error::{Error, Fallible};
42use crate::dom::bindings::inheritance::Castable;
43use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
44use crate::dom::bindings::reflector::DomGlobal;
45use crate::dom::bindings::root::{Dom, DomRoot};
46use crate::dom::bindings::str::{DOMString, USVString};
47use crate::dom::bindings::utils::to_frozen_array;
48use crate::dom::csp::{GlobalCspReporting, Violation};
49use crate::dom::eventtarget::EventTarget;
50use crate::dom::globalscope::GlobalScope;
51use crate::dom::performanceresourcetiming::InitiatorType;
52use crate::dom::permissions::{PermissionAlgorithm, Permissions, descriptor_permission_state};
53use crate::dom::permissionstatus::PermissionStatus;
54use crate::dom::promise::{Promise, RootedPromise};
55use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
56use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
57use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
58use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
59#[dom_struct]
64pub(crate) struct Notification {
65 eventtarget: EventTarget,
66 serviceworker_registration: Option<Dom<ServiceWorkerRegistration>>,
68 title: DOMString,
70 body: DOMString,
72 #[ignore_malloc_size_of = "mozjs"]
74 data: Heap<JSVal>,
75 dir: NotificationDirection,
77 image: Option<USVString>,
79 icon: Option<USVString>,
81 badge: Option<USVString>,
83 lang: DOMString,
85 silent: Option<bool>,
87 tag: DOMString,
89 #[no_trace] origin: ImmutableOrigin,
92 vibration_pattern: Vec<u32>,
94 timestamp: u64,
96 renotify: bool,
98 require_interaction: bool,
100 actions: Vec<Action>,
102 #[no_trace] pending_request_ids: DomRefCell<FxHashSet<RequestId>>,
105 #[ignore_malloc_size_of = "RasterImage"]
107 #[no_trace]
108 image_resource: DomRefCell<Option<Arc<RasterImage>>>,
109 #[ignore_malloc_size_of = "RasterImage"]
111 #[no_trace]
112 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
113 #[ignore_malloc_size_of = "RasterImage"]
115 #[no_trace]
116 badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
117}
118
119impl Notification {
120 #[expect(clippy::too_many_arguments)]
121 pub(crate) fn new(
122 cx: &mut JSContext,
123 global: &GlobalScope,
124 title: DOMString,
125 options: &NotificationOptions,
126 origin: ImmutableOrigin,
127 base_url: ServoUrl,
128 fallback_timestamp: u64,
129 proto: Option<HandleObject>,
130 ) -> DomRoot<Self> {
131 let notification = reflect_dom_object_with_proto(
132 cx,
133 Box::new(Notification::new_inherited(
134 global,
135 title,
136 options,
137 origin,
138 base_url,
139 fallback_timestamp,
140 )),
141 global,
142 proto,
143 );
144
145 notification.data.set(options.data.get());
146
147 notification
148 }
149
150 fn new_inherited(
152 global: &GlobalScope,
153 title: DOMString,
154 options: &NotificationOptions,
155 origin: ImmutableOrigin,
156 base_url: ServoUrl,
157 fallback_timestamp: u64,
158 ) -> Self {
159 let dir = options.dir;
163 let lang = options.lang.clone();
164 let body = options.body.clone();
165 let tag = options.tag.clone();
166
167 let image = options.image.as_ref().and_then(|image_url| {
170 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
171 .map(|url| USVString::from(url.to_string()))
172 .ok()
173 });
174 let icon = options.icon.as_ref().and_then(|icon_url| {
177 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
178 .map(|url| USVString::from(url.to_string()))
179 .ok()
180 });
181 let badge = options.badge.as_ref().and_then(|badge_url| {
184 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
185 .map(|url| USVString::from(url.to_string()))
186 .ok()
187 });
188 let vibration_pattern = match &options.vibrate {
191 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
192 None => Vec::new(),
193 };
194 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
197 let renotify = options.renotify;
198 let silent = options.silent;
199 let require_interaction = options.requireInteraction;
200
201 let mut actions: Vec<Action> = Vec::new();
204 let max_actions = Notification::MaxActions(global);
205 for action in options.actions.iter().take(max_actions as usize) {
206 actions.push(Action {
207 id: Uuid::new_v4().simple().to_string(),
208 name: action.action.clone(),
209 title: action.title.clone(),
210 icon_url: action.icon.as_ref().and_then(|icon_url| {
213 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
214 .map(|url| USVString::from(url.to_string()))
215 .ok()
216 }),
217 icon_resource: DomRefCell::new(None),
218 });
219 }
220
221 Self {
222 eventtarget: EventTarget::new_inherited(),
223 serviceworker_registration: None,
225 title,
226 body,
227 data: Heap::default(),
228 dir,
229 image,
230 icon,
231 badge,
232 lang,
233 silent,
234 origin,
235 vibration_pattern,
236 timestamp,
237 renotify,
238 tag,
239 require_interaction,
240 actions,
241 pending_request_ids: DomRefCell::new(Default::default()),
242 image_resource: DomRefCell::new(None),
243 icon_resource: DomRefCell::new(None),
244 badge_resource: DomRefCell::new(None),
245 }
246 }
247
248 fn show(&self) {
250 let shown = false;
252
253 if !shown {
267 self.global()
270 .send_to_embedder(EmbedderMsg::ShowNotification(
271 self.global().webview_id(),
272 self.to_embedder_notification(),
273 ));
274 }
275
276 if self.serviceworker_registration.is_none() {
284 self.global()
285 .task_manager()
286 .dom_manipulation_task_source()
287 .queue_simple_event(self.upcast(), atom!("show"));
288 }
289 }
290
291 fn to_embedder_notification(&self) -> EmbedderNotification {
293 let icon_resource = self
294 .icon_resource
295 .borrow()
296 .as_ref()
297 .map(|image| image.to_shared());
298 EmbedderNotification {
299 title: self.title.to_string(),
300 body: self.body.to_string(),
301 tag: self.tag.to_string(),
302 language: self.lang.to_string(),
303 require_interaction: self.require_interaction,
304 silent: self.silent,
305 icon_url: self
306 .icon
307 .as_ref()
308 .and_then(|icon| ServoUrl::parse(icon).ok()),
309 badge_url: self
310 .badge
311 .as_ref()
312 .and_then(|badge| ServoUrl::parse(badge).ok()),
313 image_url: self
314 .image
315 .as_ref()
316 .and_then(|image| ServoUrl::parse(image).ok()),
317 actions: self
318 .actions
319 .iter()
320 .map(|action| EmbedderNotificationAction {
321 name: action.name.to_string(),
322 title: action.title.to_string(),
323 icon_url: action
324 .icon_url
325 .as_ref()
326 .and_then(|icon| ServoUrl::parse(icon).ok()),
327 icon_resource: icon_resource.clone(),
328 })
329 .collect(),
330 icon_resource,
331 badge_resource: self
332 .badge_resource
333 .borrow()
334 .as_ref()
335 .map(|image| image.to_shared()),
336 image_resource: self
337 .image_resource
338 .borrow()
339 .as_ref()
340 .map(|image| image.to_shared()),
341 }
342 }
343}
344
345impl NotificationMethods<crate::DomTypeHolder> for Notification {
346 fn Constructor(
348 cx: &mut JSContext,
349 global: &GlobalScope,
350 proto: Option<HandleObject>,
351 title: DOMString,
352 options: &NotificationOptions,
353 ) -> Fallible<DomRoot<Notification>> {
354 if global.is::<ServiceWorkerGlobalScope>() {
356 return Err(Error::Type(
357 c"Notification constructor cannot be used in service worker.".to_owned(),
358 ));
359 }
360
361 if !options.actions.is_empty() {
363 return Err(Error::Type(
364 c"Actions are only supported for persistent notifications.".to_owned(),
365 ));
366 }
367
368 let notification =
370 create_notification_with_settings_object(cx, global, title, options, proto)?;
371
372 let permission_state = get_notifications_permission_state(global);
376 if permission_state != NotificationPermission::Granted {
377 global
378 .task_manager()
379 .dom_manipulation_task_source()
380 .queue_simple_event(notification.upcast(), atom!("error"));
381 } else {
383 notification.fetch_resources_and_show_when_ready();
387 }
388
389 Ok(notification)
390 }
391
392 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
394 Ok(get_notifications_permission_state(global))
395 }
396
397 fn RequestPermission(
399 cx: &mut JSContext,
400 global: &GlobalScope,
401 permission_callback: Option<RootedCallback<NotificationPermissionCallback>>,
402 ) -> RootedPromise {
403 let promise = Promise::new(cx, global);
405
406 let notification_permission = request_notification_permission(cx, global);
409
410 let trusted_promise = TrustedPromise::from(&promise);
412 let uuid = Uuid::new_v4().simple().to_string();
413 let uuid_ = uuid.clone();
414
415 if let Some(callback) = permission_callback {
416 global.add_notification_permission_request_callback(uuid, callback);
417 }
418
419 global.task_manager().dom_manipulation_task_source().queue(
420 task!(request_permission: move |cx| {
421 let promise = trusted_promise.root(cx);
422 let global = promise.global();
423
424 if let Some(callback) = global
427 .remove_notification_permission_request_callback(uuid_)
428 .as_deref()
429 {
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: &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: &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(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 request_id: RequestId,
786 payload: Bytes,
787 ) {
788 if self.status.is_ok() {
789 self.image_cache.notify_pending_response(
790 self.pending_image_id,
791 FetchResponseMsg::ProcessResponseChunk(request_id, payload),
792 );
793 }
794 }
795
796 fn process_response_eof(
797 self,
798 cx: &mut JSContext,
799 request_id: RequestId,
800 response: Result<(), NetworkError>,
801 timing: ResourceFetchTiming,
802 ) {
803 self.image_cache.notify_pending_response(
804 self.pending_image_id,
805 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
806 );
807 network_listener::submit_timing(cx, &self, &response, &timing);
808 }
809
810 fn process_csp_violations(
811 &mut self,
812 cx: &mut js::context::JSContext,
813 _request_id: RequestId,
814 violations: Vec<Violation>,
815 ) {
816 let global = &self.resource_timing_global();
817 global.report_csp_violations(cx, violations, None, None);
818 }
819
820 fn process_content_length(&mut self, request_id: RequestId, size: usize) {
821 self.image_cache.notify_pending_response(
822 self.pending_image_id,
823 FetchResponseMsg::ProcessContentLength(request_id, size),
824 );
825 }
826}
827
828impl ResourceTimingListener for ResourceFetchListener {
829 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
830 (InitiatorType::Other, self.url.clone())
831 }
832
833 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
834 self.notification.root().global()
835 }
836}
837
838impl Notification {
839 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
840 let global = &self.global();
841 create_a_potential_cors_request(
842 None,
843 url.clone(),
844 Destination::Image,
845 None, None,
847 global.get_referrer(),
848 )
849 .with_global_scope(global)
850 }
851
852 fn fetch_resources_and_show_when_ready(&self) {
854 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
855 if let Some(image_url) = &self.image &&
856 let Ok(url) = ServoUrl::parse(image_url)
857 {
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 if let Some(icon_url) = &self.icon &&
863 let Ok(url) = ServoUrl::parse(icon_url)
864 {
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 if let Some(badge_url) = &self.badge &&
870 let Ok(url) = ServoUrl::parse(badge_url)
871 {
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 for action in self.actions.iter() {
877 if let Some(icon_url) = &action.icon_url &&
878 let Ok(url) = ServoUrl::parse(icon_url)
879 {
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 for (request, resource_type) in pending_requests {
887 self.fetch_and_show_when_ready(request, resource_type);
888 }
889 }
890
891 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
892 let global: &GlobalScope = &self.global();
893 let request_id = request.id;
894
895 let cache_result = global.image_cache().get_cached_image_status(
896 request.url.url(),
897 global.origin().immutable().clone(),
898 None, );
900 match cache_result {
901 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
902 image, ..
903 }) => {
904 let image = image.as_raster_image();
905 if image.is_none() {
906 warn!("Vector images are not supported in notifications yet");
907 };
908 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
909 },
910 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
911 _,
912 pending_image_id,
913 )) => {
914 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
915 },
916 ImageCacheResult::Pending(pending_image_id) => {
917 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
918 },
919 ImageCacheResult::ReadyForRequest(pending_image_id) => {
920 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
921 self.fetch(pending_image_id, request, global);
922 },
923 ImageCacheResult::FailedToLoadOrDecode => {
924 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
925 },
926 };
927 }
928
929 fn register_image_cache_callback(
930 &self,
931 request_id: RequestId,
932 pending_image_id: PendingImageId,
933 resource_type: ResourceType,
934 ) {
935 let global: &GlobalScope = &self.global();
936 let trusted_this = Trusted::new(self);
937 let task_source = global.task_manager().networking_task_source().to_sendable();
938
939 let callback = Box::new(move |response| {
940 let trusted_this = trusted_this.clone();
941 let resource_type = resource_type.clone();
942 task_source.queue(task!(handle_response: move || {
943 let this = trusted_this.root();
944 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
945 warn!("Received unexpected message from image cache: {response:?}");
946 return;
947 };
948 this.handle_image_cache_response(request_id, status.response, resource_type);
949 }));
950 });
951
952 global.image_cache().add_listener(ImageLoadListener::new(
953 callback,
954 global.pipeline_id(),
955 pending_image_id,
956 ));
957 }
958
959 fn handle_image_cache_response(
960 &self,
961 request_id: RequestId,
962 response: ImageResponse,
963 resource_type: ResourceType,
964 ) {
965 match response {
966 ImageResponse::Loaded(image, _) => {
967 let image = image.as_raster_image();
968 if image.is_none() {
969 warn!("Vector images are not yet supported in notification attribute");
970 };
971 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
972 },
973 ImageResponse::FailedToLoadOrDecode => {
974 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
975 },
976 _ => (),
977 };
978 }
979
980 fn set_resource_and_show_when_ready(
981 &self,
982 request_id: RequestId,
983 resource_type: &ResourceType,
984 image: Option<Arc<RasterImage>>,
985 ) {
986 match resource_type {
987 ResourceType::Image => {
988 *self.image_resource.borrow_mut() = image;
989 },
990 ResourceType::Icon => {
991 *self.icon_resource.borrow_mut() = image;
992 },
993 ResourceType::Badge => {
994 *self.badge_resource.borrow_mut() = image;
995 },
996 ResourceType::ActionIcon(id) => {
997 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
998 *action.icon_resource.borrow_mut() = image;
999 }
1000 },
1001 }
1002
1003 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1004 pending_requests_id.remove(&request_id);
1005
1006 if pending_requests_id.is_empty() {
1009 self.show();
1010 }
1011 }
1012
1013 fn fetch(
1014 &self,
1015 pending_image_id: PendingImageId,
1016 request: RequestBuilder,
1017 global: &GlobalScope,
1018 ) {
1019 let context = ResourceFetchListener {
1020 pending_image_id,
1021 image_cache: global.image_cache(),
1022 notification: Trusted::new(self),
1023 url: request.url.url(),
1024 status: Ok(()),
1025 };
1026
1027 global.fetch(
1028 request,
1029 context,
1030 global.task_manager().networking_task_source().into(),
1031 );
1032 }
1033}