Skip to main content

script/dom/serviceworker/
cachestorage.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::RefCell;
6use std::collections::VecDeque;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12use script_bindings::root::DomRoot;
13use servo_base::generic_channel::{GenericCallback, GenericSend};
14use servo_url::ImmutableOrigin;
15use storage_traits::cache_storage::{CacheStorageThreadMessage, CacheStorageThreadResponse};
16use storage_traits::client_storage::{StorageIdentifier, StorageProxyMap, StorageType};
17
18use crate::dom::Promise;
19use crate::dom::bindings::codegen::Bindings::CacheStorageBinding::CacheStorageMethods;
20use crate::dom::bindings::error::Error;
21use crate::dom::bindings::refcounted::Trusted;
22use crate::dom::bindings::reflector::DomGlobal;
23use crate::dom::bindings::str::DOMString;
24use crate::dom::globalscope::GlobalScope;
25
26/// <https://w3c.github.io/ServiceWorker/#cachestorage-interface>
27#[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    // Dequeue of pending promises for backend operations.
36    #[conditional_malloc_size_of]
37    pending_promises: RefCell<VecDeque<Rc<Promise>>>,
38}
39
40impl CacheStorage {
41    fn new_inherited() -> CacheStorage {
42        CacheStorage {
43            reflector_: Reflector::new(),
44            callback: Default::default(),
45            pending_promises: Default::default(),
46        }
47    }
48
49    pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<CacheStorage> {
50        reflect_dom_object_with_cx(Box::new(CacheStorage::new_inherited()), global, cx)
51    }
52
53    /// Setup the callback to the backend service, if this hasn't been done already.
54    fn get_or_setup_callback(&self) -> GenericCallback<CacheStorageThreadResponse> {
55        if let Some(cb) = self.callback.borrow().as_ref() {
56            return cb.clone();
57        }
58
59        let global = self.global();
60        let response_listener = Trusted::new(self);
61
62        let task_source = global
63            .task_manager()
64            .database_access_task_source()
65            .to_sendable();
66        let callback = GenericCallback::new(move |message| {
67            let response_listener = response_listener.clone();
68            let response = match message {
69                Ok(inner) => Some(inner),
70                Err(err) => {
71                    error!("Error in CacheStorage callback {:?}.", err);
72                    None
73                },
74            };
75            task_source.queue(task!(set_request_result_to_database: move |cx| {
76                let cache_storage = response_listener.root();
77                cache_storage.handle_response(cx, response)
78            }));
79        })
80        .expect("Could not create CacheStorage callback");
81
82        *self.callback.borrow_mut() = Some(callback.clone());
83
84        callback
85    }
86
87    fn handle_response(&self, cx: &mut JSContext, response: Option<CacheStorageThreadResponse>) {
88        let response = match response {
89            Some(response) => response,
90            None => {
91                if self.pending_promises.borrow_mut().pop_front().is_none() {
92                    error!("No pending promise for CacheStorage response.");
93                }
94                return;
95            },
96        };
97        match response {
98            // https://w3c.github.io/ServiceWorker/#cache-storage-has
99            // the steps resolving the promise with the result.
100            // Note: spec forgets to queue a task see https://github.com/w3c/ServiceWorker/issues/1831
101            CacheStorageThreadResponse::HasCacheResult(result) => {
102                let promise = self.pending_promises.borrow_mut().pop_front();
103                if let Some(promise) = promise {
104                    match result {
105                        Ok(has_cache) => {
106                            // Step 2.1:For each key → value of the relevant name to cache map:
107                            // Step 2.1.1: If cacheName matches key, resolve promise with true and abort these steps.
108                            // Step 2.2: Resolve promise with false.
109                            // Note: promise resolved with the result obtained in parallel.
110                            promise.resolve_native(cx, &has_cache);
111                        },
112                        Err(err) => promise.reject_error(cx, Error::Operation(Some(err))),
113                    }
114                } else {
115                    error!("No pending promise for HasCacheResult response.");
116                }
117            },
118        }
119    }
120}
121
122/// <https://w3c.github.io/ServiceWorker/#relevant-name-to-cache-map>
123fn relevant_name_to_cache_map(
124    global: &GlobalScope,
125    origin: ImmutableOrigin,
126) -> Result<StorageProxyMap, Error> {
127    // The relevant name to cache map for a CacheStorage object
128    // is the name to cache map associated with the result of
129    // running obtain a local storage bottle map with
130    // the object’s relevant settings object and "caches".
131    let handle = global.storage_threads().client_storage_handle();
132    let message = handle
133        .obtain_a_storage_bottle_map(
134            StorageType::Local,
135            global.webview_id(),
136            StorageIdentifier::Caches,
137            origin,
138        )
139        .recv();
140    let Ok(response) = message else {
141        return Err(Error::Operation(None));
142    };
143    let Ok(proxy_map) = response else {
144        return Err(Error::Operation(None));
145    };
146    Ok(proxy_map)
147}
148
149impl CacheStorageMethods<crate::DomTypeHolder> for CacheStorage {
150    /// <https://w3c.github.io/ServiceWorker/#cache-storage-has>
151    fn Has(&self, cx: &mut JSContext, cache_name: DOMString) -> Rc<Promise> {
152        let global = self.global();
153
154        // Step 1: Let promise be a new promise.
155        let promise = Promise::new(cx, &global);
156
157        // Step 2: Run the following substeps in parallel:
158        let callback = self.get_or_setup_callback();
159        let origin = global.origin().immutable().clone();
160        let proxy_map = match relevant_name_to_cache_map(&global, origin.clone()) {
161            Ok(proxy_map) => proxy_map,
162            Err(err) => {
163                promise.reject_error(cx, err);
164                return promise;
165            },
166        };
167        if global
168            .storage_threads()
169            .send(CacheStorageThreadMessage::HasCache {
170                cache_name: cache_name.to_string(),
171                callback,
172                proxy: proxy_map,
173                origin,
174            })
175            .is_err()
176        {
177            promise.reject_error(cx, Error::Operation(None));
178            return promise;
179        }
180
181        self.pending_promises
182            .borrow_mut()
183            .push_back(promise.clone());
184
185        promise
186    }
187}