script/dom/serviceworker/
serviceworkerregistration.rs1use 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 cookie_manager: MutNullableDom<CookieStoreManager>,
47 #[no_trace]
48 scope: ServoUrl,
49 navigation_preload_enabled: Cell<bool>,
51 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 navigation_preload_enabled: Cell::new(false),
76 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 pub(crate) fn scope_url(&self) -> &ServoUrl {
104 &self.scope
106 }
107
108 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 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 fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> {
209 self.installing.get()
210 }
211
212 fn Unregister(&self, cx: &mut JSContext) -> RootedPromise {
214 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 promise.resolve_native(cx, &false);
229 return promise;
230 };
231 let service_worker_container = window.Navigator(cx).ServiceWorker(cx);
232
233 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 self.installing.set(None);
256 self.waiting.set(None);
257 self.active.set(None);
258
259 promise
261 }
262
263 fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> {
265 self.active.get()
266 }
267
268 fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> {
270 self.waiting.get()
271 }
272
273 fn Scope(&self) -> USVString {
275 USVString(self.scope.as_str().to_owned())
276 }
277
278 fn UpdateViaCache(&self) -> ServiceWorkerUpdateViaCache {
280 self.update_via_cache
281 }
282
283 fn NavigationPreload(&self, cx: &mut JSContext) -> DomRoot<NavigationPreloadManager> {
285 self.navigation_preload
286 .or_init(|| NavigationPreloadManager::new(cx, &self.global(), self))
287 }
288
289 fn Cookies(&self, cx: &mut JSContext) -> DomRoot<CookieStoreManager> {
291 self.cookie_manager
294 .or_init(|| CookieStoreManager::new(cx, &self.global(), self))
295 }
296}