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, IndexedDBKeyType,
21    IndexedDBThreadMsg, SyncOperation,
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::indexeddb::{
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};
49use crate::script_runtime::CanGc;
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<DOMString>,
101    key_path: Option<KeyPath>,
102    index_set: DomRefCell<HashMap<DOMString, 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: DOMString,
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),
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,
163        }
164    }
165
166    pub fn new(
167        global: &GlobalScope,
168        db_name: DOMString,
169        name: DOMString,
170        options: Option<&IDBObjectStoreParameters>,
171        abort_state: IDBObjectStoreAbortState,
172        can_gc: CanGc,
173        transaction: &IDBTransaction,
174    ) -> DomRoot<IDBObjectStore> {
175        reflect_dom_object(
176            Box::new(IDBObjectStore::new_inherited(
177                db_name,
178                name,
179                options,
180                abort_state,
181                transaction,
182            )),
183            global,
184            can_gc,
185        )
186    }
187
188    pub fn get_name(&self) -> DOMString {
189        self.name.borrow().clone()
190    }
191
192    /// <https://w3c.github.io/IndexedDB/#abort-an-upgrade-transaction>
193    pub(crate) fn restore_metadata_after_abort(&self, can_gc: CanGc) {
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.borrow_mut() = name;
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.borrow_mut().clear();
209        for index in abort_state.rollback_indexes {
210            self.add_index(
211                index.name.clone().into(),
212                &IDBIndexParameters {
213                    multiEntry: index.multi_entry,
214                    unique: index.unique,
215                },
216                index.key_path.clone().into(),
217                can_gc,
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    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    fn uses_inline_keys(&self) -> bool {
323        self.key_path.is_some()
324    }
325
326    fn verify_not_deleted(&self) -> ErrorResult {
327        let db = self.transaction.Db();
328        if !db.object_store_exists(&self.name.borrow()) {
329            return Err(Error::InvalidState(None));
330        }
331        Ok(())
332    }
333
334    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
335    fn check_transaction_active(&self) -> Fallible<()> {
336        // Let transaction be this object store handle's transaction.
337        let transaction = &self.transaction;
338
339        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
340        // https://w3c.github.io/IndexedDB/#transaction-inactive
341        // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
342        // No requests can be made against the transaction when it is in this state.
343        if !transaction.is_active() || !transaction.is_usable() {
344            return Err(Error::TransactionInactive(None));
345        }
346
347        Ok(())
348    }
349
350    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
351    /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
352    fn check_readwrite_transaction_active(&self) -> Fallible<()> {
353        // Let transaction be this object store handle's transaction.
354        let transaction = &self.transaction;
355
356        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
357        self.check_transaction_active()?;
358
359        if let IDBTransactionMode::Readonly = transaction.get_mode() {
360            return Err(Error::ReadOnly(None));
361        }
362        Ok(())
363    }
364
365    /// <https://www.w3.org/TR/IndexedDB-3/#add-or-put>
366    fn put(
367        &self,
368        cx: &mut JSContext,
369        value: HandleValue,
370        key: HandleValue,
371        no_overwrite: bool,
372    ) -> Fallible<DomRoot<IDBRequest>> {
373        // Step 1. Let transaction be handle’s transaction.
374        // Step 2. Let store be handle’s object store.
375        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
376        self.verify_not_deleted()?;
377
378        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
379        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
380        self.check_readwrite_transaction_active()?;
381
382        // Step 6. If store uses in-line keys and key was given, throw a "DataError"
383        // DOMException.
384        if !key.is_undefined() && self.uses_inline_keys() {
385            return Err(Error::Data(None));
386        }
387
388        // Step 7. If store uses out-of-line keys and has no key generator and key
389        // was not given, throw a "DataError" DOMException.
390        if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
391            return Err(Error::Data(None));
392        }
393
394        // Step 8. If key was given, then:
395        let mut serialized_key = None;
396        let mut key_generator_current_number_for_put = None;
397
398        if !key.is_undefined() {
399            // Step 8.1. Let r be the result of converting a value to a key with key.
400            // Rethrow any exceptions.
401            let key = convert_value_to_key(cx, key, None)?.into_result()?;
402            // Step 8.2. If r is "invalid value" or "invalid type", throw a
403            // "DataError" DOMException.
404            // Handled by `into_result()` above.
405            // Step 8.3. Let key be r.
406            key_generator_current_number_for_put = self.possibly_update_the_key_generator(&key);
407            serialized_key = Some(key);
408        }
409
410        // Step 9. Let targetRealm be a user-agent defined Realm.
411        // Step 10. Let clone be a clone of value in targetRealm during transaction.
412        // Rethrow any exceptions.
413        rooted!(&in(cx) let mut cloned_js_value = NullValue());
414        self.clone_value_in_target_realm(cx, value, cloned_js_value.handle_mut())?;
415
416        // Step 11. If store uses in-line keys, then:
417        let cloned_value = match self.key_path.as_ref() {
418            Some(key_path) => {
419                // Step 11.1. Let kpk be the result of extracting a key from a value using a key
420                // path with clone and store’s key path. Rethrow any exceptions.
421                match extract_key(cx, cloned_js_value.handle(), key_path, None)? {
422                    // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
423                    ExtractionResult::Invalid => return Err(Error::Data(None)),
424                    // Step 11.3. If kpk is not failure, let key be kpk.
425                    ExtractionResult::Key(kpk) => {
426                        key_generator_current_number_for_put =
427                            self.possibly_update_the_key_generator(&kpk);
428                        serialized_key = Some(kpk);
429                    },
430                    // Step 11.4. Otherwise (kpk is failure):
431                    ExtractionResult::Failure => {
432                        // Step 11.4.1. If store does not have a key generator, throw a
433                        // "DataError" DOMException.
434                        if !self.has_key_generator() {
435                            return Err(Error::Data(None));
436                        }
437                        let KeyPath::String(key_path) = key_path else {
438                            return Err(Error::Data(None));
439                        };
440                        // Step 11.4.2. If check that a key could be injected into a value with
441                        // clone and store’s key path return false, throw a "DataError"
442                        // DOMException.
443                        if !can_inject_key_into_value(cx, cloned_js_value.handle(), key_path)? {
444                            return Err(Error::Data(None));
445                        }
446
447                        // Prepares the generated key and injected clone here so Step 12 can
448                        // pass the final key/value pair to the storage backend.
449                        let (generated_key, next_current_number) = self.generate_key_for_put()?;
450                        if !inject_key_into_value(
451                            cx,
452                            cloned_js_value.handle(),
453                            &generated_key,
454                            key_path,
455                        )? {
456                            return Err(Error::Data(None));
457                        }
458                        serialized_key = Some(generated_key);
459                        key_generator_current_number_for_put = Some(next_current_number);
460                    },
461                }
462
463                structuredclone::write(cx, cloned_js_value.handle(), None)?
464            },
465            None => structuredclone::write(cx, cloned_js_value.handle(), None)?,
466        };
467        let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
468            return Err(Error::InvalidState(None));
469        };
470        // Step 12. Let operation be an algorithm to run store a record into an object store with
471        // store, clone, key, and no-overwrite flag.
472        let request = IDBRequest::execute_async(
473            self,
474            |callback| {
475                AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
476                    callback,
477                    key: serialized_key,
478                    value: serialized_value,
479                    should_overwrite: !no_overwrite,
480                    key_generator_current_number: key_generator_current_number_for_put,
481                })
482            },
483            None,
484            None,
485            CanGc::from_cx(cx),
486        )?;
487        // Keep the in-memory key generator in sync with the queued put request.
488        if let Some(next_key_generator_current_number) = key_generator_current_number_for_put {
489            self.key_generator_current_number
490                .set(Some(next_key_generator_current_number));
491        }
492        // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request
493        // with handle and operation.
494        Ok(request)
495    }
496
497    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
498    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
499    fn open_cursor(
500        &self,
501        cx: &mut JSContext,
502        query: HandleValue,
503        direction: IDBCursorDirection,
504        key_only: bool,
505    ) -> Fallible<DomRoot<IDBRequest>> {
506        // Step 1. Let transaction be this object store handle's transaction.
507        // Step 2. Let store be this object store handle's object store.
508
509        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
510        self.verify_not_deleted()?;
511
512        // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
513        self.check_transaction_active()?;
514
515        // Step 5. Let range be the result of running the steps to convert a value to a key range
516        // with query. Rethrow any exceptions.
517        //
518        // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
519        // or not given, an unbounded key range is used.
520        let range = convert_value_to_key_range(cx, query, Some(false))?;
521
522        // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
523        // position, direction set to direction, got value flag unset, and undefined key and value.
524        // The source of cursor is store. The range of cursor is range.
525        //
526        // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
527        // interface as well.
528        let cursor = if key_only {
529            IDBCursor::new(
530                &self.global(),
531                &self.transaction,
532                direction,
533                false,
534                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
535                range.clone(),
536                key_only,
537                CanGc::from_cx(cx),
538            )
539        } else {
540            DomRoot::upcast(IDBCursorWithValue::new(
541                &self.global(),
542                &self.transaction,
543                direction,
544                false,
545                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
546                range.clone(),
547                key_only,
548                CanGc::from_cx(cx),
549            ))
550        };
551
552        // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
553        // created by these steps. The steps are run with this object store handle as source and
554        // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
555        // cursor.
556        let iteration_param = IterationParam {
557            cursor: Trusted::new(&cursor),
558            key: None,
559            primary_key: None,
560            count: None,
561        };
562
563        IDBRequest::execute_async(
564            self,
565            |callback| {
566                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
567                    callback,
568                    key_range: range,
569                })
570            },
571            None,
572            Some(iteration_param),
573            CanGc::from_cx(cx),
574        )
575        .inspect(|request| cursor.set_request(request))
576    }
577
578    pub(crate) fn add_index(
579        &self,
580        name: DOMString,
581        options: &IDBIndexParameters,
582        key_path: KeyPath,
583        can_gc: CanGc,
584    ) -> DomRoot<IDBIndex> {
585        let index = IDBIndex::new(
586            &self.global(),
587            DomRoot::from_ref(self),
588            name.clone(),
589            options.multiEntry,
590            options.unique,
591            key_path,
592            can_gc,
593        );
594        self.index_set
595            .borrow_mut()
596            .insert(name, Dom::from_ref(&index));
597        index
598    }
599}
600
601impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
602    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
603    fn Put(
604        &self,
605        cx: &mut JSContext,
606        value: HandleValue,
607        key: HandleValue,
608    ) -> Fallible<DomRoot<IDBRequest>> {
609        // Step 1. Return the result of running add or put with this, value, key and the
610        // no-overwrite flag false.
611        self.put(cx, value, key, false)
612    }
613
614    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-add>
615    fn Add(
616        &self,
617        cx: &mut JSContext,
618        value: HandleValue,
619        key: HandleValue,
620    ) -> Fallible<DomRoot<IDBRequest>> {
621        // Step 1. Return the result of running add or put with this, value, key and the
622        // no-overwrite flag true.
623        self.put(cx, value, key, true)
624    }
625
626    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-delete>
627    fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
628        // Step 1. Let transaction be this’s transaction.
629        // Step 2. Let store be this's object store.
630        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
631        self.verify_not_deleted()?;
632
633        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
634        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
635        self.check_readwrite_transaction_active()?;
636
637        // 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.
638        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
639        // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
640        // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
641        serialized_query.and_then(|key_range| {
642            IDBRequest::execute_async(
643                self,
644                |callback| {
645                    AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
646                        callback,
647                        key_range,
648                    })
649                },
650                None,
651                None,
652                CanGc::from_cx(cx),
653            )
654        })
655    }
656
657    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-clear>
658    fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
659        // Step 1. Let transaction be this’s transaction.
660        // Step 2. Let store be this's object store.
661        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
662        self.verify_not_deleted()?;
663
664        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
665        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
666        self.check_readwrite_transaction_active()?;
667
668        // Step 6. Let operation be an algorithm to run clear an object store with store.
669        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
670        IDBRequest::execute_async(
671            self,
672            |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
673            None,
674            None,
675            CanGc::from_cx(cx),
676        )
677    }
678
679    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-get>
680    fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
681        // Step 1. Let transaction be this’s transaction.
682        // Step 2. Let store be this's object store.
683        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
684        self.verify_not_deleted()?;
685
686        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
687        self.check_transaction_active()?;
688
689        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
690        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
691
692        // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
693        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
694        serialized_query.and_then(|q| {
695            IDBRequest::execute_async(
696                self,
697                |callback| {
698                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
699                        callback,
700                        key_range: q,
701                    })
702                },
703                None,
704                None,
705                CanGc::from_cx(cx),
706            )
707        })
708    }
709
710    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getkey>
711    fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
712        // Step 1. Let transaction be this’s transaction.
713        // Step 2. Let store be this's object store.
714        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
715        self.verify_not_deleted()?;
716
717        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
718        self.check_transaction_active()?;
719
720        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
721        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
722
723        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
724        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
725        // store as operation, using store and range.
726        serialized_query.and_then(|q| {
727            IDBRequest::execute_async(
728                self,
729                |callback| {
730                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
731                        callback,
732                        key_range: q,
733                    })
734                },
735                None,
736                None,
737                CanGc::from_cx(cx),
738            )
739        })
740    }
741
742    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getall>
743    fn GetAll(
744        &self,
745        cx: &mut JSContext,
746        query: HandleValue,
747        count: Option<u32>,
748    ) -> Fallible<DomRoot<IDBRequest>> {
749        // Step 1. Let transaction be this’s transaction.
750        // Step 2. Let store be this's object store.
751        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
752        self.verify_not_deleted()?;
753
754        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
755        self.check_transaction_active()?;
756
757        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
758        let serialized_query = convert_value_to_key_range(cx, query, None);
759
760        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
761        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
762        // store as operation, using store and range.
763        serialized_query.and_then(|q| {
764            IDBRequest::execute_async(
765                self,
766                |callback| {
767                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
768                        callback,
769                        key_range: q,
770                        count,
771                    })
772                },
773                None,
774                None,
775                CanGc::from_cx(cx),
776            )
777        })
778    }
779
780    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getallkeys>
781    fn GetAllKeys(
782        &self,
783        cx: &mut JSContext,
784        query: HandleValue,
785        count: Option<u32>,
786    ) -> Fallible<DomRoot<IDBRequest>> {
787        // Step 1. Let transaction be this’s transaction.
788        // Step 2. Let store be this's object store.
789        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
790        self.verify_not_deleted()?;
791
792        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
793        self.check_transaction_active()?;
794
795        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
796        let serialized_query = convert_value_to_key_range(cx, query, None);
797
798        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
799        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
800        // store as operation, using store and range.
801        serialized_query.and_then(|q| {
802            IDBRequest::execute_async(
803                self,
804                |callback| {
805                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
806                        callback,
807                        key_range: q,
808                        count,
809                    })
810                },
811                None,
812                None,
813                CanGc::from_cx(cx),
814            )
815        })
816    }
817
818    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-count>
819    fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
820        // Step 1. Let transaction be this’s transaction.
821        // Step 2. Let store be this's object store.
822        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
823        self.verify_not_deleted()?;
824
825        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
826        self.check_transaction_active()?;
827
828        // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
829        let serialized_query = convert_value_to_key_range(cx, query, None);
830
831        // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
832        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
833        serialized_query.and_then(|q| {
834            IDBRequest::execute_async(
835                self,
836                |callback| {
837                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
838                        callback,
839                        key_range: q,
840                    })
841                },
842                None,
843                None,
844                CanGc::from_cx(cx),
845            )
846        })
847    }
848
849    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
850    fn OpenCursor(
851        &self,
852        cx: &mut JSContext,
853        query: HandleValue,
854        direction: IDBCursorDirection,
855    ) -> Fallible<DomRoot<IDBRequest>> {
856        self.open_cursor(cx, query, direction, false)
857    }
858
859    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
860    fn OpenKeyCursor(
861        &self,
862        cx: &mut JSContext,
863        query: HandleValue,
864        direction: IDBCursorDirection,
865    ) -> Fallible<DomRoot<IDBRequest>> {
866        self.open_cursor(cx, query, direction, true)
867    }
868
869    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
870    fn Name(&self) -> DOMString {
871        self.name.borrow().clone()
872    }
873
874    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
875    fn SetName(&self, value: DOMString) -> ErrorResult {
876        // Step 1. Let name be the given value.
877        let name = value;
878
879        // Step 2. Let transaction be this’s transaction.
880        let transaction = &self.transaction;
881
882        // Step 3. Let store be this’s object store.
883        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
884        self.verify_not_deleted()?;
885
886        // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
887        if transaction.Mode() != IDBTransactionMode::Versionchange {
888            return Err(Error::InvalidState(None));
889        }
890        // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
891        self.check_transaction_active()?;
892
893        // Step 7. If store’s name is equal to name, terminate these steps.
894        if *self.name.borrow() == name {
895            return Ok(());
896        }
897
898        // Step 8. If an object store named name already exists in store’s database,
899        // throw a "ConstraintError" DOMException.
900        if transaction.Db().object_store_exists(&name) {
901            return Err(Error::Constraint(None));
902        }
903
904        let old_name = self.name.borrow().clone();
905        if let Some(abort_state) = self.abort_state_on_abort.borrow_mut().as_mut() &&
906            abort_state.rollback_name.is_none()
907        {
908            abort_state.rollback_name = Some(old_name.clone());
909        }
910
911        // Step 9. Set store’s name to name.
912        transaction
913            .Db()
914            .rename_object_store_name(&old_name, name.clone());
915        // Step 10. Set this’s name to name.
916        *self.name.borrow_mut() = name.clone();
917        transaction.rename_object_store_handle_cache(&old_name, &name, self);
918        Ok(())
919    }
920
921    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-keypath>
922    fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
923        match &self.key_path {
924            Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
925            Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
926            None => ret_val.set(NullValue()),
927        }
928    }
929
930    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-indexnames>
931    fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
932        DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
933    }
934
935    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-transaction>
936    fn Transaction(&self) -> DomRoot<IDBTransaction> {
937        self.transaction()
938    }
939
940    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-autoincrement>
941    fn AutoIncrement(&self) -> bool {
942        self.has_key_generator()
943    }
944
945    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-createindex>
946    fn CreateIndex(
947        &self,
948        cx: &mut JSContext,
949        name: DOMString,
950        key_path: StringOrStringSequence,
951        options: &IDBIndexParameters,
952    ) -> Fallible<DomRoot<IDBIndex>> {
953        let key_path: KeyPath = key_path.into();
954        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
955        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
956            return Err(Error::InvalidState(None));
957        }
958
959        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
960        self.verify_not_deleted()?;
961        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
962        self.check_transaction_active()?;
963
964        // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
965        if self.index_set.borrow().contains_key(&name) {
966            return Err(Error::Constraint(None));
967        }
968
969        let js_key_path = match key_path.clone() {
970            KeyPath::String(s) => StringOrStringSequence::String(s),
971            KeyPath::StringSequence(s) => StringOrStringSequence::StringSequence(s),
972        };
973
974        // Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
975        if !is_valid_key_path(cx, &js_key_path)? {
976            return Err(Error::Syntax(None));
977        }
978        // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
979        // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
980        // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
981        if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
982            return Err(Error::InvalidAccess(None));
983        }
984
985        // Step 11. Let index be a new index in store.
986        // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
987        // If multiEntry is set, set index’s multiEntry flag.
988        let create_index_operation = SyncOperation::CreateIndex(
989            self.global().origin().immutable().clone(),
990            self.db_name.to_string(),
991            self.name.borrow().to_string(),
992            name.to_string(),
993            key_path.clone().into(),
994            options.unique,
995            options.multiEntry,
996        );
997        if self
998            .get_idb_thread()
999            .send(IndexedDBThreadMsg::Sync(create_index_operation))
1000            .is_err()
1001        {
1002            return Err(Error::Operation(None));
1003        }
1004
1005        // Step 12. Add index to this object store handle's index set.
1006        let index = self.add_index(name, options, key_path, CanGc::from_cx(cx));
1007
1008        // Step 13. Return a new index handle associated with index and this object store handle.
1009        Ok(index)
1010    }
1011
1012    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-deleteindex>
1013    fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
1014        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
1015        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
1016            return Err(Error::InvalidState(None));
1017        }
1018        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
1019        self.verify_not_deleted()?;
1020        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
1021        self.check_transaction_active()?;
1022        // Step 6. Let index be the index named name in store if one exists,
1023        // or throw a "NotFoundError" DOMException otherwise.
1024        if !self.index_set.borrow().contains_key(&name) {
1025            return Err(Error::NotFound(None));
1026        }
1027        // Step 7. Remove index from this object store handle's index set.
1028        self.index_set.borrow_mut().retain(|n, _| n != &name);
1029        // Step 8. Destroy index.
1030        let delete_index_operation = SyncOperation::DeleteIndex(
1031            self.global().origin().immutable().clone(),
1032            self.db_name.to_string(),
1033            self.name.borrow().to_string(),
1034            String::from(name),
1035        );
1036        self.get_idb_thread()
1037            .send(IndexedDBThreadMsg::Sync(delete_index_operation))
1038            .unwrap();
1039        Ok(())
1040    }
1041
1042    /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
1043    fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
1044        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
1045        self.verify_not_deleted()?;
1046
1047        // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
1048        if self.transaction.is_finished() {
1049            return Err(Error::InvalidState(None));
1050        }
1051
1052        // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
1053        let index_set = self.index_set.borrow();
1054        let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
1055
1056        // Step 6. Return an index handle associated with index and this.
1057        Ok(index.as_rooted())
1058    }
1059}