script/dom/serviceworker/
cachestorage.rs1use std::cell::RefCell;
6use std::collections::VecDeque;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
11use script_bindings::root::DomRoot;
12use servo_base::generic_channel::{GenericCallback, GenericSend};
13use servo_url::ImmutableOrigin;
14use storage_traits::cache_storage::{CacheStorageThreadMessage, CacheStorageThreadResponse};
15use storage_traits::client_storage::{StorageIdentifier, StorageProxyMap, StorageType};
16
17use crate::dom::bindings::codegen::Bindings::CacheStorageBinding::CacheStorageMethods;
18use crate::dom::bindings::error::Error;
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::str::DOMString;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::serviceworker::cache::Cache;
24use crate::dom::{Promise, RootedPromise, TracedPromise};
25
26#[dom_struct]
28pub(crate) struct CacheStorage {
29 reflector_: Reflector,
30
31 #[no_trace]
32 #[ignore_malloc_size_of = "GenericCallback"]
33 callback: RefCell<Option<GenericCallback<CacheStorageThreadResponse>>>,
34
35 pending_promises: RefCell<VecDeque<TracedPromise>>,
37}
38
39impl CacheStorage {
40 fn new_inherited() -> CacheStorage {
41 CacheStorage {
42 reflector_: Reflector::new(),
43 callback: Default::default(),
44 pending_promises: Default::default(),
45 }
46 }
47
48 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<CacheStorage> {
49 reflect_dom_object_with_cx(Box::new(CacheStorage::new_inherited()), global, cx)
50 }
51
52 fn get_or_setup_callback(&self) -> GenericCallback<CacheStorageThreadResponse> {
54 if let Some(cb) = self.callback.borrow().as_ref() {
55 return cb.clone();
56 }
57
58 let global = self.global();
59 let response_listener = Trusted::new(self);
60
61 let task_source = global
62 .task_manager()
63 .dom_manipulation_task_source()
64 .to_sendable();
65 let callback = GenericCallback::new(move |message| {
66 let response_listener = response_listener.clone();
67 let response = match message {
68 Ok(inner) => Some(inner),
69 Err(err) => {
70 error!("Error in CacheStorage callback {:?}.", err);
71 None
72 },
73 };
74 task_source.queue(task!(set_request_result_to_database: move |cx| {
75 let cache_storage = response_listener.root();
76 cache_storage.handle_response(cx, response)
77 }));
78 })
79 .expect("Could not create CacheStorage callback");
80
81 *self.callback.borrow_mut() = Some(callback.clone());
82
83 callback
84 }
85
86 fn handle_response(&self, cx: &mut JSContext, response: Option<CacheStorageThreadResponse>) {
87 let response = match response {
88 Some(response) => response,
89 None => {
90 let Some(promise) = self
91 .pending_promises
92 .borrow_mut()
93 .pop_front()
94 .map(|promise| promise.root(cx))
95 else {
96 error!("No pending promise for CacheStorage response.");
97 return;
98 };
99 promise.reject_error(
100 cx,
101 Error::Operation(Some("No response from CacheStorage backend.".to_string())),
102 );
103 return;
104 },
105 };
106 match response {
107 CacheStorageThreadResponse::HasCacheResult(result) => {
111 let Some(promise) = self
112 .pending_promises
113 .borrow_mut()
114 .pop_front()
115 .map(|promise| promise.root(cx))
116 else {
117 debug_assert!(false, "No pending promise for HasCacheResult response.");
118 return;
119 };
120 let Ok(has_cache) = result else {
121 promise.reject_error(
122 cx,
123 Error::Operation(Some(
124 result
125 .err()
126 .unwrap_or_else(|| "HasCacheResult error".to_string()),
127 )),
128 );
129 return;
130 };
131 promise.resolve_native(cx, &has_cache);
136 },
137 CacheStorageThreadResponse::OpenCacheResult { result, cache_name } => {
140 let Some(promise) = self
141 .pending_promises
142 .borrow_mut()
143 .pop_front()
144 .map(|promise| promise.root(cx))
145 else {
146 debug_assert!(false, "No pending promise for OpenCacheResult response.");
147 return;
148 };
149 if result.is_err() {
150 promise.reject_error(
151 cx,
152 Error::Operation(Some(
153 result
154 .err()
155 .unwrap_or_else(|| "OpenCacheResult error".to_string()),
156 )),
157 );
158 return;
159 };
160 let cache = Cache::new(cx, &self.global(), DOMString::from(cache_name));
162 promise.resolve_native(cx, &cache);
163 },
164 CacheStorageThreadResponse::DeleteCacheResult(result) => {
166 let Some(promise) = self
167 .pending_promises
168 .borrow_mut()
169 .pop_front()
170 .map(|promise| promise.root(cx))
171 else {
172 debug_assert!(false, "No pending promise for DeleteCacheResult response.");
173 return;
174 };
175 let Ok(deleted) = result else {
176 promise.reject_error(
177 cx,
178 Error::Operation(Some(
179 result
180 .err()
181 .unwrap_or_else(|| "DeleteCacheResult error".to_string()),
182 )),
183 );
184 return;
185 };
186 promise.resolve_native(cx, &deleted);
187 },
188 CacheStorageThreadResponse::KeysResult(_) => debug_assert!(
189 false,
190 "Unexpected KeysResult response in CacheStorage handle_response."
191 ),
192 }
193 }
194}
195
196fn relevant_name_to_cache_map(
198 global: &GlobalScope,
199 origin: ImmutableOrigin,
200) -> Result<StorageProxyMap, Error> {
201 let handle = global.storage_threads().client_storage_handle();
206 let message = handle
207 .obtain_a_storage_bottle_map(
208 StorageType::Local,
209 global.webview_id(),
210 StorageIdentifier::Caches,
211 origin,
212 )
213 .recv();
214 let Ok(response) = message else {
215 return Err(Error::Operation(Some(
216 "Could not obtain a local storage bottle map.".to_string(),
217 )));
218 };
219 let Ok(proxy_map) = response else {
220 return Err(Error::Operation(Some(
221 "Could not obtain a local storage bottle map.".to_string(),
222 )));
223 };
224 Ok(proxy_map)
225}
226
227impl CacheStorageMethods<crate::DomTypeHolder> for CacheStorage {
228 fn Has(&self, cx: &mut JSContext, cache_name: DOMString) -> RootedPromise {
230 let global = self.global();
231
232 let promise = Promise::new_rooted(cx, &global);
234
235 let callback = self.get_or_setup_callback();
237 let origin = global.origin().immutable().clone();
238 let proxy_map = match relevant_name_to_cache_map(&global, origin.clone()) {
239 Ok(proxy_map) => proxy_map,
240 Err(err) => {
241 promise.reject_error(cx, err);
242 return promise;
243 },
244 };
245 if global
246 .storage_threads()
247 .send(CacheStorageThreadMessage::HasCache {
248 cache_name: cache_name.to_string(),
249 callback,
250 proxy: proxy_map,
251 origin,
252 })
253 .is_err()
254 {
255 promise.reject_error(
256 cx,
257 Error::Operation(Some("Could not run the parallel steps.".to_string())),
258 );
259 return promise;
260 }
261
262 self.pending_promises
263 .borrow_mut()
264 .push_back(promise.to_traced());
265
266 promise
267 }
268
269 fn Open(&self, cx: &mut JSContext, cache_name: DOMString) -> RootedPromise {
271 let global = self.global();
273 let promise = Promise::new_rooted(cx, &global);
274
275 let callback = self.get_or_setup_callback();
277 let origin = global.origin().immutable().clone();
278 let proxy_map = match relevant_name_to_cache_map(&global, origin.clone()) {
279 Ok(proxy_map) => proxy_map,
280 Err(err) => {
281 promise.reject_error(cx, err);
282 return promise;
283 },
284 };
285 if global
286 .storage_threads()
287 .send(CacheStorageThreadMessage::OpenCache {
288 cache_name: cache_name.to_string(),
289 callback,
290 proxy: proxy_map,
291 origin,
292 })
293 .is_err()
294 {
295 promise.reject_error(
296 cx,
297 Error::Operation(Some("Could not run the parallel steps.".to_string())),
298 );
299 return promise;
300 }
301
302 self.pending_promises
303 .borrow_mut()
304 .push_back(promise.to_traced());
305
306 promise
308 }
309
310 fn Delete(&self, cx: &mut JSContext, cache_name: DOMString) -> RootedPromise {
312 let global = self.global();
321 let promise = Promise::new_rooted(cx, &global);
322
323 let callback = self.get_or_setup_callback();
325 let origin = global.origin().immutable().clone();
326 let proxy_map = match relevant_name_to_cache_map(&global, origin.clone()) {
327 Ok(proxy_map) => proxy_map,
328 Err(err) => {
329 promise.reject_error(cx, err);
330 return promise;
331 },
332 };
333 if global
334 .storage_threads()
335 .send(CacheStorageThreadMessage::DeleteCache {
336 cache_name: cache_name.to_string(),
337 callback,
338 proxy: proxy_map,
339 origin,
340 })
341 .is_err()
342 {
343 promise.reject_error(
344 cx,
345 Error::Operation(Some("Could not run the parallel steps.".to_string())),
346 );
347 return promise;
348 }
349
350 self.pending_promises
351 .borrow_mut()
352 .push_back(promise.to_traced());
353
354 promise
356 }
357}