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