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