blob: 7f13b00fd462a1e587b4f3b0299909739e11b343 [file] [log] [blame] [edit]
/* 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 dom_struct::dom_struct;
use js::rust::HandleValue;
use net_traits::IpcSend;
use net_traits::indexeddb_thread::{
AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyType,
IndexedDBThreadMsg, SyncOperation,
};
use profile_traits::ipc;
use script_bindings::error::ErrorResult;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
IDBTransactionMethods, IDBTransactionMode,
};
// We need to alias this name, otherwise test-tidy complains at &String reference.
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::structuredclone;
use crate::dom::domstringlist::DOMStringList;
use crate::dom::globalscope::GlobalScope;
use crate::dom::idbrequest::IDBRequest;
use crate::dom::idbtransaction::IDBTransaction;
use crate::indexed_db::{
self, ExtractionResult, convert_value_to_key, convert_value_to_key_range, extract_key,
};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
#[derive(JSTraceable, MallocSizeOf)]
pub enum KeyPath {
String(DOMString),
StringSequence(Vec<DOMString>),
}
#[dom_struct]
pub struct IDBObjectStore {
reflector_: Reflector,
name: DomRefCell<DOMString>,
key_path: Option<KeyPath>,
index_names: DomRoot<DOMStringList>,
transaction: Dom<IDBTransaction>,
// We store the db name in the object store to be able to find the correct
// store in the idb thread when checking if we have a key generator
db_name: DOMString,
}
impl IDBObjectStore {
pub fn new_inherited(
global: &GlobalScope,
db_name: DOMString,
name: DOMString,
options: Option<&IDBObjectStoreParameters>,
can_gc: CanGc,
transaction: &IDBTransaction,
) -> IDBObjectStore {
let key_path: Option<KeyPath> = match options {
Some(options) => options.keyPath.as_ref().map(|path| match path {
StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
StrOrStringSequence::StringSequence(inner) => {
KeyPath::StringSequence(inner.clone())
},
}),
None => None,
};
IDBObjectStore {
reflector_: Reflector::new(),
name: DomRefCell::new(name),
key_path,
index_names: DOMStringList::new(global, Vec::new(), can_gc),
transaction: Dom::from_ref(transaction),
db_name,
}
}
pub fn new(
global: &GlobalScope,
db_name: DOMString,
name: DOMString,
options: Option<&IDBObjectStoreParameters>,
can_gc: CanGc,
transaction: &IDBTransaction,
) -> DomRoot<IDBObjectStore> {
reflect_dom_object(
Box::new(IDBObjectStore::new_inherited(
global,
db_name,
name,
options,
can_gc,
transaction,
)),
global,
can_gc,
)
}
pub fn get_name(&self) -> DOMString {
self.name.borrow().clone()
}
pub fn transaction(&self) -> DomRoot<IDBTransaction> {
self.transaction.as_rooted()
}
fn has_key_generator(&self) -> bool {
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
let operation = SyncOperation::HasKeyGenerator(
sender,
self.global().origin().immutable().clone(),
self.db_name.to_string(),
self.name.borrow().to_string(),
);
self.global()
.resource_threads()
.send(IndexedDBThreadMsg::Sync(operation))
.unwrap();
// First unwrap for ipc
// Second unwrap will never happen unless this db gets manually deleted somehow
receiver.recv().unwrap().unwrap()
}
// fn get_stored_key_path(&mut self) -> Option<KeyPath> {
// let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
//
// let operation = SyncOperation::KeyPath(
// sender,
// self.global().origin().immutable().clone(),
// self.db_name.to_string(),
// self.name.borrow().to_string(),
// );
//
// self.global()
// .resource_threads()
// .sender()
// .send(IndexedDBThreadMsg::Sync(operation))
// .unwrap();
//
// // First unwrap for ipc
// // Second unwrap will never happen unless this db gets manually deleted somehow
// let key_path = receiver.recv().unwrap().unwrap();
// key_path.map(|p| {
// // TODO: have separate storage for string sequence of len 1 and signle string
// if p.len() == 1 {
// KeyPath::String(DOMString::from_string(p[0].clone()))
// } else {
// let strings: Vec<_> = p.into_iter().map(|s| {
// DOMString::from_string(s)
// }).collect();
// KeyPath::StringSequence(strings)
// }
// })
// }
// https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys
fn uses_inline_keys(&self) -> bool {
self.key_path.is_some()
}
fn verify_not_deleted(&self) -> ErrorResult {
let db = self.transaction.Db();
if !db.object_store_exists(&self.name.borrow()) {
return Err(Error::InvalidState);
}
Ok(())
}
/// Checks if the transation is active, throwing a "TransactionInactiveError" DOMException if not.
fn check_transaction_active(&self) -> Fallible<()> {
// Let transaction be this object store handle's transaction.
let transaction = &self.transaction;
// If transaction is not active, throw a "TransactionInactiveError" DOMException.
if !transaction.is_active() {
return Err(Error::TransactionInactive);
}
Ok(())
}
/// Checks if the transation is active, throwing a "TransactionInactiveError" DOMException if not.
/// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
fn check_readwrite_transaction_active(&self) -> Fallible<()> {
// Let transaction be this object store handle's transaction.
let transaction = &self.transaction;
// If transaction is not active, throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
if let IDBTransactionMode::Readonly = transaction.get_mode() {
return Err(Error::ReadOnly);
}
Ok(())
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
fn put(
&self,
cx: SafeJSContext,
value: HandleValue,
key: HandleValue,
overwrite: bool,
can_gc: CanGc,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be handle’s transaction.
// Step 2: Let store be this object store handle's object store.
// This is resolved in the `execute_async` function.
// Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
// Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
self.check_readwrite_transaction_active()?;
// Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
if !key.is_undefined() && self.uses_inline_keys() {
return Err(Error::Data);
}
// Step 7: If store uses out-of-line keys and has no key generator
// and key was not given, throw a "DataError" DOMException.
if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
return Err(Error::Data);
}
// Step 8: If key was given, then: convert a value to a key with key
let serialized_key: Option<IndexedDBKeyType>;
if !key.is_undefined() {
serialized_key = Some(convert_value_to_key(cx, key, None)?);
} else {
// Step 11: We should use in-line keys instead
if let Some(Ok(ExtractionResult::Key(kpk))) = self
.key_path
.as_ref()
.map(|p| extract_key(cx, value, p, None))
{
serialized_key = Some(kpk);
} else {
if !self.has_key_generator() {
return Err(Error::Data);
}
serialized_key = None;
}
}
// Step 10. Let clone be a clone of value in targetRealm during transaction. Rethrow any exceptions.
let cloned_value = structuredclone::write(cx, value, None)?;
let Ok(serialized_value) = bincode::serialize(&cloned_value) else {
return Err(Error::InvalidState);
};
let (sender, receiver) = indexed_db::create_channel(self.global());
// Step 12. Let operation be an algorithm to run store a record into an object store with store, clone, key, and no-overwrite flag.
// Step 13. Return the result (an IDBRequest) of running asynchronously execute a request with handle and operation.
IDBRequest::execute_async(
self,
AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
sender,
key: serialized_key,
value: serialized_value,
should_overwrite: overwrite,
}),
receiver,
None,
can_gc,
)
}
}
impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
fn Put(
&self,
cx: SafeJSContext,
value: HandleValue,
key: HandleValue,
) -> Fallible<DomRoot<IDBRequest>> {
self.put(cx, value, key, true, CanGc::note())
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add>
fn Add(
&self,
cx: SafeJSContext,
value: HandleValue,
key: HandleValue,
) -> Fallible<DomRoot<IDBRequest>> {
self.put(cx, value, key, false, CanGc::note())
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete>
fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
// Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
self.check_readwrite_transaction_active()?;
// Step 6
// TODO: Convert to key range instead
let serialized_query = convert_value_to_key(cx, query, None);
// Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
// Stpe 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem { sender, key: q }),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear>
fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
// Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
self.check_readwrite_transaction_active()?;
// Step 6. Let operation be an algorithm to run clear an object store with store.
// Stpe 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
let (sender, receiver) = indexed_db::create_channel(self.global());
IDBRequest::execute_async(
self,
AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(sender)),
receiver,
None,
CanGc::note(),
)
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get>
fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
// Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
sender,
key_range: q,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey>
fn GetKey(&self, cx: SafeJSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
// The steps are run with this object store handle as source and the steps to retrieve a key from an object
// store as operation, using store and range.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
sender,
key_range: q,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall>
fn GetAll(
&self,
cx: SafeJSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
// The steps are run with this object store handle as source and the steps to retrieve a key from an object
// store as operation, using store and range.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
sender,
key_range: q,
count,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys>
fn GetAllKeys(
&self,
cx: SafeJSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
// The steps are run with this object store handle as source and the steps to retrieve a key from an object
// store as operation, using store and range.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
sender,
key_range: q,
count,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count>
fn Count(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this’s transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Let operation be an algorithm to run count the records in a range with store and range.
// Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
sender,
key_range: q,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name>
fn Name(&self) -> DOMString {
self.name.borrow().clone()
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname>
fn SetName(&self, value: DOMString) -> ErrorResult {
// Step 2. Let transaction be this’s transaction.
let transaction = &self.transaction;
// Step 3. Let store be this's object store.
// Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
if transaction.Mode() != IDBTransactionMode::Versionchange {
return Err(Error::InvalidState);
}
// Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
*self.name.borrow_mut() = value;
Ok(())
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath
// fn KeyPath(&self, _cx: SafeJSContext, _val: MutableHandleValue) {
// unimplemented!();
// }
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames
// fn IndexNames(&self) -> DomRoot<DOMStringList> {
// unimplemented!();
// }
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction>
fn Transaction(&self) -> DomRoot<IDBTransaction> {
self.transaction()
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement>
fn AutoIncrement(&self) -> bool {
self.has_key_generator()
}
}