Skip to main content

script/dom/serviceworker/
serviceworkerregistration.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
5use std::cell::Cell;
6use std::rc::Rc;
7
8use devtools_traits::WorkerId;
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use net_traits::request::Referrer;
12use script_bindings::cell::DomRefCell;
13use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
14use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
15use script_bindings::reflector::reflect_dom_object_with_cx;
16use servo_base::id::ServiceWorkerRegistrationId;
17use servo_constellation_traits::{ScopeThings, WorkerScriptLoadOrigin};
18use servo_url::ServoUrl;
19use uuid::Uuid;
20
21use crate::dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::{
22    ServiceWorkerRegistrationMethods, ServiceWorkerUpdateViaCache,
23};
24use crate::dom::bindings::error::Error;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::reflector::DomGlobal;
27use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
28use crate::dom::bindings::str::{ByteString, USVString};
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::navigationpreloadmanager::NavigationPreloadManager;
32use crate::dom::promise::Promise;
33use crate::dom::serviceworker::ServiceWorker;
34use crate::dom::window::Window;
35use crate::dom::workerglobalscope::prepare_workerscope_init;
36
37#[dom_struct]
38pub(crate) struct ServiceWorkerRegistration {
39    eventtarget: EventTarget,
40    active: DomRefCell<Option<Dom<ServiceWorker>>>,
41    installing: DomRefCell<Option<Dom<ServiceWorker>>>,
42    waiting: DomRefCell<Option<Dom<ServiceWorker>>>,
43    navigation_preload: MutNullableDom<NavigationPreloadManager>,
44    #[no_trace]
45    scope: ServoUrl,
46    navigation_preload_enabled: Cell<bool>,
47    navigation_preload_header_value: DomRefCell<Option<ByteString>>,
48    update_via_cache: ServiceWorkerUpdateViaCache,
49    uninstalling: Cell<bool>,
50    #[no_trace]
51    registration_id: ServiceWorkerRegistrationId,
52}
53
54impl ServiceWorkerRegistration {
55    fn new_inherited(
56        scope: ServoUrl,
57        registration_id: ServiceWorkerRegistrationId,
58    ) -> ServiceWorkerRegistration {
59        ServiceWorkerRegistration {
60            eventtarget: EventTarget::new_inherited(),
61            active: DomRefCell::new(None),
62            installing: DomRefCell::new(None),
63            waiting: DomRefCell::new(None),
64            navigation_preload: MutNullableDom::new(None),
65            scope,
66            navigation_preload_enabled: Cell::new(false),
67            navigation_preload_header_value: DomRefCell::new(None),
68            update_via_cache: ServiceWorkerUpdateViaCache::Imports,
69            uninstalling: Cell::new(false),
70            registration_id,
71        }
72    }
73
74    pub(crate) fn new(
75        cx: &mut JSContext,
76        global: &GlobalScope,
77        scope: ServoUrl,
78        registration_id: ServiceWorkerRegistrationId,
79    ) -> DomRoot<ServiceWorkerRegistration> {
80        reflect_dom_object_with_cx(
81            Box::new(ServiceWorkerRegistration::new_inherited(
82                scope,
83                registration_id,
84            )),
85            global,
86            cx,
87        )
88    }
89
90    /// Does this registration have an active worker?
91    pub(crate) fn is_active(&self) -> bool {
92        self.active.borrow().is_some()
93    }
94
95    pub(crate) fn set_installing(&self, worker: &ServiceWorker) {
96        *self.installing.borrow_mut() = Some(Dom::from_ref(worker));
97    }
98
99    pub(crate) fn get_navigation_preload_header_value(&self) -> Option<ByteString> {
100        self.navigation_preload_header_value.borrow().clone()
101    }
102
103    pub(crate) fn set_navigation_preload_header_value(&self, value: ByteString) {
104        let mut header_value = self.navigation_preload_header_value.borrow_mut();
105        *header_value = Some(value);
106    }
107
108    pub(crate) fn get_navigation_preload_enabled(&self) -> bool {
109        self.navigation_preload_enabled.get()
110    }
111
112    pub(crate) fn set_navigation_preload_enabled(&self, flag: bool) {
113        self.navigation_preload_enabled.set(flag)
114    }
115
116    pub(crate) fn create_scope_things(global: &GlobalScope, script_url: ServoUrl) -> ScopeThings {
117        let worker_load_origin = WorkerScriptLoadOrigin {
118            referrer_url: match global.get_referrer() {
119                Referrer::Client(url) => Some(url),
120                Referrer::ReferrerUrl(url) => Some(url),
121                _ => None,
122            },
123            referrer_policy: global.get_referrer_policy(),
124            pipeline_id: global.pipeline_id(),
125        };
126
127        let webgl_chan = global
128            .downcast::<Window>()
129            .and_then(|window| window.webgl_chan_value());
130        let worker_id = WorkerId(Uuid::new_v4());
131        let devtools_chan = global.devtools_chan().cloned();
132        let init = prepare_workerscope_init(global, None, Some(worker_id), webgl_chan);
133        let browsing_context_id = global
134            .downcast::<Window>()
135            .map(|w: &Window| w.window_proxy().browsing_context_id())
136            .expect("Service worker must be registered from a Window global");
137        let webview_id = global
138            .webview_id()
139            .expect("Service worker must have a WebViewId");
140        ScopeThings {
141            script_url,
142            init,
143            worker_load_origin,
144            devtools_chan,
145            worker_id,
146            browsing_context_id,
147            webview_id,
148        }
149    }
150
151    // https://w3c.github.io/ServiceWorker/#get-newest-worker-algorithm
152    pub(crate) fn get_newest_worker(&self) -> Option<DomRoot<ServiceWorker>> {
153        let installing = self.installing.borrow();
154        let waiting = self.waiting.borrow();
155        let active = self.active.borrow();
156        installing
157            .as_ref()
158            .map(|sw| DomRoot::from_ref(&**sw))
159            .or_else(|| waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw)))
160            .or_else(|| active.as_ref().map(|sw| DomRoot::from_ref(&**sw)))
161    }
162}
163
164pub(crate) fn longest_prefix_match(stored_scope: &ServoUrl, potential_match: &ServoUrl) -> bool {
165    if stored_scope.origin() != potential_match.origin() {
166        return false;
167    }
168    let scope_chars = stored_scope.path().chars();
169    let matching_chars = potential_match.path().chars();
170    if scope_chars.count() > matching_chars.count() {
171        return false;
172    }
173
174    stored_scope
175        .path()
176        .chars()
177        .zip(potential_match.path().chars())
178        .all(|(scope, matched)| scope == matched)
179}
180
181impl ServiceWorkerRegistrationMethods<crate::DomTypeHolder> for ServiceWorkerRegistration {
182    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-installing-attribute>
183    fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> {
184        self.installing
185            .borrow()
186            .as_ref()
187            .map(|sw| DomRoot::from_ref(&**sw))
188    }
189
190    /// <https://w3c.github.io/ServiceWorker/#dom-serviceworkerregistration-unregister>
191    fn Unregister(&self, cx: &mut JSContext) -> Rc<Promise> {
192        // Step 1: Let registration be the service worker registration.
193        // Note: `self` is the registration.
194
195        // Step 2: Let promise be a new promise.
196        let promise = Promise::new(cx, &self.global());
197
198        let Some(worker) = self.get_newest_worker() else {
199            promise.resolve_native(cx, &true);
200            return promise;
201        };
202
203        let global = self.global();
204        let Some(window) = global.downcast::<Window>() else {
205            // Worker navigator does not have a service woker container yet.
206            promise.resolve_native(cx, &false);
207            return promise;
208        };
209        let service_worker_container = window.Navigator(cx).ServiceWorker(cx);
210
211        // Step 3: Let job be the result of running Create Job with unregister,
212        // registration’s storage key, registration’s scope url, null, promise,
213        // and this’s relevant settings object.
214        // Step 4: Invoke Schedule Job with job.
215        // Note: done in the container.
216        let Some(storage_key) = global.obtain_storage_key() else {
217            promise.reject_error(
218                cx,
219                Error::Type(c"Failed to obtain a storage key".to_owned()),
220            );
221            return promise;
222        };
223        service_worker_container.create_and_schedule_unregister_job(
224            cx,
225            storage_key,
226            self.scope.clone(),
227            worker.get_script_url(),
228            promise.clone(),
229        );
230
231        // Set all workers to none.
232        // Note: not clear where the spec does this.
233        *self.installing.borrow_mut() = None;
234        *self.waiting.borrow_mut() = None;
235        *self.active.borrow_mut() = None;
236
237        // Step 5: Return promise.
238        promise
239    }
240
241    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-active-attribute>
242    fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> {
243        self.active
244            .borrow()
245            .as_ref()
246            .map(|sw| DomRoot::from_ref(&**sw))
247    }
248
249    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-waiting-attribute>
250    fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> {
251        self.waiting
252            .borrow()
253            .as_ref()
254            .map(|sw| DomRoot::from_ref(&**sw))
255    }
256
257    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-scope-attribute>
258    fn Scope(&self) -> USVString {
259        USVString(self.scope.as_str().to_owned())
260    }
261
262    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-updateviacache>
263    fn UpdateViaCache(&self) -> ServiceWorkerUpdateViaCache {
264        self.update_via_cache
265    }
266
267    /// <https://w3c.github.io/ServiceWorker/#service-worker-registration-navigationpreload>
268    fn NavigationPreload(&self, cx: &mut JSContext) -> DomRoot<NavigationPreloadManager> {
269        self.navigation_preload
270            .or_init(|| NavigationPreloadManager::new(cx, &self.global(), self))
271    }
272}