1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::thread::{self, JoinHandle};
14
15use base::generic_channel::{self, GenericSender, ReceiveError, RoutedReceiver};
16use base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
17use constellation_traits::{
18 DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerMsg, SWManagerSenders,
19 ScopeThings, ServiceWorkerManagerFactory, ServiceWorkerMsg,
20};
21use crossbeam_channel::{Receiver, Sender, select, unbounded};
22use fonts::FontContext;
23use ipc_channel::ipc;
24use ipc_channel::router::ROUTER;
25use net_traits::{CoreResourceMsg, CustomResponseMediator};
26use servo_config::pref;
27use servo_url::{ImmutableOrigin, ServoUrl};
28
29use crate::dom::abstractworker::WorkerScriptMsg;
30use crate::dom::serviceworkerglobalscope::{
31 ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
32};
33use crate::dom::serviceworkerregistration::longest_prefix_match;
34use crate::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 { origin, data } = msg;
68 let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
69 WorkerScriptMsg::DOMMessage {
70 origin,
71 data: Box::new(data),
72 },
73 ));
74 }
75
76 fn send_message(&self, msg: ServiceWorkerScriptMsg) {
78 let _ = self.sender.send(msg);
79 }
80}
81
82#[allow(dead_code)]
84enum RegistrationUpdateTarget {
85 Installing,
86 Waiting,
87 Active,
88}
89
90impl Drop for ServiceWorkerRegistration {
91 fn drop(&mut self) {
93 if self
95 .control_sender
96 .take()
97 .expect("No control sender to worker thread.")
98 .send(ServiceWorkerControlMsg::Exit)
99 .is_err()
100 {
101 warn!("Failed to send exit message to service worker scope.");
102 }
103
104 self.closing
105 .take()
106 .expect("No close flag for worker")
107 .store(true, Ordering::SeqCst);
108 self.context
109 .take()
110 .expect("No context to request interrupt.")
111 .request_interrupt_callback();
112
113 if self
115 .join_handle
116 .take()
117 .expect("No handle to join on worker.")
118 .join()
119 .is_err()
120 {
121 warn!("Failed to join on service worker thread.");
122 }
123 }
124}
125
126struct ServiceWorkerRegistration {
128 id: ServiceWorkerRegistrationId,
130 active_worker: Option<ServiceWorker>,
132 waiting_worker: Option<ServiceWorker>,
134 installing_worker: Option<ServiceWorker>,
136 control_sender: Option<Sender<ServiceWorkerControlMsg>>,
139 join_handle: Option<JoinHandle<()>>,
141 context: Option<ThreadSafeJSContext>,
143 closing: Option<Arc<AtomicBool>>,
145}
146
147impl ServiceWorkerRegistration {
148 pub(crate) fn new() -> ServiceWorkerRegistration {
149 ServiceWorkerRegistration {
150 id: ServiceWorkerRegistrationId::new(),
151 active_worker: None,
152 waiting_worker: None,
153 installing_worker: None,
154 join_handle: None,
155 control_sender: None,
156 context: None,
157 closing: None,
158 }
159 }
160
161 fn note_worker_thread(
162 &mut self,
163 join_handle: JoinHandle<()>,
164 control_sender: Sender<ServiceWorkerControlMsg>,
165 context: ThreadSafeJSContext,
166 closing: Arc<AtomicBool>,
167 ) {
168 assert!(self.join_handle.is_none());
169 self.join_handle = Some(join_handle);
170
171 assert!(self.control_sender.is_none());
172 self.control_sender = Some(control_sender);
173
174 assert!(self.context.is_none());
175 self.context = Some(context);
176
177 assert!(self.closing.is_none());
178 self.closing = Some(closing);
179 }
180
181 fn get_newest_worker(&self) -> Option<ServiceWorker> {
183 if let Some(worker) = self.active_worker.as_ref() {
184 return Some(worker.clone());
185 }
186 if let Some(worker) = self.waiting_worker.as_ref() {
187 return Some(worker.clone());
188 }
189 if let Some(worker) = self.installing_worker.as_ref() {
190 return Some(worker.clone());
191 }
192 None
193 }
194
195 fn update_registration_state(
197 &mut self,
198 target: RegistrationUpdateTarget,
199 worker: ServiceWorker,
200 ) {
201 match target {
202 RegistrationUpdateTarget::Active => {
203 self.active_worker = Some(worker);
204 },
205 RegistrationUpdateTarget::Waiting => {
206 self.waiting_worker = Some(worker);
207 },
208 RegistrationUpdateTarget::Installing => {
209 self.installing_worker = Some(worker);
210 },
211 }
212 }
213}
214
215pub struct ServiceWorkerManager {
217 registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
219 _constellation_sender: GenericSender<SWManagerMsg>,
222 own_sender: GenericSender<ServiceWorkerMsg>,
224 own_port: RoutedReceiver<ServiceWorkerMsg>,
226 resource_receiver: Receiver<CustomResponseMediator>,
228 font_context: Arc<FontContext>,
230}
231
232impl ServiceWorkerManager {
233 fn new(
234 own_sender: GenericSender<ServiceWorkerMsg>,
235 from_constellation_receiver: RoutedReceiver<ServiceWorkerMsg>,
236 resource_port: Receiver<CustomResponseMediator>,
237 constellation_sender: GenericSender<SWManagerMsg>,
238 font_context: Arc<FontContext>,
239 ) -> ServiceWorkerManager {
240 PipelineNamespace::auto_install();
242
243 ServiceWorkerManager {
244 registrations: HashMap::new(),
245 own_sender,
246 own_port: from_constellation_receiver,
247 resource_receiver: resource_port,
248 _constellation_sender: constellation_sender,
249 font_context,
250 }
251 }
252
253 pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
254 for scope in self.registrations.keys() {
255 if longest_prefix_match(scope, load_url) {
256 return Some(scope.clone());
257 }
258 }
259 None
260 }
261
262 fn handle_message(&mut self) {
263 while let Ok(message) = self.receive_message() {
264 let should_continue = match message {
265 Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
266 Message::FromResource(msg) => self.handle_message_from_resource(msg),
267 };
268 if !should_continue {
269 for registration in self.registrations.drain() {
270 drop(registration);
272 }
273 break;
274 }
275 }
276 }
277
278 fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
279 if serviceworker_enabled() {
280 if let Some(scope) = self.get_matching_scope(&mediator.load_url) {
281 if let Some(registration) = self.registrations.get(&scope) {
282 if let Some(ref worker) = registration.active_worker {
283 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
284 return true;
285 }
286 }
287 }
288 }
289 let _ = mediator.response_chan.send(None);
290 true
291 }
292
293 fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
294 select! {
295 recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
296 recv(self.resource_receiver) -> msg => msg.map(Message::FromResource).map_err(|_e| ReceiveError::Disconnected),
297 }
298 }
299
300 fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
301 match msg {
302 ServiceWorkerMsg::Timeout(_scope) => {
303 },
305 ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
306 if let Some(registration) = self.registrations.get_mut(&scope_url) {
307 if let Some(ref worker) = registration.active_worker {
308 worker.forward_dom_message(msg);
309 }
310 }
311 },
312 ServiceWorkerMsg::ScheduleJob(job) => match job.job_type {
313 JobType::Register => {
314 self.handle_register_job(job);
315 },
316 JobType::Update => {
317 self.handle_update_job(job);
318 },
319 JobType::Unregister => {
320 },
322 },
323 ServiceWorkerMsg::Exit => return false,
324 }
325 true
326 }
327
328 fn handle_register_job(&mut self, mut job: Job) {
330 if !job.script_url.origin().is_potentially_trustworthy() {
331 let _ = job
333 .client
334 .send(JobResult::RejectPromise(JobError::SecurityError));
335 return;
336 }
337
338 if job.script_url.origin() != job.referrer.origin() ||
339 job.scope_url.origin() != job.referrer.origin()
340 {
341 let _ = job
343 .client
344 .send(JobResult::RejectPromise(JobError::SecurityError));
345 return;
346 }
347
348 if let Some(registration) = self.registrations.get(&job.scope_url) {
350 let newest_worker = registration.get_newest_worker();
354
355 if newest_worker.is_some() {
357 let client = job.client.clone();
361 let _ = client.send(JobResult::ResolvePromise(
362 job,
363 JobResultValue::Registration {
364 id: registration.id,
365 installing_worker: registration
366 .installing_worker
367 .as_ref()
368 .map(|worker| worker.id),
369 waiting_worker: registration
370 .waiting_worker
371 .as_ref()
372 .map(|worker| worker.id),
373 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
374 },
375 ));
376 }
377 } else {
378 let new_registration = ServiceWorkerRegistration::new();
382 self.registrations
383 .insert(job.scope_url.clone(), new_registration);
384
385 job.job_type = JobType::Update;
387 let _ = self.own_sender.send(ServiceWorkerMsg::ScheduleJob(job));
388 }
389 }
390
391 fn handle_update_job(&mut self, job: Job) {
393 if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
395 let newest_worker = registration.get_newest_worker();
397
398 if let Some(worker) = newest_worker {
400 if worker.script_url != job.script_url {
401 let _ = job
402 .client
403 .send(JobResult::RejectPromise(JobError::TypeError));
404 return;
405 }
406 }
407
408 let scope_things = job
409 .scope_things
410 .clone()
411 .expect("Update job should have scope things.");
412
413 let (new_worker, join_handle, control_sender, context, closing) = update_serviceworker(
416 self.own_sender.clone(),
417 job.scope_url.clone(),
418 scope_things,
419 self.font_context.clone(),
420 );
421
422 registration.note_worker_thread(join_handle, control_sender, context, closing);
424
425 registration
429 .update_registration_state(RegistrationUpdateTarget::Installing, new_worker);
430
431 let client = job.client.clone();
433 let _ = client.send(JobResult::ResolvePromise(
434 job,
435 JobResultValue::Registration {
436 id: registration.id,
437 installing_worker: registration
438 .installing_worker
439 .as_ref()
440 .map(|worker| worker.id),
441 waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
442 active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
443 },
444 ));
445 } else {
446 let _ = job
448 .client
449 .send(JobResult::RejectPromise(JobError::TypeError));
450 }
451 }
452}
453
454fn update_serviceworker(
456 own_sender: GenericSender<ServiceWorkerMsg>,
457 scope_url: ServoUrl,
458 scope_things: ScopeThings,
459 font_context: Arc<FontContext>,
460) -> (
461 ServiceWorker,
462 JoinHandle<()>,
463 Sender<ServiceWorkerControlMsg>,
464 ThreadSafeJSContext,
465 Arc<AtomicBool>,
466) {
467 let (sender, receiver) = unbounded();
468 let (_devtools_sender, devtools_receiver) = ipc::channel().unwrap();
469 let worker_id = ServiceWorkerId::new();
470
471 let (control_sender, control_receiver) = unbounded();
472 let (context_sender, context_receiver) = unbounded();
473 let closing = Arc::new(AtomicBool::new(false));
474
475 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
476 scope_things.clone(),
477 sender.clone(),
478 receiver,
479 devtools_receiver,
480 own_sender,
481 scope_url.clone(),
482 control_receiver,
483 context_sender,
484 closing.clone(),
485 font_context,
486 );
487
488 let context = context_receiver
489 .recv()
490 .expect("Couldn't receive a context for worker.");
491
492 (
493 ServiceWorker::new(scope_things.script_url, sender, worker_id),
494 join_handle,
495 control_sender,
496 context,
497 closing,
498 )
499}
500
501impl ServiceWorkerManagerFactory for ServiceWorkerManager {
502 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
503 let (resource_chan, resource_port) = ipc::channel().unwrap();
504
505 let SWManagerSenders {
506 resource_threads,
507 own_sender,
508 receiver,
509 swmanager_sender: constellation_sender,
510 system_font_service_sender,
511 compositor_api,
512 } = sw_senders;
513
514 let from_constellation = receiver.route_preserving_errors();
515 let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
516 let _ = resource_threads
517 .core_thread
518 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
519
520 let font_context = Arc::new(FontContext::new(
521 Arc::new(system_font_service_sender.to_proxy()),
522 compositor_api,
523 resource_threads,
524 ));
525
526 let swmanager_thread = move || {
527 ServiceWorkerManager::new(
528 own_sender,
529 from_constellation,
530 resource_port,
531 constellation_sender,
532 font_context,
533 )
534 .handle_message()
535 };
536 if thread::Builder::new()
537 .name("SvcWorkerManager".to_owned())
538 .spawn(swmanager_thread)
539 .is_err()
540 {
541 warn!("ServiceWorkerManager thread spawning failed");
542 }
543 }
544}
545
546pub(crate) fn serviceworker_enabled() -> bool {
547 pref!(dom_serviceworker_enabled)
548}