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