1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::thread::{self, JoinHandle};
14
15use base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
16use constellation_traits::{
17 DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerMsg, SWManagerSenders,
18 ScopeThings, ServiceWorkerManagerFactory, ServiceWorkerMsg,
19};
20use crossbeam_channel::{Receiver, RecvError, Sender, select, unbounded};
21use fonts::FontContext;
22use ipc_channel::ipc::{self, IpcSender};
23use ipc_channel::router::ROUTER;
24use net_traits::{CoreResourceMsg, CustomResponseMediator};
25use servo_config::pref;
26use servo_url::{ImmutableOrigin, ServoUrl};
27
28use crate::dom::abstractworker::WorkerScriptMsg;
29use crate::dom::serviceworkerglobalscope::{
30 ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
31};
32use crate::dom::serviceworkerregistration::longest_prefix_match;
33use crate::script_runtime::ThreadSafeJSContext;
34
35enum Message {
36 FromResource(CustomResponseMediator),
37 FromConstellation(Box<ServiceWorkerMsg>),
38}
39
40#[derive(Clone)]
42pub(crate) struct ServiceWorker {
43 pub(crate) id: ServiceWorkerId,
45 pub(crate) script_url: ServoUrl,
47 pub(crate) sender: Sender<ServiceWorkerScriptMsg>,
49}
50
51impl ServiceWorker {
52 fn new(
53 script_url: ServoUrl,
54 sender: Sender<ServiceWorkerScriptMsg>,
55 id: ServiceWorkerId,
56 ) -> ServiceWorker {
57 ServiceWorker {
58 id,
59 script_url,
60 sender,
61 }
62 }
63
64 fn forward_dom_message(&self, msg: DOMMessage) {
66 let DOMMessage { origin, data } = msg;
67 let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
68 WorkerScriptMsg::DOMMessage {
69 origin,
70 data: Box::new(data),
71 },
72 ));
73 }
74
75 fn send_message(&self, msg: ServiceWorkerScriptMsg) {
77 let _ = self.sender.send(msg);
78 }
79}
80
81#[allow(dead_code)]
83enum RegistrationUpdateTarget {
84 Installing,
85 Waiting,
86 Active,
87}
88
89impl Drop for ServiceWorkerRegistration {
90 fn drop(&mut self) {
92 if self
94 .control_sender
95 .take()
96 .expect("No control sender to worker thread.")
97 .send(ServiceWorkerControlMsg::Exit)
98 .is_err()
99 {
100 warn!("Failed to send exit message to service worker scope.");
101 }
102
103 self.closing
104 .take()
105 .expect("No close flag for worker")
106 .store(true, Ordering::SeqCst);
107 self.context
108 .take()
109 .expect("No context to request interrupt.")
110 .request_interrupt_callback();
111
112 if self
114 .join_handle
115 .take()
116 .expect("No handle to join on worker.")
117 .join()
118 .is_err()
119 {
120 warn!("Failed to join on service worker thread.");
121 }
122 }
123}
124
125struct ServiceWorkerRegistration {
127 id: ServiceWorkerRegistrationId,
129 active_worker: Option<ServiceWorker>,
131 waiting_worker: Option<ServiceWorker>,
133 installing_worker: Option<ServiceWorker>,
135 control_sender: Option<Sender<ServiceWorkerControlMsg>>,
138 join_handle: Option<JoinHandle<()>>,
140 context: Option<ThreadSafeJSContext>,
142 closing: Option<Arc<AtomicBool>>,
144}
145
146impl ServiceWorkerRegistration {
147 pub(crate) fn new() -> ServiceWorkerRegistration {
148 ServiceWorkerRegistration {
149 id: ServiceWorkerRegistrationId::new(),
150 active_worker: None,
151 waiting_worker: None,
152 installing_worker: None,
153 join_handle: None,
154 control_sender: None,
155 context: None,
156 closing: None,
157 }
158 }
159
160 fn note_worker_thread(
161 &mut self,
162 join_handle: JoinHandle<()>,
163 control_sender: Sender<ServiceWorkerControlMsg>,
164 context: ThreadSafeJSContext,
165 closing: Arc<AtomicBool>,
166 ) {
167 assert!(self.join_handle.is_none());
168 self.join_handle = Some(join_handle);
169
170 assert!(self.control_sender.is_none());
171 self.control_sender = Some(control_sender);
172
173 assert!(self.context.is_none());
174 self.context = Some(context);
175
176 assert!(self.closing.is_none());
177 self.closing = Some(closing);
178 }
179
180 fn get_newest_worker(&self) -> Option<ServiceWorker> {
182 if let Some(worker) = self.active_worker.as_ref() {
183 return Some(worker.clone());
184 }
185 if let Some(worker) = self.waiting_worker.as_ref() {
186 return Some(worker.clone());
187 }
188 if let Some(worker) = self.installing_worker.as_ref() {
189 return Some(worker.clone());
190 }
191 None
192 }
193
194 fn update_registration_state(
196 &mut self,
197 target: RegistrationUpdateTarget,
198 worker: ServiceWorker,
199 ) {
200 match target {
201 RegistrationUpdateTarget::Active => {
202 self.active_worker = Some(worker);
203 },
204 RegistrationUpdateTarget::Waiting => {
205 self.waiting_worker = Some(worker);
206 },
207 RegistrationUpdateTarget::Installing => {
208 self.installing_worker = Some(worker);
209 },
210 }
211 }
212}
213
214pub struct ServiceWorkerManager {
216 registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
218 _constellation_sender: IpcSender<SWManagerMsg>,
221 own_sender: IpcSender<ServiceWorkerMsg>,
223 own_port: Receiver<ServiceWorkerMsg>,
225 resource_receiver: Receiver<CustomResponseMediator>,
227 font_context: Arc<FontContext>,
229}
230
231impl ServiceWorkerManager {
232 fn new(
233 own_sender: IpcSender<ServiceWorkerMsg>,
234 from_constellation_receiver: Receiver<ServiceWorkerMsg>,
235 resource_port: Receiver<CustomResponseMediator>,
236 constellation_sender: IpcSender<SWManagerMsg>,
237 font_context: Arc<FontContext>,
238 ) -> ServiceWorkerManager {
239 PipelineNamespace::auto_install();
241
242 ServiceWorkerManager {
243 registrations: HashMap::new(),
244 own_sender,
245 own_port: from_constellation_receiver,
246 resource_receiver: resource_port,
247 _constellation_sender: constellation_sender,
248 font_context,
249 }
250 }
251
252 pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
253 for scope in self.registrations.keys() {
254 if longest_prefix_match(scope, load_url) {
255 return Some(scope.clone());
256 }
257 }
258 None
259 }
260
261 fn handle_message(&mut self) {
262 while let Ok(message) = self.receive_message() {
263 let should_continue = match message {
264 Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
265 Message::FromResource(msg) => self.handle_message_from_resource(msg),
266 };
267 if !should_continue {
268 for registration in self.registrations.drain() {
269 drop(registration);
271 }
272 break;
273 }
274 }
275 }
276
277 fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
278 if serviceworker_enabled() {
279 if let Some(scope) = self.get_matching_scope(&mediator.load_url) {
280 if let Some(registration) = self.registrations.get(&scope) {
281 if let Some(ref worker) = registration.active_worker {
282 worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
283 return true;
284 }
285 }
286 }
287 }
288 let _ = mediator.response_chan.send(None);
289 true
290 }
291
292 fn receive_message(&mut self) -> Result<Message, RecvError> {
293 select! {
294 recv(self.own_port) -> msg => msg.map(|m| Message::FromConstellation(Box::new(m))),
295 recv(self.resource_receiver) -> msg => msg.map(Message::FromResource),
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 if let Some(ref worker) = registration.active_worker {
307 worker.forward_dom_message(msg);
308 }
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 if worker.script_url != job.script_url {
400 let _ = job
401 .client
402 .send(JobResult::RejectPromise(JobError::TypeError));
403 return;
404 }
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: IpcSender<ServiceWorkerMsg>,
456 scope_url: ServoUrl,
457 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) = ipc::channel().unwrap();
468 let worker_id = ServiceWorkerId::new();
469
470 let (control_sender, control_receiver) = unbounded();
471 let (context_sender, context_receiver) = unbounded();
472 let closing = Arc::new(AtomicBool::new(false));
473
474 let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
475 scope_things.clone(),
476 sender.clone(),
477 receiver,
478 devtools_receiver,
479 own_sender,
480 scope_url.clone(),
481 control_receiver,
482 context_sender,
483 closing.clone(),
484 font_context,
485 );
486
487 let context = context_receiver
488 .recv()
489 .expect("Couldn't receive a context for worker.");
490
491 (
492 ServiceWorker::new(scope_things.script_url, sender, worker_id),
493 join_handle,
494 control_sender,
495 context,
496 closing,
497 )
498}
499
500impl ServiceWorkerManagerFactory for ServiceWorkerManager {
501 fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
502 let (resource_chan, resource_port) = ipc::channel().unwrap();
503
504 let SWManagerSenders {
505 resource_threads,
506 own_sender,
507 receiver,
508 swmanager_sender: constellation_sender,
509 system_font_service_sender,
510 compositor_api,
511 } = sw_senders;
512
513 let from_constellation = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(receiver);
514 let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
515 let _ = resource_threads
516 .core_thread
517 .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
518
519 let font_context = Arc::new(FontContext::new(
520 Arc::new(system_font_service_sender.to_proxy()),
521 compositor_api,
522 resource_threads,
523 ));
524
525 let swmanager_thread = move || {
526 ServiceWorkerManager::new(
527 own_sender,
528 from_constellation,
529 resource_port,
530 constellation_sender,
531 font_context,
532 )
533 .handle_message()
534 };
535 if thread::Builder::new()
536 .name("SvcWorkerManager".to_owned())
537 .spawn(swmanager_thread)
538 .is_err()
539 {
540 warn!("ServiceWorkerManager thread spawning failed");
541 }
542 }
543}
544
545pub(crate) fn serviceworker_enabled() -> bool {
546 pref!(dom_serviceworker_enabled)
547}