Skip to main content

script/
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::{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::{
22    self, GenericCallback, GenericSender, ReceiveError, RoutedReceiver,
23};
24use servo_base::id::{PipelineNamespace, ServiceWorkerId, ServiceWorkerRegistrationId};
25use servo_config::pref;
26use servo_constellation_traits::{
27    DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerSenders, ScopeThings,
28    ServiceWorkerAlgorithm, ServiceWorkerAlgorithmResult, ServiceWorkerManagerFactory,
29    ServiceWorkerMsg, ServiceWorkerRegistrationInfo,
30};
31use servo_url::{ImmutableOrigin, ServoUrl};
32
33use crate::dom::abstractworker::{MessageData, WorkerScriptMsg};
34use crate::dom::serviceworkerglobalscope::{
35    ServiceWorkerControlMsg, ServiceWorkerGlobalScope, ServiceWorkerScriptMsg,
36};
37use crate::dom::serviceworkerregistration::longest_prefix_match;
38use crate::script_runtime::ThreadSafeJSContext;
39
40enum Message {
41    FromResource(CustomResponseMediator),
42    FromConstellation(Box<ServiceWorkerMsg>),
43}
44
45/// <https://w3c.github.io/ServiceWorker/#dfn-service-worker>
46#[derive(Clone)]
47pub(crate) struct ServiceWorker {
48    /// A unique identifer.
49    pub(crate) id: ServiceWorkerId,
50    /// <https://w3c.github.io/ServiceWorker/#dfn-script-url>
51    pub(crate) script_url: ServoUrl,
52    /// A sender to the running service worker scope.
53    pub(crate) sender: Sender<ServiceWorkerScriptMsg>,
54}
55
56impl ServiceWorker {
57    fn new(
58        script_url: ServoUrl,
59        sender: Sender<ServiceWorkerScriptMsg>,
60        id: ServiceWorkerId,
61    ) -> ServiceWorker {
62        ServiceWorker {
63            id,
64            script_url,
65            sender,
66        }
67    }
68
69    /// Forward a DOM message to the running service worker scope.
70    fn forward_dom_message(&self, msg: DOMMessage) {
71        let DOMMessage {
72            origin,
73            data,
74            pipeline_id,
75        } = msg;
76        let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker(
77            WorkerScriptMsg::DOMMessage(MessageData {
78                origin,
79                pipeline_id,
80                data: Box::new(data),
81            }),
82        ));
83    }
84
85    /// Send a message to the running service worker scope.
86    fn send_message(&self, msg: ServiceWorkerScriptMsg) {
87        let _ = self.sender.send(msg);
88    }
89}
90
91/// When updating a registration, which worker are we targetting?
92#[expect(dead_code)]
93enum RegistrationUpdateTarget {
94    Installing,
95    Waiting,
96    Active,
97}
98
99impl Drop for ServiceWorkerRegistration {
100    /// <https://html.spec.whatwg.org/multipage/#terminate-a-worker>
101    fn drop(&mut self) {
102        // Drop the channel to signal shutdown.
103        if self
104            .control_sender
105            .take()
106            .expect("No control sender to worker thread.")
107            .send(ServiceWorkerControlMsg::Exit)
108            .is_err()
109        {
110            warn!("Failed to send exit message to service worker scope.");
111        }
112
113        self.closing
114            .take()
115            .expect("No close flag for worker")
116            .store(true, Ordering::SeqCst);
117        self.context
118            .take()
119            .expect("No context to request interrupt.")
120            .request_interrupt_callback();
121
122        // TODO: Step 1, 2 and 3.
123        if self
124            .join_handle
125            .take()
126            .expect("No handle to join on worker.")
127            .join()
128            .is_err()
129        {
130            warn!("Failed to join on service worker thread.");
131        }
132    }
133}
134
135/// <https://w3c.github.io/ServiceWorker/#service-worker-registration-concept>
136struct ServiceWorkerRegistration {
137    /// A unique identifer.
138    id: ServiceWorkerRegistrationId,
139    /// <https://w3c.github.io/ServiceWorker/#dfn-active-worker>
140    active_worker: Option<ServiceWorker>,
141    /// <https://w3c.github.io/ServiceWorker/#dfn-waiting-worker>
142    waiting_worker: Option<ServiceWorker>,
143    /// <https://w3c.github.io/ServiceWorker/#dfn-installing-worker>
144    installing_worker: Option<ServiceWorker>,
145    /// A channel to send control message to the worker,
146    /// currently only used to signal shutdown.
147    control_sender: Option<Sender<ServiceWorkerControlMsg>>,
148    /// A handle to join on the worker thread.
149    join_handle: Option<JoinHandle<()>>,
150    /// A context to request an interrupt.
151    context: Option<ThreadSafeJSContext>,
152    /// The closing flag for the worker.
153    closing: Option<Arc<AtomicBool>>,
154    /// <https://w3c.github.io/ServiceWorker/#serviceworkercontainer-service-worker-client>
155    /// The client of the container to which this registration belongs.
156    client: GenericCallback<ServiceWorkerAlgorithmResult>,
157}
158
159impl ServiceWorkerRegistration {
160    pub(crate) fn new(
161        client: GenericCallback<ServiceWorkerAlgorithmResult>,
162    ) -> ServiceWorkerRegistration {
163        ServiceWorkerRegistration {
164            id: ServiceWorkerRegistrationId::new(),
165            active_worker: None,
166            waiting_worker: None,
167            installing_worker: None,
168            join_handle: None,
169            control_sender: None,
170            context: None,
171            closing: None,
172            client,
173        }
174    }
175
176    fn note_worker_thread(
177        &mut self,
178        join_handle: JoinHandle<()>,
179        control_sender: Sender<ServiceWorkerControlMsg>,
180        context: ThreadSafeJSContext,
181        closing: Arc<AtomicBool>,
182    ) {
183        assert!(self.join_handle.is_none());
184        self.join_handle = Some(join_handle);
185
186        assert!(self.control_sender.is_none());
187        self.control_sender = Some(control_sender);
188
189        assert!(self.context.is_none());
190        self.context = Some(context);
191
192        assert!(self.closing.is_none());
193        self.closing = Some(closing);
194    }
195
196    /// <https://w3c.github.io/ServiceWorker/#get-newest-worker>
197    fn get_newest_worker(&self) -> Option<ServiceWorker> {
198        if let Some(worker) = self.active_worker.as_ref() {
199            return Some(worker.clone());
200        }
201        if let Some(worker) = self.waiting_worker.as_ref() {
202            return Some(worker.clone());
203        }
204        if let Some(worker) = self.installing_worker.as_ref() {
205            return Some(worker.clone());
206        }
207        None
208    }
209
210    /// <https://w3c.github.io/ServiceWorker/#update-registration-state>
211    fn update_registration_state(
212        &mut self,
213        target: RegistrationUpdateTarget,
214        worker: Option<ServiceWorker>,
215    ) {
216        match target {
217            RegistrationUpdateTarget::Active => {
218                self.active_worker = worker;
219            },
220            RegistrationUpdateTarget::Waiting => {
221                self.waiting_worker = worker;
222            },
223            RegistrationUpdateTarget::Installing => {
224                self.installing_worker = worker;
225            },
226        }
227    }
228}
229
230/// A structure managing all registrations and workers for a given origin.
231pub struct ServiceWorkerManager {
232    /// <https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map>
233    registrations: HashMap<ServoUrl, ServiceWorkerRegistration>,
234    // own sender to send messages here
235    own_sender: GenericSender<ServiceWorkerMsg>,
236    // receiver to receive messages from constellation
237    own_port: RoutedReceiver<ServiceWorkerMsg>,
238    // to receive resource messages
239    resource_receiver: Receiver<CustomResponseMediator>,
240    /// A shared [`FontContext`] to use for all service workers spawned by this [`ServiceWorkerManager`].
241    font_context: Arc<FontContext>,
242}
243
244impl ServiceWorkerManager {
245    fn new(
246        own_sender: GenericSender<ServiceWorkerMsg>,
247        from_constellation_receiver: RoutedReceiver<ServiceWorkerMsg>,
248        resource_port: Receiver<CustomResponseMediator>,
249        font_context: Arc<FontContext>,
250    ) -> ServiceWorkerManager {
251        // Install a pipeline-namespace in the current thread.
252        PipelineNamespace::auto_install();
253
254        ServiceWorkerManager {
255            registrations: HashMap::new(),
256            own_sender,
257            own_port: from_constellation_receiver,
258            resource_receiver: resource_port,
259            font_context,
260        }
261    }
262
263    pub(crate) fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> {
264        for scope in self.registrations.keys() {
265            if longest_prefix_match(scope, load_url) {
266                return Some(scope.clone());
267            }
268        }
269        None
270    }
271
272    fn handle_message(&mut self) {
273        while let Ok(message) = self.receive_message() {
274            let should_continue = match message {
275                Message::FromConstellation(msg) => self.handle_message_from_constellation(*msg),
276                Message::FromResource(msg) => self.handle_message_from_resource(msg),
277            };
278            if !should_continue {
279                for registration in self.registrations.drain() {
280                    // Signal shut-down, and join on the thread.
281                    drop(registration);
282                }
283                break;
284            }
285        }
286    }
287
288    fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
289        if serviceworker_enabled() &&
290            let Some(scope) = self.get_matching_scope(&mediator.load_url) &&
291            let Some(registration) = self.registrations.get(&scope) &&
292            let Some(ref worker) = registration.active_worker
293        {
294            worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
295            return true;
296        }
297        let _ = mediator.response_chan.send(None);
298        true
299    }
300
301    fn receive_message(&mut self) -> generic_channel::ReceiveResult<Message> {
302        select! {
303            recv(self.own_port) -> result_msg => generic_channel::to_receive_result::<ServiceWorkerMsg>(result_msg).map(|msg| Message::FromConstellation(Box::new(msg))),
304            recv(self.resource_receiver) -> msg => msg.map(Message::FromResource).map_err(|_e| ReceiveError::Disconnected),
305        }
306    }
307
308    fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
309        match msg {
310            ServiceWorkerMsg::Timeout(_scope) => {
311                // TODO: https://w3c.github.io/ServiceWorker/#terminate-service-worker
312            },
313            ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
314                if let Some(registration) = self.registrations.get_mut(&scope_url) {
315                    if let Some(ref worker) = registration.active_worker {
316                        worker.forward_dom_message(msg);
317                    } else if let Some(ref worker) = registration.waiting_worker {
318                        worker.forward_dom_message(msg);
319                    } else if let Some(ref worker) = registration.installing_worker {
320                        worker.forward_dom_message(msg);
321                    }
322                }
323            },
324            ServiceWorkerMsg::ForwardWorkerMessage {
325                data,
326                url,
327                source,
328                origin,
329            } => {
330                let Some(registration) = self.registrations.get(&url) else {
331                    warn!("No registration found for scope URL when forwarding message to worker.");
332                    return true;
333                };
334                let script_url = if let Some(worker) = registration.active_worker.as_ref() {
335                    worker.script_url.clone()
336                } else if let Some(worker) = registration.waiting_worker.as_ref() {
337                    worker.script_url.clone()
338                } else if let Some(worker) = registration.installing_worker.as_ref() {
339                    worker.script_url.clone()
340                } else {
341                    warn!("No worker found for scope URL when forwarding message to worker.");
342                    return true;
343                };
344                if registration
345                    .client
346                    .send(ServiceWorkerAlgorithmResult::MessageFromWorker {
347                        message: data,
348                        source,
349                        scope_url: url,
350                        script_url,
351                        origin,
352                    })
353                    .is_err()
354                {
355                    warn!("Failed to forward message from worker to script.");
356                }
357            },
358            ServiceWorkerMsg::HandleAlgorithm(algorithm) => match algorithm {
359                ServiceWorkerAlgorithm::StartRegister(job) => {
360                    self.handle_register_job(job);
361                },
362                ServiceWorkerAlgorithm::Unregister(job) => {
363                    self.handle_unregister_job(job);
364                },
365                ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
366                    storage_key,
367                    client_url,
368                    result_handler,
369                } => {
370                    self.handle_match_registration(storage_key, client_url, result_handler);
371                },
372            },
373            ServiceWorkerMsg::Exit => return false,
374        }
375        true
376    }
377
378    /// <https://w3c.github.io/ServiceWorker/#unregister>
379    fn handle_unregister_job(&mut self, job: Job) {
380        // Step 1: Let registration be the result of running Get Registration given job’s storage key and job’s scope url.
381        if self.registrations.remove(&job.scope_url).is_none() {
382            // Step 2: If registration is null, then:
383            // Step 2.1: Invoke Resolve Job Promise with job and false.
384            if job
385                .client
386                .send(ServiceWorkerAlgorithmResult::Job(
387                    JobResult::ResolvePromise(JobResultValue::Unregister(false)),
388                ))
389                .is_err()
390            {
391                warn!("Failed to send unregister result to script.");
392            }
393            // Step 2.2: Invoke Finish Job with job and abort these steps.
394            // TODO: Finish Job.
395            return;
396        };
397
398        // Step 3: Remove registration map[(registration’s storage key, job’s scope url)].
399        // Note: done by removing the registration from the map above.
400
401        // Step 4: Invoke Resolve Job Promise with job and true.
402        if job
403            .client
404            .send(ServiceWorkerAlgorithmResult::Job(
405                JobResult::ResolvePromise(JobResultValue::Unregister(true)),
406            ))
407            .is_err()
408        {
409            warn!("Failed to send unregister result to script.");
410        }
411
412        // Step 5: Invoke Try Clear Registration with registration.
413        // TODO: Try Clear Registration.
414
415        // Step 6: Invoke Finish Job with job.
416        // TODO: Finish Job.
417    }
418
419    /// <https://w3c.github.io/ServiceWorker/#match-service-worker-registration>
420    fn handle_match_registration(
421        &self,
422        storage_key: ImmutableOrigin,
423        client_url: ServoUrl,
424        result_handler: GenericCallback<ServiceWorkerAlgorithmResult>,
425    ) {
426        // Step 1: Run the following steps atomically.
427        // Note: done using the channel from which this message was received.
428
429        // Step 2: Let clientURLString be serialized clientURL.
430        let client_url_string = client_url.as_str();
431
432        // Step 3: Let matchingScopeString be the empty string.
433        let mut matching_scope_string = String::new();
434
435        // Step 4: Let scopeStringSet be an empty list.
436        let mut scope_string_set = Vec::new();
437
438        // Step 5: For each (entry storage key, entry scope) of registration map’s keys:
439        for (entry_storage_key, entry_scope) in self.registrations.keys().map(|k| (k.origin(), k)) {
440            // Step 5.1. If storage key equals entry storage key, then append entry scope to the end of scopeStringSet.
441            if storage_key == entry_storage_key {
442                scope_string_set.push(entry_scope.as_str());
443            }
444        }
445
446        // Step 6: Set matchingScopeString to the longest value in scopeStringSet which the value of clientURLString starts with, if it exists.
447        for scope in scope_string_set {
448            if client_url_string.starts_with(scope) && scope.len() > matching_scope_string.len() {
449                matching_scope_string = scope.to_owned();
450            }
451        }
452
453        // Step 7: Let matchingScope be null.
454        let mut matching_scope = None;
455
456        // Step 8: If matchingScopeString is not the empty string, then:
457        if !matching_scope_string.is_empty() {
458            // Step 8.1. Set matchingScope to the result of parsing matchingScopeString.
459            let Ok(parsed_matching_scope) = ServoUrl::parse(&matching_scope_string) else {
460                error!("Failed to parse matching scope string as URL.");
461                if result_handler
462                    .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
463                    .is_err()
464                {
465                    warn!("Failed to send match registration result to script.");
466                }
467                return;
468            };
469            matching_scope = Some(parsed_matching_scope);
470
471            // Step 8.2: Assert: matchingScope’s origin and clientURL’s origin are same origin.
472            debug_assert_eq!(
473                matching_scope.as_ref().unwrap().origin(),
474                client_url.origin()
475            );
476        }
477
478        let Some(matching_scope) = matching_scope else {
479            if result_handler
480                .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(None))
481                .is_err()
482            {
483                warn!("Failed to send match registration result to script.");
484            }
485            return;
486        };
487
488        // Step 9: Return the result of running Get Registration given storage key and matchingScope.
489        let registration = self.registrations.get(&matching_scope);
490        let info = registration
491            .as_ref()
492            .map(|registration| ServiceWorkerRegistrationInfo {
493                scope_url: matching_scope,
494                script_url: registration
495                    .get_newest_worker()
496                    .expect("Registration should have a worker.")
497                    .script_url,
498                storage_key,
499                id: registration.id,
500                installing_worker: registration
501                    .installing_worker
502                    .as_ref()
503                    .map(|worker| worker.id),
504                waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id),
505                active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
506            });
507        if result_handler
508            .send(ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(info))
509            .is_err()
510        {
511            warn!("Failed to send match registration result to script.");
512        }
513    }
514
515    /// <https://w3c.github.io/ServiceWorker/#register-algorithm>
516    fn handle_register_job(&mut self, mut job: Job) {
517        // 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:
518        if !job.script_url.origin().is_potentially_trustworthy() {
519            // Step 1.1: Invoke Reject Job Promise with job and "SecurityError" DOMException.
520            if job
521                .client
522                .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
523                    JobError::SecurityError,
524                )))
525                .is_err()
526            {
527                warn!("Failed to send reject job promise result to script.");
528            }
529
530            // TODO Step 1.2: Invoke Finish Job with job and abort these steps.
531            // TODO: Finish Job.
532            return;
533        }
534
535        // Step 2: If job’s script url’s origin and job’s referrer’s origin are not same origin, then:
536        // Step 3: If job’s scope url’s origin and job’s referrer’s origin are not same origin, then:
537        // Note: both steps done in one conditional.
538        if job.script_url.origin() != job.referrer.origin() ||
539            job.scope_url.origin() != job.referrer.origin()
540        {
541            // Step 2.1: Invoke Reject Job Promise with job and "SecurityError" DOMException
542            if job
543                .client
544                .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
545                    JobError::SecurityError,
546                )))
547                .is_err()
548            {
549                warn!("Failed to send reject job promise result to script.");
550            }
551
552            // TODO Step 2.2: Invoke Finish Job with job and abort these steps.
553            return;
554        }
555
556        // Step 4: Let registration be the result of running Get Registration given job’s storage key and job’s scope url.
557        if let Some(registration) = self.registrations.get(&job.scope_url) {
558            // Step 5: If registration is not null, then:
559
560            // Step 5.1: Let newestWorker be the result of running the Get Newest Worker algorithm passing registration as the argument.
561            let newest_worker = registration.get_newest_worker();
562
563            // 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:
564            if newest_worker.is_some() {
565                // TODO: the various checks of job versus worker.
566
567                // Step 5.2.1: Invoke Resolve Job Promise with job and registration.
568                // Step 5.2.2: Invoke Finish Job with job and abort these steps.
569                // TODO: Finish Job.
570                let client = job.client.clone();
571                let _ = client.send(ServiceWorkerAlgorithmResult::Job(
572                    JobResult::ResolvePromise(JobResultValue::Register(
573                        ServiceWorkerRegistrationInfo {
574                            scope_url: job.scope_url.clone(),
575                            script_url: job.script_url.clone(),
576                            storage_key: job.storage_key.clone(),
577                            id: registration.id,
578                            installing_worker: registration
579                                .installing_worker
580                                .as_ref()
581                                .map(|worker| worker.id),
582                            waiting_worker: registration
583                                .waiting_worker
584                                .as_ref()
585                                .map(|worker| worker.id),
586                            active_worker: registration
587                                .active_worker
588                                .as_ref()
589                                .map(|worker| worker.id),
590                        },
591                    )),
592                ));
593            }
594        } else {
595            // Step 6: Else
596            // Step 6.1: Invoke Set Registration algorithm with job’s storage key, job’s scope url, and job’s update via cache mode.
597            let new_registration = ServiceWorkerRegistration::new(job.client.clone());
598            self.registrations
599                .insert(job.scope_url.clone(), new_registration);
600
601            // Step 7: Invoke Update algorithm passing job as the argument.
602            job.job_type = JobType::Update;
603            self.handle_update_job(job);
604        }
605    }
606
607    /// <https://www.w3.org/TR/service-workers/#install>
608    fn install(&mut self, job: Job, new_worker: ServiceWorker) {
609        let Some(registration) = self.registrations.get_mut(&job.scope_url) else {
610            error!("Registration should exist when installing a worker.");
611            return;
612        };
613
614        // Step 4: Run the Update Registration State algorithm passing registration, "installing" and worker as the arguments.
615        registration.update_registration_state(
616            RegistrationUpdateTarget::Installing,
617            Some(new_worker.clone()),
618        );
619
620        // Step 7: Invoke Resolve Job Promise with job and registration
621        let client = job.client.clone();
622        if client
623            .send(ServiceWorkerAlgorithmResult::Job(
624                JobResult::ResolvePromise(JobResultValue::Register(
625                    ServiceWorkerRegistrationInfo {
626                        scope_url: job.scope_url.clone(),
627                        storage_key: job.storage_key.clone(),
628                        script_url: job.script_url.clone(),
629                        id: registration.id,
630                        installing_worker: registration
631                            .installing_worker
632                            .as_ref()
633                            .map(|worker| worker.id),
634                        waiting_worker: registration
635                            .waiting_worker
636                            .as_ref()
637                            .map(|worker| worker.id),
638                        active_worker: registration.active_worker.as_ref().map(|worker| worker.id),
639                    },
640                )),
641            ))
642            .is_err()
643        {
644            warn!("Failed to send resolve job promise result to script.");
645        }
646
647        // Step 17: Run the Update Registration State algorithm passing registration,
648        // "waiting" and registration’s installing worker as the arguments.
649        registration.update_registration_state(RegistrationUpdateTarget::Waiting, Some(new_worker));
650
651        // Step 18: Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
652        // TODO: registration.update_registration_state(RegistrationUpdateTarget::Installing, None);
653        // Note: commenting out for now, because it causes errors in tests,
654        // probably because we are still lacking functionality elsewhere.
655
656        // Step 21: Wait for all the tasks queued by Update Worker State invoked in this algorithm to have executed.
657        // TODO: queue tasks above and wait for them to execute.
658    }
659
660    /// <https://w3c.github.io/ServiceWorker/#update>
661    fn handle_update_job(&mut self, job: Job) {
662        // Step 1: Get registation
663        let (job, new_worker) =
664            if let Some(registration) = self.registrations.get_mut(&job.scope_url) {
665                // Step 3.
666                let newest_worker = registration.get_newest_worker();
667
668                // Step 4.
669                if let Some(worker) = newest_worker &&
670                    worker.script_url != job.script_url
671                {
672                    let _ = job.client.send(ServiceWorkerAlgorithmResult::Job(
673                        JobResult::RejectPromise(JobError::TypeError),
674                    ));
675                    return;
676                }
677
678                let scope_things = job
679                    .scope_things
680                    .clone()
681                    .expect("Update job should have scope things.");
682
683                // Very roughly steps 5 to 18.
684                // TODO: implement all steps precisely.
685                let (new_worker, join_handle, control_sender, context, closing) =
686                    update_serviceworker(
687                        self.own_sender.clone(),
688                        job.scope_url.clone(),
689                        scope_things,
690                        self.font_context.clone(),
691                    );
692
693                // Since we've just started the worker thread, ensure we can shut it down later.
694                registration.note_worker_thread(join_handle, control_sender, context, closing);
695
696                (job, new_worker)
697            } else {
698                // Step 2
699                let _ =
700                    job.client
701                        .send(ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(
702                            JobError::TypeError,
703                        )));
704                return;
705            };
706        // Step 17: Else, invoke Install algorithm with job, worker, and registration as its arguments.
707        self.install(job, new_worker);
708    }
709}
710
711/// <https://w3c.github.io/ServiceWorker/#update-algorithm>
712fn update_serviceworker(
713    own_sender: GenericSender<ServiceWorkerMsg>,
714    scope_url: ServoUrl,
715    mut scope_things: ScopeThings,
716    font_context: Arc<FontContext>,
717) -> (
718    ServiceWorker,
719    JoinHandle<()>,
720    Sender<ServiceWorkerControlMsg>,
721    ThreadSafeJSContext,
722    Arc<AtomicBool>,
723) {
724    let (sender, receiver) = unbounded();
725    let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
726    scope_things.init.from_devtools_sender = Some(devtools_sender);
727
728    if let Some(ref chan) = scope_things.devtools_chan &&
729        let Some(ref sender) = scope_things.init.from_devtools_sender
730    {
731        let page_info = DevtoolsPageInfo {
732            title: format!("Service Worker for {}", scope_things.script_url),
733            url: scope_things.script_url.clone(),
734            is_top_level_global: false,
735            is_service_worker: true,
736        };
737        let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
738            (
739                scope_things.browsing_context_id,
740                scope_things.init.pipeline_id,
741                Some(scope_things.worker_id),
742                scope_things.webview_id,
743            ),
744            sender.clone(),
745            page_info,
746        ));
747    }
748
749    let worker_id = ServiceWorkerId::new();
750
751    let (control_sender, control_receiver) = unbounded();
752    let (context_sender, context_receiver) = unbounded();
753    let closing = Arc::new(AtomicBool::new(false));
754
755    let join_handle = ServiceWorkerGlobalScope::run_serviceworker_scope(
756        scope_things.clone(),
757        sender.clone(),
758        receiver,
759        devtools_receiver,
760        own_sender,
761        scope_url,
762        control_receiver,
763        context_sender,
764        closing.clone(),
765        font_context,
766        worker_id,
767    );
768
769    let context = context_receiver
770        .recv()
771        .expect("Couldn't receive a context for worker.");
772
773    (
774        ServiceWorker::new(scope_things.script_url, sender, worker_id),
775        join_handle,
776        control_sender,
777        context,
778        closing,
779    )
780}
781
782impl ServiceWorkerManagerFactory for ServiceWorkerManager {
783    fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) {
784        let (resource_chan, resource_port) = ipc::channel().unwrap();
785
786        let SWManagerSenders {
787            resource_threads,
788            own_sender,
789            receiver,
790            system_font_service_sender,
791            paint_api,
792        } = sw_senders;
793
794        let from_constellation = receiver.route_preserving_errors();
795        let resource_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(resource_port);
796        let _ = resource_threads
797            .core_thread
798            .send(CoreResourceMsg::NetworkMediator(resource_chan, origin));
799
800        let font_context = Arc::new(FontContext::new(
801            Arc::new(system_font_service_sender.to_proxy()),
802            paint_api,
803            resource_threads,
804        ));
805
806        let swmanager_thread = move || {
807            ServiceWorkerManager::new(own_sender, from_constellation, resource_port, font_context)
808                .handle_message()
809        };
810        if thread::Builder::new()
811            .name("SvcWorkerManager".to_owned())
812            .spawn(swmanager_thread)
813            .is_err()
814        {
815            warn!("ServiceWorkerManager thread spawning failed");
816        }
817    }
818}
819
820pub(crate) fn serviceworker_enabled() -> bool {
821    pref!(dom_serviceworker_enabled)
822}