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