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, MallocSizeOf, 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, MallocSizeOf, 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, Debug, Deserialize, MallocSizeOf, Serialize)]
257pub struct IndexedDBIndex {
258    pub name: String,
259    pub key_path: KeyPath,
260    pub multi_entry: bool,
261    pub unique: bool,
262}
263
264#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
265pub struct IndexedDBObjectStore {
266    pub name: String,
267    pub key_path: Option<KeyPath>,
268    pub has_key_generator: bool,
269    pub indexes: Vec<IndexedDBIndex>,
270}
271
272#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
273pub enum PutItemResult {
274    Success,
275    CannotOverwrite,
276}
277
278#[derive(Debug, Deserialize, Serialize)]
279pub enum AsyncReadOnlyOperation {
280    /// Gets the value associated with the given key in the associated idb data
281    GetKey {
282        callback: GenericCallback<BackendResult<Option<IndexedDBKeyType>>>,
283        key_range: IndexedDBKeyRange,
284    },
285    GetItem {
286        callback: GenericCallback<BackendResult<Option<Vec<u8>>>>,
287        key_range: IndexedDBKeyRange,
288    },
289
290    GetAllKeys {
291        callback: GenericCallback<BackendResult<Vec<IndexedDBKeyType>>>,
292        key_range: IndexedDBKeyRange,
293        count: Option<u32>,
294    },
295    GetAllItems {
296        callback: GenericCallback<BackendResult<Vec<Vec<u8>>>>,
297        key_range: IndexedDBKeyRange,
298        count: Option<u32>,
299    },
300
301    Count {
302        callback: GenericCallback<BackendResult<u64>>,
303        key_range: IndexedDBKeyRange,
304    },
305    Iterate {
306        callback: GenericCallback<BackendResult<Vec<IndexedDBRecord>>>,
307        key_range: IndexedDBKeyRange,
308    },
309}
310
311impl AsyncReadOnlyOperation {
312    fn notify_error(&self, error: BackendError) {
313        let _ = match self {
314            Self::GetKey { callback, .. } => callback.send(Err(error)),
315            Self::GetItem { callback, .. } => callback.send(Err(error)),
316            Self::GetAllKeys { callback, .. } => callback.send(Err(error)),
317            Self::GetAllItems { callback, .. } => callback.send(Err(error)),
318            Self::Count { callback, .. } => callback.send(Err(error)),
319            Self::Iterate { callback, .. } => callback.send(Err(error)),
320        };
321    }
322}
323
324#[derive(Debug, Deserialize, Serialize)]
325pub enum AsyncReadWriteOperation {
326    /// Sets the value of the given key in the associated idb data
327    PutItem {
328        callback: GenericCallback<BackendResult<PutItemResult>>,
329        key: Option<IndexedDBKeyType>,
330        value: Vec<u8>,
331        should_overwrite: bool,
332    },
333
334    /// Removes the key/value pair for the given key in the associated idb data
335    RemoveItem {
336        callback: GenericCallback<BackendResult<()>>,
337        key_range: IndexedDBKeyRange,
338    },
339    /// Clears all key/value pairs in the associated idb data
340    Clear(GenericCallback<BackendResult<()>>),
341}
342
343impl AsyncReadWriteOperation {
344    fn notify_error(&self, error: BackendError) {
345        let _ = match self {
346            Self::PutItem { callback, .. } => callback.send(Err(error)),
347            Self::RemoveItem { callback, .. } => callback.send(Err(error)),
348            Self::Clear(callback) => callback.send(Err(error)),
349        };
350    }
351}
352
353/// Operations that are not executed instantly, but rather added to a
354/// queue that is eventually run.
355#[derive(Debug, Deserialize, Serialize)]
356pub enum AsyncOperation {
357    ReadOnly(AsyncReadOnlyOperation),
358    ReadWrite(AsyncReadWriteOperation),
359}
360
361impl AsyncOperation {
362    pub fn notify_error(&self, error: BackendError) {
363        match self {
364            Self::ReadOnly(operation) => operation.notify_error(error),
365            Self::ReadWrite(operation) => operation.notify_error(error),
366        }
367    }
368}
369
370#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
371pub enum CreateObjectResult {
372    Created,
373    AlreadyExists,
374}
375
376#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
377/// Messaging used in the context of connection lifecycle management.
378pub enum ConnectionMsg {
379    /// Error if a DB is opened for a version lower than the current db version.
380    VersionError { name: String, id: Uuid },
381    /// Opening a connection was aborted.
382    AbortError { name: String, id: Uuid },
383    /// A newly created connection with a version,
384    /// updgraded or not.
385    Connection {
386        name: String,
387        id: Uuid,
388        version: u64,
389        upgraded: bool,
390    },
391    /// An upgrade transaction for a version started.
392    Upgrade {
393        name: String,
394        id: Uuid,
395        version: u64,
396        old_version: u64,
397        transaction: u64,
398    },
399    /// A `versionchange` event should be fired for a connection.
400    VersionChange {
401        /// The id of the connection.
402        id: Uuid,
403        /// The name of the connection.
404        name: String,
405        version: u64,
406        old_version: u64,
407    },
408    /// A `blocked` event should be fired for a connection.
409    Blocked {
410        name: String,
411        id: Uuid,
412        version: u64,
413        old_version: u64,
414    },
415    /// A backend error related to the database occured.
416    DatabaseError {
417        name: String,
418        id: Uuid,
419        error: BackendError,
420    },
421}
422
423#[derive(Debug, Deserialize, Serialize)]
424pub struct DatabaseInfo {
425    pub name: String,
426    pub version: u64,
427}
428
429#[derive(Debug, Deserialize, Serialize)]
430pub enum SyncOperation {
431    /// Gets existing databases.
432    GetDatabases(
433        GenericCallback<BackendResult<Vec<DatabaseInfo>>>,
434        ImmutableOrigin,
435    ),
436    /// Upgrades the version of the database
437    UpgradeVersion(
438        /// Sender to send new version as the result of the operation
439        GenericSender<BackendResult<u64>>,
440        ImmutableOrigin,
441        String, // Database
442        u64,    // Serial number for the transaction
443        u64,    // Version to upgrade to
444    ),
445    /// Get object store info
446    GetObjectStore(
447        GenericSender<BackendResult<IndexedDBObjectStore>>,
448        ImmutableOrigin,
449        String, // Database
450        String, // Store
451    ),
452
453    /// Commits changes of a transaction to the database
454    Commit(
455        GenericSender<BackendResult<()>>,
456        ImmutableOrigin,
457        String, // Database
458        u64,    // Transaction serial number
459    ),
460
461    /// Creates a new index for the database
462    CreateIndex(
463        ImmutableOrigin,
464        String,  // Database
465        String,  // Store
466        String,  // Index name
467        KeyPath, // key path
468        bool,    // unique flag
469        bool,    // multientry flag
470    ),
471    /// Delete an index
472    DeleteIndex(
473        ImmutableOrigin,
474        String, // Database
475        String, // Store
476        String, // Index name
477    ),
478
479    /// Creates a new store for the database
480    CreateObjectStore(
481        GenericSender<BackendResult<CreateObjectResult>>,
482        ImmutableOrigin,
483        String,          // Database
484        String,          // Store
485        Option<KeyPath>, // Key Path
486        bool,
487    ),
488
489    DeleteObjectStore(
490        GenericSender<BackendResult<()>>,
491        ImmutableOrigin,
492        String, // Database
493        String, // Store
494    ),
495
496    CloseDatabase(
497        ImmutableOrigin,
498        Uuid,
499        String, // Database
500    ),
501
502    OpenDatabase(
503        // Callback for the result.
504        GenericCallback<ConnectionMsg>,
505        // Origin of the request.
506        ImmutableOrigin,
507        // Name of the database.
508        String,
509        // Requested db version(optional).
510        Option<u64>,
511        // The id of the request.
512        Uuid,
513    ),
514
515    /// Deletes the database
516    DeleteDatabase(
517        GenericCallback<BackendResult<u64>>,
518        ImmutableOrigin,
519        String, // Database
520        Uuid,
521    ),
522
523    /// Returns an unique identifier that is used to be able to
524    /// commit/abort transactions.
525    RegisterNewTxn(
526        /// The unique identifier of the transaction
527        GenericSender<u64>,
528        ImmutableOrigin,
529        String, // Database
530    ),
531
532    /// Starts executing the requests of a transaction
533    /// <https://www.w3.org/TR/IndexedDB-2/#transaction-start>
534    StartTransaction(
535        GenericSender<BackendResult<()>>,
536        ImmutableOrigin,
537        String, // Database
538        u64,    // The serial number of the mutating transaction
539    ),
540
541    /// Returns the version of the database
542    Version(
543        GenericSender<BackendResult<u64>>,
544        ImmutableOrigin,
545        String, // Database
546    ),
547
548    /// Abort pending database upgrades
549    AbortPendingUpgrades {
550        pending_upgrades: HashMap<String, HashSet<Uuid>>,
551        origin: ImmutableOrigin,
552    },
553
554    /// Abort the current pending upgrade.
555    AbortPendingUpgrade {
556        name: String,
557        id: Uuid,
558        origin: ImmutableOrigin,
559    },
560
561    NotifyEndOfVersionChange {
562        id: Uuid,
563        name: String,
564        old_version: u64,
565        origin: ImmutableOrigin,
566    },
567
568    /// Send a reply when done cleaning up thread resources and then shut it down
569    Exit(GenericSender<()>),
570}
571
572#[derive(Debug, Deserialize, Serialize)]
573pub enum IndexedDBThreadMsg {
574    Sync(SyncOperation),
575    Async(
576        ImmutableOrigin,
577        String, // Database
578        String, // ObjectStore
579        u64,    // Serial number of the transaction that requests this operation
580        IndexedDBTxnMode,
581        AsyncOperation,
582    ),
583    OpenTransactionInactive {
584        name: String,
585        origin: ImmutableOrigin,
586    },
587}
588
589#[cfg(test)]
590mod test {
591    use super::*;
592
593    #[test]
594    fn test_as_singleton() {
595        let key = IndexedDBKeyType::Number(1.0);
596        let key2 = IndexedDBKeyType::Number(2.0);
597        let range = IndexedDBKeyRange::only(key.clone());
598        assert!(range.is_singleton());
599        assert!(range.as_singleton().is_some());
600        let range = IndexedDBKeyRange::new(Some(key), Some(key2.clone()), false, false);
601        assert!(!range.is_singleton());
602        assert!(range.as_singleton().is_none());
603        let full_range = IndexedDBKeyRange::new(None, None, false, false);
604        assert!(!full_range.is_singleton());
605        assert!(full_range.as_singleton().is_none());
606    }
607}