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#[test]
247fn test_as_singleton() {
248    let key = IndexedDBKeyType::Number(1.0);
249    let key2 = IndexedDBKeyType::Number(2.0);
250    let range = IndexedDBKeyRange::only(key.clone());
251    assert!(range.is_singleton());
252    assert!(range.as_singleton().is_some());
253    let range = IndexedDBKeyRange::new(Some(key), Some(key2.clone()), false, false);
254    assert!(!range.is_singleton());
255    assert!(range.as_singleton().is_none());
256    let full_range = IndexedDBKeyRange::new(None, None, false, false);
257    assert!(!full_range.is_singleton());
258    assert!(full_range.as_singleton().is_none());
259}
260
261#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
262pub enum PutItemResult {
263    Success,
264    CannotOverwrite,
265}
266
267#[derive(Debug, Deserialize, Serialize)]
268pub enum AsyncReadOnlyOperation {
269    /// Gets the value associated with the given key in the associated idb data
270    GetKey {
271        sender: IpcSender<BackendResult<Option<IndexedDBKeyType>>>,
272        key_range: IndexedDBKeyRange,
273    },
274    GetItem {
275        sender: IpcSender<BackendResult<Option<Vec<u8>>>>,
276        key_range: IndexedDBKeyRange,
277    },
278
279    GetAllKeys {
280        sender: IpcSender<BackendResult<Vec<IndexedDBKeyType>>>,
281        key_range: IndexedDBKeyRange,
282        count: Option<u32>,
283    },
284    GetAllItems {
285        sender: IpcSender<BackendResult<Vec<Vec<u8>>>>,
286        key_range: IndexedDBKeyRange,
287        count: Option<u32>,
288    },
289
290    Count {
291        sender: IpcSender<BackendResult<u64>>,
292        key_range: IndexedDBKeyRange,
293    },
294    Iterate {
295        sender: IpcSender<BackendResult<Vec<IndexedDBRecord>>>,
296        key_range: IndexedDBKeyRange,
297    },
298}
299
300#[derive(Debug, Deserialize, Serialize)]
301pub enum AsyncReadWriteOperation {
302    /// Sets the value of the given key in the associated idb data
303    PutItem {
304        sender: IpcSender<BackendResult<PutItemResult>>,
305        key: Option<IndexedDBKeyType>,
306        value: Vec<u8>,
307        should_overwrite: bool,
308    },
309
310    /// Removes the key/value pair for the given key in the associated idb data
311    RemoveItem {
312        sender: IpcSender<BackendResult<()>>,
313        key: IndexedDBKeyType,
314    },
315    /// Clears all key/value pairs in the associated idb data
316    Clear(IpcSender<BackendResult<()>>),
317}
318
319/// Operations that are not executed instantly, but rather added to a
320/// queue that is eventually run.
321#[derive(Debug, Deserialize, Serialize)]
322pub enum AsyncOperation {
323    ReadOnly(AsyncReadOnlyOperation),
324    ReadWrite(AsyncReadWriteOperation),
325}
326
327#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
328pub enum CreateObjectResult {
329    Created,
330    AlreadyExists,
331}
332
333#[derive(Debug, Deserialize, Serialize)]
334pub enum SyncOperation {
335    /// Upgrades the version of the database
336    UpgradeVersion(
337        /// Sender to send new version as the result of the operation
338        IpcSender<BackendResult<u64>>,
339        ImmutableOrigin,
340        String, // Database
341        u64,    // Serial number for the transaction
342        u64,    // Version to upgrade to
343    ),
344    /// Checks if an object store has a key generator, used in e.g. Put
345    HasKeyGenerator(
346        IpcSender<BackendResult<bool>>,
347        ImmutableOrigin,
348        String, // Database
349        String, // Store
350    ),
351    /// Gets an object store's key path
352    KeyPath(
353        /// Object stores can optionally be created with key paths
354        IpcSender<BackendResult<Option<KeyPath>>>,
355        ImmutableOrigin,
356        String, // Database
357        String, // Store
358    ),
359
360    /// Commits changes of a transaction to the database
361    Commit(
362        IpcSender<BackendResult<()>>,
363        ImmutableOrigin,
364        String, // Database
365        u64,    // Transaction serial number
366    ),
367
368    /// Creates a new index for the database
369    CreateIndex(
370        IpcSender<BackendResult<CreateObjectResult>>,
371        ImmutableOrigin,
372        String,  // Database
373        String,  // Store
374        String,  // Index name
375        KeyPath, // key path
376        bool,    // unique flag
377        bool,    // multientry flag
378    ),
379    /// Delete an index
380    DeleteIndex(
381        IpcSender<BackendResult<()>>,
382        ImmutableOrigin,
383        String, // Database
384        String, // Store
385        String, // Index name
386    ),
387
388    /// Creates a new store for the database
389    CreateObjectStore(
390        IpcSender<BackendResult<CreateObjectResult>>,
391        ImmutableOrigin,
392        String,          // Database
393        String,          // Store
394        Option<KeyPath>, // Key Path
395        bool,
396    ),
397
398    DeleteObjectStore(
399        IpcSender<BackendResult<()>>,
400        ImmutableOrigin,
401        String, // Database
402        String, // Store
403    ),
404
405    CloseDatabase(
406        IpcSender<BackendResult<()>>,
407        ImmutableOrigin,
408        String, // Database
409    ),
410
411    OpenDatabase(
412        IpcSender<u64>, // Returns the version
413        ImmutableOrigin,
414        String,      // Database
415        Option<u64>, // Eventual version
416    ),
417
418    /// Deletes the database
419    DeleteDatabase(
420        IpcSender<BackendResult<()>>,
421        ImmutableOrigin,
422        String, // Database
423    ),
424
425    /// Returns an unique identifier that is used to be able to
426    /// commit/abort transactions.
427    RegisterNewTxn(
428        /// The unique identifier of the transaction
429        IpcSender<u64>,
430        ImmutableOrigin,
431        String, // Database
432    ),
433
434    /// Starts executing the requests of a transaction
435    /// <https://www.w3.org/TR/IndexedDB-2/#transaction-start>
436    StartTransaction(
437        IpcSender<BackendResult<()>>,
438        ImmutableOrigin,
439        String, // Database
440        u64,    // The serial number of the mutating transaction
441    ),
442
443    /// Returns the version of the database
444    Version(
445        IpcSender<BackendResult<u64>>,
446        ImmutableOrigin,
447        String, // Database
448    ),
449
450    /// Send a reply when done cleaning up thread resources and then shut it down
451    Exit(IpcSender<()>),
452}
453
454#[derive(Debug, Deserialize, Serialize)]
455pub enum IndexedDBThreadMsg {
456    Sync(SyncOperation),
457    Async(
458        ImmutableOrigin,
459        String, // Database
460        String, // ObjectStore
461        u64,    // Serial number of the transaction that requests this operation
462        IndexedDBTxnMode,
463        AsyncOperation,
464    ),
465}