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::jsapi::Heap;
15use js::jsval::JSVal;
16use js::rust::{HandleObject, MutableHandleValue};
17use net_traits::http_status::HttpStatus;
18use net_traits::image_cache::{
19 ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
20 ImageOrMetadataAvailable, ImageResponse, PendingImageId,
21};
22use net_traits::request::{Destination, RequestBuilder, RequestId};
23use net_traits::{FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming};
24use pixels::RasterImage;
25use rustc_hash::FxHashSet;
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::{RequestWithGlobalScope, 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<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 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(Default::default()),
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::ResourceLoadError(
757 "No http status code received".to_owned(),
758 ))
759 } else {
760 Err(NetworkError::ResourceLoadError(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<(), NetworkError>,
781 timing: ResourceFetchTiming,
782 ) {
783 self.image_cache.notify_pending_response(
784 self.pending_image_id,
785 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
786 );
787 network_listener::submit_timing(&self, &response, &timing, CanGc::note());
788 }
789
790 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
791 let global = &self.resource_timing_global();
792 global.report_csp_violations(violations, None, None);
793 }
794}
795
796impl ResourceTimingListener for ResourceFetchListener {
797 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
798 (InitiatorType::Other, self.url.clone())
799 }
800
801 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
802 self.notification.root().global()
803 }
804}
805
806impl Notification {
807 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
808 let global = &self.global();
809 create_a_potential_cors_request(
810 None,
811 url.clone(),
812 Destination::Image,
813 None, None,
815 global.get_referrer(),
816 )
817 .with_global_scope(global)
818 }
819
820 fn fetch_resources_and_show_when_ready(&self) {
822 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
823 if let Some(image_url) = &self.image {
824 if let Ok(url) = ServoUrl::parse(image_url) {
825 let request = self.build_resource_request(&url);
826 self.pending_request_ids.borrow_mut().insert(request.id);
827 pending_requests.push((request, ResourceType::Image));
828 }
829 }
830 if let Some(icon_url) = &self.icon {
831 if let Ok(url) = ServoUrl::parse(icon_url) {
832 let request = self.build_resource_request(&url);
833 self.pending_request_ids.borrow_mut().insert(request.id);
834 pending_requests.push((request, ResourceType::Icon));
835 }
836 }
837 if let Some(badge_url) = &self.badge {
838 if let Ok(url) = ServoUrl::parse(badge_url) {
839 let request = self.build_resource_request(&url);
840 self.pending_request_ids.borrow_mut().insert(request.id);
841 pending_requests.push((request, ResourceType::Badge));
842 }
843 }
844 for action in self.actions.iter() {
845 if let Some(icon_url) = &action.icon_url {
846 if let Ok(url) = ServoUrl::parse(icon_url) {
847 let request = self.build_resource_request(&url);
848 self.pending_request_ids.borrow_mut().insert(request.id);
849 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
850 }
851 }
852 }
853
854 for (request, resource_type) in pending_requests {
855 self.fetch_and_show_when_ready(request, resource_type);
856 }
857 }
858
859 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
860 let global: &GlobalScope = &self.global();
861 let request_id = request.id;
862
863 let cache_result = global.image_cache().get_cached_image_status(
864 request.url.clone(),
865 global.origin().immutable().clone(),
866 None, );
868 match cache_result {
869 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
870 image, ..
871 }) => {
872 let image = image.as_raster_image();
873 if image.is_none() {
874 warn!("Vector images are not supported in notifications yet");
875 };
876 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
877 },
878 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
879 _,
880 pending_image_id,
881 )) => {
882 self.register_image_cache_callback(
883 request_id,
884 pending_image_id,
885 resource_type.clone(),
886 );
887 },
888 ImageCacheResult::Pending(pending_image_id) => {
889 self.register_image_cache_callback(
890 request_id,
891 pending_image_id,
892 resource_type.clone(),
893 );
894 },
895 ImageCacheResult::ReadyForRequest(pending_image_id) => {
896 self.register_image_cache_callback(
897 request_id,
898 pending_image_id,
899 resource_type.clone(),
900 );
901 self.fetch(pending_image_id, request, global);
902 },
903 ImageCacheResult::FailedToLoadOrDecode => {
904 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
905 },
906 };
907 }
908
909 fn register_image_cache_callback(
910 &self,
911 request_id: RequestId,
912 pending_image_id: PendingImageId,
913 resource_type: ResourceType,
914 ) {
915 let global: &GlobalScope = &self.global();
916 let trusted_this = Trusted::new(self);
917 let task_source = global.task_manager().networking_task_source().to_sendable();
918
919 let callback = Box::new(move |response| {
920 let trusted_this = trusted_this.clone();
921 let resource_type = resource_type.clone();
922 task_source.queue(task!(handle_response: move || {
923 let this = trusted_this.root();
924 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
925 warn!("Received unexpected message from image cache: {response:?}");
926 return;
927 };
928 this.handle_image_cache_response(request_id, status.response, resource_type);
929 }));
930 });
931
932 global.image_cache().add_listener(ImageLoadListener::new(
933 callback,
934 global.pipeline_id(),
935 pending_image_id,
936 ));
937 }
938
939 fn handle_image_cache_response(
940 &self,
941 request_id: RequestId,
942 response: ImageResponse,
943 resource_type: ResourceType,
944 ) {
945 match response {
946 ImageResponse::Loaded(image, _) => {
947 let image = image.as_raster_image();
948 if image.is_none() {
949 warn!("Vector images are not yet supported in notification attribute");
950 };
951 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
952 },
953 ImageResponse::FailedToLoadOrDecode => {
954 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
955 },
956 _ => (),
957 };
958 }
959
960 fn set_resource_and_show_when_ready(
961 &self,
962 request_id: RequestId,
963 resource_type: &ResourceType,
964 image: Option<Arc<RasterImage>>,
965 ) {
966 match resource_type {
967 ResourceType::Image => {
968 *self.image_resource.borrow_mut() = image;
969 },
970 ResourceType::Icon => {
971 *self.icon_resource.borrow_mut() = image;
972 },
973 ResourceType::Badge => {
974 *self.badge_resource.borrow_mut() = image;
975 },
976 ResourceType::ActionIcon(id) => {
977 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
978 *action.icon_resource.borrow_mut() = image;
979 }
980 },
981 }
982
983 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
984 pending_requests_id.remove(&request_id);
985
986 if pending_requests_id.is_empty() {
989 self.show();
990 }
991 }
992
993 fn fetch(
994 &self,
995 pending_image_id: PendingImageId,
996 request: RequestBuilder,
997 global: &GlobalScope,
998 ) {
999 let context = ResourceFetchListener {
1000 pending_image_id,
1001 image_cache: global.image_cache(),
1002 notification: Trusted::new(self),
1003 url: request.url.clone(),
1004 status: Ok(()),
1005 };
1006
1007 global.fetch(
1008 request,
1009 context,
1010 global.task_manager().networking_task_source().into(),
1011 );
1012 }
1013}