Skip to main content

script/dom/storage/
storagemanager.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 dom_struct::dom_struct;
6use js::context::JSContext;
7use js::realm::CurrentRealm;
8use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
9use servo_base::generic_channel::GenericCallback;
10
11use crate::dom::RootedPromise;
12use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
13    PermissionName, PermissionState,
14};
15use crate::dom::bindings::codegen::Bindings::StorageManagerBinding::{
16    StorageEstimate, StorageManagerMethods,
17};
18use crate::dom::bindings::error::Error;
19use crate::dom::bindings::refcounted::TrustedPromise;
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::permissions::request_permission_to_use;
24use crate::dom::promise::Promise;
25use crate::tasks::task_source::SendableTaskSource;
26
27#[dom_struct]
28pub(crate) struct StorageManager {
29    reflector_: Reflector,
30}
31
32impl StorageManager {
33    fn new_inherited() -> StorageManager {
34        StorageManager {
35            reflector_: Reflector::new(),
36        }
37    }
38
39    pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<StorageManager> {
40        reflect_dom_object_with_cx(Box::new(StorageManager::new_inherited()), global, cx)
41    }
42
43    fn origin_cannot_obtain_local_storage_shelf(&self) -> bool {
44        !self.global().origin().is_tuple()
45    }
46
47    fn type_error_from_string(message: String) -> Error {
48        let message = std::ffi::CString::new(message)
49            .unwrap_or_else(|_| c"Storage operation failed".to_owned());
50        Error::Type(message)
51    }
52}
53
54struct StorageManagerBooleanResponseHandler {
55    trusted_promise: Option<TrustedPromise>,
56    task_source: SendableTaskSource,
57}
58
59impl StorageManagerBooleanResponseHandler {
60    fn new(trusted_promise: TrustedPromise, task_source: SendableTaskSource) -> Self {
61        Self {
62            trusted_promise: Some(trusted_promise),
63            task_source,
64        }
65    }
66
67    fn handle(&mut self, result: Result<bool, String>) {
68        let Some(trusted_promise) = self.trusted_promise.take() else {
69            error!("StorageManager callback called twice.");
70            return;
71        };
72
73        self.task_source
74            .queue(task!(storage_manager_boolean_response: move |cx| {
75                let promise = trusted_promise.root(cx);
76                match result {
77                    Ok(value) => promise.resolve_native(cx, &value),
78                    Err(message) => promise.reject_error(cx, StorageManager::type_error_from_string(message)),
79                }
80            }));
81    }
82}
83
84struct StorageManagerEstimateResponseHandler {
85    trusted_promise: Option<TrustedPromise>,
86    task_source: SendableTaskSource,
87}
88
89impl StorageManagerEstimateResponseHandler {
90    fn new(trusted_promise: TrustedPromise, task_source: SendableTaskSource) -> Self {
91        Self {
92            trusted_promise: Some(trusted_promise),
93            task_source,
94        }
95    }
96
97    fn handle(&mut self, result: Result<(u64, u64), String>) {
98        let Some(trusted_promise) = self.trusted_promise.take() else {
99            error!("StorageManager callback called twice.");
100            return;
101        };
102
103        self.task_source
104            .queue(task!(storage_manager_estimate_response: move |cx| {
105                let promise = trusted_promise.root(cx);
106                match result {
107                    Ok((usage, quota)) => {
108                        let mut estimate = StorageEstimate::empty();
109                        estimate.usage = Some(usage);
110                        estimate.quota = Some(quota);
111                        promise.resolve_native(cx, &estimate);
112                    },
113                    Err(message) => {
114                        promise.reject_error(cx, StorageManager::type_error_from_string(message));
115                    },
116                }
117            }));
118    }
119}
120
121impl StorageManagerMethods<crate::DomTypeHolder> for StorageManager {
122    /// <https://storage.spec.whatwg.org/#dom-storagemanager-persisted>
123    fn Persisted(&self, cx: &mut CurrentRealm) -> RootedPromise {
124        // Step 1. Let promise be a new promise.
125        let promise = Promise::new_in_realm_rooted(cx);
126        // Step 2. Let global be this’s relevant global object.
127        let global = self.global();
128
129        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
130        // settings object.
131        // Step 4. If shelf is failure, then reject promise with a TypeError.
132        if self.origin_cannot_obtain_local_storage_shelf() {
133            promise.reject_error(
134                cx,
135                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
136            );
137            return promise;
138        }
139
140        // Step 5. Otherwise, run these steps in parallel:
141        // Step 5.1. Let persisted be true if shelf’s bucket map["default"]'s mode is "persistent";
142        // otherwise false.
143        // It will be false when there’s an internal error.
144        // Step 5.2. Queue a storage task with global to resolve promise with persisted.
145        let mut handler = StorageManagerBooleanResponseHandler::new(
146            TrustedPromise::from(&promise),
147            global.task_manager().storage_task_source().to_sendable(),
148        );
149        let callback = GenericCallback::new(move |message| {
150            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
151        })
152        .expect("Could not create StorageManager persisted callback");
153
154        if global
155            .storage_threads()
156            .persisted(global.origin().immutable().clone(), callback.clone())
157            .is_err() &&
158            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
159        {
160            error!("Failed to deliver StorageManager persisted error: {error}");
161        }
162
163        // Step 6. Return promise.
164        promise
165    }
166
167    /// <https://storage.spec.whatwg.org/#dom-storagemanager-persist>
168    fn Persist(&self, cx: &mut CurrentRealm) -> RootedPromise {
169        // Step 1. Let promise be a new promise.
170        let promise = Promise::new_in_realm_rooted(cx);
171        // Step 2. Let global be this’s relevant global object.
172        let global = self.global();
173
174        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
175        // settings object.
176        // Step 4. If shelf is failure, then reject promise with a TypeError.
177        if self.origin_cannot_obtain_local_storage_shelf() {
178            promise.reject_error(
179                cx,
180                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
181            );
182            return promise;
183        }
184
185        // Step 5. Otherwise, run these steps in parallel:
186        // Step 5.1. Let permission be the result of requesting permission to use
187        // "persistent-storage".
188        let permission = request_permission_to_use(PermissionName::Persistent_storage, &global);
189
190        // Step 5.2. Let bucket be shelf’s bucket map["default"].
191        // Step 5.3. Let persisted be true if bucket’s mode is "persistent"; otherwise false.
192        // It will be false when there’s an internal error.
193        // Step 5.4. If persisted is false and permission is "granted", then:
194        // Step 5.4.1. Set bucket’s mode to "persistent".
195        // Step 5.4.2. If there was no internal error, then set persisted to true.
196        // Step 5.5. Queue a storage task with global to resolve promise with persisted.
197        let mut handler = StorageManagerBooleanResponseHandler::new(
198            TrustedPromise::from(&promise),
199            global.task_manager().storage_task_source().to_sendable(),
200        );
201        let callback = GenericCallback::new(move |message| {
202            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
203        })
204        .expect("Could not create StorageManager persist callback");
205
206        if global
207            .storage_threads()
208            .persist(
209                global.origin().immutable().clone(),
210                permission == PermissionState::Granted,
211                callback.clone(),
212            )
213            .is_err() &&
214            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
215        {
216            error!("Failed to deliver StorageManager persist error: {error}");
217        }
218
219        // Step 6. Return promise.
220        promise
221    }
222
223    /// <https://storage.spec.whatwg.org/#dom-storagemanager-estimate>
224    fn Estimate(&self, cx: &mut CurrentRealm) -> RootedPromise {
225        // Step 1. Let promise be a new promise.
226        let promise = Promise::new_in_realm_rooted(cx);
227        // Step 2. Let global be this’s relevant global object.
228        let global = self.global();
229
230        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
231        // settings object.
232        // Step 4. If shelf is failure, then reject promise with a TypeError.
233        if self.origin_cannot_obtain_local_storage_shelf() {
234            promise.reject_error(
235                cx,
236                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
237            );
238            return promise;
239        }
240
241        // Step 5. Otherwise, run these steps in parallel:
242        // Step 5.1. Let usage be storage usage for shelf.
243        // Step 5.2. Let quota be storage quota for shelf.
244        // Step 5.3. Let dictionary be a new StorageEstimate dictionary whose usage member is usage and quota
245        // member is quota.
246        // Step 5.4. If there was an internal error while obtaining usage and quota, then queue a storage
247        // task with global to reject promise with a TypeError.
248        // Step 5.5. Otherwise, queue a storage task with global to resolve promise with dictionary.
249        let mut handler = StorageManagerEstimateResponseHandler::new(
250            TrustedPromise::from(&promise),
251            global.task_manager().storage_task_source().to_sendable(),
252        );
253        let callback = GenericCallback::new(move |message| {
254            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
255        })
256        .expect("Could not create StorageManager estimate callback");
257
258        if global
259            .storage_threads()
260            .estimate(global.origin().immutable().clone(), callback.clone())
261            .is_err() &&
262            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
263        {
264            error!("Failed to deliver StorageManager estimate error: {error}");
265        }
266
267        // Step 6. Return promise.
268        promise
269    }
270}