1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::thread::{self, JoinHandle};
14
15use crossbeam_channel::{Receiver, Sender, select, unbounded};
16use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg};
17use fonts::FontContext;
18use ipc_channel::ipc;
19use ipc_channel::router::ROUTER;
20use net_traits::{CoreResourceMsg, CustomResponseMediator};
21use servo_base::generic_channel::{
22 self, GenericCallback, GenericSender, ReceiveError, RoutedReceiver,
23};
24use servo_base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
25use servo_config::pref;
26use servo_constellation_traits::{
27 DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerSenders, ScopeThings,
28 ServiceWorkerAlgorithm, ServiceWorkerAlgorithmResult, ServiceWorkerManagerFactory,
29 ServiceWorkerMsg, ServiceWorkerRegistrationInfo,
30};
31use servo_url::{ImmutableOrigin, ServoUrl};
32
33use crate::dom::abstractworker::{MessageData, WorkerScriptMsg};
34use crate::dom::serviceworkerglobalscope::{
35 ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
36};
37use crate::dom::serviceworkerregistration::longest_prefix_match;
38use crate::script_runtime::ThreadSafeJSContext;
39
40enum Message {
41 FromResource(CustomResponseMediator),
42 FromConstellation(Box<ServiceWorkerMsg>),
43}
44
45#[derive(Clone)]
47pub(crate) struct ServiceWorker {
48 pub(crate) id: ServiceWorkerId,
50 pub(crate) script_url: ServoUrl,
52 pub(crate) sender: Sender<ServiceWorkerScriptMsg>,
54}
55
56impl ServiceWorker {
57 fn new(
58 script_url: ServoUrl,
59 sender: Sender<ServiceWorkerScriptMsg>,
60 id: ServiceWorkerId,
61 ) -> ServiceWorker {
62 ServiceWorker {
63 id,
64 script_url,
65 sender,
66 }
67 }
68
69 fn forward_dom_message(&self, msg: DOMMessage) {
71 let DOMMessage {
72 origin,
73 data,
74 pipeline_id,
75 } = msg;
76 let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
77 WorkerScriptMsg::DOMMessage(MessageData {
78 origin,
79 pipeline_id,
80 data: Box::new(data),
81 }),
82 ));
83 }
84
85 fn send_message(&self, msg: ServiceWorkerScriptMsg) {
87 let _ = self.sender.send(msg);
88 }
89}
90
91#[expect(dead_code)]
93enum RegistrationUpdateTarget {
94 Installing,
95 Waiting,
96 Active,
97}
98
99impl Drop for ServiceWorkerRegistration {
100 fn drop(&mut self) {
102 if self
104 .control_sender
105 .take()
106 .expect("No control sender to worker thread.")
107 .send(ServiceWorkerControlMsg::Exit)
108 .is_err()
109 {
110 warn!("Failed to send exit message to service worker scope.");
111 }
112
113 self.closing
114 .take()
115 .expect("No close flag for worker")
116 .store(true, Ordering::SeqCst);
117 self.context
118 .take()
119 .expect("No context to request interrupt.")
120 .request_interrupt_callback();
121
122 if self
124 .join_handle
125 .take()
126 .expect("No handle to join on worker.")
127 .join()
128 .is_err()
129 {
130 warn!("Failed to join on service worker thread.");
131 }
132 }
133}
134
135struct ServiceWorkerRegistration {
137 id: ServiceWorkerRegistrationId,
139 active_worker: Option<ServiceWorker>,
141 waiting_worker: Option<ServiceWorker>,
143 installing_worker: Option<ServiceWorker>,
145 control_sender: Option<Sender<ServiceWorkerControlMsg>>,
148 join_handle: Option<JoinHandle<()>>,
150 context: Option<ThreadSafeJSContext>,
152 closing: Option<Arc<AtomicBool>>,
154 client: GenericCallback<ServiceWorkerAlgorithmResult>,
157}
158
159impl ServiceWorkerRegistration {
160 pub(crate) fn new(
161 client: GenericCallback<ServiceWorkerAlgorithmResult>,
162 ) -> ServiceWorkerRegistration {
163 ServiceWorkerRegistration {
164 id: ServiceWorkerRegistrationId::new(),
165 active_worker: None,
166 waiting_worker: None,
167 installing_worker: None,
168 join_handle: None,
169 control_sender: None,
170 context: None,
171 closing: None,
172 client,
173 }
174 }
175
176 fn note_worker_thread(
177 &mut self,
178 join_handle: JoinHandle<()>,
179 control_sender: Sender<ServiceWorkerControlMsg>,
180 context: ThreadSafeJSContext,
181 closing: Arc<AtomicBool>,
182 ) {
183 assert!(self.join_handle.is_none());
184 self.join_handle = Some(join_handle);
185
186 assert!(self.control_sender.is_none());
187 self.control_sender = Some(control_sender);
188
189 assert!(self.context.is_none());
190 self.context = Some(context);
191
192 assert!(self.closing.is_none());
193 self.closing = Some(closing);
194 }
195
196 fn get_newest_worker(&self) -> Option<ServiceWorker> {
198 if let Some(worker) = self.active_worker.as_ref() {
199 return Some(worker.clone());
200 }
201 if let Some(worker) = self.waiting_worker.as_ref() {
202 return Some(worker.clone());
203 }
204 if let Some(worker) = self.installing_worker.as_ref() {
205 return Some(worker.clone());
206 }
207 None
208 }
209
210 fn update_registration_state(
212 &mut self,
213 target: RegistrationUpdateTarget,
214 worker: Option<ServiceWorker>,
215 ) {
216 match target {
217 RegistrationUpdateTarget::Active => {
218 self.active_worker = worker;
219 },
220 RegistrationUpdateTarget::Waiting => {
221 self.waiting_worker = worker;
222 },
223 RegistrationUpdateTarget::Installing => {
224 self.installing_worker = worker;
225 },
226 }
227 }
228}
229
230pub struct ServiceWorkerManager {
232 registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
234 own_sender: GenericSender<ServiceWorkerMsg>,
236 own_port: RoutedReceiver<ServiceWorkerMsg>,
238 resource_receiver: Receiver<CustomResponseMediator>,
240 font_context: Arc<FontContext>,
242}
243
244impl ServiceWorkerManager {
245 fn new(
246 own_sender: GenericSender<ServiceWorkerMsg>,
247 from_constellation_receiver: RoutedReceiver<ServiceWorkerMsg>,
248 resource_port: Receiver<CustomResponseMediator>,
249 font_context: Arc<FontContext>,
250 ) -> ServiceWorkerManager {
251 PipelineNamespace::auto_install();
253
254 ServiceWorkerManager {
255 registrations: HashMap::new(),
256 own_sender,
257 own_port: from_constellation_receiver,
258 resource_receiver: resource_port,
259 font_context,
260 }
261 }
262
263 pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
264 for scope in self.registrations.keys() {
265 if longest_prefix_match(scope, load_url) {
266 return Some(scope.clone());
267 }
268 }
269 None
270 }
271
272 fn handle_message(&mut self) {
273 while let Ok(message) = self.receive_message() {
274 let should_continue = match message {
275 Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
276 Message::FromResource(msg) => self.handle_message_from_resource(msg),
277 };
278 if !should_continue {
279 for registration in self.registrations.drain() {
280 drop(registration);
282 }
283 break;
284 }
285 }
286 }
287
288 fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
289 if serviceworker_enabled() &&
290 let Some(scope) = self.get_matching_scope(&mediator.load_url) &&
291 let Some(registration) = self.registrations.get(&scope) &&
292 let Some(ref worker) = registration.active_worker
293 {
294 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
295 return true;
296 }
297 let _ = mediator.response_chan.send(None);
298 true
299 }
300
301 fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
302 select! {
303 recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
304 recv(self.resource_receiver) -> msg => msg.map(Message::FromResource).map_err(|_e| ReceiveError::Disconnected),
305 }
306 }
307
308 fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
309 match msg {
310 ServiceWorkerMsg::Timeout(_scope) => {
311 },
313 ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
314 if let Some(registration) = self.registrations.get_mut(&scope_url) {
315 if let Some(ref worker) = registration.active_worker {
316 worker.forward_dom_message(msg);
317 } else if let Some(ref worker) = registration.waiting_worker {
318 worker.forward_dom_message(msg);
319 } else if let Some(ref worker) = registration.installing_worker {
320 worker.forward_dom_message(msg);
321 }
322 }
323 },
324 ServiceWorkerMsg::ForwardWorkerMessage {
325 data,
326 url,
327 source,
328 origin,
329 } => {
330 let Some(registration) = self.registrations.get(&url) else {
331 warn!("No registration found for scope URL when forwarding message to worker.");
332 return true;
333 };
334 let script_url = if let Some(worker) = registration.active_worker.as_ref() {
335 worker.script_url.clone()
336 } else if let Some(worker) = registration.waiting_worker.as_ref() {
337 worker.script_url.clone()
338 } else if let Some(worker) = registration.installing_worker.as_ref() {
339 worker.script_url.clone()
340 } else {
341 warn!("No worker found for scope URL when forwarding message to worker.");
342 return true;
343 };
344 if registration
345 .client
346 .send(ServiceWorkerAlgorithmResult::MessageFromWorker {
347 message: data,
348 source,
349 scope_url: url,
350 script_url,
351 origin,
352 })
353 .is_err()
354 {
355 warn!("Failed to forward message from worker to script.");
356 }
357 },
358 ServiceWorkerMsg::HandleAlgorithm(algorithm) => match algorithm {
359 ServiceWorkerAlgorithm::StartRegister(job) => {
360 self.handle_register_job(job);
361 },
362 ServiceWorkerAlgorithm::Unregister(job) => {
363 self.handle_unregister_job(job);
364 },
365 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
366 storage_key,
367 client_url,
368 result_handler,
369 } => {
370 self.handle_match_registration(storage_key, client_url, result_handler);
371 },
372 },
373 ServiceWorkerMsg::Exit => return false,
374 }
375 true
376 }
377
378 fn handle_unregister_job(&mut self, job: Job) {
380 if self.registrations.remove(&job.scope_url).is_none() {
382 if job
385 .client
386 .send(ServiceWorkerAlgorithmResult::Job(
387 JobResult::ResolvePromise(JobResultValue::Unregister(false)),
388 ))
389 .is_err()
390 {
391 warn!("Failed to send unregister result to script.");
392 }
393 return;
396 };
397
398 if job
403 .client
404 .send(ServiceWorkerAlgorithmResult::Job(
405 JobResult::ResolvePromise(JobResultValue::Unregister(true)),
406 ))
407 .is_err()
408 {
409 warn!("Failed to send unregister result to script.");
410 }
411
412 }
418
419 fn handle_match_registration(
421 &self,
422 storage_key: ImmutableOrigin,
423 client_url: ServoUrl,
424 result_handler: GenericCallback<ServiceWorkerAlgorithmResult>,
425 ) {
426 let client_url_string = client_url.as_str();
431
432 let mut matching_scope_string = String::new();
434
435 let mut scope_string_set = Vec::new();
437
438 for (entry_storage_key, entry_scope) in self.registrations.keys().map(|k| (k.origin(), k)) {
440 if storage_key == entry_storage_key {
442 scope_string_set.push(entry_scope.as_str());
443 }
444 }
445
446 for scope in scope_string_set {
448 if client_url_string.starts_with(scope) && scope.len() > matching_scope_string.len() {
449 matching_scope_string = scope.to_owned();
450 }
451 }
452
453 let mut matching_scope = None;
455
456 if !matching_scope_string.is_empty() {
458 let Ok(parsed_matching_scope) = ServoUrl::parse(&matching_scope_string) else {
460 error!("Failed to parse matching scope string as URL.");
461 if result_handler
462 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
463 .is_err()
464 {
465 warn!("Failed to send match registration result to script.");
466 }
467 return;
468 };
469 matching_scope = Some(parsed_matching_scope);
470
471 debug_assert_eq!(
473 matching_scope.as_ref().unwrap().origin(),
474 client_url.origin()
475 );
476 }
477
478 let Some(matching_scope) = matching_scope else {
479 if result_handler
480 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
481 .is_err()
482 {
483 warn!("Failed to send match registration result to script.");
484 }
485 return;
486 };
487
488 let registration = self.registrations.get(&matching_scope);
490 let info = registration
491 .as_ref()
492 .map(|registration| ServiceWorkerRegistrationInfo {
493 scope_url: matching_scope,
494 script_url: registration
495 .get_newest_worker()
496 .expect("Registration should have a worker.")
497 .script_url,
498 storage_key,
499 id: registration.id,
500 installing_worker: registration
501 .installing_worker
502 .as_ref()
503 .map(|worker| worker.id),
504 waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
505 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
506 });
507 if result_handler
508 .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(info))
509 .is_err()
510 {
511 warn!("Failed to send match registration result to script.");
512 }
513 }
514
515 fn handle_register_job(&mut self, mut job: Job) {
517 if !job.script_url.origin().is_potentially_trustworthy() {
519 if job
521 .client
522 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
523 JobError::SecurityError,
524 )))
525 .is_err()
526 {
527 warn!("Failed to send reject job promise result to script.");
528 }
529
530 return;
533 }
534
535 if job.script_url.origin() != job.referrer.origin() ||
539 job.scope_url.origin() != job.referrer.origin()
540 {
541 if job
543 .client
544 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
545 JobError::SecurityError,
546 )))
547 .is_err()
548 {
549 warn!("Failed to send reject job promise result to script.");
550 }
551
552 return;
554 }
555
556 if let Some(registration) = self.registrations.get(&job.scope_url) {
558 let newest_worker = registration.get_newest_worker();
562
563 if newest_worker.is_some() {
565 let client = job.client.clone();
571 let _ = client.send(ServiceWorkerAlgorithmResult::Job(
572 JobResult::ResolvePromise(JobResultValue::Register(
573 ServiceWorkerRegistrationInfo {
574 scope_url: job.scope_url.clone(),
575 script_url: job.script_url.clone(),
576 storage_key: job.storage_key.clone(),
577 id: registration.id,
578 installing_worker: registration
579 .installing_worker
580 .as_ref()
581 .map(|worker| worker.id),
582 waiting_worker: registration
583 .waiting_worker
584 .as_ref()
585 .map(|worker| worker.id),
586 active_worker: registration
587 .active_worker
588 .as_ref()
589 .map(|worker| worker.id),
590 },
591 )),
592 ));
593 }
594 } else {
595 let new_registration = ServiceWorkerRegistration::new(job.client.clone());
598 self.registrations
599 .insert(job.scope_url.clone(), new_registration);
600
601 job.job_type = JobType::Update;
603 self.handle_update_job(job);
604 }
605 }
606
607 fn install(&mut self, job: Job, new_worker: ServiceWorker) {
609 let Some(registration) = self.registrations.get_mut(&job.scope_url) else {
610 error!("Registration should exist when installing a worker.");
611 return;
612 };
613
614 registration.update_registration_state(
616 RegistrationUpdateTarget::Installing,
617 Some(new_worker.clone()),
618 );
619
620 let client = job.client.clone();
622 if client
623 .send(ServiceWorkerAlgorithmResult::Job(
624 JobResult::ResolvePromise(JobResultValue::Register(
625 ServiceWorkerRegistrationInfo {
626 scope_url: job.scope_url.clone(),
627 storage_key: job.storage_key.clone(),
628 script_url: job.script_url.clone(),
629 id: registration.id,
630 installing_worker: registration
631 .installing_worker
632 .as_ref()
633 .map(|worker| worker.id),
634 waiting_worker: registration
635 .waiting_worker
636 .as_ref()
637 .map(|worker| worker.id),
638 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
639 },
640 )),
641 ))
642 .is_err()
643 {
644 warn!("Failed to send resolve job promise result to script.");
645 }
646
647 registration.update_registration_state(RegistrationUpdateTarget::Waiting, Some(new_worker));
650
651 }
659
660 fn handle_update_job(&mut self, job: Job) {
662 let (job, new_worker) =
664 if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
665 let newest_worker = registration.get_newest_worker();
667
668 if let Some(worker) = newest_worker &&
670 worker.script_url != job.script_url
671 {
672 let _ = job.client.send(ServiceWorkerAlgorithmResult::Job(
673 JobResult::RejectPromise(JobError::TypeError),
674 ));
675 return;
676 }
677
678 let scope_things = job
679 .scope_things
680 .clone()
681 .expect("Update job should have scope things.");
682
683 let (new_worker, join_handle, control_sender, context, closing) =
686 update_serviceworker(
687 self.own_sender.clone(),
688 job.scope_url.clone(),
689 scope_things,
690 self.font_context.clone(),
691 );
692
693 registration.note_worker_thread(join_handle, control_sender, context, closing);
695
696 (job, new_worker)
697 } else {
698 let _ =
700 job.client
701 .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
702 JobError::TypeError,
703 )));
704 return;
705 };
706 self.install(job, new_worker);
708 }
709}
710
711fn update_serviceworker(
713 own_sender: GenericSender<ServiceWorkerMsg>,
714 scope_url: ServoUrl,
715 mut scope_things: ScopeThings,
716 font_context: Arc<FontContext>,
717) -> (
718 ServiceWorker,
719 JoinHandle<()>,
720 Sender<ServiceWorkerControlMsg>,
721 ThreadSafeJSContext,
722 Arc<AtomicBool>,
723) {
724 let (sender, receiver) = unbounded();
725 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
726 scope_things.init.from_devtools_sender = Some(devtools_sender);
727
728 if let Some(ref chan) = scope_things.devtools_chan &&
729 let Some(ref sender) = scope_things.init.from_devtools_sender
730 {
731 let page_info = DevtoolsPageInfo {
732 title: format!("Service Worker for {}", scope_things.script_url),
733 url: scope_things.script_url.clone(),
734 is_top_level_global: false,
735 is_service_worker: true,
736 };
737 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
738 (
739 scope_things.browsing_context_id,
740 scope_things.init.pipeline_id,
741 Some(scope_things.worker_id),
742 scope_things.webview_id,
743 ),
744 sender.clone(),
745 page_info,
746 ));
747 }
748
749 let worker_id = ServiceWorkerId::new();
750
751 let (control_sender, control_receiver) = unbounded();
752 let (context_sender, context_receiver) = unbounded();
753 let closing = Arc::new(AtomicBool::new(false));
754
755 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
756 scope_things.clone(),
757 sender.clone(),
758 receiver,
759 devtools_receiver,
760 own_sender,
761 scope_url,
762 control_receiver,
763 context_sender,
764 closing.clone(),
765 font_context,
766 worker_id,
767 );
768
769 let context = context_receiver
770 .recv()
771 .expect("Couldn't receive a context for worker.");
772
773 (
774 ServiceWorker::new(scope_things.script_url, sender, worker_id),
775 join_handle,
776 control_sender,
777 context,
778 closing,
779 )
780}
781
782impl ServiceWorkerManagerFactory for ServiceWorkerManager {
783 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
784 let (resource_chan, resource_port) = ipc::channel().unwrap();
785
786 let SWManagerSenders {
787 resource_threads,
788 own_sender,
789 receiver,
790 system_font_service_sender,
791 paint_api,
792 } = sw_senders;
793
794 let from_constellation = receiver.route_preserving_errors();
795 let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
796 let _ = resource_threads
797 .core_thread
798 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
799
800 let font_context = Arc::new(FontContext::new(
801 Arc::new(system_font_service_sender.to_proxy()),
802 paint_api,
803 resource_threads,
804 ));
805
806 let swmanager_thread = move || {
807 ServiceWorkerManager::new(own_sender, from_constellation, resource_port, font_context)
808 .handle_message()
809 };
810 if thread::Builder::new()
811 .name("SvcWorkerManager".to_owned())
812 .spawn(swmanager_thread)
813 .is_err()
814 {
815 warn!("ServiceWorkerManager thread spawning failed");
816 }
817 }
818}
819
820pub(crate) fn serviceworker_enabled() -> bool {
821 pref!(dom_serviceworker_enabled)
822}