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