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