storage_traits/
indexeddb_thread.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)]
153#[allow(unused)]
154pub struct IndexedDBKeyRange {
155    pub lower: Option<IndexedDBKeyType>,
156    pub upper: Option<IndexedDBKeyType>,
157    pub lower_open: bool,
158    pub upper_open: bool,
159}
160
161impl From<IndexedDBKeyType> for IndexedDBKeyRange {
162    fn from(key: IndexedDBKeyType) -> Self {
163        IndexedDBKeyRange {
164            lower: Some(key.clone()),
165            upper: Some(key),
166            ..Default::default()
167        }
168    }
169}
170
171impl IndexedDBKeyRange {
172    pub fn only(key: IndexedDBKeyType) -> Self {
173        Self::from(key)
174    }
175
176    pub fn new(
177        lower: Option<IndexedDBKeyType>,
178        upper: Option<IndexedDBKeyType>,
179        lower_open: bool,
180        upper_open: bool,
181    ) -> Self {
182        IndexedDBKeyRange {
183            lower,
184            upper,
185            lower_open,
186            upper_open,
187        }
188    }
189
190    pub fn lower_bound(key: IndexedDBKeyType, open: bool) -> Self {
191        IndexedDBKeyRange {
192            lower: Some(key),
193            upper: None,
194            lower_open: open,
195            upper_open: false,
196        }
197    }
198
199    pub fn upper_bound(key: IndexedDBKeyType, open: bool) -> Self {
200        IndexedDBKeyRange {
201            lower: None,
202            upper: Some(key),
203            lower_open: false,
204            upper_open: open,
205        }
206    }
207
208    // <https://www.w3.org/TR/IndexedDB-2/#in>
209    pub fn contains(&self, key: &IndexedDBKeyType) -> bool {
210        // A key is in a key range if both of the following conditions are fulfilled:
211        // The lower bound is null, or it is less than key,
212        // or it is both equal to key and the lower open flag is unset.
213        // The upper bound is null, or it is greater than key,
214        // or it is both equal to key and the upper open flag is unset
215        let lower_bound_condition = self
216            .lower
217            .as_ref()
218            .is_none_or(|lower| lower < key || (!self.lower_open && lower == key));
219        let upper_bound_condition = self
220            .upper
221            .as_ref()
222            .is_none_or(|upper| key < upper || (!self.upper_open && key == upper));
223        lower_bound_condition && upper_bound_condition
224    }
225
226    pub fn is_singleton(&self) -> bool {
227        self.lower.is_some() && self.lower == self.upper && !self.lower_open && !self.upper_open
228    }
229
230    pub fn as_singleton(&self) -> Option<&IndexedDBKeyType> {
231        if self.is_singleton() {
232            return Some(self.lower.as_ref().unwrap());
233        }
234        None
235    }
236}
237
238/// <https://w3c.github.io/IndexedDB/#record-snapshot>
239#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
240pub struct IndexedDBRecord {
241    pub key: IndexedDBKeyType,
242    pub primary_key: IndexedDBKeyType,
243    pub value: Vec<u8>,
244}
245
246#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
247pub enum PutItemResult {
248    Success,
249    CannotOverwrite,
250}
251
252#[derive(Debug, Deserialize, Serialize)]
253pub enum AsyncReadOnlyOperation {
254    /// Gets the value associated with the given key in the associated idb data
255    GetKey {
256        sender: IpcSender<BackendResult<Option<IndexedDBKeyType>>>,
257        key_range: IndexedDBKeyRange,
258    },
259    GetItem {
260        sender: IpcSender<BackendResult<Option<Vec<u8>>>>,
261        key_range: IndexedDBKeyRange,
262    },
263
264    GetAllKeys {
265        sender: IpcSender<BackendResult<Vec<IndexedDBKeyType>>>,
266        key_range: IndexedDBKeyRange,
267        count: Option<u32>,
268    },
269    GetAllItems {
270        sender: IpcSender<BackendResult<Vec<Vec<u8>>>>,
271        key_range: IndexedDBKeyRange,
272        count: Option<u32>,
273    },
274
275    Count {
276        sender: IpcSender<BackendResult<u64>>,
277        key_range: IndexedDBKeyRange,
278    },
279    Iterate {
280        sender: IpcSender<BackendResult<Vec<IndexedDBRecord>>>,
281        key_range: IndexedDBKeyRange,
282    },
283}
284
285#[derive(Debug, Deserialize, Serialize)]
286pub enum AsyncReadWriteOperation {
287    /// Sets the value of the given key in the associated idb data
288    PutItem {
289        sender: IpcSender<BackendResult<PutItemResult>>,
290        key: Option<IndexedDBKeyType>,
291        value: Vec<u8>,
292        should_overwrite: bool,
293    },
294
295    /// Removes the key/value pair for the given key in the associated idb data
296    RemoveItem {
297        sender: IpcSender<BackendResult<()>>,
298        key: IndexedDBKeyType,
299    },
300    /// Clears all key/value pairs in the associated idb data
301    Clear(IpcSender<BackendResult<()>>),
302}
303
304/// Operations that are not executed instantly, but rather added to a
305/// queue that is eventually run.
306#[derive(Debug, Deserialize, Serialize)]
307pub enum AsyncOperation {
308    ReadOnly(AsyncReadOnlyOperation),
309    ReadWrite(AsyncReadWriteOperation),
310}
311
312#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
313pub enum CreateObjectResult {
314    Created,
315    AlreadyExists,
316}
317
318#[derive(Debug, Deserialize, Serialize)]
319pub enum SyncOperation {
320    /// Upgrades the version of the database
321    UpgradeVersion(
322        /// Sender to send new version as the result of the operation
323        IpcSender<BackendResult<u64>>,
324        ImmutableOrigin,
325        String, // Database
326        u64,    // Serial number for the transaction
327        u64,    // Version to upgrade to
328    ),
329    /// Checks if an object store has a key generator, used in e.g. Put
330    HasKeyGenerator(
331        IpcSender<BackendResult<bool>>,
332        ImmutableOrigin,
333        String, // Database
334        String, // Store
335    ),
336    /// Gets an object store's key path
337    KeyPath(
338        /// Object stores can optionally be created with key paths
339        IpcSender<BackendResult<Option<KeyPath>>>,
340        ImmutableOrigin,
341        String, // Database
342        String, // Store
343    ),
344
345    /// Commits changes of a transaction to the database
346    Commit(
347        IpcSender<BackendResult<()>>,
348        ImmutableOrigin,
349        String, // Database
350        u64,    // Transaction serial number
351    ),
352
353    /// Creates a new index for the database
354    CreateIndex(
355        IpcSender<BackendResult<CreateObjectResult>>,
356        ImmutableOrigin,
357        String,  // Database
358        String,  // Store
359        String,  // Index name
360        KeyPath, // key path
361        bool,    // unique flag
362        bool,    // multientry flag
363    ),
364    /// Delete an index
365    DeleteIndex(
366        IpcSender<BackendResult<()>>,
367        ImmutableOrigin,
368        String, // Database
369        String, // Store
370        String, // Index name
371    ),
372
373    /// Creates a new store for the database
374    CreateObjectStore(
375        IpcSender<BackendResult<CreateObjectResult>>,
376        ImmutableOrigin,
377        String,          // Database
378        String,          // Store
379        Option<KeyPath>, // Key Path
380        bool,
381    ),
382
383    DeleteObjectStore(
384        IpcSender<BackendResult<()>>,
385        ImmutableOrigin,
386        String, // Database
387        String, // Store
388    ),
389
390    CloseDatabase(
391        IpcSender<BackendResult<()>>,
392        ImmutableOrigin,
393        String, // Database
394    ),
395
396    OpenDatabase(
397        IpcSender<u64>, // Returns the version
398        ImmutableOrigin,
399        String,      // Database
400        Option<u64>, // Eventual version
401    ),
402
403    /// Deletes the database
404    DeleteDatabase(
405        IpcSender<BackendResult<()>>,
406        ImmutableOrigin,
407        String, // Database
408    ),
409
410    /// Returns an unique identifier that is used to be able to
411    /// commit/abort transactions.
412    RegisterNewTxn(
413        /// The unique identifier of the transaction
414        IpcSender<u64>,
415        ImmutableOrigin,
416        String, // Database
417    ),
418
419    /// Starts executing the requests of a transaction
420    /// <https://www.w3.org/TR/IndexedDB-2/#transaction-start>
421    StartTransaction(
422        IpcSender<BackendResult<()>>,
423        ImmutableOrigin,
424        String, // Database
425        u64,    // The serial number of the mutating transaction
426    ),
427
428    /// Returns the version of the database
429    Version(
430        IpcSender<BackendResult<u64>>,
431        ImmutableOrigin,
432        String, // Database
433    ),
434
435    /// Send a reply when done cleaning up thread resources and then shut it down
436    Exit(IpcSender<()>),
437}
438
439#[derive(Debug, Deserialize, Serialize)]
440pub enum IndexedDBThreadMsg {
441    Sync(SyncOperation),
442    Async(
443        ImmutableOrigin,
444        String, // Database
445        String, // ObjectStore
446        u64,    // Serial number of the transaction that requests this operation
447        IndexedDBTxnMode,
448        AsyncOperation,
449    ),
450}
451
452#[cfg(test)]
453mod test {
454    use super::*;
455
456    #[test]
457    fn test_as_singleton() {
458        let key = IndexedDBKeyType::Number(1.0);
459        let key2 = IndexedDBKeyType::Number(2.0);
460        let range = IndexedDBKeyRange::only(key.clone());
461        assert!(range.is_singleton());
462        assert!(range.as_singleton().is_some());
463        let range = IndexedDBKeyRange::new(Some(key), Some(key2.clone()), false, false);
464        assert!(!range.is_singleton());
465        assert!(range.as_singleton().is_none());
466        let full_range = IndexedDBKeyRange::new(None, None, false, false);
467        assert!(!full_range.is_singleton());
468        assert!(full_range.as_singleton().is_none());
469    }
470}