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 servo_base::generic_channel::GenericCallback;
9
10use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
11    PermissionName, PermissionState,
12};
13use crate::dom::bindings::codegen::Bindings::StorageManagerBinding::{
14    StorageEstimate, StorageManagerMethods,
15};
16use crate::dom::bindings::error::Error;
17use crate::dom::bindings::refcounted::TrustedPromise;
18use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::globalscope::GlobalScope;
21use crate::dom::permissions::request_permission_to_use;
22use crate::dom::promise::Promise;
23use crate::realms::InRealm;
24use crate::script_runtime::CanGc;
25use crate::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(global: &GlobalScope, can_gc: CanGc) -> DomRoot<StorageManager> {
40        reflect_dom_object(Box::new(StorageManager::new_inherited()), global, can_gc)
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();
76                match result {
77                    Ok(value) => promise.resolve_native(&value, CanGc::from_cx(cx)),
78                    Err(message) => promise.reject_error(
79                        StorageManager::type_error_from_string(message),
80                        CanGc::from_cx(cx),
81                    ),
82                }
83            }));
84    }
85}
86
87struct StorageManagerEstimateResponseHandler {
88    trusted_promise: Option<TrustedPromise>,
89    task_source: SendableTaskSource,
90}
91
92impl StorageManagerEstimateResponseHandler {
93    fn new(trusted_promise: TrustedPromise, task_source: SendableTaskSource) -> Self {
94        Self {
95            trusted_promise: Some(trusted_promise),
96            task_source,
97        }
98    }
99
100    fn handle(&mut self, result: Result<(u64, u64), String>) {
101        let Some(trusted_promise) = self.trusted_promise.take() else {
102            error!("StorageManager callback called twice.");
103            return;
104        };
105
106        self.task_source
107            .queue(task!(storage_manager_estimate_response: move |cx| {
108                let promise = trusted_promise.root();
109                match result {
110                    Ok((usage, quota)) => {
111                        let mut estimate = StorageEstimate::empty();
112                        estimate.usage = Some(usage);
113                        estimate.quota = Some(quota);
114                        promise.resolve_native(&estimate, CanGc::from_cx(cx));
115                    },
116                    Err(message) => {
117                        promise.reject_error(
118                            StorageManager::type_error_from_string(message),
119                            CanGc::from_cx(cx),
120                        );
121                    },
122                }
123            }));
124    }
125}
126
127impl StorageManagerMethods<crate::DomTypeHolder> for StorageManager {
128    /// <https://storage.spec.whatwg.org/#dom-storagemanager-persisted>
129    fn Persisted(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
130        // Step 1. Let promise be a new promise.
131        let promise = Promise::new_in_current_realm(comp, can_gc);
132        // Step 2. Let global be this’s relevant global object.
133        let global = self.global();
134
135        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
136        // settings object.
137        // Step 4. If shelf is failure, then reject promise with a TypeError.
138        if self.origin_cannot_obtain_local_storage_shelf() {
139            promise.reject_error(
140                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
141                can_gc,
142            );
143            return promise;
144        }
145
146        // Step 5. Otherwise, run these steps in parallel:
147        // Step 5.1. Let persisted be true if shelf’s bucket map["default"]'s mode is "persistent";
148        // otherwise false.
149        // It will be false when there’s an internal error.
150        // Step 5.2. Queue a storage task with global to resolve promise with persisted.
151        let mut handler = StorageManagerBooleanResponseHandler::new(
152            TrustedPromise::new(promise.clone()),
153            global.task_manager().storage_task_source().to_sendable(),
154        );
155        let callback = GenericCallback::new(move |message| {
156            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
157        })
158        .expect("Could not create StorageManager persisted callback");
159
160        if global
161            .storage_threads()
162            .persisted(global.origin().immutable().clone(), callback.clone())
163            .is_err()
164        {
165            if let Err(error) = callback.send(Err("Failed to queue storage task".to_owned())) {
166                error!("Failed to deliver StorageManager persisted error: {error}");
167            }
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        {
222            if let Err(error) = callback.send(Err("Failed to queue storage task".to_owned())) {
223                error!("Failed to deliver StorageManager persist error: {error}");
224            }
225        }
226
227        // Step 6. Return promise.
228        promise
229    }
230
231    /// <https://storage.spec.whatwg.org/#dom-storagemanager-estimate>
232    fn Estimate(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
233        // Step 1. Let promise be a new promise.
234        let promise = Promise::new_in_current_realm(comp, can_gc);
235        // Step 2. Let global be this’s relevant global object.
236        let global = self.global();
237
238        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
239        // settings object.
240        // Step 4. If shelf is failure, then reject promise with a TypeError.
241        if self.origin_cannot_obtain_local_storage_shelf() {
242            promise.reject_error(
243                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
244                can_gc,
245            );
246            return promise;
247        }
248
249        // Step 5. Otherwise, run these steps in parallel:
250        // Step 5.1. Let usage be storage usage for shelf.
251        // Step 5.2. Let quota be storage quota for shelf.
252        // Step 5.3. Let dictionary be a new StorageEstimate dictionary whose usage member is usage and quota
253        // member is quota.
254        // Step 5.4. If there was an internal error while obtaining usage and quota, then queue a storage
255        // task with global to reject promise with a TypeError.
256        // Step 5.5. Otherwise, queue a storage task with global to resolve promise with dictionary.
257        let mut handler = StorageManagerEstimateResponseHandler::new(
258            TrustedPromise::new(promise.clone()),
259            global.task_manager().storage_task_source().to_sendable(),
260        );
261        let callback = GenericCallback::new(move |message| {
262            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
263        })
264        .expect("Could not create StorageManager estimate callback");
265
266        if global
267            .storage_threads()
268            .estimate(global.origin().immutable().clone(), callback.clone())
269            .is_err()
270        {
271            if let Err(error) = callback.send(Err("Failed to queue storage task".to_owned())) {
272                error!("Failed to deliver StorageManager estimate error: {error}");
273            }
274        }
275
276        // Step 6. Return promise.
277        promise
278    }
279}