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