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_with_cx(cx, &value),
79                    Err(message) => promise.reject_error_with_cx(cx, StorageManager::type_error_from_string(message)),
80                }
81            }));
82    }
83}
84
85struct StorageManagerEstimateResponseHandler {
86    trusted_promise: Option<TrustedPromise>,
87    task_source: SendableTaskSource,
88}
89
90impl StorageManagerEstimateResponseHandler {
91    fn new(trusted_promise: TrustedPromise, task_source: SendableTaskSource) -> Self {
92        Self {
93            trusted_promise: Some(trusted_promise),
94            task_source,
95        }
96    }
97
98    fn handle(&mut self, result: Result<(u64, u64), String>) {
99        let Some(trusted_promise) = self.trusted_promise.take() else {
100            error!("StorageManager callback called twice.");
101            return;
102        };
103
104        self.task_source
105            .queue(task!(storage_manager_estimate_response: move |cx| {
106                let promise = trusted_promise.root();
107                match result {
108                    Ok((usage, quota)) => {
109                        let mut estimate = StorageEstimate::empty();
110                        estimate.usage = Some(usage);
111                        estimate.quota = Some(quota);
112                        promise.resolve_native_with_cx(cx, &estimate);
113                    },
114                    Err(message) => {
115                        promise.reject_error_with_cx(cx, StorageManager::type_error_from_string(message));
116                    },
117                }
118            }));
119    }
120}
121
122impl StorageManagerMethods<crate::DomTypeHolder> for StorageManager {
123    /// <https://storage.spec.whatwg.org/#dom-storagemanager-persisted>
124    fn Persisted(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
125        // Step 1. Let promise be a new promise.
126        let promise = Promise::new_in_current_realm(comp, can_gc);
127        // Step 2. Let global be this’s relevant global object.
128        let global = self.global();
129
130        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
131        // settings object.
132        // Step 4. If shelf is failure, then reject promise with a TypeError.
133        if self.origin_cannot_obtain_local_storage_shelf() {
134            promise.reject_error(
135                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
136                can_gc,
137            );
138            return promise;
139        }
140
141        // Step 5. Otherwise, run these steps in parallel:
142        // Step 5.1. Let persisted be true if shelf’s bucket map["default"]'s mode is "persistent";
143        // otherwise false.
144        // It will be false when there’s an internal error.
145        // Step 5.2. Queue a storage task with global to resolve promise with persisted.
146        let mut handler = StorageManagerBooleanResponseHandler::new(
147            TrustedPromise::new(promise.clone()),
148            global.task_manager().storage_task_source().to_sendable(),
149        );
150        let callback = GenericCallback::new(move |message| {
151            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
152        })
153        .expect("Could not create StorageManager persisted callback");
154
155        if global
156            .storage_threads()
157            .persisted(global.origin().immutable().clone(), callback.clone())
158            .is_err() &&
159            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
160        {
161            error!("Failed to deliver StorageManager persisted error: {error}");
162        }
163
164        // Step 6. Return promise.
165        promise
166    }
167
168    /// <https://storage.spec.whatwg.org/#dom-storagemanager-persist>
169    fn Persist(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
170        // Step 1. Let promise be a new promise.
171        let promise = Promise::new_in_current_realm(comp, can_gc);
172        // Step 2. Let global be this’s relevant global object.
173        let global = self.global();
174
175        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
176        // settings object.
177        // Step 4. If shelf is failure, then reject promise with a TypeError.
178        if self.origin_cannot_obtain_local_storage_shelf() {
179            promise.reject_error(
180                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
181                can_gc,
182            );
183            return promise;
184        }
185
186        // Step 5. Otherwise, run these steps in parallel:
187        // Step 5.1. Let permission be the result of requesting permission to use
188        // "persistent-storage".
189        let permission = request_permission_to_use(PermissionName::Persistent_storage, &global);
190
191        // Step 5.2. Let bucket be shelf’s bucket map["default"].
192        // Step 5.3. Let persisted be true if bucket’s mode is "persistent"; otherwise false.
193        // It will be false when there’s an internal error.
194        // Step 5.4. If persisted is false and permission is "granted", then:
195        // Step 5.4.1. Set bucket’s mode to "persistent".
196        // Step 5.4.2. If there was no internal error, then set persisted to true.
197        // Step 5.5. Queue a storage task with global to resolve promise with persisted.
198        let mut handler = StorageManagerBooleanResponseHandler::new(
199            TrustedPromise::new(promise.clone()),
200            global.task_manager().storage_task_source().to_sendable(),
201        );
202        let callback = GenericCallback::new(move |message| {
203            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
204        })
205        .expect("Could not create StorageManager persist callback");
206
207        if global
208            .storage_threads()
209            .persist(
210                global.origin().immutable().clone(),
211                permission == PermissionState::Granted,
212                callback.clone(),
213            )
214            .is_err() &&
215            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
216        {
217            error!("Failed to deliver StorageManager persist error: {error}");
218        }
219
220        // Step 6. Return promise.
221        promise
222    }
223
224    /// <https://storage.spec.whatwg.org/#dom-storagemanager-estimate>
225    fn Estimate(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
226        // Step 1. Let promise be a new promise.
227        let promise = Promise::new_in_current_realm(comp, can_gc);
228        // Step 2. Let global be this’s relevant global object.
229        let global = self.global();
230
231        // Step 3. Let shelf be the result of running obtain a local storage shelf with this’s relevant
232        // settings object.
233        // Step 4. If shelf is failure, then reject promise with a TypeError.
234        if self.origin_cannot_obtain_local_storage_shelf() {
235            promise.reject_error(
236                Error::Type(c"Storage is unavailable for opaque origins".to_owned()),
237                can_gc,
238            );
239            return promise;
240        }
241
242        // Step 5. Otherwise, run these steps in parallel:
243        // Step 5.1. Let usage be storage usage for shelf.
244        // Step 5.2. Let quota be storage quota for shelf.
245        // Step 5.3. Let dictionary be a new StorageEstimate dictionary whose usage member is usage and quota
246        // member is quota.
247        // Step 5.4. If there was an internal error while obtaining usage and quota, then queue a storage
248        // task with global to reject promise with a TypeError.
249        // Step 5.5. Otherwise, queue a storage task with global to resolve promise with dictionary.
250        let mut handler = StorageManagerEstimateResponseHandler::new(
251            TrustedPromise::new(promise.clone()),
252            global.task_manager().storage_task_source().to_sendable(),
253        );
254        let callback = GenericCallback::new(move |message| {
255            handler.handle(message.unwrap_or_else(|error| Err(error.to_string())));
256        })
257        .expect("Could not create StorageManager estimate callback");
258
259        if global
260            .storage_threads()
261            .estimate(global.origin().immutable().clone(), callback.clone())
262            .is_err() &&
263            let Err(error) = callback.send(Err("Failed to queue storage task".to_owned()))
264        {
265            error!("Failed to deliver StorageManager estimate error: {error}");
266        }
267
268        // Step 6. Return promise.
269        promise
270    }
271}