Files
servo/components/script/dom/indexeddb/idbobjectstore.rs
Taym Haddadi 4faaa254bb indexeddb: fix idbobjectstore rename error handling (#43754)
Implement the missing IDBObjectStore.name duplicate name checks so
renaming an object store to an existing store now throw ConstraintError

Testing: more indexeddb WPT test passed.

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>
2026-03-30 05:14:51 +00:00

964 lines
40 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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::cell::Cell;
use std::collections::HashMap;
use dom_struct::dom_struct;
use js::context::JSContext;
use js::conversions::ToJSValConvertible;
use js::gc::MutableHandleValue;
use js::jsval::NullValue;
use js::rust::HandleValue;
use script_bindings::codegen::GenericBindings::IDBObjectStoreBinding::IDBIndexParameters;
use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
use script_bindings::error::ErrorResult;
use servo_base::generic_channel::{GenericSend, GenericSender};
use storage_traits::indexeddb::{
self, AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyType,
IndexedDBThreadMsg, SyncOperation,
};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::IDBCursorDirection;
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::refcounted::Trusted;
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::indexeddb::idbcursor::{IDBCursor, IterationParam, ObjectStoreOrIndex};
use crate::dom::indexeddb::idbcursorwithvalue::IDBCursorWithValue;
use crate::dom::indexeddb::idbindex::IDBIndex;
use crate::dom::indexeddb::idbrequest::IDBRequest;
use crate::dom::indexeddb::idbtransaction::IDBTransaction;
use crate::indexeddb::{
ExtractionResult, can_inject_key_into_value, convert_value_to_key, convert_value_to_key_range,
extract_key, inject_key_into_value, is_valid_key_path,
};
use crate::script_runtime::CanGc;
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub enum KeyPath {
String(DOMString),
StringSequence(Vec<DOMString>),
}
impl From<StringOrStringSequence> for KeyPath {
fn from(value: StringOrStringSequence) -> Self {
match value {
StringOrStringSequence::String(s) => KeyPath::String(s),
StringOrStringSequence::StringSequence(ss) => KeyPath::StringSequence(ss),
}
}
}
impl From<indexeddb::KeyPath> for KeyPath {
fn from(value: indexeddb::KeyPath) -> Self {
match value {
indexeddb::KeyPath::String(string) => KeyPath::String(string.into()),
indexeddb::KeyPath::Sequence(ss) => {
KeyPath::StringSequence(ss.into_iter().map(Into::into).collect())
},
}
}
}
impl From<KeyPath> for indexeddb::KeyPath {
fn from(item: KeyPath) -> Self {
match item {
KeyPath::String(s) => Self::String(s.to_string()),
KeyPath::StringSequence(ss) => {
Self::Sequence(ss.into_iter().map(|s| s.to_string()).collect())
},
}
}
}
#[dom_struct]
pub struct IDBObjectStore {
reflector_: Reflector,
name: DomRefCell<DOMString>,
key_path: Option<KeyPath>,
index_set: DomRefCell<HashMap<DOMString, Dom<IDBIndex>>>,
transaction: Dom<IDBTransaction>,
has_key_generator: bool,
key_generator_current_number: Cell<Option<i32>>,
// We store the db name in the object store to address backend operations
// that are keyed by (origin, database name, object store name).
db_name: DOMString,
}
impl IDBObjectStore {
pub fn new_inherited(
db_name: DOMString,
name: DOMString,
options: Option<&IDBObjectStoreParameters>,
key_generator_current_number: Option<i32>,
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,
};
let has_key_generator = options.is_some_and(|options| options.autoIncrement);
let key_generator_current_number = if has_key_generator {
Some(key_generator_current_number.unwrap_or(1))
} else {
None
};
IDBObjectStore {
reflector_: Reflector::new(),
name: DomRefCell::new(name),
key_path,
index_set: DomRefCell::new(HashMap::new()),
transaction: Dom::from_ref(transaction),
has_key_generator,
key_generator_current_number: Cell::new(key_generator_current_number),
db_name,
}
}
pub fn new(
global: &GlobalScope,
db_name: DOMString,
name: DOMString,
options: Option<&IDBObjectStoreParameters>,
key_generator_current_number: Option<i32>,
can_gc: CanGc,
transaction: &IDBTransaction,
) -> DomRoot<IDBObjectStore> {
reflect_dom_object(
Box::new(IDBObjectStore::new_inherited(
db_name,
name,
options,
key_generator_current_number,
transaction,
)),
global,
can_gc,
)
}
pub fn get_name(&self) -> DOMString {
self.name.borrow().clone()
}
pub(crate) fn transaction(&self) -> DomRoot<IDBTransaction> {
self.transaction.as_rooted()
}
fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
self.global().storage_threads().sender()
}
fn has_key_generator(&self) -> bool {
self.has_key_generator
}
/// <https://w3c.github.io/IndexedDB/#generate-a-key>
fn generate_key_for_put(&self) -> Fallible<(IndexedDBKeyType, i32)> {
// Step 1. Let generator be store's key generator.
let Some(current_number) = self.key_generator_current_number.get() else {
return Err(Error::Data(None));
};
// Step 2. Let key be generator's current number.
let key = current_number as f64;
// Step 3. If key is greater than 2^53 (9007199254740992), then return failure.
if key > 9_007_199_254_740_992.0 {
return Err(Error::Constraint(None));
}
// Step 4. Increase generator's current number by 1.
let next_current_number = current_number
.checked_add(1)
.ok_or(Error::Constraint(None))?;
// Step 5. Return key.
Ok((IndexedDBKeyType::Number(key), next_current_number))
}
/// <https://w3c.github.io/IndexedDB/#possibly-update-the-key-generator>
fn possibly_update_the_key_generator(&self, key: &IndexedDBKeyType) -> Option<i32> {
// Step 1. If the type of key is not number, abort these steps.
let IndexedDBKeyType::Number(number) = key else {
return None;
};
// Step 2. Let value be the value of key.
let mut value = *number;
// Step 3. Set value to the minimum of value and 2^53 (9007199254740992).
value = value.min(9_007_199_254_740_992.0);
// Step 4. Set value to the largest integer not greater than value.
value = value.floor();
// Step 5. Let generator be store's key generator.
let current_number = self.key_generator_current_number.get()?;
// Step 6. If value is greater than or equal to generator's current number,
// then set generator's current number to value + 1.
if value < current_number as f64 {
return None;
}
let next = value + 1.0;
// Servo currently stores the key generator current number as i32.
// Saturate to keep "no more generated keys" behavior when this overflows.
if next >= i32::MAX as f64 {
return Some(i32::MAX);
}
Some(next as i32)
}
/// <https://www.w3.org/TR/IndexedDB-3/#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(None));
}
Ok(())
}
/// Checks if the transaction 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.
// https://w3c.github.io/IndexedDB/#transaction-inactive
// A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
// No requests can be made against the transaction when it is in this state.
if !transaction.is_active() || !transaction.is_usable() {
return Err(Error::TransactionInactive(None));
}
Ok(())
}
/// Checks if the transaction 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(None));
}
Ok(())
}
/// <https://www.w3.org/TR/IndexedDB-3/#add-or-put>
fn put(
&self,
cx: &mut JSContext,
value: HandleValue,
key: HandleValue,
no_overwrite: bool,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be handles transaction.
// Step 2. Let store be handles object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transactions 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(None));
}
// 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(None));
}
// Step 8. If key was given, then:
let mut serialized_key = None;
let mut key_generator_current_number_for_put = None;
if !key.is_undefined() {
// Step 8.1. Let r be the result of converting a value to a key with key.
// Rethrow any exceptions.
let key = convert_value_to_key(cx, key, None)?.into_result()?;
// Step 8.2. If r is "invalid value" or "invalid type", throw a
// "DataError" DOMException.
// Handled by `into_result()` above.
// Step 8.3. Let key be r.
key_generator_current_number_for_put = self.possibly_update_the_key_generator(&key);
serialized_key = Some(key);
}
// Step 9. Let targetRealm be a user-agent defined Realm.
// Step 10. Let clone be a clone of value in targetRealm during transaction.
// Rethrow any exceptions.
let cloned_value = structuredclone::write(cx.into(), value, None)?;
// Step 11. If store uses in-line keys, then:
let cloned_value = match self.key_path.as_ref() {
Some(key_path) => {
rooted!(&in(cx) let mut cloned_js_value = NullValue());
let _ = structuredclone::read(
&self.global(),
cloned_value,
cloned_js_value.handle_mut(),
CanGc::from_cx(cx),
)?;
// TODO: Avoid this deserialize/re-serialize round-trip once extract/inject can
// operate directly on the structured clone payload.
// Step 11.1. Let kpk be the result of extracting a key from a value using a key
// path with clone and stores key path. Rethrow any exceptions.
match extract_key(cx, cloned_js_value.handle(), key_path, None)? {
// Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
ExtractionResult::Invalid => return Err(Error::Data(None)),
// Step 11.3. If kpk is not failure, let key be kpk.
ExtractionResult::Key(kpk) => {
key_generator_current_number_for_put =
self.possibly_update_the_key_generator(&kpk);
serialized_key = Some(kpk);
},
// Step 11.4. Otherwise (kpk is failure):
ExtractionResult::Failure => {
// Step 11.4.1. If store does not have a key generator, throw a
// "DataError" DOMException.
if !self.has_key_generator() {
return Err(Error::Data(None));
}
let KeyPath::String(key_path) = key_path else {
return Err(Error::Data(None));
};
// Step 11.4.2. If check that a key could be injected into a value with
// clone and stores key path return false, throw a "DataError"
// DOMException.
if !can_inject_key_into_value(cx, cloned_js_value.handle(), key_path)? {
return Err(Error::Data(None));
}
// Prepares the generated key and injected clone here so Step 12 can
// pass the final key/value pair to the storage backend.
let (generated_key, next_current_number) = self.generate_key_for_put()?;
if !inject_key_into_value(
cx,
cloned_js_value.handle(),
&generated_key,
key_path,
)? {
return Err(Error::Data(None));
}
serialized_key = Some(generated_key);
key_generator_current_number_for_put = Some(next_current_number);
},
}
structuredclone::write(cx.into(), cloned_js_value.handle(), None)?
},
None => cloned_value,
};
let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
return Err(Error::InvalidState(None));
};
// Step 12. Let operation be an algorithm to run store a record into an object store with
// store, clone, key, and no-overwrite flag.
let request = IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
callback,
key: serialized_key,
value: serialized_value,
should_overwrite: !no_overwrite,
key_generator_current_number: key_generator_current_number_for_put,
})
},
None,
None,
CanGc::from_cx(cx),
)?;
// Keep the in-memory key generator in sync with the queued put request.
if let Some(next_key_generator_current_number) = key_generator_current_number_for_put {
self.key_generator_current_number
.set(Some(next_key_generator_current_number));
}
// Step 13. Return the result (an IDBRequest) of running asynchronously execute a request
// with handle and operation.
Ok(request)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
fn open_cursor(
&self,
cx: &mut JSContext,
query: HandleValue,
direction: IDBCursorDirection,
key_only: bool,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be this object store handle's transaction.
// Step 2. Let store be this object store handle's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transaction is not active, 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. Rethrow any exceptions.
//
// The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
// or not given, an unbounded key range is used.
let range = convert_value_to_key_range(cx, query, Some(false))?;
// Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
// position, direction set to direction, got value flag unset, and undefined key and value.
// The source of cursor is store. The range of cursor is range.
//
// NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
// interface as well.
let cursor = if key_only {
IDBCursor::new(
&self.global(),
&self.transaction,
direction,
false,
ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
range.clone(),
key_only,
CanGc::from_cx(cx),
)
} else {
DomRoot::upcast(IDBCursorWithValue::new(
&self.global(),
&self.transaction,
direction,
false,
ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
range.clone(),
key_only,
CanGc::from_cx(cx),
))
};
// Step 7. 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 iterate a cursor as operation, using the current Realm as targetRealm, and
// cursor.
let iteration_param = IterationParam {
cursor: Trusted::new(&cursor),
key: None,
primary_key: None,
count: None,
};
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
callback,
key_range: range,
})
},
None,
Some(iteration_param),
CanGc::from_cx(cx),
)
.inspect(|request| cursor.set_request(request))
}
pub(crate) fn add_index(
&self,
name: DOMString,
options: &IDBIndexParameters,
key_path: KeyPath,
can_gc: CanGc,
) -> DomRoot<IDBIndex> {
let index = IDBIndex::new(
&self.global(),
DomRoot::from_ref(self),
name.clone(),
options.multiEntry,
options.unique,
key_path,
can_gc,
);
self.index_set
.borrow_mut()
.insert(name, Dom::from_ref(&index));
index
}
}
impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
fn Put(
&self,
cx: &mut JSContext,
value: HandleValue,
key: HandleValue,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Return the result of running add or put with this, value, key and the
// no-overwrite flag false.
self.put(cx, value, key, false)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-add>
fn Add(
&self,
cx: &mut JSContext,
value: HandleValue,
key: HandleValue,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Return the result of running add or put with this, value, key and the
// no-overwrite flag true.
self.put(cx, value, key, true)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-delete>
fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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 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, Some(true));
// Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
// Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
serialized_query.and_then(|key_range| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
callback,
key_range,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-clear>
fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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.
// Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
IDBRequest::execute_async(
self,
|callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
None,
None,
CanGc::from_cx(cx),
)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-get>
fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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.
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
callback,
key_range: q,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getkey>
fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
// Step 1. Let transaction be thiss 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 transactions 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.
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
callback,
key_range: q,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getall>
fn GetAll(
&self,
cx: &mut JSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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.
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
callback,
key_range: q,
count,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getallkeys>
fn GetAllKeys(
&self,
cx: &mut JSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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.
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
callback,
key_range: q,
count,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-count>
fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss 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 transactions 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.
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
|callback| {
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
callback,
key_range: q,
})
},
None,
None,
CanGc::from_cx(cx),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
fn OpenCursor(
&self,
cx: &mut JSContext,
query: HandleValue,
direction: IDBCursorDirection,
) -> Fallible<DomRoot<IDBRequest>> {
self.open_cursor(cx, query, direction, false)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
fn OpenKeyCursor(
&self,
cx: &mut JSContext,
query: HandleValue,
direction: IDBCursorDirection,
) -> Fallible<DomRoot<IDBRequest>> {
self.open_cursor(cx, query, direction, true)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
fn Name(&self) -> DOMString {
self.name.borrow().clone()
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
fn SetName(&self, value: DOMString) -> ErrorResult {
// Step 1. Let name be the given value.
let name = value;
// Step 2. Let transaction be thiss transaction.
let transaction = &self.transaction;
// Step 3. Let store be thiss 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(None));
}
// Step 6. If transactions state is not active, throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 7. If stores name is equal to name, terminate these steps.
if *self.name.borrow() == name {
return Ok(());
}
// Step 8. If an object store named name already exists in stores database,
// throw a "ConstraintError" DOMException.
if transaction.Db().object_store_exists(&name) {
return Err(Error::Constraint(None));
}
// Step 9. Set stores name to name.
// Step 10. Set thiss name to name.
*self.name.borrow_mut() = name;
Ok(())
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-keypath>
fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
match &self.key_path {
Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
None => ret_val.set(NullValue()),
}
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-indexnames>
fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-transaction>
fn Transaction(&self) -> DomRoot<IDBTransaction> {
self.transaction()
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-autoincrement>
fn AutoIncrement(&self) -> bool {
self.has_key_generator()
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-createindex>
fn CreateIndex(
&self,
cx: &mut JSContext,
name: DOMString,
key_path: StringOrStringSequence,
options: &IDBIndexParameters,
) -> Fallible<DomRoot<IDBIndex>> {
let key_path: KeyPath = key_path.into();
// Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
if self.transaction.Mode() != IDBTransactionMode::Versionchange {
return Err(Error::InvalidState(None));
}
// Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
if self.index_set.borrow().contains_key(&name) {
return Err(Error::Constraint(None));
}
let js_key_path = match key_path.clone() {
KeyPath::String(s) => StringOrStringSequence::String(s),
KeyPath::StringSequence(s) => StringOrStringSequence::StringSequence(s),
};
// Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
if !is_valid_key_path(cx, &js_key_path)? {
return Err(Error::Syntax(None));
}
// Step 8. Let unique be set if optionss unique member is true, and unset otherwise.
// Step 9. Let multiEntry be set if optionss multiEntry member is true, and unset otherwise.
// Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
return Err(Error::InvalidAccess(None));
}
// Step 11. Let index be a new index in store.
// Set indexs name to name and key path to keyPath. If unique is set, set indexs unique flag.
// If multiEntry is set, set indexs multiEntry flag.
let create_index_operation = SyncOperation::CreateIndex(
self.global().origin().immutable().clone(),
self.db_name.to_string(),
self.name.borrow().to_string(),
name.to_string(),
key_path.clone().into(),
options.unique,
options.multiEntry,
);
if self
.get_idb_thread()
.send(IndexedDBThreadMsg::Sync(create_index_operation))
.is_err()
{
return Err(Error::Operation(None));
}
// Step 12. Add index to this object store handle's index set.
let index = self.add_index(name, options, key_path, CanGc::from_cx(cx));
// Step 13. Return a new index handle associated with index and this object store handle.
Ok(index)
}
/// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-deleteindex>
fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
// Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
if self.transaction.Mode() != IDBTransactionMode::Versionchange {
return Err(Error::InvalidState(None));
}
// Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 6. Let index be the index named name in store if one exists,
// or throw a "NotFoundError" DOMException otherwise.
if !self.index_set.borrow().contains_key(&name) {
return Err(Error::NotFound(None));
}
// Step 7. Remove index from this object store handle's index set.
self.index_set.borrow_mut().retain(|n, _| n != &name);
// Step 8. Destroy index.
let delete_index_operation = SyncOperation::DeleteIndex(
self.global().origin().immutable().clone(),
self.db_name.to_string(),
self.name.borrow().to_string(),
name.to_string(),
);
self.get_idb_thread()
.send(IndexedDBThreadMsg::Sync(delete_index_operation))
.unwrap();
Ok(())
}
/// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
if self.transaction.is_finished() {
return Err(Error::InvalidState(None));
}
// Step 5. Let index be the index named name in thiss index set if one exists, or throw a "NotFoundError" DOMException otherwise.
let index_set = self.index_set.borrow();
let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
// Step 6. Return an index handle associated with index and this.
Ok(index.as_rooted())
}
}