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::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 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 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 fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> {
184 self.installing
185 .borrow()
186 .as_ref()
187 .map(|sw| DomRoot::from_ref(&**sw))
188 }
189
190 fn Unregister(&self, cx: &mut JSContext) -> Rc<Promise> {
192 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 promise.resolve_native(cx, &false);
207 return promise;
208 };
209 let service_worker_container = window.Navigator(cx).ServiceWorker(cx);
210
211 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 *self.installing.borrow_mut() = None;
234 *self.waiting.borrow_mut() = None;
235 *self.active.borrow_mut() = None;
236
237 promise
239 }
240
241 fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> {
243 self.active
244 .borrow()
245 .as_ref()
246 .map(|sw| DomRoot::from_ref(&**sw))
247 }
248
249 fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> {
251 self.waiting
252 .borrow()
253 .as_ref()
254 .map(|sw| DomRoot::from_ref(&**sw))
255 }
256
257 fn Scope(&self) -> USVString {
259 USVString(self.scope.as_str().to_owned())
260 }
261
262 fn UpdateViaCache(&self) -> ServiceWorkerUpdateViaCache {
264 self.update_via_cache
265 }
266
267 fn NavigationPreload(&self, cx: &mut JSContext) -> DomRoot<NavigationPreloadManager> {
269 self.navigation_preload
270 .or_init(|| NavigationPreloadManager::new(cx, &self.global(), self))
271 }
272}