1use std::collections::HashSet;
6use std::rc::Rc;
7use std::sync::{Arc, Mutex};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use dom_struct::dom_struct;
11use embedder_traits::{
12 EmbedderMsg, Notification as EmbedderNotification,
13 NotificationAction as EmbedderNotificationAction,
14};
15use ipc_channel::ipc;
16use ipc_channel::router::ROUTER;
17use js::jsapi::Heap;
18use js::jsval::JSVal;
19use js::rust::{HandleObject, MutableHandleValue};
20use net_traits::http_status::HttpStatus;
21use net_traits::image_cache::{
22 ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
23 ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
24};
25use net_traits::request::{Destination, RequestBuilder, RequestId};
26use net_traits::{
27 FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
28 ResourceTimingType,
29};
30use pixels::RasterImage;
31use servo_url::{ImmutableOrigin, ServoUrl};
32use uuid::Uuid;
33
34use super::bindings::cell::DomRefCell;
35use super::bindings::refcounted::{Trusted, TrustedPromise};
36use super::bindings::reflector::DomGlobal;
37use super::performanceresourcetiming::InitiatorType;
38use super::permissionstatus::PermissionStatus;
39use crate::dom::bindings::callback::ExceptionHandling;
40use crate::dom::bindings::codegen::Bindings::NotificationBinding::{
41 NotificationAction, NotificationDirection, NotificationMethods, NotificationOptions,
42 NotificationPermission, NotificationPermissionCallback,
43};
44use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
45use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
46 PermissionDescriptor, PermissionName, PermissionState,
47};
48use crate::dom::bindings::codegen::UnionTypes::UnsignedLongOrUnsignedLongSequence;
49use crate::dom::bindings::error::{Error, Fallible};
50use crate::dom::bindings::inheritance::Castable;
51use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
52use crate::dom::bindings::root::{Dom, DomRoot};
53use crate::dom::bindings::str::{DOMString, USVString};
54use crate::dom::bindings::trace::RootedTraceableBox;
55use crate::dom::bindings::utils::to_frozen_array;
56use crate::dom::csp::{GlobalCspReporting, Violation};
57use crate::dom::eventtarget::EventTarget;
58use crate::dom::globalscope::GlobalScope;
59use crate::dom::permissions::{PermissionAlgorithm, Permissions, descriptor_permission_state};
60use crate::dom::promise::Promise;
61use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
62use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
63use crate::fetch::create_a_potential_cors_request;
64use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
65use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
66
67#[dom_struct]
72pub(crate) struct Notification {
73 eventtarget: EventTarget,
74 serviceworker_registration: Option<Dom<ServiceWorkerRegistration>>,
76 title: DOMString,
78 body: DOMString,
80 #[ignore_malloc_size_of = "mozjs"]
82 data: Heap<JSVal>,
83 dir: NotificationDirection,
85 image: Option<USVString>,
87 icon: Option<USVString>,
89 badge: Option<USVString>,
91 lang: DOMString,
93 silent: Option<bool>,
95 tag: DOMString,
97 #[no_trace] origin: ImmutableOrigin,
100 vibration_pattern: Vec<u32>,
102 timestamp: u64,
104 renotify: bool,
106 require_interaction: bool,
108 actions: Vec<Action>,
110 #[no_trace] pending_request_ids: DomRefCell<HashSet<RequestId>>,
113 #[ignore_malloc_size_of = "Arc"]
115 #[no_trace]
116 image_resource: DomRefCell<Option<Arc<RasterImage>>>,
117 #[ignore_malloc_size_of = "Arc"]
119 #[no_trace]
120 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
121 #[ignore_malloc_size_of = "Arc"]
123 #[no_trace]
124 badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
125}
126
127impl Notification {
128 #[allow(clippy::too_many_arguments)]
129 pub(crate) fn new(
130 global: &GlobalScope,
131 title: DOMString,
132 options: RootedTraceableBox<NotificationOptions>,
133 origin: ImmutableOrigin,
134 base_url: ServoUrl,
135 fallback_timestamp: u64,
136 proto: Option<HandleObject>,
137 can_gc: CanGc,
138 ) -> DomRoot<Self> {
139 let notification = reflect_dom_object_with_proto(
140 Box::new(Notification::new_inherited(
141 global,
142 title,
143 &options,
144 origin,
145 base_url,
146 fallback_timestamp,
147 )),
148 global,
149 proto,
150 can_gc,
151 );
152
153 notification.data.set(options.data.get());
154
155 notification
156 }
157
158 fn new_inherited(
160 global: &GlobalScope,
161 title: DOMString,
162 options: &RootedTraceableBox<NotificationOptions>,
163 origin: ImmutableOrigin,
164 base_url: ServoUrl,
165 fallback_timestamp: u64,
166 ) -> Self {
167 let title = title.clone();
171 let dir = options.dir;
172 let lang = options.lang.clone();
173 let body = options.body.clone();
174 let tag = options.tag.clone();
175
176 let image = options.image.as_ref().and_then(|image_url| {
179 ServoUrl::parse_with_base(Some(&base_url), image_url.as_ref())
180 .map(|url| USVString::from(url.to_string()))
181 .ok()
182 });
183 let icon = options.icon.as_ref().and_then(|icon_url| {
186 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
187 .map(|url| USVString::from(url.to_string()))
188 .ok()
189 });
190 let badge = options.badge.as_ref().and_then(|badge_url| {
193 ServoUrl::parse_with_base(Some(&base_url), badge_url.as_ref())
194 .map(|url| USVString::from(url.to_string()))
195 .ok()
196 });
197 let vibration_pattern = match &options.vibrate {
200 Some(pattern) => validate_and_normalize_vibration_pattern(pattern),
201 None => Vec::new(),
202 };
203 let timestamp = options.timestamp.unwrap_or(fallback_timestamp);
206 let renotify = options.renotify;
207 let silent = options.silent;
208 let require_interaction = options.requireInteraction;
209
210 let mut actions: Vec<Action> = Vec::new();
213 let max_actions = Notification::MaxActions(global);
214 for action in options.actions.iter().take(max_actions as usize) {
215 actions.push(Action {
216 id: Uuid::new_v4().simple().to_string(),
217 name: action.action.clone(),
218 title: action.title.clone(),
219 icon_url: action.icon.as_ref().and_then(|icon_url| {
222 ServoUrl::parse_with_base(Some(&base_url), icon_url.as_ref())
223 .map(|url| USVString::from(url.to_string()))
224 .ok()
225 }),
226 icon_resource: DomRefCell::new(None),
227 });
228 }
229
230 Self {
231 eventtarget: EventTarget::new_inherited(),
232 serviceworker_registration: None,
234 title,
235 body,
236 data: Heap::default(),
237 dir,
238 image,
239 icon,
240 badge,
241 lang,
242 silent,
243 origin,
244 vibration_pattern,
245 timestamp,
246 renotify,
247 tag,
248 require_interaction,
249 actions,
250 pending_request_ids: DomRefCell::new(HashSet::new()),
251 image_resource: DomRefCell::new(None),
252 icon_resource: DomRefCell::new(None),
253 badge_resource: DomRefCell::new(None),
254 }
255 }
256
257 fn show(&self) {
259 let shown = false;
261
262 if !shown {
276 self.global()
279 .send_to_embedder(EmbedderMsg::ShowNotification(
280 self.global().webview_id(),
281 self.to_embedder_notification(),
282 ));
283 }
284
285 if self.serviceworker_registration.is_none() {
293 self.global()
294 .task_manager()
295 .dom_manipulation_task_source()
296 .queue_simple_event(self.upcast(), atom!("show"));
297 }
298 }
299
300 fn to_embedder_notification(&self) -> EmbedderNotification {
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: action.icon_resource.borrow().clone(),
332 })
333 .collect(),
334 icon_resource: self.icon_resource.borrow().clone(),
335 badge_resource: self.badge_resource.borrow().clone(),
336 image_resource: self.image_resource.borrow().clone(),
337 }
338 }
339}
340
341impl NotificationMethods<crate::DomTypeHolder> for Notification {
342 fn Constructor(
344 global: &GlobalScope,
345 proto: Option<HandleObject>,
346 can_gc: CanGc,
347 title: DOMString,
348 options: RootedTraceableBox<NotificationOptions>,
349 ) -> Fallible<DomRoot<Notification>> {
350 if global.is::<ServiceWorkerGlobalScope>() {
352 return Err(Error::Type(
353 "Notification constructor cannot be used in service worker.".to_string(),
354 ));
355 }
356
357 if !options.actions.is_empty() {
359 return Err(Error::Type(
360 "Actions are only supported for persistent notifications.".to_string(),
361 ));
362 }
363
364 let notification =
366 create_notification_with_settings_object(global, title, options, proto, can_gc)?;
367
368 let permission_state = get_notifications_permission_state(global);
372 if permission_state != NotificationPermission::Granted {
373 global
374 .task_manager()
375 .dom_manipulation_task_source()
376 .queue_simple_event(notification.upcast(), atom!("error"));
377 } else {
379 notification.fetch_resources_and_show_when_ready();
383 }
384
385 Ok(notification)
386 }
387
388 fn GetPermission(global: &GlobalScope) -> Fallible<NotificationPermission> {
390 Ok(get_notifications_permission_state(global))
391 }
392
393 fn RequestPermission(
395 global: &GlobalScope,
396 permission_callback: Option<Rc<NotificationPermissionCallback>>,
397 can_gc: CanGc,
398 ) -> Rc<Promise> {
399 let promise = Promise::new(global, can_gc);
401
402 let notification_permission = request_notification_permission(global, can_gc);
405
406 let trusted_promise = TrustedPromise::new(promise.clone());
408 let uuid = Uuid::new_v4().simple().to_string();
409 let uuid_ = uuid.clone();
410
411 if let Some(callback) = permission_callback {
412 global.add_notification_permission_request_callback(uuid.clone(), callback.clone());
413 }
414
415 global.task_manager().dom_manipulation_task_source().queue(
416 task!(request_permission: move || {
417 let promise = trusted_promise.root();
418 let global = promise.global();
419
420 if let Some(callback) = global.remove_notification_permission_request_callback(uuid_) {
423 let _ = callback.Call__(notification_permission, ExceptionHandling::Report, CanGc::note());
424 }
425
426 promise.resolve_native(¬ification_permission, CanGc::note());
428 }),
429 );
430
431 promise
432 }
433
434 event_handler!(click, GetOnclick, SetOnclick);
436 event_handler!(show, GetOnshow, SetOnshow);
438 event_handler!(error, GetOnerror, SetOnerror);
440 event_handler!(close, GetOnclose, SetOnclose);
442
443 fn MaxActions(_global: &GlobalScope) -> u32 {
445 2
447 }
448 fn Title(&self) -> DOMString {
450 self.title.clone()
451 }
452 fn Dir(&self) -> NotificationDirection {
454 self.dir
455 }
456 fn Lang(&self) -> DOMString {
458 self.lang.clone()
459 }
460 fn Body(&self) -> DOMString {
462 self.body.clone()
463 }
464 fn Tag(&self) -> DOMString {
466 self.tag.clone()
467 }
468 fn Image(&self) -> USVString {
470 self.image.clone().unwrap_or_default()
473 }
474 fn Icon(&self) -> USVString {
476 self.icon.clone().unwrap_or_default()
479 }
480 fn Badge(&self) -> USVString {
482 self.badge.clone().unwrap_or_default()
485 }
486 fn Renotify(&self) -> bool {
488 self.renotify
489 }
490 fn GetSilent(&self) -> Option<bool> {
492 self.silent
493 }
494 fn RequireInteraction(&self) -> bool {
496 self.require_interaction
497 }
498 fn Data(&self, _cx: SafeJSContext, mut retval: MutableHandleValue) {
500 retval.set(self.data.get());
501 }
502 fn Actions(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
504 let mut frozen_actions: Vec<NotificationAction> = Vec::new();
506
507 for action in self.actions.iter() {
509 let action = NotificationAction {
510 action: action.name.clone(),
511 title: action.title.clone(),
512 icon: action.icon_url.clone(),
515 };
516
517 frozen_actions.push(action);
520 }
521
522 to_frozen_array(frozen_actions.as_slice(), cx, retval, can_gc);
524 }
525 fn Vibrate(&self, cx: SafeJSContext, can_gc: CanGc, retval: MutableHandleValue) {
527 to_frozen_array(self.vibration_pattern.as_slice(), cx, retval, can_gc);
528 }
529 fn Timestamp(&self) -> u64 {
531 self.timestamp
532 }
533 fn Close(&self) {
535 if self.serviceworker_registration.is_none() {
541 self.global()
542 .task_manager()
543 .dom_manipulation_task_source()
544 .queue_simple_event(self.upcast(), atom!("close"));
545 }
546 }
547}
548
549#[derive(JSTraceable, MallocSizeOf)]
551struct Action {
552 id: String,
553 name: DOMString,
555 title: DOMString,
557 icon_url: Option<USVString>,
559 #[ignore_malloc_size_of = "Arc"]
561 #[no_trace]
562 icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
563}
564
565fn create_notification_with_settings_object(
567 global: &GlobalScope,
568 title: DOMString,
569 options: RootedTraceableBox<NotificationOptions>,
570 proto: Option<HandleObject>,
571 can_gc: CanGc,
572) -> Fallible<DomRoot<Notification>> {
573 let origin = global.origin().immutable().clone();
575 let base_url = global.api_base_url();
577 let fallback_timestamp = SystemTime::now()
580 .duration_since(UNIX_EPOCH)
581 .unwrap_or_default()
582 .as_millis() as u64;
583 create_notification(
586 global,
587 title,
588 options,
589 origin,
590 base_url,
591 fallback_timestamp,
592 proto,
593 can_gc,
594 )
595}
596
597#[allow(clippy::too_many_arguments)]
599fn create_notification(
600 global: &GlobalScope,
601 title: DOMString,
602 options: RootedTraceableBox<NotificationOptions>,
603 origin: ImmutableOrigin,
604 base_url: ServoUrl,
605 fallback_timestamp: u64,
606 proto: Option<HandleObject>,
607 can_gc: CanGc,
608) -> Fallible<DomRoot<Notification>> {
609 if options.silent.is_some() && options.vibrate.is_some() {
611 return Err(Error::Type(
612 "Can't specify vibration patterns when setting notification to silent.".to_string(),
613 ));
614 }
615 if options.renotify && options.tag.is_empty() {
617 return Err(Error::Type(
618 "tag must be set to renotify as an existing notification.".to_string(),
619 ));
620 }
621
622 Ok(Notification::new(
623 global,
624 title,
625 options,
626 origin,
627 base_url,
628 fallback_timestamp,
629 proto,
630 can_gc,
631 ))
632}
633
634fn validate_and_normalize_vibration_pattern(
636 pattern: &UnsignedLongOrUnsignedLongSequence,
637) -> Vec<u32> {
638 let mut pattern: Vec<u32> = match pattern {
640 UnsignedLongOrUnsignedLongSequence::UnsignedLong(value) => {
641 vec![*value]
644 },
645 UnsignedLongOrUnsignedLongSequence::UnsignedLongSequence(values) => values.clone(),
646 };
647
648 pattern.truncate(10);
652
653 if pattern.len() % 2 == 0 && !pattern.is_empty() {
656 pattern.pop();
657 }
658
659 pattern.iter_mut().for_each(|entry| {
663 *entry = 10000.min(*entry);
664 });
665
666 pattern
668}
669
670fn get_notifications_permission_state(global: &GlobalScope) -> NotificationPermission {
672 let permission_state = descriptor_permission_state(PermissionName::Notifications, Some(global));
673 match permission_state {
674 PermissionState::Granted => NotificationPermission::Granted,
675 PermissionState::Denied => NotificationPermission::Denied,
676 PermissionState::Prompt => NotificationPermission::Default,
677 }
678}
679
680fn request_notification_permission(global: &GlobalScope, can_gc: CanGc) -> NotificationPermission {
681 let cx = GlobalScope::get_cx();
682 let promise = &Promise::new(global, can_gc);
683 let descriptor = PermissionDescriptor {
684 name: PermissionName::Notifications,
685 };
686 let status = PermissionStatus::new(global, &descriptor, can_gc);
687
688 Permissions::permission_request(cx, promise, &descriptor, &status);
690
691 match status.State() {
692 PermissionState::Granted => NotificationPermission::Granted,
693 PermissionState::Denied => NotificationPermission::Denied,
694 PermissionState::Prompt => NotificationPermission::Default,
696 }
697}
698
699#[derive(Clone, Debug, Eq, Hash, PartialEq)]
700enum ResourceType {
701 Image,
702 Icon,
703 Badge,
704 ActionIcon(String), }
706
707struct ResourceFetchListener {
708 pending_image_id: PendingImageId,
710 image_cache: Arc<dyn ImageCache>,
712 notification: Trusted<Notification>,
714 status: Result<(), NetworkError>,
716 url: ServoUrl,
718 resource_timing: ResourceFetchTiming,
720}
721
722impl FetchResponseListener for ResourceFetchListener {
723 fn process_request_body(&mut self, _: RequestId) {}
724 fn process_request_eof(&mut self, _: RequestId) {}
725
726 fn process_response(
727 &mut self,
728 request_id: RequestId,
729 metadata: Result<FetchMetadata, NetworkError>,
730 ) {
731 self.image_cache.notify_pending_response(
732 self.pending_image_id,
733 FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
734 );
735
736 let metadata = metadata.ok().map(|meta| match meta {
737 FetchMetadata::Unfiltered(m) => m,
738 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
739 });
740
741 let status = metadata
742 .as_ref()
743 .map(|m| m.status.clone())
744 .unwrap_or_else(HttpStatus::new_error);
745
746 self.status = {
747 if status.is_success() {
748 Ok(())
749 } else if status.is_error() {
750 Err(NetworkError::Internal(
751 "No http status code received".to_owned(),
752 ))
753 } else {
754 Err(NetworkError::Internal(format!(
755 "HTTP error code {}",
756 status.code()
757 )))
758 }
759 };
760 }
761
762 fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
763 if self.status.is_ok() {
764 self.image_cache.notify_pending_response(
765 self.pending_image_id,
766 FetchResponseMsg::ProcessResponseChunk(request_id, payload),
767 );
768 }
769 }
770
771 fn process_response_eof(
772 &mut self,
773 request_id: RequestId,
774 response: Result<ResourceFetchTiming, NetworkError>,
775 ) {
776 self.image_cache.notify_pending_response(
777 self.pending_image_id,
778 FetchResponseMsg::ProcessResponseEOF(request_id, response),
779 );
780 }
781
782 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
783 &mut self.resource_timing
784 }
785
786 fn resource_timing(&self) -> &ResourceFetchTiming {
787 &self.resource_timing
788 }
789
790 fn submit_resource_timing(&mut self) {
791 network_listener::submit_timing(self, CanGc::note())
792 }
793
794 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
795 let global = &self.resource_timing_global();
796 global.report_csp_violations(violations, None, None);
797 }
798}
799
800impl ResourceTimingListener for ResourceFetchListener {
801 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
802 (InitiatorType::Other, self.url.clone())
803 }
804
805 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
806 self.notification.root().global()
807 }
808}
809
810impl PreInvoke for ResourceFetchListener {
811 fn should_invoke(&self) -> bool {
812 true
813 }
814}
815
816impl Notification {
817 fn build_resource_request(&self, url: &ServoUrl) -> RequestBuilder {
818 let global = &self.global();
819 create_a_potential_cors_request(
820 None,
821 url.clone(),
822 Destination::Image,
823 None, None,
825 global.get_referrer(),
826 global.insecure_requests_policy(),
827 global.has_trustworthy_ancestor_or_current_origin(),
828 global.policy_container(),
829 )
830 .origin(global.origin().immutable().clone())
831 .pipeline_id(Some(global.pipeline_id()))
832 }
833
834 fn fetch_resources_and_show_when_ready(&self) {
836 let mut pending_requests: Vec<(RequestBuilder, ResourceType)> = vec![];
837 if let Some(image_url) = &self.image {
838 if let Ok(url) = ServoUrl::parse(image_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::Image));
842 }
843 }
844 if let Some(icon_url) = &self.icon {
845 if let Ok(url) = ServoUrl::parse(icon_url) {
846 let request = self.build_resource_request(&url);
847 self.pending_request_ids.borrow_mut().insert(request.id);
848 pending_requests.push((request, ResourceType::Icon));
849 }
850 }
851 if let Some(badge_url) = &self.badge {
852 if let Ok(url) = ServoUrl::parse(badge_url) {
853 let request = self.build_resource_request(&url);
854 self.pending_request_ids.borrow_mut().insert(request.id);
855 pending_requests.push((request, ResourceType::Badge));
856 }
857 }
858 for action in self.actions.iter() {
859 if let Some(icon_url) = &action.icon_url {
860 if let Ok(url) = ServoUrl::parse(icon_url) {
861 let request = self.build_resource_request(&url);
862 self.pending_request_ids.borrow_mut().insert(request.id);
863 pending_requests.push((request, ResourceType::ActionIcon(action.id.clone())));
864 }
865 }
866 }
867
868 for (request, resource_type) in pending_requests {
869 self.fetch_and_show_when_ready(request, resource_type);
870 }
871 }
872
873 fn fetch_and_show_when_ready(&self, request: RequestBuilder, resource_type: ResourceType) {
874 let global: &GlobalScope = &self.global();
875 let request_id = request.id;
876
877 let cache_result = global.image_cache().get_cached_image_status(
878 request.url.clone(),
879 global.origin().immutable().clone(),
880 None, UsePlaceholder::No,
882 );
883 match cache_result {
884 ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
885 image, ..
886 }) => {
887 let image = image.as_raster_image();
888 if image.is_none() {
889 warn!("Vector images are not supported in notifications yet");
890 };
891 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
892 },
893 ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
894 _,
895 pending_image_id,
896 )) => {
897 self.register_image_cache_callback(
898 request_id,
899 pending_image_id,
900 resource_type.clone(),
901 );
902 },
903 ImageCacheResult::Pending(pending_image_id) => {
904 self.register_image_cache_callback(
905 request_id,
906 pending_image_id,
907 resource_type.clone(),
908 );
909 },
910 ImageCacheResult::ReadyForRequest(pending_image_id) => {
911 self.register_image_cache_callback(
912 request_id,
913 pending_image_id,
914 resource_type.clone(),
915 );
916 self.fetch(pending_image_id, request, global);
917 },
918 ImageCacheResult::LoadError => {
919 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
920 },
921 };
922 }
923
924 fn register_image_cache_callback(
925 &self,
926 request_id: RequestId,
927 pending_image_id: PendingImageId,
928 resource_type: ResourceType,
929 ) {
930 let (sender, receiver) = ipc::channel().expect("ipc channel failure");
931
932 let global: &GlobalScope = &self.global();
933
934 let trusted_this = Trusted::new(self);
935 let resource_type = resource_type.clone();
936 let task_source = global.task_manager().networking_task_source().to_sendable();
937
938 ROUTER.add_typed_route(
939 receiver,
940 Box::new(move |response| {
941 let trusted_this = trusted_this.clone();
942 let resource_type = resource_type.clone();
943 task_source.queue(task!(handle_response: move || {
944 let this = trusted_this.root();
945 if let Ok(response) = response {
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 } else {
952 this.handle_image_cache_response(request_id, ImageResponse::None, resource_type);
953 }
954 }));
955 }),
956 );
957
958 global.image_cache().add_listener(ImageLoadListener::new(
959 sender,
960 global.pipeline_id(),
961 pending_image_id,
962 ));
963 }
964
965 fn handle_image_cache_response(
966 &self,
967 request_id: RequestId,
968 response: ImageResponse,
969 resource_type: ResourceType,
970 ) {
971 match response {
972 ImageResponse::Loaded(image, _) => {
973 let image = image.as_raster_image();
974 if image.is_none() {
975 warn!("Vector images are not yet supported in notification attribute");
976 };
977 self.set_resource_and_show_when_ready(request_id, &resource_type, image);
978 },
979 ImageResponse::PlaceholderLoaded(image, _) => {
980 self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
981 },
982 ImageResponse::None => {
983 self.set_resource_and_show_when_ready(request_id, &resource_type, None);
984 },
985 _ => (),
986 };
987 }
988
989 fn set_resource_and_show_when_ready(
990 &self,
991 request_id: RequestId,
992 resource_type: &ResourceType,
993 image: Option<Arc<RasterImage>>,
994 ) {
995 match resource_type {
996 ResourceType::Image => {
997 *self.image_resource.borrow_mut() = image;
998 },
999 ResourceType::Icon => {
1000 *self.icon_resource.borrow_mut() = image;
1001 },
1002 ResourceType::Badge => {
1003 *self.badge_resource.borrow_mut() = image;
1004 },
1005 ResourceType::ActionIcon(id) => {
1006 if let Some(action) = self.actions.iter().find(|&action| *action.id == *id) {
1007 *action.icon_resource.borrow_mut() = image;
1008 }
1009 },
1010 }
1011
1012 let mut pending_requests_id = self.pending_request_ids.borrow_mut();
1013 pending_requests_id.remove(&request_id);
1014
1015 if pending_requests_id.is_empty() {
1018 self.show();
1019 }
1020 }
1021
1022 fn fetch(
1023 &self,
1024 pending_image_id: PendingImageId,
1025 request: RequestBuilder,
1026 global: &GlobalScope,
1027 ) {
1028 let context = Arc::new(Mutex::new(ResourceFetchListener {
1029 pending_image_id,
1030 image_cache: global.image_cache(),
1031 notification: Trusted::new(self),
1032 url: request.url.clone(),
1033 status: Ok(()),
1034 resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
1035 }));
1036
1037 global.fetch(
1038 request,
1039 context,
1040 global.task_manager().networking_task_source().into(),
1041 );
1042 }
1043}