Skip to main content

script/dom/indexeddb/
idbcursor.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/. */
4
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::jsapi::Heap;
12use js::jsval::{JSVal, NullValue, UndefinedValue};
13use js::rust::{HandleValue, MutableHandleValue};
14use script_bindings::cell::DomRefCell;
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16use storage_traits::indexeddb::{
17    AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyRange,
18    IndexedDBKeyType, IndexedDBRecord,
19};
20
21use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
22    IDBCursorDirection, IDBCursorMethods,
23};
24use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
25use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
26use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
29use crate::dom::bindings::structuredclone;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::indexeddb::idbindex::IDBIndex;
32use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
33use crate::dom::indexeddb::idbrequest::IDBRequest;
34use crate::dom::indexeddb::idbtransaction::IDBTransaction;
35use crate::dom::indexeddb::key::{
36    ExtractionResult, convert_value_to_key, extract_key, key_type_to_jsval,
37};
38
39#[derive(JSTraceable, MallocSizeOf)]
40#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41pub(crate) enum ObjectStoreOrIndex {
42    ObjectStore(Dom<IDBObjectStore>),
43    Index(Dom<IDBIndex>),
44}
45
46#[dom_struct]
47pub(crate) struct IDBCursor {
48    reflector_: Reflector,
49
50    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-transaction>
51    transaction: Dom<IDBTransaction>,
52    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-range>
53    #[no_trace]
54    range: IndexedDBKeyRange,
55    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-source>
56    source: ObjectStoreOrIndex,
57    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-direction>
58    direction: IDBCursorDirection,
59    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-position>
60    #[no_trace]
61    position: DomRefCell<Option<IndexedDBKeyType>>,
62    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-key>
63    #[no_trace]
64    key: DomRefCell<Option<IndexedDBKeyType>>,
65    #[ignore_malloc_size_of = "mozjs"]
66    cached_key: DomRefCell<Option<Heap<JSVal>>>,
67    #[ignore_malloc_size_of = "mozjs"]
68    cached_primary_key: DomRefCell<Option<Heap<JSVal>>>,
69    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-value>
70    #[ignore_malloc_size_of = "mozjs"]
71    value: Heap<JSVal>,
72    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-got-value-flag>
73    got_value: Cell<bool>,
74    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-object-store-position>
75    #[no_trace]
76    object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
77    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-key-only-flag>
78    key_only: bool,
79
80    /// <https://w3c.github.io/IndexedDB/#cursor-request>
81    request: MutNullableDom<IDBRequest>,
82}
83
84impl IDBCursor {
85    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
86    pub(crate) fn new_inherited(
87        transaction: &IDBTransaction,
88        direction: IDBCursorDirection,
89        got_value: bool,
90        source: ObjectStoreOrIndex,
91        range: IndexedDBKeyRange,
92        key_only: bool,
93    ) -> IDBCursor {
94        IDBCursor {
95            reflector_: Reflector::new(),
96            transaction: Dom::from_ref(transaction),
97            range,
98            source,
99            direction,
100            position: DomRefCell::new(None),
101            key: DomRefCell::new(None),
102            cached_key: DomRefCell::new(None),
103            cached_primary_key: DomRefCell::new(None),
104            value: Heap::default(),
105            got_value: Cell::new(got_value),
106            object_store_position: DomRefCell::new(None),
107            key_only,
108            request: Default::default(),
109        }
110    }
111
112    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
113    #[allow(clippy::too_many_arguments)]
114    pub(crate) fn new(
115        cx: &mut JSContext,
116        global: &GlobalScope,
117        transaction: &IDBTransaction,
118        direction: IDBCursorDirection,
119        got_value: bool,
120        source: ObjectStoreOrIndex,
121        range: IndexedDBKeyRange,
122        key_only: bool,
123    ) -> DomRoot<IDBCursor> {
124        reflect_dom_object(
125            cx,
126            Box::new(IDBCursor::new_inherited(
127                transaction,
128                direction,
129                got_value,
130                source,
131                range,
132                key_only,
133            )),
134            global,
135        )
136    }
137
138    fn set_position(&self, position: Option<IndexedDBKeyType>) {
139        let changed = *self.position.borrow() != position;
140        *self.position.borrow_mut() = position;
141        if changed {
142            *self.cached_primary_key.borrow_mut() = None;
143        }
144    }
145
146    fn set_key(&self, key: Option<IndexedDBKeyType>) {
147        let key_changed = {
148            let current_key = self.key.borrow();
149            current_key.as_ref() != key.as_ref()
150        };
151        *self.key.borrow_mut() = key;
152        if key_changed {
153            *self.cached_key.borrow_mut() = None;
154        }
155    }
156
157    fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
158        let changed = *self.object_store_position.borrow() != object_store_position;
159        *self.object_store_position.borrow_mut() = object_store_position;
160        if changed {
161            *self.cached_primary_key.borrow_mut() = None;
162        }
163    }
164
165    pub(crate) fn set_request(&self, request: &IDBRequest) {
166        self.request.set(Some(request));
167    }
168
169    pub(crate) fn value(&self, mut out: MutableHandleValue) {
170        out.set(self.value.get());
171    }
172
173    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-effective-key>
174    pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
175        match &self.source {
176            ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
177            ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
178        }
179    }
180
181    /// <https://www.w3.org/TR/IndexedDB-3/#cursor-effective-object-store>
182    pub(crate) fn effective_object_store(&self) -> DomRoot<IDBObjectStore> {
183        match &self.source {
184            ObjectStoreOrIndex::ObjectStore(store) => store.as_rooted(),
185            ObjectStoreOrIndex::Index(index) => index.object_store(),
186        }
187    }
188
189    pub(crate) fn verify_not_deleted(&self) -> ErrorResult {
190        match &self.source {
191            ObjectStoreOrIndex::ObjectStore(store) => store.verify_not_deleted(),
192            ObjectStoreOrIndex::Index(index) => index.verify_not_deleted(),
193        }
194    }
195
196    pub(crate) fn check_transaction_active(&self) -> ErrorResult {
197        if !self.transaction.is_active() || !self.transaction.is_usable() {
198            Err(Error::TransactionInactive(Some(
199                "Transaction is not active".to_owned(),
200            )))
201        } else {
202            Ok(())
203        }
204    }
205
206    pub(crate) fn check_transaction_readwrite(&self) -> ErrorResult {
207        if let IDBTransactionMode::Readonly = self.transaction.get_mode() {
208            return Err(Error::ReadOnly(Some(
209                "Transaction is a read-only transaction".to_owned(),
210            )));
211        }
212        Ok(())
213    }
214}
215
216impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
217    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-source>
218    fn Source(&self) -> IDBObjectStoreOrIDBIndex {
219        match &self.source {
220            ObjectStoreOrIndex::ObjectStore(source) => {
221                IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
222            },
223            ObjectStoreOrIndex::Index(source) => {
224                IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
225            },
226        }
227    }
228
229    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-direction>
230    fn Direction(&self) -> IDBCursorDirection {
231        self.direction
232    }
233
234    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-key>
235    fn Key(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
236        // The key getter steps are to return the result of converting a key to a value with the cursor’s current key.
237        //
238        // NOTE: If key returns an object (e.g. a Date or Array), it returns the
239        // same object instance every time it is inspected, until the cursor’s key is changed.
240        // This means that if the object is modified, those modifications will be seen by
241        // anyone inspecting the value of the cursor. However modifying such an object does not
242        // modify the contents of the database.
243        if let Some(cached) = &*self.cached_key.borrow() {
244            value.set(cached.get());
245            return;
246        }
247
248        match self.key.borrow().as_ref() {
249            Some(key) => key_type_to_jsval(cx, key, value.reborrow()),
250            None => value.set(UndefinedValue()),
251        }
252
253        *self.cached_key.borrow_mut() = Some(Heap::default());
254        self.cached_key.borrow().as_ref().unwrap().set(value.get());
255    }
256
257    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-primarykey>
258    fn PrimaryKey(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
259        // NOTE: If primaryKey returns an object (e.g. a Date or Array),
260        // it returns the same object instance every time it is inspected,
261        // until the cursor’s effective key is changed. This means that if the object is modified,
262        // those modifications will be seen by anyone inspecting the value of the cursor.
263        // However modifying such an object does not modify the contents of the database.
264        if let Some(cached) = &*self.cached_primary_key.borrow() {
265            value.set(cached.get());
266            return;
267        }
268
269        match self.effective_key() {
270            Some(effective_key) => key_type_to_jsval(cx, &effective_key, value.reborrow()),
271            None => value.set(UndefinedValue()),
272        }
273
274        *self.cached_primary_key.borrow_mut() = Some(Heap::default());
275        self.cached_primary_key
276            .borrow()
277            .as_ref()
278            .unwrap()
279            .set(value.get());
280    }
281
282    /// <https://w3c.github.io/IndexedDB/#dom-idbcursor-request>
283    fn Request(&self) -> DomRoot<IDBRequest> {
284        self.request
285            .get()
286            .expect("IDBCursor.request should be set when cursor is opened")
287    }
288
289    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-advance>
290    fn Advance(&self, cx: &mut JSContext, count: u32) -> ErrorResult {
291        // Step 1: If count is 0 (zero), throw a TypeError.
292        if count == 0 {
293            return Err(Error::Type(c"Count cannot be zero".to_owned()));
294        }
295
296        // Step 2: Let transaction be this cursor's transaction.
297        // Step 3: If transaction is not active, throw a "TransactionInactiveError" DOMException.
298        self.check_transaction_active()?;
299
300        // Step 4: If the cursor's source or effective object store has been deleted, throw an
301        // "InvalidStateError" DOMException.
302        self.verify_not_deleted()?;
303
304        // Step 5: If this cursor's got value flag is unset, indicating that the cursor is being
305        // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
306        if !self.got_value.get() {
307            return Err(Error::InvalidState(Some(
308                "The cursor is being iterated or has iterated past its end".to_owned(),
309            )));
310        }
311
312        // Step 7: Unset the got value flag on the cursor.
313        self.got_value.set(false);
314
315        // Step 8: Let request be the request created when this cursor was created.
316        let request = self.Request();
317
318        // Step 9: Unset the request's done flag on request.
319        request.unset_done_flag();
320
321        // Step 10: Run the steps to asynchronously execute a request with the cursor's source as
322        // source, the steps to iterate a cursor as operation and request, using the current Realm
323        // as targetRealm, this cursor and count.
324        let iteration_param = IterationParam {
325            cursor: Trusted::new(self),
326            key: None,
327            primary_key: None,
328            count: Some(count),
329        };
330
331        let range = self.range.clone();
332        // NOTE: According to the spec, execute_async should take the cursor's source
333        // (which may be an index). Using effective_object_store here is a temporary
334        // workaround until execute_async supports ObjectStoreOrIndex as source.
335        IDBRequest::execute_async(
336            cx,
337            &self.effective_object_store(),
338            |callback| {
339                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
340                    callback,
341                    key_range: range,
342                })
343            },
344            Some(request),
345            Some(iteration_param),
346        )?;
347
348        Ok(())
349    }
350
351    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-continue>
352    fn Continue(&self, cx: &mut JSContext, key: HandleValue) -> ErrorResult {
353        // Step 1: Let transaction be this cursor's transaction.
354        // Step 2: If transaction is not active, throw a "TransactionInactiveError" DOMException.
355        self.check_transaction_active()?;
356
357        // Step 3: If the cursor's source or effective object store has been deleted, throw an
358        // "InvalidStateError" DOMException.
359        self.verify_not_deleted()?;
360
361        // Step 4: If this cursor's got value flag is unset, indicating that the cursor is being
362        // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
363        if !self.got_value.get() {
364            return Err(Error::InvalidState(Some(
365                "The cursor is being iterated or has iterated past its end".to_owned(),
366            )));
367        }
368
369        // Step 5: If key is given, then:
370        let key = if !key.is_undefined() {
371            // Step 5.1: Let r be the result of running the steps to convert a value to a key with
372            // key. Rethrow any exceptions.
373            // Step 5.2: If r is invalid, throw a "DataError" DOMException.
374            // Step 5.3: Set key to r.
375            let key = convert_value_to_key(cx, key, None)?.into_result()?;
376
377            // Step 5.4: If key is less than or equal to this cursor's position and this cursor's
378            // direction is "next" or "nextunique", throw a "DataError" DOMException.
379            if let Some(position) = self.position.borrow().as_ref() {
380                match self.direction {
381                    IDBCursorDirection::Next | IDBCursorDirection::Nextunique => {
382                        if &key <= position {
383                            return Err(Error::Data(Some(
384                                "Key is less than or equal to this cursor's position".to_owned(),
385                            )));
386                        }
387                    },
388                    // Step 5.5: If key is greater than or equal to this cursor's position
389                    // and this cursor's direction is "prev" or "prevunique", throw a
390                    // "DataError" DOMException.
391                    IDBCursorDirection::Prev | IDBCursorDirection::Prevunique => {
392                        if &key >= position {
393                            return Err(Error::Data(Some(
394                                "Key is greater than or equal to this cursor's position".to_owned(),
395                            )));
396                        }
397                    },
398                }
399            }
400
401            Some(key)
402        } else {
403            None
404        };
405
406        // Step 6: Unset the got value flag on the cursor.
407        self.got_value.set(false);
408
409        // Step 7: Let request be the request created when this cursor was created.
410        let request = self.Request();
411
412        // Step 8: Unset the request's done flag on request.
413        request.unset_done_flag();
414
415        // Step 9: Run the steps to asynchronously execute a request with the cursor's source as
416        // source, the steps to iterate a cursor as operation and request, using the current Realm
417        // as targetRealm, this cursor and key (if given).
418        let iteration_param = IterationParam {
419            cursor: Trusted::new(self),
420            key,
421            primary_key: None,
422            count: None,
423        };
424
425        let range = self.range.clone();
426        // NOTE: According to the spec, execute_async should take the cursor's source
427        // (which may be an index). Using effective_object_store here is a temporary
428        // workaround until execute_async supports ObjectStoreOrIndex as source.
429        IDBRequest::execute_async(
430            cx,
431            &self.effective_object_store(),
432            |callback| {
433                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
434                    callback,
435                    key_range: range,
436                })
437            },
438            Some(request),
439            Some(iteration_param),
440        )?;
441
442        Ok(())
443    }
444
445    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-continueprimarykey>
446    fn ContinuePrimaryKey(
447        &self,
448        cx: &mut JSContext,
449        key: HandleValue,
450        primary_key: HandleValue,
451    ) -> ErrorResult {
452        // Step 1: Let transaction be this cursor's transaction.
453        // Step 2: If transaction is not active, throw a "TransactionInactiveError" DOMException.
454        self.check_transaction_active()?;
455
456        // Step 3: If the cursor's source or effective object store has been deleted, throw an
457        // "InvalidStateError" DOMException.
458        self.verify_not_deleted()?;
459
460        // Step 4: If this cursor's source is not an index, throw an "InvalidAccessError"
461        // DOMException.
462        if !matches!(self.source, ObjectStoreOrIndex::Index(_)) {
463            return Err(Error::InvalidAccess(Some(
464                "The cursor's source is not an index".to_owned(),
465            )));
466        }
467
468        // Step 5: If this cursor's direction is not "next" or "prev", throw an "InvalidAccessError"
469        // DOMException.
470        if !matches!(
471            self.direction,
472            IDBCursorDirection::Next | IDBCursorDirection::Prev
473        ) {
474            return Err(Error::InvalidAccess(Some(
475                "The cursor's direction is not 'next' or 'prev'".to_owned(),
476            )));
477        }
478
479        // Step 6: If this cursor's got value flag is unset, indicating that the cursor is being
480        // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
481        if !self.got_value.get() {
482            return Err(Error::InvalidState(Some(
483                "The cursor is being iterated or has iterated past its end".to_owned(),
484            )));
485        }
486
487        // Step 7: Let r be the result of running the steps to convert a value to a key with key.
488        // Step 8: If r is invalid, throw a "DataError" DOMException.
489        // Step 9: Set key to r.
490        let key = convert_value_to_key(cx, key, None)?.into_result()?;
491
492        // Step 10: Let r be the result of running the steps to convert a value to a key with
493        // primaryKey.
494        // Step 11: If r is invalid, throw a "DataError" DOMException.
495        // Step 12: Set primaryKey to r.
496        let primary_key = convert_value_to_key(cx, primary_key, None)?.into_result()?;
497
498        let position = self.position.borrow().clone();
499        let object_store_position = self.object_store_position.borrow().clone();
500
501        match self.direction {
502            IDBCursorDirection::Next => {
503                // Step 13: If key is less than this cursor's position and this cursor's direction
504                // is "next", throw a "DataError" DOMException.
505                if let Some(pos) = &position {
506                    if &key < pos {
507                        return Err(Error::Data(Some(
508                            "Key is less than this cursor's position".to_owned(),
509                        )));
510                    }
511                    // Step 15: If key is equal to this cursor's position and primaryKey is less
512                    // than or equal to this cursor's object store position and this cursor's
513                    // direction is "next", throw a "DataError" DOMException.
514                    if &key == pos &&
515                        object_store_position
516                            .as_ref()
517                            .is_some_and(|osp| &primary_key <= osp)
518                    {
519                        return Err(Error::Data(Some(
520                            "Primary key is less than or equal to object store position".to_owned(),
521                        )));
522                    }
523                }
524            },
525            IDBCursorDirection::Prev => {
526                // Step 14: If key is greater than this cursor's position and this cursor's
527                // direction is "prev", throw a "DataError" DOMException.
528                if let Some(pos) = &position {
529                    if &key > pos {
530                        return Err(Error::Data(Some(
531                            "Key is greater than this cursor's position".to_owned(),
532                        )));
533                    }
534                    // Step 16: If key is equal to this cursor's position and primaryKey is greater
535                    // than or equal to this cursor's object store position and this cursor's
536                    // direction is "prev", throw a "DataError" DOMException.
537                    if &key == pos &&
538                        object_store_position
539                            .as_ref()
540                            .is_some_and(|osp| &primary_key >= osp)
541                    {
542                        return Err(Error::Data(Some(
543                            "Primary key is greater than or equal to object store position"
544                                .to_owned(),
545                        )));
546                    }
547                }
548            },
549            _ => unreachable!("Guaranteed by Step 5"),
550        }
551
552        // Step 17: Unset the got value flag on the cursor.
553        self.got_value.set(false);
554
555        // Step 18: Let request be the request created when this cursor was created.
556        let request = self.Request();
557
558        // Step 19: Unset the request's done flag on request.
559        request.unset_done_flag();
560
561        // Step 20: Run the steps to asynchronously execute a request with the cursor's source as
562        // source, the steps to iterate a cursor as operation and request, using the current Realm
563        // as targetRealm, this cursor, key and primaryKey.
564        let iteration_param = IterationParam {
565            cursor: Trusted::new(self),
566            key: Some(key),
567            primary_key: Some(primary_key),
568            count: None,
569        };
570
571        let range = self.range.clone();
572        // NOTE: According to the spec, execute_async should take the cursor's source
573        // (which may be an index). Using effective_object_store here is a temporary
574        // workaround until execute_async supports ObjectStoreOrIndex as source.
575        IDBRequest::execute_async(
576            cx,
577            &self.effective_object_store(),
578            |callback| {
579                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
580                    callback,
581                    key_range: range,
582                })
583            },
584            Some(request),
585            Some(iteration_param),
586        )?;
587
588        Ok(())
589    }
590
591    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-update>
592    fn Update(&self, cx: &mut JSContext, value: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
593        // Step 1. Let transaction be this's transaction.
594        // Step 2. If transaction's state is not active, then throw a "TransactionInactiveError"
595        // DOMException.
596        self.check_transaction_active()?;
597
598        // Step 3. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
599        self.check_transaction_readwrite()?;
600
601        // Step 4. If this's source or effective object store has been deleted, throw an
602        // "InvalidStateError" DOMException.
603        self.verify_not_deleted()?;
604        let effective_object_store = self.effective_object_store();
605        effective_object_store.verify_not_deleted()?;
606
607        // Step 5. If this's got value flag is false, indicating that the cursor is being iterated
608        // or has iterated past its end, throw an "InvalidStateError" DOMException.
609        if !self.got_value.get() {
610            return Err(Error::InvalidState(Some(
611                "The cursor is being iterated or has iterated past its end".to_owned(),
612            )));
613        }
614
615        // Step 6. If this's key only flag is true, throw an "InvalidStateError" DOMException.
616        if self.key_only {
617            return Err(Error::InvalidState(Some(
618                "This cursor is a key-only cursor".to_owned(),
619            )));
620        }
621
622        // Step 7. Let targetRealm be a user-agent defined Realm.
623        // Step 8. Let clone be a clone of value in targetRealm during transaction. Rethrow any
624        // exceptions.
625        rooted!(&in(cx) let mut cloned_js_value = NullValue());
626        effective_object_store.clone_value_in_target_realm(
627            cx,
628            value,
629            cloned_js_value.handle_mut(),
630        )?;
631
632        // Step 9. If this's effective object store uses in-line keys, then:
633        if effective_object_store.uses_inline_keys() {
634            // Step 9.1. Let kpk be the result of extracting a key from a value using a key path
635            // with clone and the key path of this's effective object store. Rethrow any exceptions.
636            let key_path = effective_object_store
637                .key_path()
638                .expect("Uses in-line keys requires key_path to be Some");
639            let kpk = extract_key(cx, cloned_js_value.handle(), key_path, None)?;
640
641            // Step 9.2. If kpk is failure, invalid, or not equal to this's effective key, throw a
642            // "DataError" DOMException.
643            match kpk {
644                ExtractionResult::Failure | ExtractionResult::Invalid => {
645                    return Err(Error::Data(Some(
646                        "Failed to extract key from value using key path".to_owned(),
647                    )));
648                },
649                ExtractionResult::Key(key) => {
650                    if !self
651                        .effective_key()
652                        .is_some_and(|effective_key| effective_key == key)
653                    {
654                        return Err(Error::Data(Some(
655                            "Extracted key does not match the effective key".to_owned(),
656                        )));
657                    }
658                },
659            }
660        }
661
662        let cloned_value = structuredclone::write(cx, cloned_js_value.handle(), None)?;
663        let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
664            return Err(Error::InvalidState(Some(
665                "Failed to serialize cloned value".to_owned(),
666            )));
667        };
668
669        // Step 10. Let operation be an algorithm to run store a record into an object store with
670        // this's effective object store, clone, this's effective key, and false.
671        // Step 11. Return the result (an IDBRequest) of running asynchronously execute a request
672        // with this and operation.
673        IDBRequest::execute_async(
674            cx,
675            &effective_object_store,
676            |callback| {
677                AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
678                    callback,
679                    key: self.effective_key(),
680                    value: serialized_value,
681                    should_overwrite: true,
682                    key_generator_current_number: None,
683                })
684            },
685            None,
686            None,
687        )
688    }
689
690    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-delete>
691    fn Delete(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
692        // Step 1. Let transaction be this's transaction.
693        // Step 2. If transaction's state is not active, then throw a "TransactionInactiveError"
694        // DOMException.
695        self.check_transaction_active()?;
696
697        // Step 3. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
698        self.check_transaction_readwrite()?;
699
700        // Step 4. If this's source or effective object store has been deleted, throw an
701        // "InvalidStateError" DOMException.
702        self.verify_not_deleted()?;
703        let effective_object_store = self.effective_object_store();
704        effective_object_store.verify_not_deleted()?;
705
706        // Step 5. If this's got value flag is false, indicating that the cursor is being iterated
707        // or has iterated past its end, throw an "InvalidStateError" DOMException.
708        if !self.got_value.get() {
709            return Err(Error::InvalidState(Some(
710                "The cursor is being iterated or has iterated past its end".to_owned(),
711            )));
712        }
713
714        // Step 6. If this's key only flag is true, throw an "InvalidStateError" DOMException.
715        if self.key_only {
716            return Err(Error::InvalidState(Some(
717                "This cursor is a key-only cursor".to_owned(),
718            )));
719        }
720
721        // Step 7. Let operation be an algorithm to run delete records from an object store with
722        // this's effective object store and this's effective key.
723        // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request
724        // with this and operation.
725        let effective_key = self.effective_key().ok_or_else(|| {
726            Error::InvalidState(Some("Missing effective key on cursor".to_owned()))
727        })?;
728
729        IDBRequest::execute_async(
730            cx,
731            &effective_object_store,
732            |callback| {
733                AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
734                    callback,
735                    key_range: IndexedDBKeyRange::only(effective_key),
736                })
737            },
738            None,
739            None,
740        )
741    }
742}
743
744/// A struct containing parameters for
745/// <https://www.w3.org/TR/IndexedDB-3/#iterate-a-cursor>
746#[derive(Clone)]
747pub(crate) struct IterationParam {
748    pub(crate) cursor: Trusted<IDBCursor>,
749    pub(crate) key: Option<IndexedDBKeyType>,
750    pub(crate) primary_key: Option<IndexedDBKeyType>,
751    pub(crate) count: Option<u32>,
752}
753
754/// <https://www.w3.org/TR/IndexedDB-3/#iterate-a-cursor>
755///
756/// NOTE: Be cautious: this part of the specification seems to assume the cursor’s source is an
757/// index. Therefore,
758///   "record’s key" means the key of the record,
759///   "record’s value" means the primary key of the record, and
760///   "record’s referenced value" means the value of the record.
761pub(crate) fn iterate_cursor(
762    global: &GlobalScope,
763    cx: &mut JSContext,
764    param: &IterationParam,
765    records: Vec<IndexedDBRecord>,
766) -> Result<Option<DomRoot<IDBCursor>>, Error> {
767    // Unpack IterationParam
768    let cursor = param.cursor.root();
769    let key = param.key.clone();
770    let primary_key = param.primary_key.clone();
771    let count = param.count;
772
773    // Step 1. Let source be cursor’s source.
774    let source = &cursor.source;
775
776    // Step 2. Let direction be cursor’s direction.
777    let direction = cursor.direction;
778
779    // Step 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
780    if primary_key.is_some() {
781        assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
782        assert!(matches!(
783            direction,
784            IDBCursorDirection::Next | IDBCursorDirection::Prev
785        ));
786    }
787
788    // Step 4. Let records be the list of records in source.
789    // NOTE: It is given as a function parameter.
790
791    // Step 5. Let range be cursor’s range.
792    let range = &cursor.range;
793
794    // Step 6. Let position be cursor’s position.
795    let mut position = cursor.position.borrow().clone();
796
797    // Step 7. Let object store position be cursor’s object store position.
798    let mut object_store_position = cursor.object_store_position.borrow().clone();
799
800    // Step 8. If count is not given, let count be 1.
801    let mut count = count.unwrap_or(1);
802
803    let mut found_record: Option<&IndexedDBRecord> = None;
804
805    // Step 9. While count is greater than 0:
806    while count > 0 {
807        // Step 9.1. Switch on direction:
808        found_record = match direction {
809            // "next"
810            IDBCursorDirection::Next => records.iter().find(|record| {
811                // Let found record be the first record in records which satisfy all of the
812                // following requirements:
813
814                // If key is defined, the record’s key is greater than or equal to key.
815                let requirement1 = || match &key {
816                    Some(key) => &record.key >= key,
817                    None => true,
818                };
819
820                // If primaryKey is defined, the record’s key is equal to key and the record’s
821                // value is greater than or equal to primaryKey, or the record’s key is greater
822                // than key.
823                let requirement2 = || match &primary_key {
824                    Some(primary_key) => key.as_ref().is_some_and(|key| {
825                        (&record.key == key && &record.primary_key >= primary_key) ||
826                            &record.key > key
827                    }),
828                    _ => true,
829                };
830
831                // If position is defined, and source is an object store, the record’s key is
832                // greater than position.
833                let requirement3 = || match (&position, source) {
834                    (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
835                    _ => true,
836                };
837
838                // If position is defined, and source is an index, the record’s key is equal to
839                // position and the record’s value is greater than object store position or the
840                // record’s key is greater than position.
841                let requirement4 = || match (&position, source) {
842                    (Some(position), ObjectStoreOrIndex::Index(_)) => {
843                        (&record.key == position &&
844                            object_store_position.as_ref().is_some_and(
845                                |object_store_position| &record.primary_key > object_store_position,
846                            )) ||
847                            &record.key > position
848                    },
849                    _ => true,
850                };
851
852                // The record’s key is in range.
853                let requirement5 = || range.contains(&record.key);
854
855                // NOTE: Use closures here for lazy computation on requirements.
856                requirement1() &&
857                    requirement2() &&
858                    requirement3() &&
859                    requirement4() &&
860                    requirement5()
861            }),
862            // "nextunique"
863            IDBCursorDirection::Nextunique => records.iter().find(|record| {
864                // Let found record be the first record in records which satisfy all of the
865                // following requirements:
866
867                // If key is defined, the record’s key is greater than or equal to key.
868                let requirement1 = || match &key {
869                    Some(key) => &record.key >= key,
870                    None => true,
871                };
872
873                // If position is defined, the record’s key is greater than position.
874                let requirement2 = || match &position {
875                    Some(position) => &record.key > position,
876                    None => true,
877                };
878
879                // The record’s key is in range.
880                let requirement3 = || range.contains(&record.key);
881
882                // NOTE: Use closures here for lazy computation on requirements.
883                requirement1() && requirement2() && requirement3()
884            }),
885            // "prev"
886            IDBCursorDirection::Prev => {
887                records.iter().rev().find(|&record| {
888                    // Let found record be the last record in records which satisfy all of the
889                    // following requirements:
890
891                    // If key is defined, the record’s key is less than or equal to key.
892                    let requirement1 = || match &key {
893                        Some(key) => &record.key <= key,
894                        None => true,
895                    };
896
897                    // If primaryKey is defined, the record’s key is equal to key and the record’s
898                    // value is less than or equal to primaryKey, or the record’s key is less than
899                    // key.
900                    let requirement2 = || match &primary_key {
901                        Some(primary_key) => key.as_ref().is_some_and(|key| {
902                            (&record.key == key && &record.primary_key <= primary_key) ||
903                                &record.key < key
904                        }),
905                        _ => true,
906                    };
907
908                    // If position is defined, and source is an object store, the record’s key is
909                    // less than position.
910                    let requirement3 = || match (&position, source) {
911                        (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
912                            &record.key < position
913                        },
914                        _ => true,
915                    };
916
917                    // If position is defined, and source is an index, the record’s key is equal to
918                    // position and the record’s value is less than object store position or the
919                    // record’s key is less than position.
920                    let requirement4 = || match (&position, source) {
921                        (Some(position), ObjectStoreOrIndex::Index(_)) => {
922                            (&record.key == position &&
923                                object_store_position.as_ref().is_some_and(
924                                    |object_store_position| {
925                                        &record.primary_key < object_store_position
926                                    },
927                                )) ||
928                                &record.key < position
929                        },
930                        _ => true,
931                    };
932
933                    // The record’s key is in range.
934                    let requirement5 = || range.contains(&record.key);
935
936                    // NOTE: Use closures here for lazy computation on requirements.
937                    requirement1() &&
938                        requirement2() &&
939                        requirement3() &&
940                        requirement4() &&
941                        requirement5()
942                })
943            },
944            // "prevunique"
945            IDBCursorDirection::Prevunique => records
946                .iter()
947                .rev()
948                .find(|&record| {
949                    // Let temp record be the last record in records which satisfy all of the
950                    // following requirements:
951
952                    // If key is defined, the record’s key is less than or equal to key.
953                    let requirement1 = || match &key {
954                        Some(key) => &record.key <= key,
955                        None => true,
956                    };
957
958                    // If position is defined, the record’s key is less than position.
959                    let requirement2 = || match &position {
960                        Some(position) => &record.key < position,
961                        None => true,
962                    };
963
964                    // The record’s key is in range.
965                    let requirement3 = || range.contains(&record.key);
966
967                    // NOTE: Use closures here for lazy computation on requirements.
968                    requirement1() && requirement2() && requirement3()
969                })
970                // If temp record is defined, let found record be the first record in records
971                // whose key is equal to temp record’s key.
972                .map(|temp_record| {
973                    records
974                        .iter()
975                        .find(|&record| record.key == temp_record.key)
976                        .expect(
977                            "Record with key equal to temp record's key should exist in records",
978                        )
979                }),
980        };
981
982        match found_record {
983            // Step 9.2. If found record is not defined, then:
984            None => {
985                // Step 9.2.1. Set cursor’s key to undefined.
986                cursor.set_key(None);
987
988                // Step 9.2.2. If source is an index, set cursor’s object store position to undefined.
989                if matches!(source, ObjectStoreOrIndex::Index(_)) {
990                    cursor.set_object_store_position(None);
991                }
992
993                // Step 9.2.3. If cursor’s key only flag is unset, set cursor’s value to undefined.
994                if !cursor.key_only {
995                    cursor.value.set(UndefinedValue());
996                }
997
998                // Step 9.2.4. Return null.
999                return Ok(None);
1000            },
1001            Some(found_record) => {
1002                // Step 9.3. Let position be found record’s key.
1003                position = Some(found_record.key.clone());
1004
1005                // Step 9.4. If source is an index, let object store position be found record’s value.
1006                if matches!(source, ObjectStoreOrIndex::Index(_)) {
1007                    object_store_position = Some(found_record.primary_key.clone());
1008                }
1009
1010                // Step 9.5. Decrease count by 1.
1011                count -= 1;
1012            },
1013        }
1014    }
1015    let found_record =
1016        found_record.expect("The while loop above guarantees found_record is defined");
1017
1018    // Step 10. Set cursor’s position to position.
1019    cursor.set_position(position);
1020
1021    // Step 11. If source is an index, set cursor’s object store position to object store position.
1022    if let ObjectStoreOrIndex::Index(_) = source {
1023        cursor.set_object_store_position(object_store_position);
1024    }
1025
1026    // Step 12. Set cursor’s key to found record’s key.
1027    cursor.set_key(Some(found_record.key.clone()));
1028
1029    // Step 13. If cursor’s key only flag is unset, then:
1030    if !cursor.key_only {
1031        // Step 13.1. Let serialized be found record’s referenced value.
1032        // Step 13.2. Set cursor’s value to ! StructuredDeserialize(serialized, targetRealm)
1033        rooted!(&in(cx) let mut new_cursor_value = UndefinedValue());
1034        postcard::from_bytes(&found_record.value)
1035            .map_err(|_| Error::Data(None))
1036            .and_then(|data| {
1037                structuredclone::read(cx, global, data, new_cursor_value.handle_mut())
1038            })?;
1039        cursor.value.set(new_cursor_value.get());
1040    }
1041
1042    // Step 14. Set cursor’s got value flag.
1043    cursor.got_value.set(true);
1044
1045    // Step 15. Return cursor.
1046    Ok(Some(cursor))
1047}