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::collections::HashMap;
5
6use base::generic_channel::{GenericSend, GenericSender};
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 profile_traits::generic_channel::channel;
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, convert_value_to_key, convert_value_to_key_range, extract_key,
46};
47use crate::script_runtime::CanGc;
48
49#[derive(Clone, JSTraceable, MallocSizeOf)]
50pub enum KeyPath {
51    String(DOMString),
52    StringSequence(Vec<DOMString>),
53}
54
55impl From<StringOrStringSequence> for KeyPath {
56    fn from(value: StringOrStringSequence) -> Self {
57        match value {
58            StringOrStringSequence::String(s) => KeyPath::String(s),
59            StringOrStringSequence::StringSequence(ss) => KeyPath::StringSequence(ss),
60        }
61    }
62}
63
64impl From<indexeddb::KeyPath> for KeyPath {
65    fn from(value: indexeddb::KeyPath) -> Self {
66        match value {
67            indexeddb::KeyPath::String(s) => KeyPath::String(DOMString::from_string(s)),
68            indexeddb::KeyPath::Sequence(ss) => {
69                KeyPath::StringSequence(ss.into_iter().map(DOMString::from_string).collect())
70            },
71        }
72    }
73}
74
75impl From<KeyPath> for indexeddb::KeyPath {
76    fn from(item: KeyPath) -> Self {
77        match item {
78            KeyPath::String(s) => Self::String(s.to_string()),
79            KeyPath::StringSequence(ss) => {
80                Self::Sequence(ss.into_iter().map(|s| s.to_string()).collect())
81            },
82        }
83    }
84}
85
86#[dom_struct]
87pub struct IDBObjectStore {
88    reflector_: Reflector,
89    name: DomRefCell<DOMString>,
90    key_path: Option<KeyPath>,
91    index_set: DomRefCell<HashMap<DOMString, Dom<IDBIndex>>>,
92    transaction: Dom<IDBTransaction>,
93
94    // We store the db name in the object store to be able to find the correct
95    // store in the idb thread when checking if we have a key generator
96    db_name: DOMString,
97}
98
99impl IDBObjectStore {
100    pub fn new_inherited(
101        db_name: DOMString,
102        name: DOMString,
103        options: Option<&IDBObjectStoreParameters>,
104        transaction: &IDBTransaction,
105    ) -> IDBObjectStore {
106        let key_path: Option<KeyPath> = match options {
107            Some(options) => options.keyPath.as_ref().map(|path| match path {
108                StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
109                StrOrStringSequence::StringSequence(inner) => {
110                    KeyPath::StringSequence(inner.clone())
111                },
112            }),
113            None => None,
114        };
115
116        IDBObjectStore {
117            reflector_: Reflector::new(),
118            name: DomRefCell::new(name),
119            key_path,
120            index_set: DomRefCell::new(HashMap::new()),
121            transaction: Dom::from_ref(transaction),
122            db_name,
123        }
124    }
125
126    pub fn new(
127        global: &GlobalScope,
128        db_name: DOMString,
129        name: DOMString,
130        options: Option<&IDBObjectStoreParameters>,
131        can_gc: CanGc,
132        transaction: &IDBTransaction,
133    ) -> DomRoot<IDBObjectStore> {
134        reflect_dom_object(
135            Box::new(IDBObjectStore::new_inherited(
136                db_name,
137                name,
138                options,
139                transaction,
140            )),
141            global,
142            can_gc,
143        )
144    }
145
146    pub fn get_name(&self) -> DOMString {
147        self.name.borrow().clone()
148    }
149
150    pub(crate) fn transaction(&self) -> DomRoot<IDBTransaction> {
151        self.transaction.as_rooted()
152    }
153
154    fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
155        self.global().storage_threads().sender()
156    }
157
158    fn has_key_generator(&self) -> bool {
159        // FIXME: blocking IPC call
160        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
161
162        let operation = SyncOperation::GetObjectStore(
163            sender,
164            self.global().origin().immutable().clone(),
165            self.db_name.to_string(),
166            self.name.borrow().to_string(),
167        );
168
169        self.get_idb_thread()
170            .send(IndexedDBThreadMsg::Sync(operation))
171            .unwrap();
172
173        // First unwrap for ipc
174        // Second unwrap will never happen unless this db gets manually deleted somehow
175        receiver.recv().unwrap().unwrap().has_key_generator
176    }
177
178    /// <https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys>
179    fn uses_inline_keys(&self) -> bool {
180        self.key_path.is_some()
181    }
182
183    fn verify_not_deleted(&self) -> ErrorResult {
184        let db = self.transaction.Db();
185        if !db.object_store_exists(&self.name.borrow()) {
186            return Err(Error::InvalidState(None));
187        }
188        Ok(())
189    }
190
191    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
192    fn check_transaction_active(&self) -> Fallible<()> {
193        // Let transaction be this object store handle's transaction.
194        let transaction = &self.transaction;
195
196        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
197        // https://w3c.github.io/IndexedDB/#transaction-inactive
198        // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
199        // No requests can be made against the transaction when it is in this state.
200        if !transaction.is_active() || !transaction.is_usable() {
201            return Err(Error::TransactionInactive(None));
202        }
203
204        Ok(())
205    }
206
207    /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
208    /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
209    fn check_readwrite_transaction_active(&self) -> Fallible<()> {
210        // Let transaction be this object store handle's transaction.
211        let transaction = &self.transaction;
212
213        // If transaction is not active, throw a "TransactionInactiveError" DOMException.
214        self.check_transaction_active()?;
215
216        if let IDBTransactionMode::Readonly = transaction.get_mode() {
217            return Err(Error::ReadOnly(None));
218        }
219        Ok(())
220    }
221
222    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
223    fn put(
224        &self,
225        cx: &mut JSContext,
226        value: HandleValue,
227        key: HandleValue,
228        overwrite: bool,
229    ) -> Fallible<DomRoot<IDBRequest>> {
230        // Step 1. Let transaction be handle’s transaction.
231        // Step 2: Let store be this object store handle's object store.
232        // This is resolved in the `execute_async` function.
233        // Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
234        self.verify_not_deleted()?;
235
236        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
237        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
238        self.check_readwrite_transaction_active()?;
239
240        // Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
241        if !key.is_undefined() && self.uses_inline_keys() {
242            return Err(Error::Data(None));
243        }
244
245        // Step 7: If store uses out-of-line keys and has no key generator
246        // and key was not given, throw a "DataError" DOMException.
247        if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
248            return Err(Error::Data(None));
249        }
250
251        // Step 8: If key was given, then: convert a value to a key with key
252        let serialized_key: Option<IndexedDBKeyType>;
253
254        if !key.is_undefined() {
255            serialized_key = Some(convert_value_to_key(cx, key, None)?.into_result()?);
256        } else {
257            // Step 11: We should use in-line keys instead
258            // Step 11.1: Let kpk be the result of running the steps to extract a
259            // key from a value using a key path with clone and store’s key path.
260            let extraction_result = self
261                .key_path
262                .as_ref()
263                .map(|p| extract_key(cx, value, p, None));
264
265            match extraction_result {
266                Some(Ok(ExtractionResult::Failure)) | None => {
267                    // Step 11.4. Otherwise:
268                    // Step 11.4.1. If store does not have a key generator, throw
269                    // a "DataError" DOMException.
270                    if !self.has_key_generator() {
271                        return Err(Error::Data(None));
272                    }
273                    // Step 11.4.2. Otherwise, if the steps to check that a key could
274                    // be injected into a value with clone and store’s key path return
275                    // false, throw a "DataError" DOMException.
276                    // TODO
277                    serialized_key = None;
278                },
279                // Step 11.1. Rethrow any exceptions.
280                Some(extraction_result) => match extraction_result? {
281                    // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
282                    ExtractionResult::Invalid => return Err(Error::Data(None)),
283                    // Step 11.3. If kpk is not failure, let key be kpk.
284                    ExtractionResult::Key(kpk) => serialized_key = Some(kpk),
285                    ExtractionResult::Failure => unreachable!(),
286                },
287            }
288        }
289
290        // Step 10. Let clone be a clone of value in targetRealm during transaction. Rethrow any exceptions.
291        let cloned_value = structuredclone::write(cx.into(), value, None)?;
292        let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
293            return Err(Error::InvalidState(None));
294        };
295        // Step 12. Let operation be an algorithm to run store a record into an object store with store, clone, key, and no-overwrite flag.
296        // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request with handle and operation.
297        IDBRequest::execute_async(
298            self,
299            |callback| {
300                AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
301                    callback,
302                    key: serialized_key,
303                    value: serialized_value,
304                    should_overwrite: overwrite,
305                })
306            },
307            None,
308            None,
309            CanGc::from_cx(cx),
310        )
311    }
312
313    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
314    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
315    fn open_cursor(
316        &self,
317        cx: &mut JSContext,
318        query: HandleValue,
319        direction: IDBCursorDirection,
320        key_only: bool,
321    ) -> Fallible<DomRoot<IDBRequest>> {
322        // Step 1. Let transaction be this object store handle's transaction.
323        // Step 2. Let store be this object store handle's object store.
324
325        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
326        self.verify_not_deleted()?;
327
328        // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
329        self.check_transaction_active()?;
330
331        // Step 5. Let range be the result of running the steps to convert a value to a key range
332        // with query. Rethrow any exceptions.
333        //
334        // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
335        // or not given, an unbounded key range is used.
336        let range = convert_value_to_key_range(cx, query, Some(false))?;
337
338        // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
339        // position, direction set to direction, got value flag unset, and undefined key and value.
340        // The source of cursor is store. The range of cursor is range.
341        //
342        // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
343        // interface as well.
344        let cursor = if key_only {
345            IDBCursor::new(
346                &self.global(),
347                &self.transaction,
348                direction,
349                false,
350                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
351                range.clone(),
352                key_only,
353                CanGc::from_cx(cx),
354            )
355        } else {
356            DomRoot::upcast(IDBCursorWithValue::new(
357                &self.global(),
358                &self.transaction,
359                direction,
360                false,
361                ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
362                range.clone(),
363                key_only,
364                CanGc::from_cx(cx),
365            ))
366        };
367
368        // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
369        // created by these steps. The steps are run with this object store handle as source and
370        // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
371        // cursor.
372        let iteration_param = IterationParam {
373            cursor: Trusted::new(&cursor),
374            key: None,
375            primary_key: None,
376            count: None,
377        };
378
379        IDBRequest::execute_async(
380            self,
381            |callback| {
382                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
383                    callback,
384                    key_range: range,
385                })
386            },
387            None,
388            Some(iteration_param),
389            CanGc::from_cx(cx),
390        )
391        .inspect(|request| cursor.set_request(request))
392    }
393
394    pub(crate) fn add_index(
395        &self,
396        name: DOMString,
397        options: &IDBIndexParameters,
398        key_path: KeyPath,
399        can_gc: CanGc,
400    ) -> DomRoot<IDBIndex> {
401        let index = IDBIndex::new(
402            &self.global(),
403            DomRoot::from_ref(self),
404            name.clone(),
405            options.multiEntry,
406            options.unique,
407            key_path,
408            can_gc,
409        );
410        self.index_set
411            .borrow_mut()
412            .insert(name, Dom::from_ref(&index));
413        index
414    }
415}
416
417impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
418    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
419    fn Put(
420        &self,
421        cx: &mut JSContext,
422        value: HandleValue,
423        key: HandleValue,
424    ) -> Fallible<DomRoot<IDBRequest>> {
425        self.put(cx, value, key, true)
426    }
427
428    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add>
429    fn Add(
430        &self,
431        cx: &mut JSContext,
432        value: HandleValue,
433        key: HandleValue,
434    ) -> Fallible<DomRoot<IDBRequest>> {
435        self.put(cx, value, key, false)
436    }
437
438    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete>
439    fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
440        // Step 1. Let transaction be this’s transaction.
441        // Step 2. Let store be this's object store.
442        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
443        self.verify_not_deleted()?;
444
445        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
446        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
447        self.check_readwrite_transaction_active()?;
448
449        // 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.
450        let serialized_query = convert_value_to_key_range(cx, query, Some(true));
451        // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
452        // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
453        serialized_query.and_then(|key_range| {
454            IDBRequest::execute_async(
455                self,
456                |callback| {
457                    AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
458                        callback,
459                        key_range,
460                    })
461                },
462                None,
463                None,
464                CanGc::from_cx(cx),
465            )
466        })
467    }
468
469    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear>
470    fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
471        // Step 1. Let transaction be this’s transaction.
472        // Step 2. Let store be this's object store.
473        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
474        self.verify_not_deleted()?;
475
476        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
477        // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
478        self.check_readwrite_transaction_active()?;
479
480        // Step 6. Let operation be an algorithm to run clear an object store with store.
481        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
482        IDBRequest::execute_async(
483            self,
484            |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
485            None,
486            None,
487            CanGc::from_cx(cx),
488        )
489    }
490
491    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get>
492    fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
493        // Step 1. Let transaction be this’s transaction.
494        // Step 2. Let store be this's object store.
495        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
496        self.verify_not_deleted()?;
497
498        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
499        self.check_transaction_active()?;
500
501        // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
502        let serialized_query = convert_value_to_key_range(cx, query, None);
503
504        // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
505        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
506        serialized_query.and_then(|q| {
507            IDBRequest::execute_async(
508                self,
509                |callback| {
510                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
511                        callback,
512                        key_range: q,
513                    })
514                },
515                None,
516                None,
517                CanGc::from_cx(cx),
518            )
519        })
520    }
521
522    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey>
523    fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
524        // Step 1. Let transaction be this’s transaction.
525        // Step 2. Let store be this's object store.
526        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
527        self.verify_not_deleted()?;
528
529        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
530        self.check_transaction_active()?;
531
532        // 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.
533        let serialized_query = convert_value_to_key_range(cx, query, None);
534
535        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
536        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
537        // store as operation, using store and range.
538        serialized_query.and_then(|q| {
539            IDBRequest::execute_async(
540                self,
541                |callback| {
542                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
543                        callback,
544                        key_range: q,
545                    })
546                },
547                None,
548                None,
549                CanGc::from_cx(cx),
550            )
551        })
552    }
553
554    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall>
555    fn GetAll(
556        &self,
557        cx: &mut JSContext,
558        query: HandleValue,
559        count: Option<u32>,
560    ) -> Fallible<DomRoot<IDBRequest>> {
561        // Step 1. Let transaction be this’s transaction.
562        // Step 2. Let store be this's object store.
563        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
564        self.verify_not_deleted()?;
565
566        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
567        self.check_transaction_active()?;
568
569        // 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.
570        let serialized_query = convert_value_to_key_range(cx, query, None);
571
572        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
573        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
574        // store as operation, using store and range.
575        serialized_query.and_then(|q| {
576            IDBRequest::execute_async(
577                self,
578                |callback| {
579                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
580                        callback,
581                        key_range: q,
582                        count,
583                    })
584                },
585                None,
586                None,
587                CanGc::from_cx(cx),
588            )
589        })
590    }
591
592    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys>
593    fn GetAllKeys(
594        &self,
595        cx: &mut JSContext,
596        query: HandleValue,
597        count: Option<u32>,
598    ) -> Fallible<DomRoot<IDBRequest>> {
599        // Step 1. Let transaction be this’s transaction.
600        // Step 2. Let store be this's object store.
601        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
602        self.verify_not_deleted()?;
603
604        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
605        self.check_transaction_active()?;
606
607        // 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.
608        let serialized_query = convert_value_to_key_range(cx, query, None);
609
610        // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
611        // The steps are run with this object store handle as source and the steps to retrieve a key from an object
612        // store as operation, using store and range.
613        serialized_query.and_then(|q| {
614            IDBRequest::execute_async(
615                self,
616                |callback| {
617                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
618                        callback,
619                        key_range: q,
620                        count,
621                    })
622                },
623                None,
624                None,
625                CanGc::from_cx(cx),
626            )
627        })
628    }
629
630    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count>
631    fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
632        // Step 1. Let transaction be this’s transaction.
633        // Step 2. Let store be this's object store.
634        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
635        self.verify_not_deleted()?;
636
637        // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
638        self.check_transaction_active()?;
639
640        // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
641        let serialized_query = convert_value_to_key_range(cx, query, None);
642
643        // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
644        // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
645        serialized_query.and_then(|q| {
646            IDBRequest::execute_async(
647                self,
648                |callback| {
649                    AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
650                        callback,
651                        key_range: q,
652                    })
653                },
654                None,
655                None,
656                CanGc::from_cx(cx),
657            )
658        })
659    }
660
661    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
662    fn OpenCursor(
663        &self,
664        cx: &mut JSContext,
665        query: HandleValue,
666        direction: IDBCursorDirection,
667    ) -> Fallible<DomRoot<IDBRequest>> {
668        self.open_cursor(cx, query, direction, false)
669    }
670
671    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
672    fn OpenKeyCursor(
673        &self,
674        cx: &mut JSContext,
675        query: HandleValue,
676        direction: IDBCursorDirection,
677    ) -> Fallible<DomRoot<IDBRequest>> {
678        self.open_cursor(cx, query, direction, true)
679    }
680
681    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name>
682    fn Name(&self) -> DOMString {
683        self.name.borrow().clone()
684    }
685
686    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname>
687    fn SetName(&self, value: DOMString) -> ErrorResult {
688        // Step 2. Let transaction be this’s transaction.
689        let transaction = &self.transaction;
690
691        // Step 3. Let store be this's object store.
692        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
693        self.verify_not_deleted()?;
694
695        // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
696        if transaction.Mode() != IDBTransactionMode::Versionchange {
697            return Err(Error::InvalidState(None));
698        }
699        // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
700        self.check_transaction_active()?;
701
702        *self.name.borrow_mut() = value;
703        Ok(())
704    }
705
706    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath>
707    fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
708        match &self.key_path {
709            Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
710            Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
711            None => ret_val.set(NullValue()),
712        }
713    }
714
715    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames>
716    fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
717        DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
718    }
719
720    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction>
721    fn Transaction(&self) -> DomRoot<IDBTransaction> {
722        self.transaction()
723    }
724
725    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement>
726    fn AutoIncrement(&self) -> bool {
727        self.has_key_generator()
728    }
729
730    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-createindex>
731    fn CreateIndex(
732        &self,
733        name: DOMString,
734        key_path: StringOrStringSequence,
735        options: &IDBIndexParameters,
736        can_gc: CanGc,
737    ) -> Fallible<DomRoot<IDBIndex>> {
738        let key_path: KeyPath = key_path.into();
739        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
740        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
741            return Err(Error::InvalidState(None));
742        }
743
744        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
745        self.verify_not_deleted()?;
746        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
747        self.check_transaction_active()?;
748
749        // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
750        if self.index_set.borrow().contains_key(&name) {
751            return Err(Error::Constraint(None));
752        }
753
754        // TODO: Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
755        // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
756        // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
757        // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
758        if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
759            return Err(Error::InvalidAccess(None));
760        }
761
762        // Step 11. Let index be a new index in store.
763        // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
764        // If multiEntry is set, set index’s multiEntry flag.
765        let create_index_operation = SyncOperation::CreateIndex(
766            self.global().origin().immutable().clone(),
767            self.db_name.to_string(),
768            self.name.borrow().to_string(),
769            name.to_string(),
770            key_path.clone().into(),
771            options.unique,
772            options.multiEntry,
773        );
774        if self
775            .get_idb_thread()
776            .send(IndexedDBThreadMsg::Sync(create_index_operation))
777            .is_err()
778        {
779            return Err(Error::Operation(None));
780        }
781
782        // Step 12. Add index to this object store handle's index set.
783        let index = self.add_index(name, options, key_path, can_gc);
784
785        // Step 13. Return a new index handle associated with index and this object store handle.
786        Ok(index)
787    }
788
789    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-deleteindex>
790    fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
791        // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
792        if self.transaction.Mode() != IDBTransactionMode::Versionchange {
793            return Err(Error::InvalidState(None));
794        }
795        // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
796        self.verify_not_deleted()?;
797        // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
798        self.check_transaction_active()?;
799        // Step 6. Let index be the index named name in store if one exists,
800        // or throw a "NotFoundError" DOMException otherwise.
801        if !self.index_set.borrow().contains_key(&name) {
802            return Err(Error::NotFound(None));
803        }
804        // Step 7. Remove index from this object store handle's index set.
805        self.index_set.borrow_mut().retain(|n, _| n != &name);
806        // Step 8. Destroy index.
807        let delete_index_operation = SyncOperation::DeleteIndex(
808            self.global().origin().immutable().clone(),
809            self.db_name.to_string(),
810            self.name.borrow().to_string(),
811            name.to_string(),
812        );
813        self.get_idb_thread()
814            .send(IndexedDBThreadMsg::Sync(delete_index_operation))
815            .unwrap();
816        Ok(())
817    }
818
819    /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
820    fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
821        // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
822        self.verify_not_deleted()?;
823
824        // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
825        if self.transaction.is_finished() {
826            return Err(Error::InvalidState(None));
827        }
828
829        // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
830        let index_set = self.index_set.borrow();
831        let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
832
833        // Step 6. Return an index handle associated with index and this.
834        Ok(index.as_rooted())
835    }
836}