storage_traits/
indexeddb.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::cmp::{Ordering, PartialEq, PartialOrd};
6use std::error::Error;
7use std::fmt::{Debug, Display, Formatter};
8
9use ipc_channel::ipc::IpcSender;
10use malloc_size_of_derive::MallocSizeOf;
11use serde::{Deserialize, Serialize};
12use servo_url::origin::ImmutableOrigin;
13
14// TODO Box<dyn Error> is not serializable, fix needs to be found
15pub type DbError = String;
16/// A DbResult wraps any part of a call that has to reach into the backend (in this case sqlite.rs)
17/// These errors could be anything, depending on the backend
18pub type DbResult<T> = Result<T, DbError>;
19
20/// Any error from the backend, a super-set of [`DbError`]
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22pub enum BackendError {
23    /// The requested database does not exist
24    DbNotFound,
25    /// The requested object store does not exist
26    StoreNotFound,
27    DbErr(DbError),
28}
29
30impl From<DbError> for BackendError {
31    fn from(value: DbError) -> Self {
32        BackendError::DbErr(value)
33    }
34}
35
36impl Display for BackendError {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        write!(f, "{:?}", self)
39    }
40}
41
42impl Error for BackendError {}
43
44pub type BackendResult<T> = Result<T, BackendError>;
45
46#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
47pub enum KeyPath {
48    String(String),
49    Sequence(Vec<String>),
50}
51
52// https://www.w3.org/TR/IndexedDB-2/#enumdef-idbtransactionmode
53#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
54pub enum IndexedDBTxnMode {
55    Readonly,
56    Readwrite,
57    Versionchange,
58}
59
60/// <https://www.w3.org/TR/IndexedDB-2/#key-type>
61#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
62pub enum IndexedDBKeyType {
63    Number(f64),
64    String(String),
65    Binary(Vec<u8>),
66    Date(f64),
67    Array(Vec<IndexedDBKeyType>),
68    // FIXME:(arihant2math) implment ArrayBuffer
69}
70
71/// <https://www.w3.org/TR/IndexedDB-2/#compare-two-keys>
72impl PartialOrd for IndexedDBKeyType {
73    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74        // 1. Let ta be the type of a.
75        // 2. Let tb be the type of b.
76
77        match (self, other) {
78            // Step 3: If ta is array and tb is binary, string, date or number, return 1.
79            (
80                IndexedDBKeyType::Array(_),
81                IndexedDBKeyType::Binary(_) |
82                IndexedDBKeyType::Date(_) |
83                IndexedDBKeyType::Number(_) |
84                IndexedDBKeyType::String(_),
85            ) => Some(Ordering::Greater),
86            // Step 4: If tb is array and ta is binary, string, date or number, return -1.
87            (
88                IndexedDBKeyType::Binary(_) |
89                IndexedDBKeyType::Date(_) |
90                IndexedDBKeyType::Number(_) |
91                IndexedDBKeyType::String(_),
92                IndexedDBKeyType::Array(_),
93            ) => Some(Ordering::Less),
94            // Step 5: If ta is binary and tb is string, date or number, return 1.
95            (
96                IndexedDBKeyType::Binary(_),
97                IndexedDBKeyType::String(_) |
98                IndexedDBKeyType::Date(_) |
99                IndexedDBKeyType::Number(_),
100            ) => Some(Ordering::Greater),
101            // Step 6: If tb is binary and ta is string, date or number, return -1.
102            (
103                IndexedDBKeyType::String(_) |
104                IndexedDBKeyType::Date(_) |
105                IndexedDBKeyType::Number(_),
106                IndexedDBKeyType::Binary(_),
107            ) => Some(Ordering::Less),
108            // Step 7: If ta is string and tb is date or number, return 1.
109            (
110                IndexedDBKeyType::String(_),
111                IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_),
112            ) => Some(Ordering::Greater),
113            // Step 8: If tb is string and ta is date or number, return -1.
114            (
115                IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_),
116                IndexedDBKeyType::String(_),
117            ) => Some(Ordering::Less),
118            // Step 9: If ta is date and tb is number, return 1.
119            (IndexedDBKeyType::Date(_), IndexedDBKeyType::Number(_)) => Some(Ordering::Greater),
120            // Step 10: If tb is date and ta is number, return -1.
121            (IndexedDBKeyType::Number(_), IndexedDBKeyType::Date(_)) => Some(Ordering::Less),
122            // Step 11 skipped
123            // TODO: Likely a tiny bit wrong (use js number comparison)
124            (IndexedDBKeyType::Number(a), IndexedDBKeyType::Number(b)) => a.partial_cmp(b),
125            // TODO: Likely a tiny bit wrong (use js string comparison)
126            (IndexedDBKeyType::String(a), IndexedDBKeyType::String(b)) => a.partial_cmp(b),
127            // TODO: Likely a little wrong (use js binary comparison)
128            (IndexedDBKeyType::Binary(a), IndexedDBKeyType::Binary(b)) => a.partial_cmp(b),
129            (IndexedDBKeyType::Date(a), IndexedDBKeyType::Date(b)) => a.partial_cmp(b),
130            // TODO: Probably also wrong (the items in a and b should be compared, double check against the spec)
131            (IndexedDBKeyType::Array(a), IndexedDBKeyType::Array(b)) => a.partial_cmp(b),
132            // No catch-all is used, rust ensures that all variants are handled
133        }
134    }
135}
136
137impl PartialEq for IndexedDBKeyType {
138    fn eq(&self, other: &Self) -> bool {
139        let cmp = self.partial_cmp(other);
140        match cmp {
141            Some(Ordering::Equal) => true,
142            Some(Ordering::Less) | Some(Ordering::Greater) => false,
143            None => {
144                // If we can't compare the two keys, we assume they are not equal.
145                false
146            },
147        }
148    }
149}
150
151// <https://www.w3.org/TR/IndexedDB-2/#key-range>
152#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
153pub struct IndexedDBKeyRange {
154    pub lower: Option<IndexedDBKeyType>,
155    pub upper: Option<IndexedDBKeyType>,
156    pub lower_open: bool,
157    pub upper_open: bool,
158}
159
160impl From<IndexedDBKeyType> for IndexedDBKeyRange {
161    fn from(key: IndexedDBKeyType) -> Self {
162        IndexedDBKeyRange {
163            lower: Some(key.clone()),
164            upper: Some(key),
165            ..Default::default()
166        }
167    }
168}
169
170impl IndexedDBKeyRange {
171    pub fn only(key: IndexedDBKeyType) -> Self {
172        Self::from(key)
173    }
174
175    pub fn new(
176        lower: Option<IndexedDBKeyType>,
177        upper: Option<IndexedDBKeyType>,
178        lower_open: bool,
179        upper_open: bool,
180    ) -> Self {
181        IndexedDBKeyRange {
182            lower,
183            upper,
184            lower_open,
185            upper_open,
186        }
187    }
188
189    pub fn lower_bound(key: IndexedDBKeyType, open: bool) -> Self {
190        IndexedDBKeyRange {
191            lower: Some(key),
192            upper: None,
193            lower_open: open,
194            upper_open: false,
195        }
196    }
197
198    pub fn upper_bound(key: IndexedDBKeyType, open: bool) -> Self {
199        IndexedDBKeyRange {
200            lower: None,
201            upper: Some(key),
202            lower_open: false,
203            upper_open: open,
204        }
205    }
206
207    // <https://www.w3.org/TR/IndexedDB-2/#in>
208    pub fn contains(&self, key: &IndexedDBKeyType) -> bool {
209        // A key is in a key range if both of the following conditions are fulfilled:
210        // The lower bound is null, or it is less than key,
211        // or it is both equal to key and the lower open flag is unset.
212        // The upper bound is null, or it is greater than key,
213        // or it is both equal to key and the upper open flag is unset
214        let lower_bound_condition = self
215            .lower
216            .as_ref()
217            .is_none_or(|lower| lower < key || (!self.lower_open && lower == key));
218        let upper_bound_condition = self
219            .upper
220            .as_ref()
221            .is_none_or(|upper| key < upper || (!self.upper_open && key == upper));
222        lower_bound_condition && upper_bound_condition
223    }
224
225    pub fn is_singleton(&self) -> bool {
226        self.lower.is_some() && self.lower == self.upper && !self.lower_open && !self.upper_open
227    }
228
229    pub fn as_singleton(&self) -> Option<&IndexedDBKeyType> {
230        if self.is_singleton() {
231            return Some(self.lower.as_ref().unwrap());
232        }
233        None
234    }
235}
236
237/// <https://w3c.github.io/IndexedDB/#record-snapshot>
238#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
239pub struct IndexedDBRecord {
240    pub key: IndexedDBKeyType,
241    pub primary_key: IndexedDBKeyType,
242    pub value: Vec<u8>,
243}
244
245#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
246pub enum PutItemResult {
247    Success,
248    CannotOverwrite,
249}
250
251#[derive(Debug, Deserialize, Serialize)]
252pub enum AsyncReadOnlyOperation {
253    /// Gets the value associated with the given key in the associated idb data
254    GetKey {
255        sender: IpcSender<BackendResult<Option<IndexedDBKeyType>>>,
256        key_range: IndexedDBKeyRange,
257    },
258    GetItem {
259        sender: IpcSender<BackendResult<Option<Vec<u8>>>>,
260        key_range: IndexedDBKeyRange,
261    },
262
263    GetAllKeys {
264        sender: IpcSender<BackendResult<Vec<IndexedDBKeyType>>>,
265        key_range: IndexedDBKeyRange,
266        count: Option<u32>,
267    },
268    GetAllItems {
269        sender: IpcSender<BackendResult<Vec<Vec<u8>>>>,
270        key_range: IndexedDBKeyRange,
271        count: Option<u32>,
272    },
273
274    Count {
275        sender: IpcSender<BackendResult<u64>>,
276        key_range: IndexedDBKeyRange,
277    },
278    Iterate {
279        sender: IpcSender<BackendResult<Vec<IndexedDBRecord>>>,
280        key_range: IndexedDBKeyRange,
281    },
282}
283
284#[derive(Debug, Deserialize, Serialize)]
285pub enum AsyncReadWriteOperation {
286    /// Sets the value of the given key in the associated idb data
287    PutItem {
288        sender: IpcSender<BackendResult<PutItemResult>>,
289        key: Option<IndexedDBKeyType>,
290        value: Vec<u8>,
291        should_overwrite: bool,
292    },
293
294    /// Removes the key/value pair for the given key in the associated idb data
295    RemoveItem {
296        sender: IpcSender<BackendResult<()>>,
297        key_range: IndexedDBKeyRange,
298    },
299    /// Clears all key/value pairs in the associated idb data
300    Clear(IpcSender<BackendResult<()>>),
301}
302
303/// Operations that are not executed instantly, but rather added to a
304/// queue that is eventually run.
305#[derive(Debug, Deserialize, Serialize)]
306pub enum AsyncOperation {
307    ReadOnly(AsyncReadOnlyOperation),
308    ReadWrite(AsyncReadWriteOperation),
309}
310
311#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
312pub enum CreateObjectResult {
313    Created,
314    AlreadyExists,
315}
316
317#[derive(Debug, Deserialize, Serialize)]
318pub enum SyncOperation {
319    /// Upgrades the version of the database
320    UpgradeVersion(
321        /// Sender to send new version as the result of the operation
322        IpcSender<BackendResult<u64>>,
323        ImmutableOrigin,
324        String, // Database
325        u64,    // Serial number for the transaction
326        u64,    // Version to upgrade to
327    ),
328    /// Checks if an object store has a key generator, used in e.g. Put
329    HasKeyGenerator(
330        IpcSender<BackendResult<bool>>,
331        ImmutableOrigin,
332        String, // Database
333        String, // Store
334    ),
335    /// Gets an object store's key path
336    KeyPath(
337        /// Object stores can optionally be created with key paths
338        IpcSender<BackendResult<Option<KeyPath>>>,
339        ImmutableOrigin,
340        String, // Database
341        String, // Store
342    ),
343
344    /// Commits changes of a transaction to the database
345    Commit(
346        IpcSender<BackendResult<()>>,
347        ImmutableOrigin,
348        String, // Database
349        u64,    // Transaction serial number
350    ),
351
352    /// Creates a new index for the database
353    CreateIndex(
354        IpcSender<BackendResult<CreateObjectResult>>,
355        ImmutableOrigin,
356        String,  // Database
357        String,  // Store
358        String,  // Index name
359        KeyPath, // key path
360        bool,    // unique flag
361        bool,    // multientry flag
362    ),
363    /// Delete an index
364    DeleteIndex(
365        IpcSender<BackendResult<()>>,
366        ImmutableOrigin,
367        String, // Database
368        String, // Store
369        String, // Index name
370    ),
371
372    /// Creates a new store for the database
373    CreateObjectStore(
374        IpcSender<BackendResult<CreateObjectResult>>,
375        ImmutableOrigin,
376        String,          // Database
377        String,          // Store
378        Option<KeyPath>, // Key Path
379        bool,
380    ),
381
382    DeleteObjectStore(
383        IpcSender<BackendResult<()>>,
384        ImmutableOrigin,
385        String, // Database
386        String, // Store
387    ),
388
389    CloseDatabase(
390        IpcSender<BackendResult<()>>,
391        ImmutableOrigin,
392        String, // Database
393    ),
394
395    OpenDatabase(
396        IpcSender<u64>, // Returns the version
397        ImmutableOrigin,
398        String,      // Database
399        Option<u64>, // Eventual version
400    ),
401
402    /// Deletes the database
403    DeleteDatabase(
404        IpcSender<BackendResult<()>>,
405        ImmutableOrigin,
406        String, // Database
407    ),
408
409    /// Returns an unique identifier that is used to be able to
410    /// commit/abort transactions.
411    RegisterNewTxn(
412        /// The unique identifier of the transaction
413        IpcSender<u64>,
414        ImmutableOrigin,
415        String, // Database
416    ),
417
418    /// Starts executing the requests of a transaction
419    /// <https://www.w3.org/TR/IndexedDB-2/#transaction-start>
420    StartTransaction(
421        IpcSender<BackendResult<()>>,
422        ImmutableOrigin,
423        String, // Database
424        u64,    // The serial number of the mutating transaction
425    ),
426
427    /// Returns the version of the database
428    Version(
429        IpcSender<BackendResult<u64>>,
430        ImmutableOrigin,
431        String, // Database
432    ),
433
434    /// Send a reply when done cleaning up thread resources and then shut it down
435    Exit(IpcSender<()>),
436}
437
438#[derive(Debug, Deserialize, Serialize)]
439pub enum IndexedDBThreadMsg {
440    Sync(SyncOperation),
441    Async(
442        ImmutableOrigin,
443        String, // Database
444        String, // ObjectStore
445        u64,    // Serial number of the transaction that requests this operation
446        IndexedDBTxnMode,
447        AsyncOperation,
448    ),
449}
450
451#[cfg(test)]
452mod test {
453    use super::*;
454
455    #[test]
456    fn test_as_singleton() {
457        let key = IndexedDBKeyType::Number(1.0);
458        let key2 = IndexedDBKeyType::Number(2.0);
459        let range = IndexedDBKeyRange::only(key.clone());
460        assert!(range.is_singleton());
461        assert!(range.as_singleton().is_some());
462        let range = IndexedDBKeyRange::new(Some(key), Some(key2.clone()), false, false);
463        assert!(!range.is_singleton());
464        assert!(range.as_singleton().is_none());
465        let full_range = IndexedDBKeyRange::new(None, None, false, false);
466        assert!(!full_range.is_singleton());
467        assert!(full_range.as_singleton().is_none());
468    }
469}