script/dom/serviceworker/
serviceworkerregistration.rs1use 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::cookiestoremanager::CookieStoreManager;
30use crate::dom::eventtarget::EventTarget;
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::navigationpreloadmanager::NavigationPreloadManager;
33use crate::dom::promise::Promise;
34use crate::dom::serviceworker::ServiceWorker;
35use crate::dom::window::Window;
36use crate::dom::workerglobalscope::prepare_workerscope_init;
37
38#[dom_struct]
39pub(crate) struct ServiceWorkerRegistration {
40 eventtarget: EventTarget,
41 active: DomRefCell<Option<Dom<ServiceWorker>>>,
42 installing: DomRefCell<Option<Dom<ServiceWorker>>>,
43 waiting: DomRefCell<Option<Dom<ServiceWorker>>>,
44 navigation_preload: MutNullableDom<NavigationPreloadManager>,
45 cookie_manager: MutNullableDom<CookieStoreManager>,
48 #[no_trace]
49 scope: ServoUrl,
50 navigation_preload_enabled: Cell<bool>,
51 navigation_preload_header_value: DomRefCell<Option<ByteString>>,
52 update_via_cache: ServiceWorkerUpdateViaCache,
53 uninstalling: Cell<bool>,
54 #[no_trace]
55 registration_id: ServiceWorkerRegistrationId,
56}
57
58impl ServiceWorkerRegistration {
59 fn new_inherited(
60 scope: ServoUrl,
61 registration_id: ServiceWorkerRegistrationId,
62 ) -> ServiceWorkerRegistration {
63 ServiceWorkerRegistration {
64 eventtarget: EventTarget::new_inherited(),
65 active: DomRefCell::new(None),
66 installing: DomRefCell::new(None),
67 waiting: DomRefCell::new(None),
68 navigation_preload: MutNullableDom::new(None),
69 cookie_manager: MutNullableDom::new(None),
70 scope,
71 navigation_preload_enabled: Cell::new(false),
72 navigation_preload_header_value: DomRefCell::new(None),
73 update_via_cache: ServiceWorkerUpdateViaCache::Imports,
74 uninstalling: Cell::new(false),
75 registration_id,
76 }
77 }
78
79 pub(crate) fn new(
80 cx: &mut JSContext,
81 global: &GlobalScope,
82 scope: ServoUrl,
83 registration_id: ServiceWorkerRegistrationId,
84 ) -> DomRoot<ServiceWorkerRegistration> {
85 reflect_dom_object_with_cx(
86 Box::new(ServiceWorkerRegistration::new_inherited(
87 scope,
88 registration_id,
89 )),
90 global,
91 cx,
92 )
93 }
94
95 pub(crate) fn scope_url(&self) -> &ServoUrl {
97 &self.scope
99 }
100
101 pub(crate) fn is_active(&self) -> bool {
103 self.active.borrow().is_some()
104 }
105
106 pub(crate) fn set_installing(&self, worker: &ServiceWorker) {
107 *self.installing.borrow_mut() = Some(Dom::from_ref(worker));
108 }
109
110 pub(crate) fn get_navigation_preload_header_value(&self) -> Option<ByteString> {
111 self.navigation_preload_header_value.borrow().clone()
112 }
113
114 pub(crate) fn set_navigation_preload_header_value(&self, value: ByteString) {
115 let mut header_value = self.navigation_preload_header_value.borrow_mut();
116 *header_value = Some(value);
117 }
118
119 pub(crate) fn get_navigation_preload_enabled(&self) -> bool {
120 self.navigation_preload_enabled.get()
121 }
122
123 pub(crate) fn set_navigation_preload_enabled(&self, flag: bool) {
124 self.navigation_preload_enabled.set(flag)
125 }
126
127 pub(crate) fn create_scope_things(global: &GlobalScope, script_url: ServoUrl) -> ScopeThings {
128 let worker_load_origin = WorkerScriptLoadOrigin {
129 referrer_url: match global.get_referrer() {
130 Referrer::Client(url) => Some(url),
131 Referrer::ReferrerUrl(url) => Some(url),
132 _ => None,
133 },
134 referrer_policy: global.get_referrer_policy(),
135 pipeline_id: global.pipeline_id(),
136 };
137
138 #[cfg(feature = "webgl")]
139 let webgl_chan = global
140 .downcast::<Window>()
141 .and_then(|window| window.webgl_chan_value());
142 let worker_id = WorkerId(Uuid::new_v4());
143 let devtools_chan = global.devtools_chan().cloned();
144 let init = prepare_workerscope_init(
145 global,
146 None,
147 Some(worker_id),
148 #[cfg(feature = "webgl")]
149 webgl_chan,
150 );
151 let browsing_context_id = global
152 .downcast::<Window>()
153 .map(|w: &Window| w.window_proxy().browsing_context_id())
154 .expect("Service worker must be registered from a Window global");
155 let webview_id = global
156 .webview_id()
157 .expect("Service worker must have a WebViewId");
158 ScopeThings {
159 script_url,
160 init,
161 worker_load_origin,
162 devtools_chan,
163 worker_id,
164 browsing_context_id,
165 webview_id,
166 }
167 }
168
169 pub(crate) fn get_newest_worker(&self) -> Option<DomRoot<ServiceWorker>> {
171 let installing = self.installing.borrow();
172 let waiting = self.waiting.borrow();
173 let active = self.active.borrow();
174 installing
175 .as_ref()
176 .map(|sw| DomRoot::from_ref(&**sw))
177 .or_else(|| waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw)))
178 .or_else(|| active.as_ref().map(|sw| DomRoot::from_ref(&**sw)))
179 }
180}
181
182pub(crate) fn longest_prefix_match(stored_scope: &ServoUrl, potential_match: &ServoUrl) -> bool {
183 if stored_scope.origin() != potential_match.origin() {
184 return false;
185 }
186 let scope_chars = stored_scope.path().chars();
187 let matching_chars = potential_match.path().chars();
188 if scope_chars.count() > matching_chars.count() {
189 return false;
190 }
191
192 stored_scope
193 .path()
194 .chars()
195 .zip(potential_match.path().chars())
196 .all(|(scope, matched)| scope == matched)
197}
198
199impl ServiceWorkerRegistrationMethods<crate::DomTypeHolder> for ServiceWorkerRegistration {
200 fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> {
202 self.installing
203 .borrow()
204 .as_ref()
205 .map(|sw| DomRoot::from_ref(&**sw))
206 }
207
208 fn Unregister(&self, cx: &mut JSContext) -> Rc<Promise> {
210 let promise = Promise::new(cx, &self.global());
215
216 let Some(worker) = self.get_newest_worker() else {
217 promise.resolve_native(cx, &true);
218 return promise;
219 };
220
221 let global = self.global();
222 let Some(window) = global.downcast::<Window>() else {
223 promise.resolve_native(cx, &false);
225 return promise;
226 };
227 let service_worker_container = window.Navigator(cx).ServiceWorker(cx);
228
229 let Some(storage_key) = global.obtain_storage_key() else {
235 promise.reject_error(
236 cx,
237 Error::Type(c"Failed to obtain a storage key".to_owned()),
238 );
239 return promise;
240 };
241 service_worker_container.create_and_schedule_unregister_job(
242 cx,
243 storage_key,
244 self.scope.clone(),
245 worker.get_script_url(),
246 promise.clone(),
247 );
248
249 *self.installing.borrow_mut() = None;
252 *self.waiting.borrow_mut() = None;
253 *self.active.borrow_mut() = None;
254
255 promise
257 }
258
259 fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> {
261 self.active
262 .borrow()
263 .as_ref()
264 .map(|sw| DomRoot::from_ref(&**sw))
265 }
266
267 fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> {
269 self.waiting
270 .borrow()
271 .as_ref()
272 .map(|sw| DomRoot::from_ref(&**sw))
273 }
274
275 fn Scope(&self) -> USVString {
277 USVString(self.scope.as_str().to_owned())
278 }
279
280 fn UpdateViaCache(&self) -> ServiceWorkerUpdateViaCache {
282 self.update_via_cache
283 }
284
285 fn NavigationPreload(&self, cx: &mut JSContext) -> DomRoot<NavigationPreloadManager> {
287 self.navigation_preload
288 .or_init(|| NavigationPreloadManager::new(cx, &self.global(), self))
289 }
290
291 fn Cookies(&self, cx: &mut JSContext) -> DomRoot<CookieStoreManager> {
293 self.cookie_manager
296 .or_init(|| CookieStoreManager::new(cx, &self.global(), self))
297 }
298}