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::{self, GenericSender, ReceiveError, RoutedReceiver};
22use servo_base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
23use servo_config::pref;
24use servo_constellation_traits::{
25 DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerMsg, SWManagerSenders,
26 ScopeThings, ServiceWorkerManagerFactory, ServiceWorkerMsg,
27};
28use servo_url::{ImmutableOrigin, ServoUrl};
29
30use crate::dom::abstractworker::{MessageData, WorkerScriptMsg};
31use crate::dom::serviceworkerglobalscope::{
32 ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
33};
34use crate::dom::serviceworkerregistration::longest_prefix_match;
35use crate::script_runtime::ThreadSafeJSContext;
36
37enum Message {
38 FromResource(CustomResponseMediator),
39 FromConstellation(Box<ServiceWorkerMsg>),
40}
41
42#[derive(Clone)]
44pub(crate) struct ServiceWorker {
45 pub(crate) id: ServiceWorkerId,
47 pub(crate) script_url: ServoUrl,
49 pub(crate) sender: Sender<ServiceWorkerScriptMsg>,
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 }
64 }
65
66 fn forward_dom_message(&self, msg: DOMMessage) {
68 let DOMMessage { origin, data } = msg;
69 let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
70 WorkerScriptMsg::DOMMessage(MessageData {
71 origin,
72 data: Box::new(data),
73 }),
74 ));
75 }
76
77 fn send_message(&self, msg: ServiceWorkerScriptMsg) {
79 let _ = self.sender.send(msg);
80 }
81}
82
83#[expect(dead_code)]
85enum RegistrationUpdateTarget {
86 Installing,
87 Waiting,
88 Active,
89}
90
91impl Drop for ServiceWorkerRegistration {
92 fn drop(&mut self) {
94 if self
96 .control_sender
97 .take()
98 .expect("No control sender to worker thread.")
99 .send(ServiceWorkerControlMsg::Exit)
100 .is_err()
101 {
102 warn!("Failed to send exit message to service worker scope.");
103 }
104
105 self.closing
106 .take()
107 .expect("No close flag for worker")
108 .store(true, Ordering::SeqCst);
109 self.context
110 .take()
111 .expect("No context to request interrupt.")
112 .request_interrupt_callback();
113
114 if self
116 .join_handle
117 .take()
118 .expect("No handle to join on worker.")
119 .join()
120 .is_err()
121 {
122 warn!("Failed to join on service worker thread.");
123 }
124 }
125}
126
127struct ServiceWorkerRegistration {
129 id: ServiceWorkerRegistrationId,
131 active_worker: Option<ServiceWorker>,
133 waiting_worker: Option<ServiceWorker>,
135 installing_worker: Option<ServiceWorker>,
137 control_sender: Option<Sender<ServiceWorkerControlMsg>>,
140 join_handle: Option<JoinHandle<()>>,
142 context: Option<ThreadSafeJSContext>,
144 closing: Option<Arc<AtomicBool>>,
146}
147
148impl ServiceWorkerRegistration {
149 pub(crate) fn new() -> ServiceWorkerRegistration {
150 ServiceWorkerRegistration {
151 id: ServiceWorkerRegistrationId::new(),
152 active_worker: None,
153 waiting_worker: None,
154 installing_worker: None,
155 join_handle: None,
156 control_sender: None,
157 context: None,
158 closing: None,
159 }
160 }
161
162 fn note_worker_thread(
163 &mut self,
164 join_handle: JoinHandle<()>,
165 control_sender: Sender<ServiceWorkerControlMsg>,
166 context: ThreadSafeJSContext,
167 closing: Arc<AtomicBool>,
168 ) {
169 assert!(self.join_handle.is_none());
170 self.join_handle = Some(join_handle);
171
172 assert!(self.control_sender.is_none());
173 self.control_sender = Some(control_sender);
174
175 assert!(self.context.is_none());
176 self.context = Some(context);
177
178 assert!(self.closing.is_none());
179 self.closing = Some(closing);
180 }
181
182 fn get_newest_worker(&self) -> Option<ServiceWorker> {
184 if let Some(worker) = self.active_worker.as_ref() {
185 return Some(worker.clone());
186 }
187 if let Some(worker) = self.waiting_worker.as_ref() {
188 return Some(worker.clone());
189 }
190 if let Some(worker) = self.installing_worker.as_ref() {
191 return Some(worker.clone());
192 }
193 None
194 }
195
196 fn update_registration_state(
198 &mut self,
199 target: RegistrationUpdateTarget,
200 worker: ServiceWorker,
201 ) {
202 match target {
203 RegistrationUpdateTarget::Active => {
204 self.active_worker = Some(worker);
205 },
206 RegistrationUpdateTarget::Waiting => {
207 self.waiting_worker = Some(worker);
208 },
209 RegistrationUpdateTarget::Installing => {
210 self.installing_worker = Some(worker);
211 },
212 }
213 }
214}
215
216pub struct ServiceWorkerManager {
218 registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
220 _constellation_sender: GenericSender<SWManagerMsg>,
223 own_sender: GenericSender<ServiceWorkerMsg>,
225 own_port: RoutedReceiver<ServiceWorkerMsg>,
227 resource_receiver: Receiver<CustomResponseMediator>,
229 font_context: Arc<FontContext>,
231}
232
233impl ServiceWorkerManager {
234 fn new(
235 own_sender: GenericSender<ServiceWorkerMsg>,
236 from_constellation_receiver: RoutedReceiver<ServiceWorkerMsg>,
237 resource_port: Receiver<CustomResponseMediator>,
238 constellation_sender: GenericSender<SWManagerMsg>,
239 font_context: Arc<FontContext>,
240 ) -> ServiceWorkerManager {
241 PipelineNamespace::auto_install();
243
244 ServiceWorkerManager {
245 registrations: HashMap::new(),
246 own_sender,
247 own_port: from_constellation_receiver,
248 resource_receiver: resource_port,
249 _constellation_sender: constellation_sender,
250 font_context,
251 }
252 }
253
254 pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
255 for scope in self.registrations.keys() {
256 if longest_prefix_match(scope, load_url) {
257 return Some(scope.clone());
258 }
259 }
260 None
261 }
262
263 fn handle_message(&mut self) {
264 while let Ok(message) = self.receive_message() {
265 let should_continue = match message {
266 Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
267 Message::FromResource(msg) => self.handle_message_from_resource(msg),
268 };
269 if !should_continue {
270 for registration in self.registrations.drain() {
271 drop(registration);
273 }
274 break;
275 }
276 }
277 }
278
279 fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
280 if serviceworker_enabled() {
281 if let Some(scope) = self.get_matching_scope(&mediator.load_url) {
282 if let Some(registration) = self.registrations.get(&scope) {
283 if let Some(ref worker) = registration.active_worker {
284 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
285 return true;
286 }
287 }
288 }
289 }
290 let _ = mediator.response_chan.send(None);
291 true
292 }
293
294 fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
295 select! {
296 recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
297 recv(self.resource_receiver) -> msg => msg.map(Message::FromResource).map_err(|_e| ReceiveError::Disconnected),
298 }
299 }
300
301 fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
302 match msg {
303 ServiceWorkerMsg::Timeout(_scope) => {
304 },
306 ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
307 if let Some(registration) = self.registrations.get_mut(&scope_url) {
308 if let Some(ref worker) = registration.active_worker {
309 worker.forward_dom_message(msg);
310 }
311 }
312 },
313 ServiceWorkerMsg::ScheduleJob(job) => match job.job_type {
314 JobType::Register => {
315 self.handle_register_job(job);
316 },
317 JobType::Update => {
318 self.handle_update_job(job);
319 },
320 JobType::Unregister => {
321 },
323 },
324 ServiceWorkerMsg::Exit => return false,
325 }
326 true
327 }
328
329 fn handle_register_job(&mut self, mut job: Job) {
331 if !job.script_url.origin().is_potentially_trustworthy() {
332 let _ = job
334 .client
335 .send(JobResult::RejectPromise(JobError::SecurityError));
336 return;
337 }
338
339 if job.script_url.origin() != job.referrer.origin() ||
340 job.scope_url.origin() != job.referrer.origin()
341 {
342 let _ = job
344 .client
345 .send(JobResult::RejectPromise(JobError::SecurityError));
346 return;
347 }
348
349 if let Some(registration) = self.registrations.get(&job.scope_url) {
351 let newest_worker = registration.get_newest_worker();
355
356 if newest_worker.is_some() {
358 let client = job.client.clone();
362 let _ = client.send(JobResult::ResolvePromise(
363 job,
364 JobResultValue::Registration {
365 id: registration.id,
366 installing_worker: registration
367 .installing_worker
368 .as_ref()
369 .map(|worker| worker.id),
370 waiting_worker: registration
371 .waiting_worker
372 .as_ref()
373 .map(|worker| worker.id),
374 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
375 },
376 ));
377 }
378 } else {
379 let new_registration = ServiceWorkerRegistration::new();
383 self.registrations
384 .insert(job.scope_url.clone(), new_registration);
385
386 job.job_type = JobType::Update;
388 let _ = self.own_sender.send(ServiceWorkerMsg::ScheduleJob(job));
389 }
390 }
391
392 fn handle_update_job(&mut self, job: Job) {
394 if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
396 let newest_worker = registration.get_newest_worker();
398
399 if let Some(worker) = newest_worker {
401 if worker.script_url != job.script_url {
402 let _ = job
403 .client
404 .send(JobResult::RejectPromise(JobError::TypeError));
405 return;
406 }
407 }
408
409 let scope_things = job
410 .scope_things
411 .clone()
412 .expect("Update job should have scope things.");
413
414 let (new_worker, join_handle, control_sender, context, closing) = update_serviceworker(
417 self.own_sender.clone(),
418 job.scope_url.clone(),
419 scope_things,
420 self.font_context.clone(),
421 );
422
423 registration.note_worker_thread(join_handle, control_sender, context, closing);
425
426 registration
430 .update_registration_state(RegistrationUpdateTarget::Installing, new_worker);
431
432 let client = job.client.clone();
434 let _ = client.send(JobResult::ResolvePromise(
435 job,
436 JobResultValue::Registration {
437 id: registration.id,
438 installing_worker: registration
439 .installing_worker
440 .as_ref()
441 .map(|worker| worker.id),
442 waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
443 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
444 },
445 ));
446 } else {
447 let _ = job
449 .client
450 .send(JobResult::RejectPromise(JobError::TypeError));
451 }
452 }
453}
454
455fn update_serviceworker(
457 own_sender: GenericSender<ServiceWorkerMsg>,
458 scope_url: ServoUrl,
459 mut scope_things: ScopeThings,
460 font_context: Arc<FontContext>,
461) -> (
462 ServiceWorker,
463 JoinHandle<()>,
464 Sender<ServiceWorkerControlMsg>,
465 ThreadSafeJSContext,
466 Arc<AtomicBool>,
467) {
468 let (sender, receiver) = unbounded();
469 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
470 scope_things.init.from_devtools_sender = Some(devtools_sender);
471
472 if let Some(ref chan) = scope_things.devtools_chan {
473 if let Some(ref sender) = scope_things.init.from_devtools_sender {
474 let page_info = DevtoolsPageInfo {
475 title: format!("Service Worker for {}", scope_things.script_url),
476 url: scope_things.script_url.clone(),
477 is_top_level_global: false,
478 is_service_worker: true,
479 };
480 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
481 (
482 scope_things.browsing_context_id,
483 scope_things.init.pipeline_id,
484 Some(scope_things.worker_id),
485 scope_things.webview_id,
486 ),
487 sender.clone(),
488 page_info,
489 ));
490 }
491 }
492
493 let worker_id = ServiceWorkerId::new();
494
495 let (control_sender, control_receiver) = unbounded();
496 let (context_sender, context_receiver) = unbounded();
497 let closing = Arc::new(AtomicBool::new(false));
498
499 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
500 scope_things.clone(),
501 sender.clone(),
502 receiver,
503 devtools_receiver,
504 own_sender,
505 scope_url,
506 control_receiver,
507 context_sender,
508 closing.clone(),
509 font_context,
510 );
511
512 let context = context_receiver
513 .recv()
514 .expect("Couldn't receive a context for worker.");
515
516 (
517 ServiceWorker::new(scope_things.script_url, sender, worker_id),
518 join_handle,
519 control_sender,
520 context,
521 closing,
522 )
523}
524
525impl ServiceWorkerManagerFactory for ServiceWorkerManager {
526 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
527 let (resource_chan, resource_port) = ipc::channel().unwrap();
528
529 let SWManagerSenders {
530 resource_threads,
531 own_sender,
532 receiver,
533 swmanager_sender: constellation_sender,
534 system_font_service_sender,
535 paint_api,
536 } = sw_senders;
537
538 let from_constellation = receiver.route_preserving_errors();
539 let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
540 let _ = resource_threads
541 .core_thread
542 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
543
544 let font_context = Arc::new(FontContext::new(
545 Arc::new(system_font_service_sender.to_proxy()),
546 paint_api,
547 resource_threads,
548 ));
549
550 let swmanager_thread = move || {
551 ServiceWorkerManager::new(
552 own_sender,
553 from_constellation,
554 resource_port,
555 constellation_sender,
556 font_context,
557 )
558 .handle_message()
559 };
560 if thread::Builder::new()
561 .name("SvcWorkerManager".to_owned())
562 .spawn(swmanager_thread)
563 .is_err()
564 {
565 warn!("ServiceWorkerManager thread spawning failed");
566 }
567 }
568}
569
570pub(crate) fn serviceworker_enabled() -> bool {
571 pref!(dom_serviceworker_enabled)
572}