1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::thread::{self, JoinHandle};
14
15use crossbeam_channel::{Sender, select, unbounded};
16use fonts::FontContext;
17use net_traits::{CoreResourceMsg, CustomResponseMediator};
18use servo_base::generic_channel::{self, GenericCallback, GenericSender, RoutedReceiver};
19use servo_base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
20use servo_config::pref;
21use servo_constellation_traits::{
22 DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerSenders, ScopeThings,
23 ServiceWorkerAlgorithm, ServiceWorkerAlgorithmResult, ServiceWorkerManagerFactory,
24 ServiceWorkerManagerMsg, ServiceWorkerMsg, ServiceWorkerRegistrationInfo, ServiceWorkerState,
25};
26use servo_url::{ImmutableOrigin, ServoUrl};
27
28use crate::dom::abstractworker::{MessageData, WorkerScriptMsg};
29use crate::dom::serviceworkerglobalscope::{
30 ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
31};
32use crate::dom::serviceworkerregistration::longest_prefix_match;
33use crate::runtime::script_runtime::ThreadSafeJSContext;
34
35enum Message {
36 FromResource(CustomResponseMediator),
37 FromConstellation(Box<ServiceWorkerMsg>),
38}
39
40#[derive(Clone)]
42pub(crate) struct ServiceWorker {
43 pub(crate) id: ServiceWorkerId,
45 pub(crate) script_url: ServoUrl,
47 pub(crate) sender: Sender<ServiceWorkerScriptMsg>,
49 pub(crate) state: ServiceWorkerState,
51}
52
53impl ServiceWorker {
54 fn new(
55 script_url: ServoUrl,
56 sender: Sender<ServiceWorkerScriptMsg>,
57 id: ServiceWorkerId,
58 ) -> ServiceWorker {
59 ServiceWorker {
60 id,
61 script_url,
62 sender,
63 state: ServiceWorkerState::Parsed,
64 }
65 }
66
67 fn forward_dom_message(&self, msg: DOMMessage) {
69 let DOMMessage {
70 origin,
71 data,
72 pipeline_id,
73 } = msg;
74 let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
75 WorkerScriptMsg::DOMMessage(MessageData {
76 origin,
77 pipeline_id,
78 data: Box::new(data),
79 }),
80 ));
81 }
82
83 fn send_message(&self, msg: ServiceWorkerScriptMsg) {
85 let _ = self.sender.send(msg);
86 }
87}
88
89#[expect(dead_code)]
91enum RegistrationUpdateTarget {
92 Installing,
93 Waiting,
94 Active,
95}
96
97impl Drop for ServiceWorkerRegistration {
98 fn drop(&mut self) {
100 if self
102 .control_sender
103 .take()
104 .expect("No control sender to worker thread.")
105 .send(ServiceWorkerControlMsg::Exit)
106 .is_err()
107 {
108 warn!("Failed to send exit message to service worker scope.");
109 }
110
111 self.closing
112 .take()
113 .expect("No close flag for worker")
114 .store(true, Ordering::SeqCst);
115 self.context
116 .take()
117 .expect("No context to request interrupt.")
118 .request_interrupt_callback();
119
120 if self
122 .join_handle
123 .take()
124 .expect("No handle to join on worker.")
125 .join()
126 .is_err()
127 {
128 warn!("Failed to join on service worker thread.");
129 }
130 }
131}
132
133struct ServiceWorkerRegistration {
135 id: ServiceWorkerRegistrationId,
137 active_worker: Option<ServiceWorker>,
139 waiting_worker: Option<ServiceWorker>,
141 installing_worker: Option<ServiceWorker>,
143 control_sender: Option<Sender<ServiceWorkerControlMsg>>,
146 join_handle: Option<JoinHandle<()>>,
148 context: Option<ThreadSafeJSContext>,
150 closing: Option<Arc<AtomicBool>>,
152 client: GenericCallback<ServiceWorkerAlgorithmResult>,
155}
156
157impl ServiceWorkerRegistration {
158 pub(crate) fn new(
159 client: GenericCallback<ServiceWorkerAlgorithmResult>,
160 ) -> ServiceWorkerRegistration {
161 ServiceWorkerRegistration {
162 id: ServiceWorkerRegistrationId::new(),
163 active_worker: None,
164 waiting_worker: None,
165 installing_worker: None,
166 join_handle: None,
167 control_sender: None,
168 context: None,
169 closing: None,
170 client,
171 }
172 }
173
174 fn note_worker_thread(
175 &mut self,
176 join_handle: JoinHandle<()>,
177 control_sender: Sender<ServiceWorkerControlMsg>,
178 context: ThreadSafeJSContext,
179 closing: Arc<AtomicBool>,
180 ) {
181 assert!(self.join_handle.is_none());
182 self.join_handle = Some(join_handle);
183
184 assert!(self.control_sender.is_none());
185 self.control_sender = Some(control_sender);
186
187 assert!(self.context.is_none());
188 self.context = Some(context);
189
190 assert!(self.closing.is_none());
191 self.closing = Some(closing);
192 }
193
194 fn get_newest_worker(&self) -> Option<ServiceWorker> {
196 if let Some(worker) = self.active_worker.as_ref() {
197 return Some(worker.clone());
198 }
199 if let Some(worker) = self.waiting_worker.as_ref() {
200 return Some(worker.clone());
201 }
202 if let Some(worker) = self.installing_worker.as_ref() {
203 return Some(worker.clone());
204 }
205 None
206 }
207
208 fn update_registration_state(
210 &mut self,
211 target: RegistrationUpdateTarget,
212 worker: Option<ServiceWorker>,
213 ) {
214 match target {
215 RegistrationUpdateTarget::Active => {
216 self.active_worker = worker;
217 },
218 RegistrationUpdateTarget::Waiting => {
219 self.waiting_worker = worker;
220 },
221 RegistrationUpdateTarget::Installing => {
222 self.installing_worker = worker;
223 },
224 }
225 }
226}
227
228pub struct ServiceWorkerManager {
230 registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
232 own_sender: GenericSender<ServiceWorkerMsg>,
234 constellation_sender: GenericSender<ServiceWorkerManagerMsg>,
236 own_port: RoutedReceiver<ServiceWorkerMsg>,
238 resource_receiver: RoutedReceiver<CustomResponseMediator>,
240 font_context: Arc<FontContext>,
242}
243
244impl ServiceWorkerManager {
245 fn new(
246 own_sender: GenericSender<ServiceWorkerMsg>,
247 constellation_sender: GenericSender<ServiceWorkerManagerMsg>,
248 from_constellation_receiver: RoutedReceiver<ServiceWorkerMsg>,
249 resource_port: RoutedReceiver<CustomResponseMediator>,
250 font_context: Arc<FontContext>,
251 ) -> ServiceWorkerManager {
252 PipelineNamespace::auto_install();
254
255 ServiceWorkerManager {
256 registrations: HashMap::new(),
257 own_sender,
258 constellation_sender,
259 own_port: from_constellation_receiver,
260 resource_receiver: resource_port,
261 font_context,
262 }
263 }
264
265 pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
266 for scope in self.registrations.keys() {
267 if longest_prefix_match(scope, load_url) {
268 return Some(scope.clone());
269 }
270 }
271 None
272 }
273
274 fn handle_message(&mut self) {
275 while let Ok(message) = self.receive_message() {
276 let should_continue = match message {
277 Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
278 Message::FromResource(msg) => self.handle_message_from_resource(msg),
279 };
280 if !should_continue {
281 for registration in self.registrations.drain() {
282 drop(registration);
284 }
285 break;
286 }
287 }
288 }
289
290 fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
291 if serviceworker_enabled() &&
292 let Some(scope) = self.get_matching_scope(&mediator.load_url) &&
293 let Some(registration) = self.registrations.get(&scope) &&
294 let Some(ref worker) = registration.active_worker
295 {
296 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
297 return true;
298 }
299 let _ = mediator.response_chan.send(None);
300 true
301 }
302
303 fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
304 select! {
305 recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
306 recv(self.resource_receiver) -> result_msg => generic_channel::to_receive_result::<CustomResponseMediator>(result_msg).map(Message::FromResource),
307 }
308 }
309
310 fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
311 match msg {
312 ServiceWorkerMsg::Timeout(_scope) => {
313 },
315 ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
316 if let Some(registration) = self.registrations.get_mut(&scope_url) {
317 if let Some(ref worker) = registration.active_worker {
318 worker.forward_dom_message(msg);
319 } else if let Some(ref worker) = registration.waiting_worker {
320 worker.forward_dom_message(msg);
321 } else if let Some(ref worker) = registration.installing_worker {
322 worker.forward_dom_message(msg);
323 }
324 }
325 },
326 ServiceWorkerMsg::ForwardWorkerMessage {
327 data,
328 url,
329 source,
330 origin,
331 } => {
332 let Some(registration) = self.registrations.get(&url) else {
333 warn!("No registration found for scope URL when forwarding message to worker.");
334 return true;
335 };
336 let script_url = if let Some(worker) = registration.active_worker.as_ref() {
337 worker.script_url.clone()
338 } else if let Some(worker) = registration.waiting_worker.as_ref() {
339 worker.script_url.clone()
340 } else if let Some(worker) = registration.installing_worker.as_ref() {
341 worker.script_url.clone()
342 } else {
343 warn!("No worker found for scope URL when forwarding message to worker.");
344 return true;
345 };
346 if registration
347 .client
348 .send(ServiceWorkerAlgorithmResult::MessageFromWorker {
349 message: data,
350 source,
351 scope_url: url,
352 script_url,
353 origin,
354 })
355 .is_err()
356 {
357 warn!("Failed to forward message from worker to script.");
358 }
359 },
360 ServiceWorkerMsg::HandleAlgorithm(algorithm) => match algorithm {
361 ServiceWorkerAlgorithm::StartRegister(job) => {
362 self.handle_register_job(job);
363 },
364 ServiceWorkerAlgorithm::Unregister(job) => {
365 self.handle_unregister_job(job);
366 },
367 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
368 storage_key,
369 client_url,
370 result_handler,
371 } => {
372 self.handle_match_registration(storage_key, client_url, result_handler);
373 },
374 },
375 ServiceWorkerMsg::Exit => return false,
376 ServiceWorkerMsg::SetNetworkOnlineState(online) => {
377 for registration in self.registrations.values() {
378 for worker in [
379 ®istration.active_worker,
380 ®istration.waiting_worker,
381 ®istration.installing_worker,
382 ] {
383 let Some(worker) = worker else { continue };
384 worker.send_message(ServiceWorkerScriptMsg::SetNetworkOnlineState(online));
385 }
386 }
387 },
388 }
389 true
390 }
391
392 fn handle_unregister_job(&mut self, job: Job) {
394 if self.registrations.remove(&job.scope_url).is_none() {
396 if job
399 .client
400 .send(ServiceWorkerAlgorithmResult::Job(
401 JobResult::ResolvePromise(JobResultValue::Unregister(false)),
402 ))
403 .is_err()
404 {
405 warn!("Failed to send unregister result to script.");
406 }
407 return;
410 };
411
412 if job
417 .client
418 .send(ServiceWorkerAlgorithmResult::Job(
419 JobResult::ResolvePromise(JobResultValue::Unregister(true)),
420 ))
421 .is_err()
422 {
423 warn!("Failed to send unregister result to script.");
424 }
425
426 }
432
433 fn handle_match_registration(
435 &self,
436 storage_key: ImmutableOrigin,
437 client_url: ServoUrl,
438 result_handler: GenericCallback<ServiceWorkerAlgorithmResult>,
439 ) {
440 let client_url_string = client_url.as_str();
445
446 let mut matching_scope_string = String::new();
448
449 let mut scope_string_set = Vec::new();
451
452 for (entry_storage_key, entry_scope) in self.registrations.keys().map(|k| (k.origin(), k)) {
454 if storage_key == entry_storage_key {
456 scope_string_set.push(entry_scope.as_str());
457 }
458 }
459
460 for scope in scope_string_set {
462 if client_url_string.starts_with(scope) && scope.len() > matching_scope_string.len() {
463 matching_scope_string = scope.to_owned();
464 }
465 }
466
467 let mut matching_scope = None;
469
470 if !matching_scope_string.is_empty() {
472 let Ok(parsed_matching_scope) = ServoUrl::parse(&matching_scope_string) else {
474 error!("Failed to parse matching scope string as URL.");
475 if result_handler
476 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
477 .is_err()
478 {
479 warn!("Failed to send match registration result to script.");
480 }
481 return;
482 };
483 matching_scope = Some(parsed_matching_scope);
484
485 debug_assert_eq!(
487 matching_scope.as_ref().unwrap().origin(),
488 client_url.origin()
489 );
490 }
491
492 let Some(matching_scope) = matching_scope else {
493 if result_handler
494 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
495 .is_err()
496 {
497 warn!("Failed to send match registration result to script.");
498 }
499 return;
500 };
501
502 let registration = self.registrations.get(&matching_scope);
504 let info = registration
505 .as_ref()
506 .map(|registration| ServiceWorkerRegistrationInfo {
507 scope_url: matching_scope,
508 script_url: registration
509 .get_newest_worker()
510 .expect("Registration should have a worker.")
511 .script_url,
512 storage_key,
513 id: registration.id,
514 installing_worker: registration
515 .installing_worker
516 .as_ref()
517 .map(|worker| worker.id),
518 waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
519 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
520 });
521 if result_handler
522 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(info))
523 .is_err()
524 {
525 warn!("Failed to send match registration result to script.");
526 }
527 }
528
529 fn handle_register_job(&mut self, mut job: Job) {
531 if !job.script_url.origin().is_potentially_trustworthy() {
533 if job
535 .client
536 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
537 JobError::SecurityError,
538 )))
539 .is_err()
540 {
541 warn!("Failed to send reject job promise result to script.");
542 }
543
544 return;
547 }
548
549 if job.script_url.origin() != job.referrer.origin() ||
553 job.scope_url.origin() != job.referrer.origin()
554 {
555 if job
557 .client
558 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
559 JobError::SecurityError,
560 )))
561 .is_err()
562 {
563 warn!("Failed to send reject job promise result to script.");
564 }
565
566 return;
568 }
569
570 if let Some(registration) = self.registrations.get(&job.scope_url) {
572 let newest_worker = registration.get_newest_worker();
576
577 if newest_worker.is_some() {
579 let client = job.client.clone();
585 let _ = client.send(ServiceWorkerAlgorithmResult::Job(
586 JobResult::ResolvePromise(JobResultValue::Register(
587 ServiceWorkerRegistrationInfo {
588 scope_url: job.scope_url.clone(),
589 script_url: job.script_url.clone(),
590 storage_key: job.storage_key.clone(),
591 id: registration.id,
592 installing_worker: registration
593 .installing_worker
594 .as_ref()
595 .map(|worker| worker.id),
596 waiting_worker: registration
597 .waiting_worker
598 .as_ref()
599 .map(|worker| worker.id),
600 active_worker: registration
601 .active_worker
602 .as_ref()
603 .map(|worker| worker.id),
604 },
605 )),
606 ));
607 }
608 } else {
609 let new_registration = ServiceWorkerRegistration::new(job.client.clone());
612 self.registrations
613 .insert(job.scope_url.clone(), new_registration);
614
615 job.job_type = JobType::Update;
617 self.handle_update_job(job);
618 }
619 }
620
621 fn update_worker_state(&mut self, worker: &mut ServiceWorker, state: ServiceWorkerState) {
623 debug_assert!(!matches!(state, ServiceWorkerState::Parsed));
625
626 worker.state = state.clone();
628
629 let origin = worker.script_url.origin();
633 if self
634 .constellation_sender
635 .send(ServiceWorkerManagerMsg::UpdateWorkerState {
636 worker_id: worker.id,
637 state,
638 origin,
639 })
640 .is_err()
641 {
642 error!("Failed to send UpdateWorkerState message to constellation.");
643 }
644 }
645
646 fn install(&mut self, job: Job, mut new_worker: ServiceWorker) {
648 self.update_worker_state(&mut new_worker, ServiceWorkerState::Installing);
653
654 let (registration_id, installing_worker_id, waiting_worker_id, active_worker_id) = {
655 let Some(registration) = self.registrations.get_mut(&job.scope_url) else {
656 error!("Registration should exist when installing a worker.");
657 return;
658 };
659
660 registration.update_registration_state(
662 RegistrationUpdateTarget::Installing,
663 Some(new_worker.clone()),
664 );
665 (
666 registration.id,
667 registration
668 .installing_worker
669 .as_ref()
670 .map(|worker| worker.id),
671 registration.waiting_worker.as_ref().map(|worker| worker.id),
672 registration.active_worker.as_ref().map(|worker| worker.id),
673 )
674 };
675
676 let client = job.client.clone();
678 if client
679 .send(ServiceWorkerAlgorithmResult::Job(
680 JobResult::ResolvePromise(JobResultValue::Register(
681 ServiceWorkerRegistrationInfo {
682 scope_url: job.scope_url.clone(),
683 storage_key: job.storage_key.clone(),
684 script_url: job.script_url.clone(),
685 id: registration_id,
686 installing_worker: installing_worker_id,
687 waiting_worker: waiting_worker_id,
688 active_worker: active_worker_id,
689 },
690 )),
691 ))
692 .is_err()
693 {
694 warn!("Failed to send resolve job promise result to script.");
695 }
696
697 self.update_worker_state(&mut new_worker, ServiceWorkerState::Installed);
701
702 let Some(registration) = self.registrations.get_mut(&job.scope_url) else {
703 error!("Registration should exist when installing a worker.");
704 return;
705 };
706
707 registration.update_registration_state(RegistrationUpdateTarget::Waiting, Some(new_worker));
710
711 }
723
724 fn handle_update_job(&mut self, job: Job) {
726 let (job, new_worker) =
728 if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
729 let newest_worker = registration.get_newest_worker();
731
732 if let Some(worker) = newest_worker &&
734 worker.script_url != job.script_url
735 {
736 let _ = job.client.send(ServiceWorkerAlgorithmResult::Job(
737 JobResult::RejectPromise(JobError::TypeError),
738 ));
739 return;
740 }
741
742 let scope_things = job
743 .scope_things
744 .clone()
745 .expect("Update job should have scope things.");
746
747 let (new_worker, join_handle, control_sender, context, closing) =
750 update_serviceworker(
751 self.own_sender.clone(),
752 job.scope_url.clone(),
753 scope_things,
754 self.font_context.clone(),
755 );
756
757 registration.note_worker_thread(join_handle, control_sender, context, closing);
759
760 (job, new_worker)
761 } else {
762 let _ =
764 job.client
765 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
766 JobError::TypeError,
767 )));
768 return;
769 };
770 self.install(job, new_worker);
772 }
773}
774
775fn update_serviceworker(
777 own_sender: GenericSender<ServiceWorkerMsg>,
778 scope_url: ServoUrl,
779 mut scope_things: ScopeThings,
780 font_context: Arc<FontContext>,
781) -> (
782 ServiceWorker,
783 JoinHandle<()>,
784 Sender<ServiceWorkerControlMsg>,
785 ThreadSafeJSContext,
786 Arc<AtomicBool>,
787) {
788 let (sender, receiver) = unbounded();
789 #[cfg(feature = "devtools")]
790 let devtools_receiver = {
791 use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg};
792
793 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
794
795 scope_things.init.from_devtools_sender = Some(devtools_sender);
796
797 if let Some(ref chan) = scope_things.devtools_chan &&
798 let Some(ref sender) = scope_things.init.from_devtools_sender
799 {
800 let page_info = DevtoolsPageInfo {
801 title: format!("Service Worker for {}", scope_things.script_url),
802 url: scope_things.script_url.clone(),
803 is_top_level_global: false,
804 is_service_worker: true,
805 };
806 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
807 (
808 scope_things.browsing_context_id,
809 scope_things.init.pipeline_id,
810 Some(scope_things.worker_id),
811 scope_things.webview_id,
812 ),
813 sender.clone(),
814 page_info,
815 ));
816 }
817
818 Some(devtools_receiver)
819 };
820
821 #[cfg(not(feature = "devtools"))]
822 let devtools_receiver = None;
823
824 let worker_id = ServiceWorkerId::new();
825
826 let (control_sender, control_receiver) = unbounded();
827 let (context_sender, context_receiver) = unbounded();
828 let closing = Arc::new(AtomicBool::new(false));
829
830 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
831 scope_things.clone(),
832 sender.clone(),
833 receiver,
834 devtools_receiver,
835 own_sender,
836 scope_url,
837 control_receiver,
838 context_sender,
839 closing.clone(),
840 font_context,
841 worker_id,
842 );
843
844 let context = context_receiver
845 .recv()
846 .expect("Couldn't receive a context for worker.");
847
848 (
849 ServiceWorker::new(scope_things.script_url, sender, worker_id),
850 join_handle,
851 control_sender,
852 context,
853 closing,
854 )
855}
856
857impl ServiceWorkerManagerFactory for ServiceWorkerManager {
858 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
859 let (resource_chan, resource_port) = generic_channel::channel().unwrap();
860
861 let SWManagerSenders {
862 constellation_sender,
863 resource_threads,
864 own_sender,
865 receiver,
866 system_font_service_sender,
867 paint_api,
868 } = sw_senders;
869
870 let from_constellation = receiver.route_preserving_errors();
871 let resource_port = resource_port.route_preserving_errors();
872 let _ = resource_threads
873 .core_thread
874 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
875
876 let font_context = Arc::new(FontContext::new(
877 Arc::new(system_font_service_sender.to_proxy()),
878 paint_api,
879 resource_threads,
880 ));
881
882 let swmanager_thread = move || {
883 ServiceWorkerManager::new(
884 own_sender,
885 constellation_sender,
886 from_constellation,
887 resource_port,
888 font_context,
889 )
890 .handle_message()
891 };
892 if thread::Builder::new()
893 .name("SvcWorkerManager".to_owned())
894 .spawn(swmanager_thread)
895 .is_err()
896 {
897 warn!("ServiceWorkerManager thread spawning failed");
898 }
899 }
900}
901
902pub(crate) fn serviceworker_enabled() -> bool {
903 pref!(dom_serviceworker_enabled)
904}