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_and_cx;
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};
61use crate::script_runtime::CanGc;
62#[dom_struct]
67pub(crate) struct Notification {
68 eventtarget: EventTarget,
69 serviceworker_registration: Option<Dom<ServiceWorkerRegistration>>,
71 title: DOMString,
73 body: DOMString,
75 #[ignore_malloc_size_of = "mozjs"]
77 data: Heap<JSVal>,
78 dir: NotificationDirection,
80 image: Option<USVString>,
82 icon: Option<USVString>,
84 badge: Option<USVString>,
86 lang: DOMString,
88 silent: Option<bool>,
90 tag: DOMString,
92 #[no_trace] origin: ImmutableOrigin,
95 vibration_pattern: Vec<u32>,
97 timestamp: u64,
99 renotify: bool,
101 require_interaction: bool,
103 actions: Vec<Action>,
105 #[no_trace] pending_request_ids: DomRefCell<FxHashSet<RequestId>>,
108 #[ignore_malloc_size_of = "RasterImage"]
110 #[no_trace]
111 image_resource: DomRefCell<Option<Arc<RasterImage>>>,
112 #[ignore_malloc_size_of = "RasterImage"]
114 #[no_trace]
115 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
116 #[ignore_malloc_size_of = "RasterImage"]
118 #[no_trace]
119 badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
120}
121
122impl Notification {
123 #[expect(clippy::too_many_arguments)]
124 pub(crate) fn new(
125 cx: &mut JSContext,
126 global: &GlobalScope,
127 title: DOMString,
128 options: RootedTraceableBox<NotificationOptions>,
129 origin: ImmutableOrigin,
130 base_url: ServoUrl,
131 fallback_timestamp: u64,
132 proto: Option<HandleObject>,
133 ) -> DomRoot<Self> {
134 let notification = reflect_dom_object_with_proto_and_cx(
135 Box::new(Notification::new_inherited(
136 global,
137 title,
138 &options,
139 origin,
140 base_url,
141 fallback_timestamp,
142 )),
143 global,
144 proto,
145 cx,
146 );
147
148 notification.data.set(options.data.get());
149
150 notification
151 }
152
153 fn new_inherited(
155 global: &GlobalScope,
156 title: DOMString,
157 options: &RootedTraceableBox<NotificationOptions>,
158 origin: ImmutableOrigin,
159 base_url: ServoUrl,
160 fallback_timestamp: u64,
161 ) -> Self {
162 let dir = options.dir;
166 let lang = options.lang.clone();
167 let body = options.body.clone();
168 let tag = options.tag.clone();
169
170 let image = options.image.as_ref().and_then(|image_url| {
173 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
174 .map(|url| USVString::from(url.to_string()))
175 .ok()
176 });
177 let icon = options.icon.as_ref().and_then(|icon_url| {
180 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
181 .map(|url| USVString::from(url.to_string()))
182 .ok()
183 });
184 let badge = options.badge.as_ref().and_then(|badge_url| {
187 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
188 .map(|url| USVString::from(url.to_string()))
189 .ok()
190 });
191 let vibration_pattern = match &options.vibrate {
194 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
195 None => Vec::new(),
196 };
197 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
200 let renotify = options.renotify;
201 let silent = options.silent;
202 let require_interaction = options.requireInteraction;
203
204 let mut actions: Vec<Action> = Vec::new();
207 let max_actions = Notification::MaxActions(global);
208 for action in options.actions.iter().take(max_actions as usize) {
209 actions.push(Action {
210 id: Uuid::new_v4().simple().to_string(),
211 name: action.action.clone(),
212 title: action.title.clone(),
213 icon_url: action.icon.as_ref().and_then(|icon_url| {
216 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
217 .map(|url| USVString::from(url.to_string()))
218 .ok()
219 }),
220 icon_resource: DomRefCell::new(None),
221 });
222 }
223
224 Self {
225 eventtarget: EventTarget::new_inherited(),
226 serviceworker_registration: None,
228 title,
229 body,
230 data: Heap::default(),
231 dir,
232 image,
233 icon,
234 badge,
235 lang,
236 silent,
237 origin,
238 vibration_pattern,
239 timestamp,
240 renotify,
241 tag,
242 require_interaction,
243 actions,
244 pending_request_ids: DomRefCell::new(Default::default()),
245 image_resource: DomRefCell::new(None),
246 icon_resource: DomRefCell::new(None),
247 badge_resource: DomRefCell::new(None),
248 }
249 }
250
251 fn show(&self) {
253 let shown = false;
255
256 if !shown {
270 self.global()
273 .send_to_embedder(EmbedderMsg::ShowNotification(
274 self.global().webview_id(),
275 self.to_embedder_notification(),
276 ));
277 }
278
279 if self.serviceworker_registration.is_none() {
287 self.global()
288 .task_manager()
289 .dom_manipulation_task_source()
290 .queue_simple_event(self.upcast(), atom!("show"));
291 }
292 }
293
294 fn to_embedder_notification(&self) -> EmbedderNotification {
296 let icon_resource = self
297 .icon_resource
298 .borrow()
299 .as_ref()
300 .map(|image| image.to_shared());
301 EmbedderNotification {
302 title: self.title.to_string(),
303 body: self.body.to_string(),
304 tag: self.tag.to_string(),
305 language: self.lang.to_string(),
306 require_interaction: self.require_interaction,
307 silent: self.silent,
308 icon_url: self
309 .icon
310 .as_ref()
311 .and_then(|icon| ServoUrl::parse(icon).ok()),
312 badge_url: self
313 .badge
314 .as_ref()
315 .and_then(|badge| ServoUrl::parse(badge).ok()),
316 image_url: self
317 .image
318 .as_ref()
319 .and_then(|image| ServoUrl::parse(image).ok()),
320 actions: self
321 .actions
322 .iter()
323 .map(|action| EmbedderNotificationAction {
324 name: action.name.to_string(),
325 title: action.title.to_string(),
326 icon_url: action
327 .icon_url
328 .as_ref()
329 .and_then(|icon| ServoUrl::parse(icon).ok()),
330 icon_resource: icon_resource.clone(),
331 })
332 .collect(),
333 icon_resource,
334 badge_resource: self
335 .badge_resource
336 .borrow()
337 .as_ref()
338 .map(|image| image.to_shared()),
339 image_resource: self
340 .image_resource
341 .borrow()
342 .as_ref()
343 .map(|image| image.to_shared()),
344 }
345 }
346}
347
348impl NotificationMethods<crate::DomTypeHolder> for Notification {
349 fn Constructor(
351 cx: &mut JSContext,
352 global: &GlobalScope,
353 proto: Option<HandleObject>,
354 title: DOMString,
355 options: RootedTraceableBox<NotificationOptions>,
356 ) -> Fallible<DomRoot<Notification>> {
357 if global.is::<ServiceWorkerGlobalScope>() {
359 return Err(Error::Type(
360 c"Notification constructor cannot be used in service worker.".to_owned(),
361 ));
362 }
363
364 if !options.actions.is_empty() {
366 return Err(Error::Type(
367 c"Actions are only supported for persistent notifications.".to_owned(),
368 ));
369 }
370
371 let notification =
373 create_notification_with_settings_object(cx, global, title, options, proto)?;
374
375 let permission_state = get_notifications_permission_state(global);
379 if permission_state != NotificationPermission::Granted {
380 global
381 .task_manager()
382 .dom_manipulation_task_source()
383 .queue_simple_event(notification.upcast(), atom!("error"));
384 } else {
386 notification.fetch_resources_and_show_when_ready();
390 }
391
392 Ok(notification)
393 }
394
395 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
397 Ok(get_notifications_permission_state(global))
398 }
399
400 fn RequestPermission(
402 cx: &mut JSContext,
403 global: &GlobalScope,
404 permission_callback: Option<Rc<NotificationPermissionCallback>>,
405 ) -> Rc<Promise> {
406 let promise = Promise::new(cx, global);
408
409 let notification_permission = request_notification_permission(cx, global);
412
413 let trusted_promise = TrustedPromise::new(promise.clone());
415 let uuid = Uuid::new_v4().simple().to_string();
416 let uuid_ = uuid.clone();
417
418 if let Some(callback) = permission_callback {
419 global.add_notification_permission_request_callback(uuid, callback);
420 }
421
422 global.task_manager().dom_manipulation_task_source().queue(
423 task!(request_permission: move |cx| {
424 let promise = trusted_promise.root();
425 let global = promise.global();
426
427 if let Some(callback) = global.remove_notification_permission_request_callback(uuid_) {
430 let _ = callback.Call__(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: RootedTraceableBox<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: RootedTraceableBox<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(global, &descriptor, CanGc::from_cx(cx));
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(|meta| match meta {
760 FetchMetadata::Unfiltered(m) => m,
761 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
762 });
763
764 let status = metadata
765 .as_ref()
766 .map(|m| m.status.clone())
767 .unwrap_or_else(HttpStatus::new_error);
768
769 self.status = {
770 if status.is_success() {
771 Ok(())
772 } else if status.is_error() {
773 Err(NetworkError::ResourceLoadError(
774 "No http status code received".to_owned(),
775 ))
776 } else {
777 Err(NetworkError::ResourceLoadError(format!(
778 "HTTP error code {}",
779 status.code()
780 )))
781 }
782 };
783 }
784
785 fn process_response_chunk(
786 &mut self,
787 _: &mut js::context::JSContext,
788 request_id: RequestId,
789 payload: Vec<u8>,
790 ) {
791 if self.status.is_ok() {
792 self.image_cache.notify_pending_response(
793 self.pending_image_id,
794 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
795 );
796 }
797 }
798
799 fn process_response_eof(
800 self,
801 cx: &mut JSContext,
802 request_id: RequestId,
803 response: Result<(), NetworkError>,
804 timing: ResourceFetchTiming,
805 ) {
806 self.image_cache.notify_pending_response(
807 self.pending_image_id,
808 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
809 );
810 network_listener::submit_timing(cx, &self, &response, &timing);
811 }
812
813 fn process_csp_violations(
814 &mut self,
815 cx: &mut js::context::JSContext,
816 _request_id: RequestId,
817 violations: Vec<Violation>,
818 ) {
819 let global = &self.resource_timing_global();
820 global.report_csp_violations(cx, violations, None, None);
821 }
822}
823
824impl ResourceTimingListener for ResourceFetchListener {
825 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
826 (InitiatorType::Other, self.url.clone())
827 }
828
829 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
830 self.notification.root().global()
831 }
832}
833
834impl Notification {
835 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
836 let global = &self.global();
837 create_a_potential_cors_request(
838 None,
839 url.clone(),
840 Destination::Image,
841 None, None,
843 global.get_referrer(),
844 )
845 .with_global_scope(global)
846 }
847
848 fn fetch_resources_and_show_when_ready(&self) {
850 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
851 if let Some(image_url) = &self.image &&
852 let Ok(url) = ServoUrl::parse(image_url)
853 {
854 let request = self.build_resource_request(&url);
855 self.pending_request_ids.borrow_mut().insert(request.id);
856 pending_requests.push((request, ResourceType::Image));
857 }
858 if let Some(icon_url) = &self.icon &&
859 let Ok(url) = ServoUrl::parse(icon_url)
860 {
861 let request = self.build_resource_request(&url);
862 self.pending_request_ids.borrow_mut().insert(request.id);
863 pending_requests.push((request, ResourceType::Icon));
864 }
865 if let Some(badge_url) = &self.badge &&
866 let Ok(url) = ServoUrl::parse(badge_url)
867 {
868 let request = self.build_resource_request(&url);
869 self.pending_request_ids.borrow_mut().insert(request.id);
870 pending_requests.push((request, ResourceType::Badge));
871 }
872 for action in self.actions.iter() {
873 if let Some(icon_url) = &action.icon_url &&
874 let Ok(url) = ServoUrl::parse(icon_url)
875 {
876 let request = self.build_resource_request(&url);
877 self.pending_request_ids.borrow_mut().insert(request.id);
878 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
879 }
880 }
881
882 for (request, resource_type) in pending_requests {
883 self.fetch_and_show_when_ready(request, resource_type);
884 }
885 }
886
887 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
888 let global: &GlobalScope = &self.global();
889 let request_id = request.id;
890
891 let cache_result = global.image_cache().get_cached_image_status(
892 request.url.url(),
893 global.origin().immutable().clone(),
894 None, );
896 match cache_result {
897 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
898 image, ..
899 }) => {
900 let image = image.as_raster_image();
901 if image.is_none() {
902 warn!("Vector images are not supported in notifications yet");
903 };
904 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
905 },
906 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
907 _,
908 pending_image_id,
909 )) => {
910 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
911 },
912 ImageCacheResult::Pending(pending_image_id) => {
913 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
914 },
915 ImageCacheResult::ReadyForRequest(pending_image_id) => {
916 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
917 self.fetch(pending_image_id, request, global);
918 },
919 ImageCacheResult::FailedToLoadOrDecode => {
920 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
921 },
922 };
923 }
924
925 fn register_image_cache_callback(
926 &self,
927 request_id: RequestId,
928 pending_image_id: PendingImageId,
929 resource_type: ResourceType,
930 ) {
931 let global: &GlobalScope = &self.global();
932 let trusted_this = Trusted::new(self);
933 let task_source = global.task_manager().networking_task_source().to_sendable();
934
935 let callback = Box::new(move |response| {
936 let trusted_this = trusted_this.clone();
937 let resource_type = resource_type.clone();
938 task_source.queue(task!(handle_response: move || {
939 let this = trusted_this.root();
940 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
941 warn!("Received unexpected message from image cache: {response:?}");
942 return;
943 };
944 this.handle_image_cache_response(request_id, status.response, resource_type);
945 }));
946 });
947
948 global.image_cache().add_listener(ImageLoadListener::new(
949 callback,
950 global.pipeline_id(),
951 pending_image_id,
952 ));
953 }
954
955 fn handle_image_cache_response(
956 &self,
957 request_id: RequestId,
958 response: ImageResponse,
959 resource_type: ResourceType,
960 ) {
961 match response {
962 ImageResponse::Loaded(image, _) => {
963 let image = image.as_raster_image();
964 if image.is_none() {
965 warn!("Vector images are not yet supported in notification attribute");
966 };
967 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
968 },
969 ImageResponse::FailedToLoadOrDecode => {
970 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
971 },
972 _ => (),
973 };
974 }
975
976 fn set_resource_and_show_when_ready(
977 &self,
978 request_id: RequestId,
979 resource_type: &ResourceType,
980 image: Option<Arc<RasterImage>>,
981 ) {
982 match resource_type {
983 ResourceType::Image => {
984 *self.image_resource.borrow_mut() = image;
985 },
986 ResourceType::Icon => {
987 *self.icon_resource.borrow_mut() = image;
988 },
989 ResourceType::Badge => {
990 *self.badge_resource.borrow_mut() = image;
991 },
992 ResourceType::ActionIcon(id) => {
993 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
994 *action.icon_resource.borrow_mut() = image;
995 }
996 },
997 }
998
999 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1000 pending_requests_id.remove(&request_id);
1001
1002 if pending_requests_id.is_empty() {
1005 self.show();
1006 }
1007 }
1008
1009 fn fetch(
1010 &self,
1011 pending_image_id: PendingImageId,
1012 request: RequestBuilder,
1013 global: &GlobalScope,
1014 ) {
1015 let context = ResourceFetchListener {
1016 pending_image_id,
1017 image_cache: global.image_cache(),
1018 notification: Trusted::new(self),
1019 url: request.url.url(),
1020 status: Ok(()),
1021 };
1022
1023 global.fetch(
1024 request,
1025 context,
1026 global.task_manager().networking_task_source().into(),
1027 );
1028 }
1029}