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_and_cx;
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 cx: &mut js::context::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 js::context::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 js::context::JSContext,
403 global: &GlobalScope,
404 permission_callback: Option<Rc<NotificationPermissionCallback>>,
405 ) -> Rc<Promise> {
406 let promise = Promise::new(global, CanGc::from_cx(cx));
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 || {
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__(notification_permission, ExceptionHandling::Report, CanGc::note());
431 }
432
433 promise.resolve_native(¬ification_permission, CanGc::note());
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 fn Title(&self) -> DOMString {
457 self.title.clone()
458 }
459 fn Dir(&self) -> NotificationDirection {
461 self.dir
462 }
463 fn Lang(&self) -> DOMString {
465 self.lang.clone()
466 }
467 fn Body(&self) -> DOMString {
469 self.body.clone()
470 }
471 fn Tag(&self) -> DOMString {
473 self.tag.clone()
474 }
475 fn Image(&self) -> USVString {
477 self.image.clone().unwrap_or_default()
480 }
481 fn Icon(&self) -> USVString {
483 self.icon.clone().unwrap_or_default()
486 }
487 fn Badge(&self) -> USVString {
489 self.badge.clone().unwrap_or_default()
492 }
493 fn Renotify(&self) -> bool {
495 self.renotify
496 }
497 fn GetSilent(&self) -> Option<bool> {
499 self.silent
500 }
501 fn RequireInteraction(&self) -> bool {
503 self.require_interaction
504 }
505 fn Data(&self, _cx: SafeJSContext, mut retval: MutableHandleValue) {
507 retval.set(self.data.get());
508 }
509 fn Actions(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
511 let mut frozen_actions: Vec<NotificationAction> = Vec::new();
513
514 for action in self.actions.iter() {
516 let action = NotificationAction {
517 action: action.name.clone(),
518 title: action.title.clone(),
519 icon: action.icon_url.clone(),
522 };
523
524 frozen_actions.push(action);
527 }
528
529 to_frozen_array(frozen_actions.as_slice(), cx, retval, can_gc);
531 }
532 fn Vibrate(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
534 to_frozen_array(self.vibration_pattern.as_slice(), cx, retval, can_gc);
535 }
536 fn Timestamp(&self) -> u64 {
538 self.timestamp
539 }
540 fn Close(&self) {
542 if self.serviceworker_registration.is_none() {
548 self.global()
549 .task_manager()
550 .dom_manipulation_task_source()
551 .queue_simple_event(self.upcast(), atom!("close"));
552 }
553 }
554}
555
556#[derive(JSTraceable, MallocSizeOf)]
558struct Action {
559 id: String,
560 name: DOMString,
562 title: DOMString,
564 icon_url: Option<USVString>,
566 #[ignore_malloc_size_of = "RasterImage"]
568 #[no_trace]
569 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
570}
571
572fn create_notification_with_settings_object(
574 cx: &mut js::context::JSContext,
575 global: &GlobalScope,
576 title: DOMString,
577 options: RootedTraceableBox<NotificationOptions>,
578 proto: Option<HandleObject>,
579) -> Fallible<DomRoot<Notification>> {
580 let origin = global.origin().immutable().clone();
582 let base_url = global.api_base_url();
584 let fallback_timestamp = SystemTime::now()
587 .duration_since(UNIX_EPOCH)
588 .unwrap_or_default()
589 .as_millis() as u64;
590 create_notification(
593 cx,
594 global,
595 title,
596 options,
597 origin,
598 base_url,
599 fallback_timestamp,
600 proto,
601 )
602}
603
604#[expect(clippy::too_many_arguments)]
606fn create_notification(
607 cx: &mut js::context::JSContext,
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) -> Fallible<DomRoot<Notification>> {
616 if options.silent.is_some() && options.vibrate.is_some() {
618 return Err(Error::Type(
619 c"Can't specify vibration patterns when setting notification to silent.".to_owned(),
620 ));
621 }
622 if options.renotify && options.tag.is_empty() {
624 return Err(Error::Type(
625 c"tag must be set to renotify as an existing notification.".to_owned(),
626 ));
627 }
628
629 Ok(Notification::new(
630 cx,
631 global,
632 title,
633 options,
634 origin,
635 base_url,
636 fallback_timestamp,
637 proto,
638 ))
639}
640
641fn validate_and_normalize_vibration_pattern(
643 pattern: &UnsignedLongOrUnsignedLongSequence,
644) -> Vec<u32> {
645 let mut pattern: Vec<u32> = match pattern {
647 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
648 vec![*value]
651 },
652 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
653 };
654
655 pattern.truncate(10);
659
660 if pattern.len() % 2 == 0 && !pattern.is_empty() {
663 pattern.pop();
664 }
665
666 pattern.iter_mut().for_each(|entry| {
670 *entry = 10000.min(*entry);
671 });
672
673 pattern
675}
676
677fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
679 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
680 match permission_state {
681 PermissionState::Granted => NotificationPermission::Granted,
682 PermissionState::Denied => NotificationPermission::Denied,
683 PermissionState::Prompt => NotificationPermission::Default,
684 }
685}
686
687fn request_notification_permission(
688 cx: &mut js::context::JSContext,
689 global: &GlobalScope,
690) -> NotificationPermission {
691 let promise = &Promise::new(global, CanGc::from_cx(cx));
692 let descriptor = PermissionDescriptor {
693 name: PermissionName::Notifications,
694 };
695 let status = PermissionStatus::new(global, &descriptor, CanGc::from_cx(cx));
696
697 Permissions::permission_request(cx, promise, &descriptor, &status);
699
700 match status.State() {
701 PermissionState::Granted => NotificationPermission::Granted,
702 PermissionState::Denied => NotificationPermission::Denied,
703 PermissionState::Prompt => NotificationPermission::Default,
705 }
706}
707
708#[derive(Clone, Debug, Eq, Hash, PartialEq)]
709enum ResourceType {
710 Image,
711 Icon,
712 Badge,
713 ActionIcon(String), }
715
716struct ResourceFetchListener {
717 pending_image_id: PendingImageId,
719 image_cache: Arc<dyn ImageCache>,
721 notification: Trusted<Notification>,
723 status: Result<(), NetworkError>,
725 url: ServoUrl,
727}
728
729impl FetchResponseListener for ResourceFetchListener {
730 fn process_request_body(&mut self, _: RequestId) {}
731 fn process_request_eof(&mut self, _: RequestId) {}
732
733 fn process_response(
734 &mut self,
735 request_id: RequestId,
736 metadata: Result<FetchMetadata, NetworkError>,
737 ) {
738 self.image_cache.notify_pending_response(
739 self.pending_image_id,
740 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
741 );
742
743 let metadata = metadata.ok().map(|meta| match meta {
744 FetchMetadata::Unfiltered(m) => m,
745 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
746 });
747
748 let status = metadata
749 .as_ref()
750 .map(|m| m.status.clone())
751 .unwrap_or_else(HttpStatus::new_error);
752
753 self.status = {
754 if status.is_success() {
755 Ok(())
756 } else if status.is_error() {
757 Err(NetworkError::ResourceLoadError(
758 "No http status code received".to_owned(),
759 ))
760 } else {
761 Err(NetworkError::ResourceLoadError(format!(
762 "HTTP error code {}",
763 status.code()
764 )))
765 }
766 };
767 }
768
769 fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
770 if self.status.is_ok() {
771 self.image_cache.notify_pending_response(
772 self.pending_image_id,
773 FetchResponseMsg::ProcessResponseChunk(request_id, payload.into()),
774 );
775 }
776 }
777
778 fn process_response_eof(
779 self,
780 cx: &mut js::context::JSContext,
781 request_id: RequestId,
782 response: Result<(), NetworkError>,
783 timing: ResourceFetchTiming,
784 ) {
785 self.image_cache.notify_pending_response(
786 self.pending_image_id,
787 FetchResponseMsg::ProcessResponseEOF(request_id, response.clone(), timing.clone()),
788 );
789 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
790 }
791
792 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
793 let global = &self.resource_timing_global();
794 global.report_csp_violations(violations, None, None);
795 }
796}
797
798impl ResourceTimingListener for ResourceFetchListener {
799 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
800 (InitiatorType::Other, self.url.clone())
801 }
802
803 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
804 self.notification.root().global()
805 }
806}
807
808impl Notification {
809 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
810 let global = &self.global();
811 create_a_potential_cors_request(
812 None,
813 url.clone(),
814 Destination::Image,
815 None, None,
817 global.get_referrer(),
818 )
819 .with_global_scope(global)
820 }
821
822 fn fetch_resources_and_show_when_ready(&self) {
824 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
825 if let Some(image_url) = &self.image {
826 if let Ok(url) = ServoUrl::parse(image_url) {
827 let request = self.build_resource_request(&url);
828 self.pending_request_ids.borrow_mut().insert(request.id);
829 pending_requests.push((request, ResourceType::Image));
830 }
831 }
832 if let Some(icon_url) = &self.icon {
833 if let Ok(url) = ServoUrl::parse(icon_url) {
834 let request = self.build_resource_request(&url);
835 self.pending_request_ids.borrow_mut().insert(request.id);
836 pending_requests.push((request, ResourceType::Icon));
837 }
838 }
839 if let Some(badge_url) = &self.badge {
840 if let Ok(url) = ServoUrl::parse(badge_url) {
841 let request = self.build_resource_request(&url);
842 self.pending_request_ids.borrow_mut().insert(request.id);
843 pending_requests.push((request, ResourceType::Badge));
844 }
845 }
846 for action in self.actions.iter() {
847 if let Some(icon_url) = &action.icon_url {
848 if let Ok(url) = ServoUrl::parse(icon_url) {
849 let request = self.build_resource_request(&url);
850 self.pending_request_ids.borrow_mut().insert(request.id);
851 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
852 }
853 }
854 }
855
856 for (request, resource_type) in pending_requests {
857 self.fetch_and_show_when_ready(request, resource_type);
858 }
859 }
860
861 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
862 let global: &GlobalScope = &self.global();
863 let request_id = request.id;
864
865 let cache_result = global.image_cache().get_cached_image_status(
866 request.url.clone(),
867 global.origin().immutable().clone(),
868 None, );
870 match cache_result {
871 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
872 image, ..
873 }) => {
874 let image = image.as_raster_image();
875 if image.is_none() {
876 warn!("Vector images are not supported in notifications yet");
877 };
878 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
879 },
880 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
881 _,
882 pending_image_id,
883 )) => {
884 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
885 },
886 ImageCacheResult::Pending(pending_image_id) => {
887 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
888 },
889 ImageCacheResult::ReadyForRequest(pending_image_id) => {
890 self.register_image_cache_callback(request_id, pending_image_id, resource_type);
891 self.fetch(pending_image_id, request, global);
892 },
893 ImageCacheResult::FailedToLoadOrDecode => {
894 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
895 },
896 };
897 }
898
899 fn register_image_cache_callback(
900 &self,
901 request_id: RequestId,
902 pending_image_id: PendingImageId,
903 resource_type: ResourceType,
904 ) {
905 let global: &GlobalScope = &self.global();
906 let trusted_this = Trusted::new(self);
907 let task_source = global.task_manager().networking_task_source().to_sendable();
908
909 let callback = Box::new(move |response| {
910 let trusted_this = trusted_this.clone();
911 let resource_type = resource_type.clone();
912 task_source.queue(task!(handle_response: move || {
913 let this = trusted_this.root();
914 let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
915 warn!("Received unexpected message from image cache: {response:?}");
916 return;
917 };
918 this.handle_image_cache_response(request_id, status.response, resource_type);
919 }));
920 });
921
922 global.image_cache().add_listener(ImageLoadListener::new(
923 callback,
924 global.pipeline_id(),
925 pending_image_id,
926 ));
927 }
928
929 fn handle_image_cache_response(
930 &self,
931 request_id: RequestId,
932 response: ImageResponse,
933 resource_type: ResourceType,
934 ) {
935 match response {
936 ImageResponse::Loaded(image, _) => {
937 let image = image.as_raster_image();
938 if image.is_none() {
939 warn!("Vector images are not yet supported in notification attribute");
940 };
941 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
942 },
943 ImageResponse::FailedToLoadOrDecode => {
944 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
945 },
946 _ => (),
947 };
948 }
949
950 fn set_resource_and_show_when_ready(
951 &self,
952 request_id: RequestId,
953 resource_type: &ResourceType,
954 image: Option<Arc<RasterImage>>,
955 ) {
956 match resource_type {
957 ResourceType::Image => {
958 *self.image_resource.borrow_mut() = image;
959 },
960 ResourceType::Icon => {
961 *self.icon_resource.borrow_mut() = image;
962 },
963 ResourceType::Badge => {
964 *self.badge_resource.borrow_mut() = image;
965 },
966 ResourceType::ActionIcon(id) => {
967 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
968 *action.icon_resource.borrow_mut() = image;
969 }
970 },
971 }
972
973 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
974 pending_requests_id.remove(&request_id);
975
976 if pending_requests_id.is_empty() {
979 self.show();
980 }
981 }
982
983 fn fetch(
984 &self,
985 pending_image_id: PendingImageId,
986 request: RequestBuilder,
987 global: &GlobalScope,
988 ) {
989 let context = ResourceFetchListener {
990 pending_image_id,
991 image_cache: global.image_cache(),
992 notification: Trusted::new(self),
993 url: request.url.clone(),
994 status: Ok(()),
995 };
996
997 global.fetch(
998 request,
999 context,
1000 global.task_manager().networking_task_source().into(),
1001 );
1002 }
1003}