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 let Some(scope) = self.get_matching_scope(&mediator.load_url) &&
282 let Some(registration) = self.registrations.get(&scope) &&
283 let Some(ref worker) = registration.active_worker
284 {
285 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
286 return true;
287 }
288 let _ = mediator.response_chan.send(None);
289 true
290 }
291
292 fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
293 select! {
294 recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
295 recv(self.resource_receiver) -> msg => msg.map(Message::FromResource).map_err(|_e| ReceiveError::Disconnected),
296 }
297 }
298
299 fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
300 match msg {
301 ServiceWorkerMsg::Timeout(_scope) => {
302 },
304 ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
305 if let Some(registration) = self.registrations.get_mut(&scope_url) &&
306 let Some(ref worker) = registration.active_worker
307 {
308 worker.forward_dom_message(msg);
309 }
310 },
311 ServiceWorkerMsg::ScheduleJob(job) => match job.job_type {
312 JobType::Register => {
313 self.handle_register_job(job);
314 },
315 JobType::Update => {
316 self.handle_update_job(job);
317 },
318 JobType::Unregister => {
319 },
321 },
322 ServiceWorkerMsg::Exit => return false,
323 }
324 true
325 }
326
327 fn handle_register_job(&mut self, mut job: Job) {
329 if !job.script_url.origin().is_potentially_trustworthy() {
330 let _ = job
332 .client
333 .send(JobResult::RejectPromise(JobError::SecurityError));
334 return;
335 }
336
337 if job.script_url.origin() != job.referrer.origin() ||
338 job.scope_url.origin() != job.referrer.origin()
339 {
340 let _ = job
342 .client
343 .send(JobResult::RejectPromise(JobError::SecurityError));
344 return;
345 }
346
347 if let Some(registration) = self.registrations.get(&job.scope_url) {
349 let newest_worker = registration.get_newest_worker();
353
354 if newest_worker.is_some() {
356 let client = job.client.clone();
360 let _ = client.send(JobResult::ResolvePromise(
361 job,
362 JobResultValue::Registration {
363 id: registration.id,
364 installing_worker: registration
365 .installing_worker
366 .as_ref()
367 .map(|worker| worker.id),
368 waiting_worker: registration
369 .waiting_worker
370 .as_ref()
371 .map(|worker| worker.id),
372 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
373 },
374 ));
375 }
376 } else {
377 let new_registration = ServiceWorkerRegistration::new();
381 self.registrations
382 .insert(job.scope_url.clone(), new_registration);
383
384 job.job_type = JobType::Update;
386 let _ = self.own_sender.send(ServiceWorkerMsg::ScheduleJob(job));
387 }
388 }
389
390 fn handle_update_job(&mut self, job: Job) {
392 if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
394 let newest_worker = registration.get_newest_worker();
396
397 if let Some(worker) = newest_worker &&
399 worker.script_url != job.script_url
400 {
401 let _ = job
402 .client
403 .send(JobResult::RejectPromise(JobError::TypeError));
404 return;
405 }
406
407 let scope_things = job
408 .scope_things
409 .clone()
410 .expect("Update job should have scope things.");
411
412 let (new_worker, join_handle, control_sender, context, closing) = update_serviceworker(
415 self.own_sender.clone(),
416 job.scope_url.clone(),
417 scope_things,
418 self.font_context.clone(),
419 );
420
421 registration.note_worker_thread(join_handle, control_sender, context, closing);
423
424 registration
428 .update_registration_state(RegistrationUpdateTarget::Installing, new_worker);
429
430 let client = job.client.clone();
432 let _ = client.send(JobResult::ResolvePromise(
433 job,
434 JobResultValue::Registration {
435 id: registration.id,
436 installing_worker: registration
437 .installing_worker
438 .as_ref()
439 .map(|worker| worker.id),
440 waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
441 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
442 },
443 ));
444 } else {
445 let _ = job
447 .client
448 .send(JobResult::RejectPromise(JobError::TypeError));
449 }
450 }
451}
452
453fn update_serviceworker(
455 own_sender: GenericSender<ServiceWorkerMsg>,
456 scope_url: ServoUrl,
457 mut scope_things: ScopeThings,
458 font_context: Arc<FontContext>,
459) -> (
460 ServiceWorker,
461 JoinHandle<()>,
462 Sender<ServiceWorkerControlMsg>,
463 ThreadSafeJSContext,
464 Arc<AtomicBool>,
465) {
466 let (sender, receiver) = unbounded();
467 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
468 scope_things.init.from_devtools_sender = Some(devtools_sender);
469
470 if let Some(ref chan) = scope_things.devtools_chan &&
471 let Some(ref sender) = scope_things.init.from_devtools_sender
472 {
473 let page_info = DevtoolsPageInfo {
474 title: format!("Service Worker for {}", scope_things.script_url),
475 url: scope_things.script_url.clone(),
476 is_top_level_global: false,
477 is_service_worker: true,
478 };
479 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
480 (
481 scope_things.browsing_context_id,
482 scope_things.init.pipeline_id,
483 Some(scope_things.worker_id),
484 scope_things.webview_id,
485 ),
486 sender.clone(),
487 page_info,
488 ));
489 }
490
491 let worker_id = ServiceWorkerId::new();
492
493 let (control_sender, control_receiver) = unbounded();
494 let (context_sender, context_receiver) = unbounded();
495 let closing = Arc::new(AtomicBool::new(false));
496
497 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
498 scope_things.clone(),
499 sender.clone(),
500 receiver,
501 devtools_receiver,
502 own_sender,
503 scope_url,
504 control_receiver,
505 context_sender,
506 closing.clone(),
507 font_context,
508 );
509
510 let context = context_receiver
511 .recv()
512 .expect("Couldn't receive a context for worker.");
513
514 (
515 ServiceWorker::new(scope_things.script_url, sender, worker_id),
516 join_handle,
517 control_sender,
518 context,
519 closing,
520 )
521}
522
523impl ServiceWorkerManagerFactory for ServiceWorkerManager {
524 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
525 let (resource_chan, resource_port) = ipc::channel().unwrap();
526
527 let SWManagerSenders {
528 resource_threads,
529 own_sender,
530 receiver,
531 swmanager_sender: constellation_sender,
532 system_font_service_sender,
533 paint_api,
534 } = sw_senders;
535
536 let from_constellation = receiver.route_preserving_errors();
537 let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
538 let _ = resource_threads
539 .core_thread
540 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
541
542 let font_context = Arc::new(FontContext::new(
543 Arc::new(system_font_service_sender.to_proxy()),
544 paint_api,
545 resource_threads,
546 ));
547
548 let swmanager_thread = move || {
549 ServiceWorkerManager::new(
550 own_sender,
551 from_constellation,
552 resource_port,
553 constellation_sender,
554 font_context,
555 )
556 .handle_message()
557 };
558 if thread::Builder::new()
559 .name("SvcWorkerManager".to_owned())
560 .spawn(swmanager_thread)
561 .is_err()
562 {
563 warn!("ServiceWorkerManager thread spawning failed");
564 }
565 }
566}
567
568pub(crate) fn serviceworker_enabled() -> bool {
569 pref!(dom_serviceworker_enabled)
570}