Skip to main content

script/dom/indexeddb/
idbobjectstore.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/. */
4use std::cell::Cell;
5use std::collections::HashMap;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::conversions::ToJSValConvertible;
10use js::gc::MutableHandleValue;
11use js::jsval::NullValue;
12use js::rust::HandleValue;
13use script_bindings::cell::DomRefCell;
14use script_bindings::codegen::GenericBindings::IDBObjectStoreBinding::IDBIndexParameters;
15use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
16use script_bindings::domstring::TracedDOMString;
17use script_bindings::error::ErrorResult;
18use script_bindings::reflector::{Reflector, reflect_dom_object};
19use servo_base::generic_channel::{GenericSend, GenericSender};
20use storage_traits::indexeddb::{
21    self, AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, AsyncSchemaOperation,
22    IndexedDBKeyType, IndexedDBThreadMsg,
23};
24
25use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::IDBCursorDirection;
26use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
27use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
28use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
29    IDBTransactionMethods, IDBTransactionMode,
30};
31// We need to alias this name, otherwise test-tidy complains at &String reference.
32use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
33use crate::dom::bindings::error::{Error, Fallible};
34use crate::dom::bindings::refcounted::Trusted;
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::{Dom, DomRoot};
37use crate::dom::bindings::str::DOMString;
38use crate::dom::bindings::structuredclone;
39use crate::dom::domstringlist::DOMStringList;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::indexeddb::idbcursor::{IDBCursor, IterationParam, ObjectStoreOrIndex};
42use crate::dom::indexeddb::idbcursorwithvalue::IDBCursorWithValue;
43use crate::dom::indexeddb::idbindex::IDBIndex;
44use crate::dom::indexeddb::idbrequest::IDBRequest;
45use crate::dom::indexeddb::idbtransaction::IDBTransaction;
46use crate::dom::indexeddb::key::{
47    ExtractionResult, can_inject_key_into_value, convert_value_to_key, convert_value_to_key_range,
48    extract_key, inject_key_into_value, is_valid_key_path,
49};
50
51#[derive(Clone, JSTraceable, MallocSizeOf)]
52pub enum KeyPath {
53    String(DOMString),
54    StringSequence(Vec<DOMString>),
55}
56
57impl From<StringOrStringSequence> for KeyPath {
58    fn from(value: StringOrStringSequence) -> Self {
59        match value {
60            StringOrStringSequence::String(s) => KeyPath::String(s),
61            StringOrStringSequence::StringSequence(ss) => KeyPath::StringSequence(ss),
62        }
63    }
64}
65
66impl From<indexeddb::KeyPath> for KeyPath {
67    fn from(value: indexeddb::KeyPath) -> Self {
68        match value {
69            indexeddb::KeyPath::String(string) => KeyPath::String(string.into()),
70            indexeddb::KeyPath::Sequence(ss) => {
71                KeyPath::StringSequence(ss.into_iter().map(Into::into).collect())
72            },
73        }
74    }
75}
76
77impl From<KeyPath> for indexeddb::KeyPath {
78    fn from(item: KeyPath) -> Self {
79        match item {
80            KeyPath::String(s) => Self::String(String::from(s)),
81            KeyPath::StringSequence(ss) => {
82                Self::Sequence(ss.into_iter().map(String::from).collect())
83            },
84        }
85    }
86}
87
88#[derive(Clone, JSTraceable, MallocSizeOf)]
89struct IDBObjectStoreRollbackState {
90    newly_created_during_transaction: bool,
91    rollback_name: Option<DOMString>,
92    #[no_trace]
93    rollback_indexes: Vec<indexeddb::IndexedDBIndex>,
94    key_generator_current_number: Option<i64>,
95}
96
97#[dom_struct]
98pub struct IDBObjectStore {
99    reflector_: Reflector,
100    name: DomRefCell<TracedDOMString>,
101    key_path: Option<KeyPath>,
102    index_set: DomRefCell<HashMap<TracedDOMString, Dom<IDBIndex>>>,
103    abort_state_on_abort: DomRefCell<Option<IDBObjectStoreRollbackState>>,
104    transaction: Dom<IDBTransaction>,
105    has_key_generator: bool,
106    key_generator_current_number: Cell<Option<i64>>,
107
108    // We store the db name in the object store to address backend operations
109    // that are keyed by (origin, database name, object store name).
110    db_name: TracedDOMString,
111}
112
113pub(crate) struct IDBObjectStoreAbortState {
114    pub(crate) newly_created_during_transaction: bool,
115    pub(crate) rollback_indexes_on_abort: Vec<indexeddb::IndexedDBIndex>,
116    pub(crate) key_generator_current_number: Option<i64>,
117}
118
119impl IDBObjectStore {
120    pub fn new_inherited(
121        db_name: DOMString,
122        name: DOMString,
123        options: Option<&IDBObjectStoreParameters>,
124        abort_state: IDBObjectStoreAbortState,
125        transaction: &IDBTransaction,
126    ) -> IDBObjectStore {
127        let key_path: Option<KeyPath> = match options {
128            Some(options) => options.keyPath.as_ref().map(|path| match path {
129                StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
130                StrOrStringSequence::StringSequence(inner) => {
131                    KeyPath::StringSequence(inner.clone())
132                },
133            }),
134            None => None,
135        };
136        let has_key_generator = options.is_some_and(|options| options.autoIncrement);
137        let IDBObjectStoreAbortState {
138            newly_created_during_transaction,
139            rollback_indexes_on_abort,
140            key_generator_current_number,
141        } = abort_state;
142        let key_generator_current_number = if has_key_generator {
143            Some(key_generator_current_number.unwrap_or(1))
144        } else {
145            None
146        };
147
148        IDBObjectStore {
149            reflector_: Reflector::new(),
150            name: DomRefCell::new(name.traced()),
151            key_path,
152            index_set: DomRefCell::new(HashMap::new()),
153            abort_state_on_abort: DomRefCell::new(Some(IDBObjectStoreRollbackState {
154                newly_created_during_transaction,
155                rollback_name: None,
156                rollback_indexes: rollback_indexes_on_abort,
157                key_generator_current_number,
158            })),
159            transaction: Dom::from_ref(transaction),
160            has_key_generator,
161            key_generator_current_number: Cell::new(key_generator_current_number),
162            db_name: db_name.traced(),
163        }
164    }
165
166    pub fn new(
167        cx: &mut JSContext,
168        global: &GlobalScope,
169        db_name: DOMString,
170        name: DOMString,
171        options: Option<&IDBObjectStoreParameters>,
172        abort_state: IDBObjectStoreAbortState,
173        transaction: &IDBTransaction,
174    ) -> DomRoot<IDBObjectStore> {
175        reflect_dom_object(
176            cx,
177            Box::new(IDBObjectStore::new_inherited(
178                db_name,
179                name,
180                options,
181                abort_state,
182                transaction,
183            )),
184            global,
185        )
186    }
187
188    pub fn get_name(&self) -> DOMString {
189        self.name.borrow().clone().root()
190    }
191
192    /// <https://w3c.github.io/IndexedDB/#abort-an-upgrade-transaction>
193    pub(crate) fn restore_metadata_after_abort(&self, cx: &mut JSContext) {
194        let Some(abort_state) = self.abort_state_on_abort.borrow().as_ref().cloned() else {
195            return;
196        };
197
198        // Step 5.1. If handle’s object store was not newly created during transaction,
199        // set handle’s name to its object store’s name.
200        if !abort_state.newly_created_during_transaction &&
201            let Some(name) = abort_state.rollback_name
202        {
203            *self.name.safe_borrow_mut(cx.no_gc()) = name.traced();
204        }
205
206        // Step 5.2. Set handle’s index set to the set of indexes that reference
207        // its object store.
208        self.index_set.safe_borrow_mut(cx.no_gc()).clear();
209        for index in abort_state.rollback_indexes {
210            self.add_index(
211                cx,
212                index.name.clone().into(),
213                &IDBIndexParameters {
214                    multiEntry: index.multi_entry,
215                    unique: index.unique,
216                },
217                index.key_path.clone().into(),
218            );
219        }
220
221        // Restore key generator state for existing object store handles.
222        if self.has_key_generator && !abort_state.newly_created_during_transaction {
223            self.key_generator_current_number
224                .set(abort_state.key_generator_current_number);
225        }
226    }
227
228    pub(crate) fn transaction(&self) -> DomRoot<IDBTransaction> {
229        self.transaction.as_rooted()
230    }
231
232    fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
233        self.global().storage_threads().sender()
234    }
235
236    /// <https://www.w3.org/TR/IndexedDB-3/#clone>
237    pub(crate) fn clone_value_in_target_realm(
238        &self,
239        cx: &mut JSContext,
240        value: HandleValue,
241        clone: MutableHandleValue<'_>,
242    ) -> Fallible<()> {
243        // Step 1. Assert: transaction's state is active.
244        debug_assert!(self.transaction.is_active());
245
246        // Step 2. Set transaction's state to inactive.
247        //
248        // NOTE: The transaction is made inactive so that getters or other side
249        // effects triggered by the cloning operation are unable to make
250        // additional requests against the transaction.
251        self.transaction.set_active_flag(false);
252
253        let result = (|| {
254            // Step 3. Let serialized be ? StructuredSerializeForStorage(value).
255            let serialized = structuredclone::write(cx, value, None)?;
256
257            // Step 4. Let clone be ? StructuredDeserialize(serialized, targetRealm).
258            let _ = structuredclone::read(cx, &self.global(), serialized, clone)?;
259            Ok(())
260        })();
261
262        // Step 5. Set transaction's state to active.
263        self.transaction.set_active_flag(true);
264
265        // Step 6. Return clone.
266        result
267    }
268
269    fn has_key_generator(&self) -> bool {
270        self.has_key_generator
271    }
272
273    /// <https://w3c.github.io/IndexedDB/#generate-a-key>
274    fn generate_key_for_put(&self) -> Fallible<(IndexedDBKeyType, i64)> {
275        // Step 1. Let generator be store's key generator.
276        let Some(current_number) = self.key_generator_current_number.get() else {
277            return Err(Error::Data(None));
278        };
279        // Step 2. Let key be generator's current number.
280        let key = current_number as f64;
281        // Step 3. If key is greater than 2^53 (9007199254740992), then return failure.
282        if key > 9_007_199_254_740_992.0 {
283            return Err(Error::Constraint(None));
284        }
285        // Step 4. Increase generator's current number by 1.
286        let next_current_number = current_number
287            .checked_add(1)
288            .ok_or(Error::Constraint(None))?;
289        // Step 5. Return key.
290        Ok((IndexedDBKeyType::Number(key), next_current_number))
291    }
292
293    /// <https://w3c.github.io/IndexedDB/#possibly-update-the-key-generator>
294    fn possibly_update_the_key_generator(&self, key: &IndexedDBKeyType) -> Option<i64> {
295        // Step 1. If the type of key is not number, abort these steps.
296        let IndexedDBKeyType::Number(number) = key else {
297            return None;
298        };
299
300        // Step 2. Let value be the value of key.
301        let mut value = *number;
302        // Step 3. Set value to the minimum of value and 2^53 (9007199254740992).
303        value = value.min(9_007_199_254_740_992.0);
304        // Step 4. Set value to the largest integer not greater than value.
305        value = value.floor();
306        // Step 5. Let generator be store's key generator.
307        let current_number = self.key_generator_current_number.get()?;
308        // Step 6. If value is greater than or equal to generator's current number,
309        // then set generator's current number to value + 1.
310        if value < current_number as f64 {
311            return None;
312        }
313
314        let next = value + 1.0;
315        if next > i64::MAX as f64 {
316            return Some(i64::MAX);
317        }
318        Some(next as i64)
319    }
320
321    /// <https://www.w3.org/TR/IndexedDB-3/#object-store-in-line-keys>
322    pub(crate) fn uses_inline_keys(&self) -> bool {
323        self.key_path.is_some()
324    }
325
326    pub(crate) fn key_path(&self) -> Option<&KeyPath> {
327        self.key_path.as_ref()
328    }
329
330    pub(crate) fn verify_not_deleted(&self) -> ErrorResult {
331        let db = self.transaction.Db();
332        if !db.object_store_exists(&self.name.borrow()) {
333            return Err(Error::InvalidState(None));
334        }
335        Ok(())
336    }
337
338    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
339    fn check_transaction_active(&self) -> Fallible<()> {
340        // Let transaction be this object store handle's transaction.
341        let transaction = &self.transaction;
342
343        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
344        // https://w3c.github.io/IndexedDB/#transaction-inactive
345        // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
346        // No requests can be made against the transaction when it is in this state.
347        if !transaction.is_active() || !transaction.is_usable() {
348            return Err(Error::TransactionInactive(None));
349        }
350
351        Ok(())
352    }
353
354    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
355    /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
356    fn check_readwrite_transaction_active(&self) -> Fallible<()> {
357        // Let transaction be this object store handle's transaction.
358        let transaction = &self.transaction;
359
360        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
361        self.check_transaction_active()?;
362
363        if let IDBTransactionMode::Readonly = transaction.get_mode() {
364            return Err(Error::ReadOnly(None));
365        }
366        Ok(())
367    }
368
369    /// <https://www.w3.org/TR/IndexedDB-3/#add-or-put>
370    fn put(
371        &self,
372        cx: &mut JSContext,
373        value: HandleValue,
374        key: HandleValue,
375        no_overwrite: bool,
376    ) -> Fallible<DomRoot<IDBRequest>> {
377        // Step 1. Let transaction be handle’s transaction.
378        // Step 2. Let store be handle’s object store.
379        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
380        self.verify_not_deleted()?;
381
382        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
383        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
384        self.check_readwrite_transaction_active()?;
385
386        // Step 6. If store uses in-line keys and key was given, throw a "DataError"
387        // DOMException.
388        if !key.is_undefined() && self.uses_inline_keys() {
389            return Err(Error::Data(None));
390        }
391
392        // Step 7. If store uses out-of-line keys and has no key generator and key
393        // was not given, throw a "DataError" DOMException.
394        if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
395            return Err(Error::Data(None));
396        }
397
398        // Step 8. If key was given, then:
399        let mut serialized_key = None;
400        let mut key_generator_current_number_for_put = None;
401
402        if !key.is_undefined() {
403            // Step 8.1. Let r be the result of converting a value to a key with key.
404            // Rethrow any exceptions.
405            let key = convert_value_to_key(cx, key, None)?.into_result()?;
406            // Step 8.2. If r is "invalid value" or "invalid type", throw a
407            // "DataError" DOMException.
408            // Handled by `into_result()` above.
409            // Step 8.3. Let key be r.
410            key_generator_current_number_for_put = self.possibly_update_the_key_generator(&key);
411            serialized_key = Some(key);
412        }
413
414        // Step 9. Let targetRealm be a user-agent defined Realm.
415        // Step 10. Let clone be a clone of value in targetRealm during transaction.
416        // Rethrow any exceptions.
417        rooted!(&in(cx) let mut cloned_js_value = NullValue());
418        self.clone_value_in_target_realm(cx, value, cloned_js_value.handle_mut())?;
419
420        // Step 11. If store uses in-line keys, then:
421        let cloned_value = match self.key_path.as_ref() {
422            Some(key_path) => {
423                // Step 11.1. Let kpk be the result of extracting a key from a value using a key
424                // path with clone and store’s key path. Rethrow any exceptions.
425                match extract_key(cx, cloned_js_value.handle(), key_path, None)? {
426                    // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
427                    ExtractionResult::Invalid => return Err(Error::Data(None)),
428                    // Step 11.3. If kpk is not failure, let key be kpk.
429                    ExtractionResult::Key(kpk) => {
430                        key_generator_current_number_for_put =
431                            self.possibly_update_the_key_generator(&kpk);
432                        serialized_key = Some(kpk);
433                    },
434                    // Step 11.4. Otherwise (kpk is failure):
435                    ExtractionResult::Failure => {
436                        // Step 11.4.1. If store does not have a key generator, throw a
437                        // "DataError" DOMException.
438                        if !self.has_key_generator() {
439                            return Err(Error::Data(None));
440                        }
441                        let KeyPath::String(key_path) = key_path else {
442                            return Err(Error::Data(None));
443                        };
444                        // Step 11.4.2. If check that a key could be injected into a value with
445                        // clone and store’s key path return false, throw a "DataError"
446                        // DOMException.
447                        if !can_inject_key_into_value(cx, cloned_js_value.handle(), key_path)? {
448                            return Err(Error::Data(None));
449                        }
450
451                        // Prepares the generated key and injected clone here so Step 12 can
452                        // pass the final key/value pair to the storage backend.
453                        let (generated_key, next_current_number) = self.generate_key_for_put()?;
454                        if !inject_key_into_value(
455                            cx,
456                            cloned_js_value.handle(),
457                            &generated_key,
458                            key_path,
459                        )? {
460                            return Err(Error::Data(None));
461                        }
462                        serialized_key = Some(generated_key);
463                        key_generator_current_number_for_put = Some(next_current_number);
464                    },
465                }
466
467                structuredclone::write(cx, cloned_js_value.handle(), None)?
468            },
469            None => structuredclone::write(cx, cloned_js_value.handle(), None)?,
470        };
471        let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
472            return Err(Error::InvalidState(None));
473        };
474        // Step 12. Let operation be an algorithm to run store a record into an object store with
475        // store, clone, key, and no-overwrite flag.
476        let request = IDBRequest::execute_async(
477            cx,
478            self,
479            |callback| {
480                AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
481                    callback,
482                    key: serialized_key,
483                    value: serialized_value,
484                    should_overwrite: !no_overwrite,
485                    key_generator_current_number: key_generator_current_number_for_put,
486                })
487            },
488            None,
489            None,
490        )?;
491        // Keep the in-memory key generator in sync with the queued put request.
492        if let Some(next_key_generator_current_number) = key_generator_current_number_for_put {
493            self.key_generator_current_number
494                .set(Some(next_key_generator_current_number));
495        }
496        // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request
497        // with handle and operation.
498        Ok(request)
499    }
500
501    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
502    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
503    fn open_cursor(
504        &self,
505        cx: &mut JSContext,
506        query: HandleValue,
507        direction: IDBCursorDirection,
508        key_only: bool,
509    ) -> Fallible<DomRoot<IDBRequest>> {
510        // Step 1. Let transaction be this object store handle's transaction.
511        // Step 2. Let store be this object store handle's object store.
512
513        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
514        self.verify_not_deleted()?;
515
516        // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
517        self.check_transaction_active()?;
518
519        // Step 5. Let range be the result of running the steps to convert a value to a key range
520        // with query. Rethrow any exceptions.
521        //
522        // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
523        // or not given, an unbounded key range is used.
524        let range = convert_value_to_key_range(cx, query, Some(false))?;
525
526        // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
527        // position, direction set to direction, got value flag unset, and undefined key and value.
528        // The source of cursor is store. The range of cursor is range.
529        //
530        // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
531        // interface as well.
532        let cursor = if key_only {
533            IDBCursor::new(
534                cx,
535                &self.global(),
536                &self.transaction,
537                direction,
538                false,
539                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
540                range.clone(),
541                key_only,
542            )
543        } else {
544            DomRoot::upcast(IDBCursorWithValue::new(
545                cx,
546                &self.global(),
547                &self.transaction,
548                direction,
549                false,
550                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
551                range.clone(),
552                key_only,
553            ))
554        };
555
556        // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
557        // created by these steps. The steps are run with this object store handle as source and
558        // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
559        // cursor.
560        let iteration_param = IterationParam {
561            cursor: Trusted::new(&cursor),
562            key: None,
563            primary_key: None,
564            count: None,
565        };
566
567        IDBRequest::execute_async(
568            cx,
569            self,
570            |callback| {
571                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
572                    callback,
573                    key_range: range,
574                })
575            },
576            None,
577            Some(iteration_param),
578        )
579        .inspect(|request| cursor.set_request(request))
580    }
581
582    pub(crate) fn add_index(
583        &self,
584        cx: &mut JSContext,
585        name: DOMString,
586        options: &IDBIndexParameters,
587        key_path: KeyPath,
588    ) -> DomRoot<IDBIndex> {
589        let index = IDBIndex::new(
590            cx,
591            &self.global(),
592            self,
593            name.clone(),
594            options.multiEntry,
595            options.unique,
596            key_path,
597        );
598        self.index_set
599            .safe_borrow_mut(cx.no_gc())
600            .insert(name.traced(), Dom::from_ref(&index));
601        index
602    }
603
604    pub(crate) fn has_index(&self, name: &TracedDOMString) -> bool {
605        self.index_set.borrow().contains_key(name)
606    }
607
608    /// The caller must ensure that the original index exists.
609    pub(crate) fn rename_index(&self, name: &TracedDOMString, new_name: &DOMString) {
610        let operation = AsyncSchemaOperation::RenameIndex {
611            callback: self.transaction.create_abort_callback(),
612            index_name: name.to_string(),
613            new_name: new_name.to_string(),
614        };
615
616        if self
617            .get_idb_thread()
618            .send(IndexedDBThreadMsg::AsyncSchemaOperation {
619                origin: self.global().origin().immutable().clone(),
620                database_name: self.db_name.to_string(),
621                store_name: self.name.borrow().clone().into(),
622                operation,
623                transaction_serial_number: self.transaction.get_serial_number(),
624            })
625            .is_err()
626        {
627            warn!("Could not send AsyncSchemaOperation");
628        }
629
630        // We also need to update the key in the index set
631        let index = self
632            .index_set
633            .borrow_mut()
634            .remove(name)
635            .expect("Earlier steps of the algorithm checked that the index exists")
636            .as_rooted();
637        self.index_set
638            .borrow_mut()
639            .insert(new_name.clone().traced(), Dom::from_ref(&index));
640    }
641}
642
643impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
644    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
645    fn Put(
646        &self,
647        cx: &mut JSContext,
648        value: HandleValue,
649        key: HandleValue,
650    ) -> Fallible<DomRoot<IDBRequest>> {
651        // Step 1. Return the result of running add or put with this, value, key and the
652        // no-overwrite flag false.
653        self.put(cx, value, key, false)
654    }
655
656    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-add>
657    fn Add(
658        &self,
659        cx: &mut JSContext,
660        value: HandleValue,
661        key: HandleValue,
662    ) -> Fallible<DomRoot<IDBRequest>> {
663        // Step 1. Return the result of running add or put with this, value, key and the
664        // no-overwrite flag true.
665        self.put(cx, value, key, true)
666    }
667
668    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-delete>
669    fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
670        // Step 1. Let transaction be this’s transaction.
671        // Step 2. Let store be this's object store.
672        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
673        self.verify_not_deleted()?;
674
675        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
676        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
677        self.check_readwrite_transaction_active()?;
678
679        // 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.
680        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
681        // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
682        // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
683        serialized_query.and_then(|key_range| {
684            IDBRequest::execute_async(
685                cx,
686                self,
687                |callback| {
688                    AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
689                        callback,
690                        key_range,
691                    })
692                },
693                None,
694                None,
695            )
696        })
697    }
698
699    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-clear>
700    fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
701        // Step 1. Let transaction be this’s transaction.
702        // Step 2. Let store be this's object store.
703        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
704        self.verify_not_deleted()?;
705
706        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
707        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
708        self.check_readwrite_transaction_active()?;
709
710        // Step 6. Let operation be an algorithm to run clear an object store with store.
711        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
712        IDBRequest::execute_async(
713            cx,
714            self,
715            |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
716            None,
717            None,
718        )
719    }
720
721    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-get>
722    fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
723        // Step 1. Let transaction be this’s transaction.
724        // Step 2. Let store be this's object store.
725        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
726        self.verify_not_deleted()?;
727
728        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
729        self.check_transaction_active()?;
730
731        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
732        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
733
734        // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
735        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
736        serialized_query.and_then(|q| {
737            IDBRequest::execute_async(
738                cx,
739                self,
740                |callback| {
741                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
742                        callback,
743                        key_range: q,
744                    })
745                },
746                None,
747                None,
748            )
749        })
750    }
751
752    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getkey>
753    fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
754        // Step 1. Let transaction be this’s transaction.
755        // Step 2. Let store be this's object store.
756        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
757        self.verify_not_deleted()?;
758
759        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
760        self.check_transaction_active()?;
761
762        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
763        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
764
765        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
766        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
767        // store as operation, using store and range.
768        serialized_query.and_then(|q| {
769            IDBRequest::execute_async(
770                cx,
771                self,
772                |callback| {
773                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
774                        callback,
775                        key_range: q,
776                    })
777                },
778                None,
779                None,
780            )
781        })
782    }
783
784    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getall>
785    fn GetAll(
786        &self,
787        cx: &mut JSContext,
788        query: HandleValue,
789        count: Option<u32>,
790    ) -> Fallible<DomRoot<IDBRequest>> {
791        // Step 1. Let transaction be this’s transaction.
792        // Step 2. Let store be this's object store.
793        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
794        self.verify_not_deleted()?;
795
796        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
797        self.check_transaction_active()?;
798
799        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
800        let serialized_query = convert_value_to_key_range(cx, query, None);
801
802        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
803        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
804        // store as operation, using store and range.
805        serialized_query.and_then(|q| {
806            IDBRequest::execute_async(
807                cx,
808                self,
809                |callback| {
810                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
811                        callback,
812                        key_range: q,
813                        count,
814                    })
815                },
816                None,
817                None,
818            )
819        })
820    }
821
822    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getallkeys>
823    fn GetAllKeys(
824        &self,
825        cx: &mut JSContext,
826        query: HandleValue,
827        count: Option<u32>,
828    ) -> Fallible<DomRoot<IDBRequest>> {
829        // Step 1. Let transaction be this’s transaction.
830        // Step 2. Let store be this's object store.
831        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
832        self.verify_not_deleted()?;
833
834        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
835        self.check_transaction_active()?;
836
837        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
838        let serialized_query = convert_value_to_key_range(cx, query, None);
839
840        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
841        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
842        // store as operation, using store and range.
843        serialized_query.and_then(|q| {
844            IDBRequest::execute_async(
845                cx,
846                self,
847                |callback| {
848                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
849                        callback,
850                        key_range: q,
851                        count,
852                    })
853                },
854                None,
855                None,
856            )
857        })
858    }
859
860    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-count>
861    fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
862        // Step 1. Let transaction be this’s transaction.
863        // Step 2. Let store be this's object store.
864        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
865        self.verify_not_deleted()?;
866
867        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
868        self.check_transaction_active()?;
869
870        // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
871        let serialized_query = convert_value_to_key_range(cx, query, None);
872
873        // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
874        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
875        serialized_query.and_then(|q| {
876            IDBRequest::execute_async(
877                cx,
878                self,
879                |callback| {
880                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
881                        callback,
882                        key_range: q,
883                    })
884                },
885                None,
886                None,
887            )
888        })
889    }
890
891    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
892    fn OpenCursor(
893        &self,
894        cx: &mut JSContext,
895        query: HandleValue,
896        direction: IDBCursorDirection,
897    ) -> Fallible<DomRoot<IDBRequest>> {
898        self.open_cursor(cx, query, direction, false)
899    }
900
901    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
902    fn OpenKeyCursor(
903        &self,
904        cx: &mut JSContext,
905        query: HandleValue,
906        direction: IDBCursorDirection,
907    ) -> Fallible<DomRoot<IDBRequest>> {
908        self.open_cursor(cx, query, direction, true)
909    }
910
911    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
912    fn Name(&self) -> DOMString {
913        self.name.borrow().clone().root()
914    }
915
916    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
917    fn SetName(&self, value: DOMString) -> ErrorResult {
918        // Step 1. Let name be the given value.
919        let name = value;
920
921        // Step 2. Let transaction be this’s transaction.
922        let transaction = &self.transaction;
923
924        // Step 3. Let store be this’s object store.
925        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
926        self.verify_not_deleted()?;
927
928        // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
929        if transaction.Mode() != IDBTransactionMode::Versionchange {
930            return Err(Error::InvalidState(None));
931        }
932        // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
933        self.check_transaction_active()?;
934
935        // Step 7. If store’s name is equal to name, terminate these steps.
936        if *self.name.borrow() == *name {
937            return Ok(());
938        }
939
940        // Step 8. If an object store named name already exists in store’s database,
941        // throw a "ConstraintError" DOMException.
942        if transaction.Db().object_store_exists(&name) {
943            return Err(Error::Constraint(None));
944        }
945
946        let old_name = self.name.borrow().clone().root();
947        if let Some(abort_state) = self.abort_state_on_abort.borrow_mut().as_mut() &&
948            abort_state.rollback_name.is_none()
949        {
950            abort_state.rollback_name = Some(old_name.clone());
951        }
952
953        // Step 9. Set store’s name to name.
954        transaction
955            .Db()
956            .rename_object_store_name(&old_name, name.clone());
957        // Step 10. Set this’s name to name.
958        *self.name.borrow_mut() = name.clone().traced();
959        transaction.rename_object_store_handle_cache(&old_name, &name, self);
960        Ok(())
961    }
962
963    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-keypath>
964    fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
965        match &self.key_path {
966            Some(KeyPath::String(path)) => path.to_jsval(cx, ret_val),
967            Some(KeyPath::StringSequence(paths)) => paths.to_jsval(cx, ret_val),
968            None => ret_val.set(NullValue()),
969        }
970    }
971
972    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-indexnames>
973    fn IndexNames(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
974        DOMStringList::new_sorted(cx, &self.global(), self.index_set.borrow().keys())
975    }
976
977    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-transaction>
978    fn Transaction(&self) -> DomRoot<IDBTransaction> {
979        self.transaction()
980    }
981
982    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-autoincrement>
983    fn AutoIncrement(&self) -> bool {
984        self.has_key_generator()
985    }
986
987    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-createindex>
988    fn CreateIndex(
989        &self,
990        cx: &mut JSContext,
991        name: DOMString,
992        key_path: StringOrStringSequence,
993        options: &IDBIndexParameters,
994    ) -> Fallible<DomRoot<IDBIndex>> {
995        let key_path: KeyPath = key_path.into();
996        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
997        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
998            return Err(Error::InvalidState(None));
999        }
1000
1001        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
1002        self.verify_not_deleted()?;
1003        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
1004        self.check_transaction_active()?;
1005
1006        // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
1007        if self.has_index(&name) {
1008            return Err(Error::Constraint(None));
1009        }
1010
1011        let js_key_path = match key_path.clone() {
1012            KeyPath::String(s) => StringOrStringSequence::String(s),
1013            KeyPath::StringSequence(s) => StringOrStringSequence::StringSequence(s),
1014        };
1015
1016        // Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
1017        if !is_valid_key_path(cx, &js_key_path)? {
1018            return Err(Error::Syntax(None));
1019        }
1020        // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
1021        // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
1022        // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
1023        if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
1024            return Err(Error::InvalidAccess(None));
1025        }
1026
1027        // Step 11. Let index be a new index in store.
1028        // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
1029        // If multiEntry is set, set index’s multiEntry flag.
1030        let operation = AsyncSchemaOperation::CreateIndex {
1031            callback: self.transaction.create_abort_callback(),
1032            index_name: name.to_string(),
1033            key_path: key_path.clone().into(),
1034            unique: options.unique,
1035            multi_entry: options.multiEntry,
1036        };
1037
1038        if self
1039            .get_idb_thread()
1040            .send(IndexedDBThreadMsg::AsyncSchemaOperation {
1041                origin: self.global().origin().immutable().clone(),
1042                database_name: self.db_name.to_string(),
1043                store_name: self.name.borrow().clone().into(),
1044                operation,
1045                transaction_serial_number: self.transaction.get_serial_number(),
1046            })
1047            .is_err()
1048        {
1049            return Err(Error::Operation(None));
1050        }
1051
1052        // Step 12. Add index to this object store handle's index set.
1053        let index = self.add_index(cx, name, options, key_path);
1054
1055        // Step 13. Return a new index handle associated with index and this object store handle.
1056        Ok(index)
1057    }
1058
1059    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-deleteindex>
1060    fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
1061        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
1062        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
1063            return Err(Error::InvalidState(None));
1064        }
1065        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
1066        self.verify_not_deleted()?;
1067        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
1068        self.check_transaction_active()?;
1069        // Step 6. Let index be the index named name in store if one exists,
1070        // or throw a "NotFoundError" DOMException otherwise.
1071        if !self.index_set.borrow().contains_key(&name) {
1072            return Err(Error::NotFound(None));
1073        }
1074        // Step 7. Remove index from this object store handle's index set.
1075        self.index_set.borrow_mut().retain(|n, _| n != &*name);
1076        // Step 8. Destroy index.
1077        let operation = AsyncSchemaOperation::DeleteIndex {
1078            callback: self.transaction.create_abort_callback(),
1079            index_name: name.to_string(),
1080        };
1081        if self
1082            .get_idb_thread()
1083            .send(IndexedDBThreadMsg::AsyncSchemaOperation {
1084                origin: self.global().origin().immutable().clone(),
1085                database_name: self.db_name.to_string(),
1086                store_name: self.name.borrow().clone().into(),
1087                operation,
1088                transaction_serial_number: self.transaction.get_serial_number(),
1089            })
1090            .is_err()
1091        {
1092            return Err(Error::Operation(None));
1093        }
1094        Ok(())
1095    }
1096
1097    /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
1098    fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
1099        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
1100        self.verify_not_deleted()?;
1101
1102        // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
1103        if self.transaction.is_finished() {
1104            return Err(Error::InvalidState(None));
1105        }
1106
1107        // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
1108        let index_set = self.index_set.borrow();
1109        let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
1110
1111        // Step 6. Return an index handle associated with index and this.
1112        Ok(index.as_rooted())
1113    }
1114}