[IndexedDB] Adhere better to the specification for idb object store related operations (#37682)

Many object store related operations require the transaction to be
checked: to ensure it is still active, and, if the operation is a write,
that the transaction is not read-only. I've added the
`check_transaction` method to perform these checks.

Additionally `Clear` was still half-implemented, so I went ahead and
implemented that.

---------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren 2025-07-15 19:11:06 -07:00 committed by GitHub
parent 2e3c280f46
commit 71e7019d45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 35 deletions

View file

@ -262,6 +262,16 @@ impl KvsEngine for HeedEngine {
.expect("Could not get store"); .expect("Could not get store");
// FIXME:(arihant2math) Return count with sender // FIXME:(arihant2math) Return count with sender
}, },
AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear) => {
let stores = stores
.write()
.expect("Could not acquire write lock on stores");
let store = stores
.get(&request.store_name)
.expect("Could not get store");
// FIXME:(arihant2math) Error handling
let _ = store.inner.clear(&mut wtxn);
},
} }
} }

View file

@ -386,6 +386,36 @@ impl IDBObjectStore {
self.key_path.is_some() self.key_path.is_some()
} }
/// 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.get().ok_or(Error::TransactionInactive)?;
// 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.get().ok_or(Error::TransactionInactive)?;
// If transaction is not active, throw a "TransactionInactiveError" DOMException.
if !transaction.is_active() {
return Err(Error::TransactionInactive);
}
if let IDBTransactionMode::Readonly = transaction.get_mode() {
return Err(Error::ReadOnly);
}
Ok(())
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
fn put( fn put(
&self, &self,
@ -395,27 +425,15 @@ impl IDBObjectStore {
overwrite: bool, overwrite: bool,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<DomRoot<IDBRequest>> { ) -> Fallible<DomRoot<IDBRequest>> {
// Step 1: Let transaction be this object store handle's transaction. // Step 1: Unneeded, handled by self.check_readwrite_transaction_active()
let transaction = self
.transaction
.get()
.expect("No transaction in Object Store");
// Step 2: Let store be this object store handle's object store. // Step 2: Let store be this object store handle's object store.
// This is resolved in the `execute_async` function. // This is resolved in the `execute_async` function.
// Step 3: If store has been deleted, throw an "InvalidStateError" DOMException. // Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
// FIXME:(rasviitanen) // FIXME:(rasviitanen)
// Step 4-5: If transaction is not active, throw a "TransactionInactiveError" DOMException. // Steps 4-5
if !transaction.is_active() { self.check_readwrite_transaction_active()?;
return Err(Error::TransactionInactive);
}
// Step 5: If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
if let IDBTransactionMode::Readonly = transaction.get_mode() {
return Err(Error::ReadOnly);
}
// Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException. // Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
if !key.is_undefined() && self.uses_inline_keys() { if !key.is_undefined() && self.uses_inline_keys() {
@ -489,7 +507,15 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete
fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> { fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1: Unneeded, handled by self.check_readwrite_transaction_active()
// TODO: Step 2
// TODO: Step 3
// Steps 4-5
self.check_readwrite_transaction_active()?;
// Step 6
// TODO: Convert to key range instead
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None); let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
// Step 7
serialized_query.and_then(|q| { serialized_query.and_then(|q| {
IDBRequest::execute_async( IDBRequest::execute_async(
self, self,
@ -502,12 +528,30 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear
fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> { fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> {
unimplemented!(); // Step 1: Unneeded, handled by self.check_readwrite_transaction_active()
// TODO: Step 2
// TODO: Step 3
// Steps 4-5
self.check_readwrite_transaction_active()?;
IDBRequest::execute_async(
self,
AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear),
None,
CanGc::note(),
)
} }
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get
fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> { fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1: Unneeded, handled by self.check_transaction_active()
// TODO: Step 2
// TODO: Step 3
// Step 4
self.check_transaction_active()?;
// Step 5
// TODO: Convert to key range instead
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None); let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
// Step 6
serialized_query.and_then(|q| { serialized_query.and_then(|q| {
IDBRequest::execute_async( IDBRequest::execute_async(
self, self,
@ -520,6 +564,14 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey
// fn GetKey(&self, _cx: SafeJSContext, _query: HandleValue) -> DomRoot<IDBRequest> { // fn GetKey(&self, _cx: SafeJSContext, _query: HandleValue) -> DomRoot<IDBRequest> {
// // Step 1: Unneeded, handled by self.check_transaction_active()
// // TODO: Step 2
// // TODO: Step 3
// // Step 4
// self.check_transaction_active()?;
// // Step 5
// // TODO: Convert to key range instead
// let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
// unimplemented!(); // unimplemented!();
// } // }
@ -545,29 +597,24 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count
fn Count(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> { fn Count(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1 // Step 1: Unneeded, handled by self.check_transaction_active()
let transaction = self.transaction.get().expect("Could not get transaction"); // TODO: Step 2
// TODO: Step 3
// Step 2 // Steps 4
// FIXME(arihant2math): investigate further self.check_transaction_active()?;
// Step 3
// FIXME(arihant2math): Cannot tell if store has been deleted
// Step 4
if !transaction.is_active() {
return Err(Error::TransactionInactive);
}
// Step 5 // Step 5
let _serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None); let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
// Step 6 // Step 6
// match serialized_query { serialized_query.and_then(|q| {
// Ok(q) => IDBRequest::execute_async(&*self, AsyncOperation::Count(q), None), IDBRequest::execute_async(
// Err(e) => Err(e), self,
// } AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count(q)),
Err(Error::NotSupported) None,
CanGc::note(),
)
})
} }
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name

View file

@ -92,6 +92,8 @@ pub enum AsyncReadWriteOperation {
RemoveItem( RemoveItem(
IndexedDBKeyType, // Key IndexedDBKeyType, // Key
), ),
/// Clears all key/value pairs in the associated idb data
Clear,
} }
/// Operations that are not executed instantly, but rather added to a /// Operations that are not executed instantly, but rather added to a