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