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