Skip to main content

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