/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cmp::{Ordering, PartialEq, PartialOrd}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use ipc_channel::ipc::IpcSender; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use servo_url::origin::ImmutableOrigin; // TODO Box is not serializable, fix needs to be found pub type DbError = String; /// A DbResult wraps any part of a call that has to reach into the backend (in this case sqlite.rs) /// These errors could be anything, depending on the backend pub type DbResult = Result; /// Any error from the backend #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum BackendError { DbNotFound, StoreNotFound, DbErr(DbError), } impl From for BackendError { fn from(value: DbError) -> Self { BackendError::DbErr(value) } } impl Display for BackendError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl Error for BackendError {} pub type BackendResult = Result; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum KeyPath { String(String), Sequence(Vec), } // https://www.w3.org/TR/IndexedDB-2/#enumdef-idbtransactionmode #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum IndexedDBTxnMode { Readonly, Readwrite, Versionchange, } /// #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum IndexedDBKeyType { Number(f64), String(String), Binary(Vec), Date(f64), Array(Vec), // FIXME:(arihant2math) implment ArrayBuffer } /// impl PartialOrd for IndexedDBKeyType { fn partial_cmp(&self, other: &Self) -> Option { // 1. Let ta be the type of a. // 2. Let tb be the type of b. match (self, other) { // Step 3: If ta is array and tb is binary, string, date or number, return 1. ( IndexedDBKeyType::Array(_), IndexedDBKeyType::Binary(_) | IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_) | IndexedDBKeyType::String(_), ) => Some(Ordering::Greater), // Step 4: If tb is array and ta is binary, string, date or number, return -1. ( IndexedDBKeyType::Binary(_) | IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_) | IndexedDBKeyType::String(_), IndexedDBKeyType::Array(_), ) => Some(Ordering::Less), // Step 5: If ta is binary and tb is string, date or number, return 1. ( IndexedDBKeyType::Binary(_), IndexedDBKeyType::String(_) | IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_), ) => Some(Ordering::Greater), // Step 6: If tb is binary and ta is string, date or number, return -1. ( IndexedDBKeyType::String(_) | IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_), IndexedDBKeyType::Binary(_), ) => Some(Ordering::Less), // Step 7: If ta is string and tb is date or number, return 1. ( IndexedDBKeyType::String(_), IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_), ) => Some(Ordering::Greater), // Step 8: If tb is string and ta is date or number, return -1. ( IndexedDBKeyType::Date(_) | IndexedDBKeyType::Number(_), IndexedDBKeyType::String(_), ) => Some(Ordering::Less), // Step 9: If ta is date and tb is number, return 1. (IndexedDBKeyType::Date(_), IndexedDBKeyType::Number(_)) => Some(Ordering::Greater), // Step 10: If tb is date and ta is number, return -1. (IndexedDBKeyType::Number(_), IndexedDBKeyType::Date(_)) => Some(Ordering::Less), // Step 11 skipped // TODO: Likely a tiny bit wrong (use js number comparison) (IndexedDBKeyType::Number(a), IndexedDBKeyType::Number(b)) => a.partial_cmp(b), // TODO: Likely a tiny bit wrong (use js string comparison) (IndexedDBKeyType::String(a), IndexedDBKeyType::String(b)) => a.partial_cmp(b), // TODO: Likely a little wrong (use js binary comparison) (IndexedDBKeyType::Binary(a), IndexedDBKeyType::Binary(b)) => a.partial_cmp(b), (IndexedDBKeyType::Date(a), IndexedDBKeyType::Date(b)) => a.partial_cmp(b), // TODO: Probably also wrong (the items in a and b should be compared, double check against the spec) (IndexedDBKeyType::Array(a), IndexedDBKeyType::Array(b)) => a.partial_cmp(b), // No catch-all is used, rust ensures that all variants are handled } } } impl PartialEq for IndexedDBKeyType { fn eq(&self, other: &Self) -> bool { let cmp = self.partial_cmp(other); match cmp { Some(Ordering::Equal) => true, Some(Ordering::Less) | Some(Ordering::Greater) => false, None => { // If we can't compare the two keys, we assume they are not equal. false }, } } } // #[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] #[allow(unused)] pub struct IndexedDBKeyRange { pub lower: Option, pub upper: Option, pub lower_open: bool, pub upper_open: bool, } impl From for IndexedDBKeyRange { fn from(key: IndexedDBKeyType) -> Self { IndexedDBKeyRange { lower: Some(key.clone()), upper: Some(key), ..Default::default() } } } impl IndexedDBKeyRange { pub fn only(key: IndexedDBKeyType) -> Self { Self::from(key) } pub fn new( lower: Option, upper: Option, lower_open: bool, upper_open: bool, ) -> Self { IndexedDBKeyRange { lower, upper, lower_open, upper_open, } } pub fn lower_bound(key: IndexedDBKeyType, open: bool) -> Self { IndexedDBKeyRange { lower: Some(key), upper: None, lower_open: open, upper_open: false, } } pub fn upper_bound(key: IndexedDBKeyType, open: bool) -> Self { IndexedDBKeyRange { lower: None, upper: Some(key), lower_open: false, upper_open: open, } } // pub fn contains(&self, key: &IndexedDBKeyType) -> bool { // A key is in a key range if both of the following conditions are fulfilled: // The lower bound is null, or it is less than key, // or it is both equal to key and the lower open flag is unset. // The upper bound is null, or it is greater than key, // or it is both equal to key and the upper open flag is unset let lower_bound_condition = self .lower .as_ref() .is_none_or(|lower| lower < key || (!self.lower_open && lower == key)); let upper_bound_condition = self .upper .as_ref() .is_none_or(|upper| key < upper || (!self.upper_open && key == upper)); lower_bound_condition && upper_bound_condition } pub fn is_singleton(&self) -> bool { self.lower.is_some() && self.lower == self.upper && !self.lower_open && !self.upper_open } pub fn as_singleton(&self) -> Option<&IndexedDBKeyType> { if self.is_singleton() { return Some(self.lower.as_ref().unwrap()); } None } } /// #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct IndexedDBRecord { pub key: IndexedDBKeyType, pub primary_key: IndexedDBKeyType, pub value: Vec, } #[test] fn test_as_singleton() { let key = IndexedDBKeyType::Number(1.0); let key2 = IndexedDBKeyType::Number(2.0); let range = IndexedDBKeyRange::only(key.clone()); assert!(range.is_singleton()); assert!(range.as_singleton().is_some()); let range = IndexedDBKeyRange::new(Some(key), Some(key2.clone()), false, false); assert!(!range.is_singleton()); assert!(range.as_singleton().is_none()); let full_range = IndexedDBKeyRange::new(None, None, false, false); assert!(!full_range.is_singleton()); assert!(full_range.as_singleton().is_none()); } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub enum PutItemResult { Success, CannotOverwrite, } #[derive(Debug, Deserialize, Serialize)] pub enum AsyncReadOnlyOperation { /// Gets the value associated with the given key in the associated idb data GetKey { sender: IpcSender>>, key_range: IndexedDBKeyRange, }, GetItem { sender: IpcSender>>>, key_range: IndexedDBKeyRange, }, GetAllKeys { sender: IpcSender>>, key_range: IndexedDBKeyRange, count: Option, }, GetAllItems { sender: IpcSender>>>, key_range: IndexedDBKeyRange, count: Option, }, Count { sender: IpcSender>, key_range: IndexedDBKeyRange, }, Iterate { sender: IpcSender>>, key_range: IndexedDBKeyRange, }, } #[derive(Debug, Deserialize, Serialize)] pub enum AsyncReadWriteOperation { /// Sets the value of the given key in the associated idb data PutItem { sender: IpcSender>, key: Option, value: Vec, should_overwrite: bool, }, /// Removes the key/value pair for the given key in the associated idb data RemoveItem { sender: IpcSender>, key: IndexedDBKeyType, }, /// Clears all key/value pairs in the associated idb data Clear(IpcSender>), } /// Operations that are not executed instantly, but rather added to a /// queue that is eventually run. #[derive(Debug, Deserialize, Serialize)] pub enum AsyncOperation { ReadOnly(AsyncReadOnlyOperation), ReadWrite(AsyncReadWriteOperation), } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub enum CreateObjectResult { Created, AlreadyExists, } #[derive(Debug, Deserialize, Serialize)] pub enum SyncOperation { /// Upgrades the version of the database UpgradeVersion( IpcSender>, ImmutableOrigin, String, // Database u64, // Serial number for the transaction u64, // Version to upgrade to ), /// Checks if an object store has a key generator, used in e.g. Put HasKeyGenerator( IpcSender>, ImmutableOrigin, String, // Database String, // Store ), /// Gets an object store's key path KeyPath( /// Object stores do not have to have key paths IpcSender>>, ImmutableOrigin, String, // Database String, // Store ), /// Commits changes of a transaction to the database Commit( IpcSender>, ImmutableOrigin, String, // Database u64, // Transaction serial number ), /// Creates a new index for the database CreateIndex( IpcSender>, ImmutableOrigin, String, // Database String, // Store String, // Index name KeyPath, // key path bool, // unique flag bool, // multientry flag ), /// Delete an index DeleteIndex( IpcSender>, ImmutableOrigin, String, // Database String, // Store String, // Index name ), /// Creates a new store for the database CreateObjectStore( IpcSender>, ImmutableOrigin, String, // Database String, // Store Option, // Key Path bool, ), DeleteObjectStore( IpcSender>, ImmutableOrigin, String, // Database String, // Store ), CloseDatabase( IpcSender>, ImmutableOrigin, String, // Database ), OpenDatabase( IpcSender, // Returns the version ImmutableOrigin, String, // Database Option, // Eventual version ), /// Deletes the database DeleteDatabase( IpcSender>, ImmutableOrigin, String, // Database ), /// Returns an unique identifier that is used to be able to /// commit/abort transactions. RegisterNewTxn( IpcSender, ImmutableOrigin, String, // Database ), /// Starts executing the requests of a transaction /// StartTransaction( IpcSender>, ImmutableOrigin, String, // Database u64, // The serial number of the mutating transaction ), /// Returns the version of the database Version( IpcSender>, ImmutableOrigin, String, // Database ), /// Send a reply when done cleaning up thread resources and then shut it down Exit(IpcSender<()>), } #[derive(Debug, Deserialize, Serialize)] pub enum IndexedDBThreadMsg { Sync(SyncOperation), Async( ImmutableOrigin, String, // Database String, // ObjectStore u64, // Serial number of the transaction that requests this operation IndexedDBTxnMode, AsyncOperation, ), }