Skip to main content

script/dom/serviceworker/
serviceworker_manager.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The service worker manager persists the descriptor of any registered service workers.
6//! It also stores an active workers map, which holds descriptors of running service workers.
7//! If an active service worker timeouts, then it removes the descriptor entry from its
8//! active_workers map
9
10use 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/// <https://w3c.github.io/ServiceWorker/#dfn-service-worker>
42#[derive(Clone)]
43pub(crate) struct ServiceWorker {
44    /// A unique identifer.
45    pub(crate) id: ServiceWorkerId,
46    /// <https://w3c.github.io/ServiceWorker/#dfn-script-url>
47    pub(crate) script_url: ServoUrl,
48    /// A sender to the running service worker scope.
49    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    /// Forward a DOM message to the running service worker scope.
66    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    /// Send a message to the running service worker scope.
82    fn send_message(&self, msg: ServiceWorkerScriptMsg) {
83        let _ = self.sender.send(msg);
84    }
85}
86
87/// When updating a registration, which worker are we targetting?
88#[expect(dead_code)]
89enum RegistrationUpdateTarget {
90    Installing,
91    Waiting,
92    Active,
93}
94
95impl Drop for ServiceWorkerRegistration {
96    /// <https://html.spec.whatwg.org/multipage/#terminate-a-worker>
97    fn drop(&mut self) {
98        // Drop the channel to signal shutdown.
99        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        // TODO: Step 1, 2 and 3.
119        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
131/// <https://w3c.github.io/ServiceWorker/#service-worker-registration-concept>
132struct ServiceWorkerRegistration {
133    /// A unique identifer.
134    id: ServiceWorkerRegistrationId,
135    /// <https://w3c.github.io/ServiceWorker/#dfn-active-worker>
136    active_worker: Option<ServiceWorker>,
137    /// <https://w3c.github.io/ServiceWorker/#dfn-waiting-worker>
138    waiting_worker: Option<ServiceWorker>,
139    /// <https://w3c.github.io/ServiceWorker/#dfn-installing-worker>
140    installing_worker: Option<ServiceWorker>,
141    /// A channel to send control message to the worker,
142    /// currently only used to signal shutdown.
143    control_sender: Option<Sender<ServiceWorkerControlMsg>>,
144    /// A handle to join on the worker thread.
145    join_handle: Option<JoinHandle<()>>,
146    /// A context to request an interrupt.
147    context: Option<ThreadSafeJSContext>,
148    /// The closing flag for the worker.
149    closing: Option<Arc<AtomicBool>>,
150    /// <https://w3c.github.io/ServiceWorker/#serviceworkercontainer-service-worker-client>
151    /// The client of the container to which this registration belongs.
152    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    /// <https://w3c.github.io/ServiceWorker/#get-newest-worker>
193    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    /// <https://w3c.github.io/ServiceWorker/#update-registration-state>
207    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
226/// A structure managing all registrations and workers for a given origin.
227pub struct ServiceWorkerManager {
228    /// <https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map>
229    registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
230    // own sender to send messages here
231    own_sender: GenericSender<ServiceWorkerMsg>,
232    // receiver to receive messages from constellation
233    own_port: RoutedReceiver<ServiceWorkerMsg>,
234    // to receive resource messages
235    resource_receiver: RoutedReceiver<CustomResponseMediator>,
236    /// A shared [`FontContext`] to use for all service workers spawned by this [`ServiceWorkerManager`].
237    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        // Install a pipeline-namespace in the current thread.
248        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                    // Signal shut-down, and join on the thread.
277                    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                // TODO: https://w3c.github.io/ServiceWorker/#terminate-service-worker
308            },
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    /// <https://w3c.github.io/ServiceWorker/#unregister>
375    fn handle_unregister_job(&mut self, job: Job) {
376        // Step 1: Let registration be the result of running Get Registration given job’s storage key and job’s scope url.
377        if self.registrations.remove(&job.scope_url).is_none() {
378            // Step 2: If registration is null, then:
379            // Step 2.1: Invoke Resolve Job Promise with job and false.
380            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            // Step 2.2: Invoke Finish Job with job and abort these steps.
390            // TODO: Finish Job.
391            return;
392        };
393
394        // Step 3: Remove registration map[(registration’s storage key, job’s scope url)].
395        // Note: done by removing the registration from the map above.
396
397        // Step 4: Invoke Resolve Job Promise with job and true.
398        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        // Step 5: Invoke Try Clear Registration with registration.
409        // TODO: Try Clear Registration.
410
411        // Step 6: Invoke Finish Job with job.
412        // TODO: Finish Job.
413    }
414
415    /// <https://w3c.github.io/ServiceWorker/#match-service-worker-registration>
416    fn handle_match_registration(
417        &self,
418        storage_key: ImmutableOrigin,
419        client_url: ServoUrl,
420        result_handler: GenericCallback<ServiceWorkerAlgorithmResult>,
421    ) {
422        // Step 1: Run the following steps atomically.
423        // Note: done using the channel from which this message was received.
424
425        // Step 2: Let clientURLString be serialized clientURL.
426        let client_url_string = client_url.as_str();
427
428        // Step 3: Let matchingScopeString be the empty string.
429        let mut matching_scope_string = String::new();
430
431        // Step 4: Let scopeStringSet be an empty list.
432        let mut scope_string_set = Vec::new();
433
434        // Step 5: For each (entry storage key, entry scope) of registration map’s keys:
435        for (entry_storage_key, entry_scope) in self.registrations.keys().map(|k| (k.origin(), k)) {
436            // Step 5.1. If storage key equals entry storage key, then append entry scope to the end of scopeStringSet.
437            if storage_key == entry_storage_key {
438                scope_string_set.push(entry_scope.as_str());
439            }
440        }
441
442        // Step 6: Set matchingScopeString to the longest value in scopeStringSet which the value of clientURLString starts with, if it exists.
443        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        // Step 7: Let matchingScope be null.
450        let mut matching_scope = None;
451
452        // Step 8: If matchingScopeString is not the empty string, then:
453        if !matching_scope_string.is_empty() {
454            // Step 8.1. Set matchingScope to the result of parsing matchingScopeString.
455            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            // Step 8.2: Assert: matchingScope’s origin and clientURL’s origin are same origin.
468            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        // Step 9: Return the result of running Get Registration given storage key and matchingScope.
485        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    /// <https://w3c.github.io/ServiceWorker/#register-algorithm>
512    fn handle_register_job(&mut self, mut job: Job) {
513        // Step 1: If the result of running potentially trustworthy origin with the origin of job’s script url as the argument is Not Trusted, then:
514        if !job.script_url.origin().is_potentially_trustworthy() {
515            // Step 1.1: Invoke Reject Job Promise with job and "SecurityError" DOMException.
516            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            // TODO Step 1.2: Invoke Finish Job with job and abort these steps.
527            // TODO: Finish Job.
528            return;
529        }
530
531        // Step 2: If job’s script url’s origin and job’s referrer’s origin are not same origin, then:
532        // Step 3: If job’s scope url’s origin and job’s referrer’s origin are not same origin, then:
533        // Note: both steps done in one conditional.
534        if job.script_url.origin() != job.referrer.origin() ||
535            job.scope_url.origin() != job.referrer.origin()
536        {
537            // Step 2.1: Invoke Reject Job Promise with job and "SecurityError" DOMException
538            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            // TODO Step 2.2: Invoke Finish Job with job and abort these steps.
549            return;
550        }
551
552        // Step 4: Let registration be the result of running Get Registration given job’s storage key and job’s scope url.
553        if let Some(registration) = self.registrations.get(&job.scope_url) {
554            // Step 5: If registration is not null, then:
555
556            // Step 5.1: Let newestWorker be the result of running the Get Newest Worker algorithm passing registration as the argument.
557            let newest_worker = registration.get_newest_worker();
558
559            // step 5.2: If newestWorker is not null, job’s script url equals newestWorker’s script url, job’s worker type equals newestWorker’s type, and job’s update via cache mode’s value equals registration’s update via cache mode, then:
560            if newest_worker.is_some() {
561                // TODO: the various checks of job versus worker.
562
563                // Step 5.2.1: Invoke Resolve Job Promise with job and registration.
564                // Step 5.2.2: Invoke Finish Job with job and abort these steps.
565                // TODO: Finish Job.
566                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            // Step 6: Else
592            // Step 6.1: Invoke Set Registration algorithm with job’s storage key, job’s scope url, and job’s update via cache mode.
593            let new_registration = ServiceWorkerRegistration::new(job.client.clone());
594            self.registrations
595                .insert(job.scope_url.clone(), new_registration);
596
597            // Step 7: Invoke Update algorithm passing job as the argument.
598            job.job_type = JobType::Update;
599            self.handle_update_job(job);
600        }
601    }
602
603    /// <https://www.w3.org/TR/service-workers/#install>
604    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        // Step 4: Run the Update Registration State algorithm passing registration, "installing" and worker as the arguments.
611        registration.update_registration_state(
612            RegistrationUpdateTarget::Installing,
613            Some(new_worker.clone()),
614        );
615
616        // Step 7: Invoke Resolve Job Promise with job and registration
617        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        // Step 17: Run the Update Registration State algorithm passing registration,
644        // "waiting" and registration’s installing worker as the arguments.
645        registration.update_registration_state(RegistrationUpdateTarget::Waiting, Some(new_worker));
646
647        // Step 18: Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
648        // TODO: registration.update_registration_state(RegistrationUpdateTarget::Installing, None);
649        // Note: commenting out for now, because it causes errors in tests,
650        // probably because we are still lacking functionality elsewhere.
651
652        // Step 21: Wait for all the tasks queued by Update Worker State invoked in this algorithm to have executed.
653        // TODO: queue tasks above and wait for them to execute.
654    }
655
656    /// <https://w3c.github.io/ServiceWorker/#update>
657    fn handle_update_job(&mut self, job: Job) {
658        // Step 1: Get registation
659        let (job, new_worker) =
660            if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
661                // Step 3.
662                let newest_worker = registration.get_newest_worker();
663
664                // Step 4.
665                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                // Very roughly steps 5 to 18.
680                // TODO: implement all steps precisely.
681                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                // Since we've just started the worker thread, ensure we can shut it down later.
690                registration.note_worker_thread(join_handle, control_sender, context, closing);
691
692                (job, new_worker)
693            } else {
694                // Step 2
695                let _ =
696                    job.client
697                        .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
698                            JobError::TypeError,
699                        )));
700                return;
701            };
702        // Step 17: Else, invoke Install algorithm with job, worker, and registration as its arguments.
703        self.install(job, new_worker);
704    }
705}
706
707/// <https://w3c.github.io/ServiceWorker/#update-algorithm>
708fn 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}