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 script_bindings::cell::DomRefCell;
28use script_bindings::reflector::reflect_dom_object_with_proto;
29use servo_url::{ImmutableOrigin, ServoUrl};
30use uuid::Uuid;
31
32use super::bindings::refcounted::{Trusted, TrustedPromise};
33use super::bindings::reflector::DomGlobal;
34use super::performanceresourcetiming::InitiatorType;
35use super::permissionstatus::PermissionStatus;
36use crate::dom::bindings::callback::ExceptionHandling;
37use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
38 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
39 NotificationPermission, NotificationPermissionCallback,
40};
41use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
42use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
43 PermissionDescriptor, PermissionName, PermissionState,
44};
45use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::Castable;
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};
61#[dom_struct]
66pub(crate) struct Notification {
67 eventtarget: EventTarget,
68 serviceworker_registration: Option<Dom<ServiceWorkerRegistration>>,
70 title: DOMString,
72 body: DOMString,
74 #[ignore_malloc_size_of = "mozjs"]
76 data: Heap<JSVal>,
77 dir: NotificationDirection,
79 image: Option<USVString>,
81 icon: Option<USVString>,
83 badge: Option<USVString>,
85 lang: DOMString,
87 silent: Option<bool>,
89 tag: DOMString,
91 #[no_trace] origin: ImmutableOrigin,
94 vibration_pattern: Vec<u32>,
96 timestamp: u64,
98 renotify: bool,
100 require_interaction: bool,
102 actions: Vec<Action>,
104 #[no_trace] pending_request_ids: DomRefCell<FxHashSet<RequestId>>,
107 #[ignore_malloc_size_of = "RasterImage"]
109 #[no_trace]
110 image_resource: DomRefCell<Option<Arc<RasterImage>>>,
111 #[ignore_malloc_size_of = "RasterImage"]
113 #[no_trace]
114 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
115 #[ignore_malloc_size_of = "RasterImage"]
117 #[no_trace]
118 badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
119}
120
121impl Notification {
122 #[expect(clippy::too_many_arguments)]
123 pub(crate) fn new(
124 cx: &mut JSContext,
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 ) -> DomRoot<Self> {
133 let notification = reflect_dom_object_with_proto(
134 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 );
146
147 notification.data.set(options.data.get());
148
149 notification
150 }
151
152 fn new_inherited(
154 global: &GlobalScope,
155 title: DOMString,
156 options: &RootedTraceableBox<NotificationOptions>,
157 origin: ImmutableOrigin,
158 base_url: ServoUrl,
159 fallback_timestamp: u64,
160 ) -> Self {
161 let dir = options.dir;
165 let lang = options.lang.clone();
166 let body = options.body.clone();
167 let tag = options.tag.clone();
168
169 let image = options.image.as_ref().and_then(|image_url| {
172 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
173 .map(|url| USVString::from(url.to_string()))
174 .ok()
175 });
176 let icon = options.icon.as_ref().and_then(|icon_url| {
179 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
180 .map(|url| USVString::from(url.to_string()))
181 .ok()
182 });
183 let badge = options.badge.as_ref().and_then(|badge_url| {
186 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
187 .map(|url| USVString::from(url.to_string()))
188 .ok()
189 });
190 let vibration_pattern = match &options.vibrate {
193 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
194 None => Vec::new(),
195 };
196 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
199 let renotify = options.renotify;
200 let silent = options.silent;
201 let require_interaction = options.requireInteraction;
202
203 let mut actions: Vec<Action> = Vec::new();
206 let max_actions = Notification::MaxActions(global);
207 for action in options.actions.iter().take(max_actions as usize) {
208 actions.push(Action {
209 id: Uuid::new_v4().simple().to_string(),
210 name: action.action.clone(),
211 title: action.title.clone(),
212 icon_url: action.icon.as_ref().and_then(|icon_url| {
215 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
216 .map(|url| USVString::from(url.to_string()))
217 .ok()
218 }),
219 icon_resource: DomRefCell::new(None),
220 });
221 }
222
223 Self {
224 eventtarget: EventTarget::new_inherited(),
225 serviceworker_registration: None,
227 title,
228 body,
229 data: Heap::default(),
230 dir,
231 image,
232 icon,
233 badge,
234 lang,
235 silent,
236 origin,
237 vibration_pattern,
238 timestamp,
239 renotify,
240 tag,
241 require_interaction,
242 actions,
243 pending_request_ids: DomRefCell::new(Default::default()),
244 image_resource: DomRefCell::new(None),
245 icon_resource: DomRefCell::new(None),
246 badge_resource: DomRefCell::new(None),
247 }
248 }
249
250 fn show(&self) {
252 let shown = false;
254
255 if !shown {
269 self.global()
272 .send_to_embedder(EmbedderMsg::ShowNotification(
273 self.global().webview_id(),
274 self.to_embedder_notification(),
275 ));
276 }
277
278 if self.serviceworker_registration.is_none() {
286 self.global()
287 .task_manager()
288 .dom_manipulation_task_source()
289 .queue_simple_event(self.upcast(), atom!("show"));
290 }
291 }
292
293 fn to_embedder_notification(&self) -> EmbedderNotification {
295 let icon_resource = self
296 .icon_resource
297 .borrow()
298 .as_ref()
299 .map(|image| image.to_shared());
300 EmbedderNotification {
301 title: self.title.to_string(),
302 body: self.body.to_string(),
303 tag: self.tag.to_string(),
304 language: self.lang.to_string(),
305 require_interaction: self.require_interaction,
306 silent: self.silent,
307 icon_url: self
308 .icon
309 .as_ref()
310 .and_then(|icon| ServoUrl::parse(icon).ok()),
311 badge_url: self
312 .badge
313 .as_ref()
314 .and_then(|badge| ServoUrl::parse(badge).ok()),
315 image_url: self
316 .image
317 .as_ref()
318 .and_then(|image| ServoUrl::parse(image).ok()),
319 actions: self
320 .actions
321 .iter()
322 .map(|action| EmbedderNotificationAction {
323 name: action.name.to_string(),
324 title: action.title.to_string(),
325 icon_url: action
326 .icon_url
327 .as_ref()
328 .and_then(|icon| ServoUrl::parse(icon).ok()),
329 icon_resource: icon_resource.clone(),
330 })
331 .collect(),
332 icon_resource,
333 badge_resource: self
334 .badge_resource
335 .borrow()
336 .as_ref()
337 .map(|image| image.to_shared()),
338 image_resource: self
339 .image_resource
340 .borrow()
341 .as_ref()
342 .map(|image| image.to_shared()),
343 }
344 }
345}
346
347impl NotificationMethods<crate::DomTypeHolder> for Notification {
348 fn Constructor(
350 cx: &mut JSContext,
351 global: &GlobalScope,
352 proto: Option<HandleObject>,
353 title: DOMString,
354 options: RootedTraceableBox<NotificationOptions>,
355 ) -> Fallible<DomRoot<Notification>> {
356 if global.is::<ServiceWorkerGlobalScope>() {
358 return Err(Error::Type(
359 c"Notification constructor cannot be used in service worker.".to_owned(),
360 ));
361 }
362
363 if !options.actions.is_empty() {
365 return Err(Error::Type(
366 c"Actions are only supported for persistent notifications.".to_owned(),
367 ));
368 }
369
370 let notification =
372 create_notification_with_settings_object(cx, global, title, options, proto)?;
373
374 let permission_state = get_notifications_permission_state(global);
378 if permission_state != NotificationPermission::Granted {
379 global
380 .task_manager()
381 .dom_manipulation_task_source()
382 .queue_simple_event(notification.upcast(), atom!("error"));
383 } else {
385 notification.fetch_resources_and_show_when_ready();
389 }
390
391 Ok(notification)
392 }
393
394 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
396 Ok(get_notifications_permission_state(global))
397 }
398
399 fn RequestPermission(
401 cx: &mut JSContext,
402 global: &GlobalScope,
403 permission_callback: Option<Rc<NotificationPermissionCallback>>,
404 ) -> Rc<Promise> {
405 let promise = Promise::new(cx, global);
407
408 let notification_permission = request_notification_permission(cx, global);
411
412 let trusted_promise = TrustedPromise::new(promise.clone());
414 let uuid = Uuid::new_v4().simple().to_string();
415 let uuid_ = uuid.clone();
416
417 if let Some(callback) = permission_callback {
418 global.add_notification_permission_request_callback(uuid, callback);
419 }
420
421 global.task_manager().dom_manipulation_task_source().queue(
422 task!(request_permission: move |cx| {
423 let promise = trusted_promise.root();
424 let global = promise.global();
425
426 if let Some(callback) = global.remove_notification_permission_request_callback(uuid_) {
429 let _ = callback.Call__(cx, notification_permission, ExceptionHandling::Report);
430 }
431
432 promise.resolve_native(cx, ¬ification_permission);
434 }),
435 );
436
437 promise
438 }
439
440 event_handler!(click, GetOnclick, SetOnclick);
442 event_handler!(show, GetOnshow, SetOnshow);
444 event_handler!(error, GetOnerror, SetOnerror);
446 event_handler!(close, GetOnclose, SetOnclose);
448
449 fn MaxActions(_global: &GlobalScope) -> u32 {
451 2
453 }
454
455 fn Title(&self) -> DOMString {
457 self.title.clone()
458 }
459
460 fn Dir(&self) -> NotificationDirection {
462 self.dir
463 }
464
465 fn Lang(&self) -> DOMString {
467 self.lang.clone()
468 }
469
470 fn Body(&self) -> DOMString {
472 self.body.clone()
473 }
474
475 fn Tag(&self) -> DOMString {
477 self.tag.clone()
478 }
479
480 fn Image(&self) -> USVString {
482 self.image.clone().unwrap_or_default()
485 }
486
487 fn Icon(&self) -> USVString {
489 self.icon.clone().unwrap_or_default()
492 }
493
494 fn Badge(&self) -> USVString {
496 self.badge.clone().unwrap_or_default()
499 }
500
501 fn Renotify(&self) -> bool {
503 self.renotify
504 }
505
506 fn GetSilent(&self) -> Option<bool> {
508 self.silent
509 }
510
511 fn RequireInteraction(&self) -> bool {
513 self.require_interaction
514 }
515
516 fn Data(&self, mut retval: MutableHandleValue) {
518 retval.set(self.data.get());
519 }
520
521 fn Actions(&self, cx: &mut JSContext, retval: MutableHandleValue) {
523 let mut frozen_actions: Vec<NotificationAction> = Vec::new();
525
526 for action in self.actions.iter() {
528 let action = NotificationAction {
529 action: action.name.clone(),
530 title: action.title.clone(),
531 icon: action.icon_url.clone(),
534 };
535
536 frozen_actions.push(action);
539 }
540
541 to_frozen_array(cx, frozen_actions.as_slice(), retval);
543 }
544
545 fn Vibrate(&self, cx: &mut JSContext, retval: MutableHandleValue) {
547 to_frozen_array(cx, self.vibration_pattern.as_slice(), retval);
548 }
549
550 fn Timestamp(&self) -> u64 {
552 self.timestamp
553 }
554
555 fn Close(&self) {
557 if self.serviceworker_registration.is_none() {
563 self.global()
564 .task_manager()
565 .dom_manipulation_task_source()
566 .queue_simple_event(self.upcast(), atom!("close"));
567 }
568 }
569}
570
571#[derive(JSTraceable, MallocSizeOf)]
573struct Action {
574 id: String,
575 name: DOMString,
577 title: DOMString,
579 icon_url: Option<USVString>,
581 #[ignore_malloc_size_of = "RasterImage"]
583 #[no_trace]
584 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
585}
586
587fn create_notification_with_settings_object(
589 cx: &mut JSContext,
590 global: &GlobalScope,
591 title: DOMString,
592 options: RootedTraceableBox<NotificationOptions>,
593 proto: Option<HandleObject>,
594) -> Fallible<DomRoot<Notification>> {
595 let origin = global.origin().immutable().clone();
597 let base_url = global.api_base_url();
599 let fallback_timestamp = SystemTime::now()
602 .duration_since(UNIX_EPOCH)
603 .unwrap_or_default()
604 .as_millis() as u64;
605 create_notification(
608 cx,
609 global,
610 title,
611 options,
612 origin,
613 base_url,
614 fallback_timestamp,
615 proto,
616 )
617}
618
619#[expect(clippy::too_many_arguments)]
621fn create_notification(
622 cx: &mut JSContext,
623 global: &GlobalScope,
624 title: DOMString,
625 options: RootedTraceableBox<NotificationOptions>,
626 origin: ImmutableOrigin,
627 base_url: ServoUrl,
628 fallback_timestamp: u64,
629 proto: Option<HandleObject>,
630) -> Fallible<DomRoot<Notification>> {
631 if options.silent.is_some() && options.vibrate.is_some() {
633 return Err(Error::Type(
634 c"Can't specify vibration patterns when setting notification to silent.".to_owned(),
635 ));
636 }
637 if options.renotify && options.tag.is_empty() {
639 return Err(Error::Type(
640 c"tag must be set to renotify as an existing notification.".to_owned(),
641 ));
642 }
643
644 Ok(Notification::new(
645 cx,
646 global,
647 title,
648 options,
649 origin,
650 base_url,
651 fallback_timestamp,
652 proto,
653 ))
654}
655
656fn validate_and_normalize_vibration_pattern(
658 pattern: &UnsignedLongOrUnsignedLongSequence,
659) -> Vec<u32> {
660 let mut pattern: Vec<u32> = match pattern {
662 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
663 vec![*value]
666 },
667 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
668 };
669
670 pattern.truncate(10);
674
675 if pattern.len().is_multiple_of(2) && !pattern.is_empty() {
678 pattern.pop();
679 }
680
681 pattern.iter_mut().for_each(|entry| {
685 *entry = 10000.min(*entry);
686 });
687
688 pattern
690}
691
692fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
694 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
695 match permission_state {
696 PermissionState::Granted => NotificationPermission::Granted,
697 PermissionState::Denied => NotificationPermission::Denied,
698 PermissionState::Prompt => NotificationPermission::Default,
699 }
700}
701
702fn request_notification_permission(
703 cx: &mut JSContext,
704 global: &GlobalScope,
705) -> NotificationPermission {
706 let promise = &Promise::new(cx, global);
707 let descriptor = PermissionDescriptor {
708 name: PermissionName::Notifications,
709 };
710 let status = PermissionStatus::new(cx, global, &descriptor);
711
712 Permissions::permission_request(cx, promise, &descriptor, &status);
714
715 match status.State() {
716 PermissionState::Granted => NotificationPermission::Granted,
717 PermissionState::Denied => NotificationPermission::Denied,
718 PermissionState::Prompt => NotificationPermission::Default,
720 }
721}
722
723#[derive(Clone, Debug, Eq, Hash, PartialEq)]
724enum ResourceType {
725 Image,
726 Icon,
727 Badge,
728 ActionIcon(String), }
730
731struct ResourceFetchListener {
732 pending_image_id: PendingImageId,
734 image_cache: Arc<dyn ImageCache>,
736 notification: Trusted<Notification>,
738 status: Result<(), NetworkError>,
740 url: ServoUrl,
742}
743
744impl FetchResponseListener for ResourceFetchListener {
745 fn process_request_body(&mut self, _: RequestId) {}
746
747 fn process_response(
748 &mut self,
749 _: &mut js::context::JSContext,
750 request_id: RequestId,
751 metadata: Result<FetchMetadata, NetworkError>,
752 ) {
753 self.image_cache.notify_pending_response(
754 self.pending_image_id,
755 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
756 );
757
758 let metadata = metadata.ok().map(|meta| match meta {
759 FetchMetadata::Unfiltered(m) => m,
760 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
761 });
762
763 let status = metadata
764 .as_ref()
765 .map(|m| m.status.clone())
766 .unwrap_or_else(HttpStatus::new_error);
767
768 self.status = {
769 if status.is_success() {
770 Ok(())
771 } else if status.is_error() {
772 Err(NetworkError::ResourceLoadError(
773 "No http status code received".to_owned(),
774 ))
775 } else {
776 Err(NetworkError::ResourceLoadError(format!(
777 "HTTP error code {}",
778 status.code()
779 )))
780 }
781 };
782 }
783
784 fn process_response_chunk(
785 &mut self,
786 _: &mut js::context::JSContext,
787 request_id: RequestId,
788 payload: Vec<u8>,
789 ) {
790 if self.status.is_ok() {
791 self.image_cache.notify_pending_response(
792 self.pending_image_id,
793 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
794 );
795 }
796 }
797
798 fn process_response_eof(
799 self,
800 cx: &mut JSContext,
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, &self, &response, &timing);
810 }
811
812 fn process_csp_violations(
813 &mut self,
814 cx: &mut js::context::JSContext,
815 _request_id: RequestId,
816 violations: Vec<Violation>,
817 ) {
818 let global = &self.resource_timing_global();
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 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
836 self.notification.root().global()
837 }
838}
839
840impl Notification {
841 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
842 let global = &self.global();
843 create_a_potential_cors_request(
844 None,
845 url.clone(),
846 Destination::Image,
847 None, None,
849 global.get_referrer(),
850 )
851 .with_global_scope(global)
852 }
853
854 fn fetch_resources_and_show_when_ready(&self) {
856 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
857 if let Some(image_url) = &self.image &&
858 let Ok(url) = ServoUrl::parse(image_url)
859 {
860 let request = self.build_resource_request(&url);
861 self.pending_request_ids.borrow_mut().insert(request.id);
862 pending_requests.push((request, ResourceType::Image));
863 }
864 if let Some(icon_url) = &self.icon &&
865 let Ok(url) = ServoUrl::parse(icon_url)
866 {
867 let request = self.build_resource_request(&url);
868 self.pending_request_ids.borrow_mut().insert(request.id);
869 pending_requests.push((request, ResourceType::Icon));
870 }
871 if let Some(badge_url) = &self.badge &&
872 let Ok(url) = ServoUrl::parse(badge_url)
873 {
874 let request = self.build_resource_request(&url);
875 self.pending_request_ids.borrow_mut().insert(request.id);
876 pending_requests.push((request, ResourceType::Badge));
877 }
878 for action in self.actions.iter() {
879 if let Some(icon_url) = &action.icon_url &&
880 let Ok(url) = ServoUrl::parse(icon_url)
881 {
882 let request = self.build_resource_request(&url);
883 self.pending_request_ids.borrow_mut().insert(request.id);
884 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
885 }
886 }
887
888 for (request, resource_type) in pending_requests {
889 self.fetch_and_show_when_ready(request, resource_type);
890 }
891 }
892
893 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
894 let global: &GlobalScope = &self.global();
895 let request_id = request.id;
896
897 let cache_result = global.image_cache().get_cached_image_status(
898 request.url.url(),
899 global.origin().immutable().clone(),
900 None, );
902 match cache_result {
903 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
904 image, ..
905 }) => {
906 let image = image.as_raster_image();
907 if image.is_none() {
908 warn!("Vector images are not supported in notifications yet");
909 };
910 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
911 },
912 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
913 _,
914 pending_image_id,
915 )) => {
916 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
917 },
918 ImageCacheResult::Pending(pending_image_id) => {
919 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
920 },
921 ImageCacheResult::ReadyForRequest(pending_image_id) => {
922 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
923 self.fetch(pending_image_id, request, global);
924 },
925 ImageCacheResult::FailedToLoadOrDecode => {
926 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
927 },
928 };
929 }
930
931 fn register_image_cache_callback(
932 &self,
933 request_id: RequestId,
934 pending_image_id: PendingImageId,
935 resource_type: ResourceType,
936 ) {
937 let global: &GlobalScope = &self.global();
938 let trusted_this = Trusted::new(self);
939 let task_source = global.task_manager().networking_task_source().to_sendable();
940
941 let callback = Box::new(move |response| {
942 let trusted_this = trusted_this.clone();
943 let resource_type = resource_type.clone();
944 task_source.queue(task!(handle_response: move || {
945 let this = trusted_this.root();
946 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
947 warn!("Received unexpected message from image cache: {response:?}");
948 return;
949 };
950 this.handle_image_cache_response(request_id, status.response, resource_type);
951 }));
952 });
953
954 global.image_cache().add_listener(ImageLoadListener::new(
955 callback,
956 global.pipeline_id(),
957 pending_image_id,
958 ));
959 }
960
961 fn handle_image_cache_response(
962 &self,
963 request_id: RequestId,
964 response: ImageResponse,
965 resource_type: ResourceType,
966 ) {
967 match response {
968 ImageResponse::Loaded(image, _) => {
969 let image = image.as_raster_image();
970 if image.is_none() {
971 warn!("Vector images are not yet supported in notification attribute");
972 };
973 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
974 },
975 ImageResponse::FailedToLoadOrDecode => {
976 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
977 },
978 _ => (),
979 };
980 }
981
982 fn set_resource_and_show_when_ready(
983 &self,
984 request_id: RequestId,
985 resource_type: &ResourceType,
986 image: Option<Arc<RasterImage>>,
987 ) {
988 match resource_type {
989 ResourceType::Image => {
990 *self.image_resource.borrow_mut() = image;
991 },
992 ResourceType::Icon => {
993 *self.icon_resource.borrow_mut() = image;
994 },
995 ResourceType::Badge => {
996 *self.badge_resource.borrow_mut() = image;
997 },
998 ResourceType::ActionIcon(id) => {
999 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
1000 *action.icon_resource.borrow_mut() = image;
1001 }
1002 },
1003 }
1004
1005 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1006 pending_requests_id.remove(&request_id);
1007
1008 if pending_requests_id.is_empty() {
1011 self.show();
1012 }
1013 }
1014
1015 fn fetch(
1016 &self,
1017 pending_image_id: PendingImageId,
1018 request: RequestBuilder,
1019 global: &GlobalScope,
1020 ) {
1021 let context = ResourceFetchListener {
1022 pending_image_id,
1023 image_cache: global.image_cache(),
1024 notification: Trusted::new(self),
1025 url: request.url.url(),
1026 status: Ok(()),
1027 };
1028
1029 global.fetch(
1030 request,
1031 context,
1032 global.task_manager().networking_task_source().into(),
1033 );
1034 }
1035}