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
5use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::jsapi::Heap;
9use js::jsval::{JSVal, UndefinedValue};
10use js::rust::MutableHandleValue;
11use storage_traits::indexeddb_thread::{IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord};
12
13use crate::dom::bindings::cell::DomRefCell;
14use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
15    IDBCursorDirection, IDBCursorMethods,
16};
17use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
18use crate::dom::bindings::error::Error;
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
21use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
22use crate::dom::bindings::structuredclone;
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::indexeddb::idbindex::IDBIndex;
25use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
26use crate::dom::indexeddb::idbrequest::IDBRequest;
27use crate::dom::indexeddb::idbtransaction::IDBTransaction;
28use crate::indexed_db::key_type_to_jsval;
29use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
30
31#[derive(JSTraceable, MallocSizeOf)]
32#[expect(unused)]
33#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
34pub(crate) enum ObjectStoreOrIndex {
35    ObjectStore(Dom<IDBObjectStore>),
36    Index(Dom<IDBIndex>),
37}
38
39#[dom_struct]
40pub(crate) struct IDBCursor {
41    reflector_: Reflector,
42
43    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-transaction>
44    transaction: Dom<IDBTransaction>,
45    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-range>
46    #[no_trace]
47    range: IndexedDBKeyRange,
48    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-source>
49    source: ObjectStoreOrIndex,
50    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-direction>
51    direction: IDBCursorDirection,
52    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-position>
53    #[no_trace]
54    position: DomRefCell<Option<IndexedDBKeyType>>,
55    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-key>
56    #[no_trace]
57    key: DomRefCell<Option<IndexedDBKeyType>>,
58    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-value>
59    #[ignore_malloc_size_of = "mozjs"]
60    value: Heap<JSVal>,
61    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-got-value-flag>
62    got_value: Cell<bool>,
63    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-object-store-position>
64    #[no_trace]
65    object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
66    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-key-only-flag>
67    key_only: bool,
68
69    /// <https://w3c.github.io/IndexedDB/#cursor-request>
70    request: MutNullableDom<IDBRequest>,
71}
72
73impl IDBCursor {
74    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
75    pub(crate) fn new_inherited(
76        transaction: &IDBTransaction,
77        direction: IDBCursorDirection,
78        got_value: bool,
79        source: ObjectStoreOrIndex,
80        range: IndexedDBKeyRange,
81        key_only: bool,
82    ) -> IDBCursor {
83        IDBCursor {
84            reflector_: Reflector::new(),
85            transaction: Dom::from_ref(transaction),
86            range,
87            source,
88            direction,
89            position: DomRefCell::new(None),
90            key: DomRefCell::new(None),
91            value: Heap::default(),
92            got_value: Cell::new(got_value),
93            object_store_position: DomRefCell::new(None),
94            key_only,
95            request: Default::default(),
96        }
97    }
98
99    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
100    #[allow(clippy::too_many_arguments)]
101    pub(crate) fn new(
102        global: &GlobalScope,
103        transaction: &IDBTransaction,
104        direction: IDBCursorDirection,
105        got_value: bool,
106        source: ObjectStoreOrIndex,
107        range: IndexedDBKeyRange,
108        key_only: bool,
109        can_gc: CanGc,
110    ) -> DomRoot<IDBCursor> {
111        reflect_dom_object(
112            Box::new(IDBCursor::new_inherited(
113                transaction,
114                direction,
115                got_value,
116                source,
117                range,
118                key_only,
119            )),
120            global,
121            can_gc,
122        )
123    }
124
125    fn set_position(&self, position: Option<IndexedDBKeyType>) {
126        *self.position.borrow_mut() = position;
127    }
128
129    fn set_key(&self, key: Option<IndexedDBKeyType>) {
130        *self.key.borrow_mut() = key;
131    }
132
133    fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
134        *self.object_store_position.borrow_mut() = object_store_position;
135    }
136
137    pub(crate) fn set_request(&self, request: &IDBRequest) {
138        self.request.set(Some(request));
139    }
140
141    pub(crate) fn value(&self, mut out: MutableHandleValue) {
142        out.set(self.value.get());
143    }
144
145    /// <https://www.w3.org/TR/IndexedDB-2/#cursor-effective-key>
146    pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
147        match &self.source {
148            ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
149            ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
150        }
151    }
152}
153
154impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
155    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbcursor-source>
156    fn Source(&self) -> IDBObjectStoreOrIDBIndex {
157        match &self.source {
158            ObjectStoreOrIndex::ObjectStore(source) => {
159                IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
160            },
161            ObjectStoreOrIndex::Index(source) => {
162                IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
163            },
164        }
165    }
166
167    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbcursor-direction>
168    fn Direction(&self) -> IDBCursorDirection {
169        self.direction
170    }
171
172    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbcursor-key>
173    fn Key(&self, cx: SafeJSContext, can_gc: CanGc, mut value: MutableHandleValue) {
174        match self.key.borrow().as_ref() {
175            Some(key) => key_type_to_jsval(cx, key, value, can_gc),
176            None => value.set(UndefinedValue()),
177        }
178    }
179
180    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbcursor-primarykey>
181    fn PrimaryKey(&self, cx: SafeJSContext, can_gc: CanGc, mut value: MutableHandleValue) {
182        match self.effective_key() {
183            Some(effective_key) => key_type_to_jsval(cx, &effective_key, value, can_gc),
184            None => value.set(UndefinedValue()),
185        }
186    }
187
188    /// <https://w3c.github.io/IndexedDB/#dom-idbcursor-request>
189    fn Request(&self) -> DomRoot<IDBRequest> {
190        self.request
191            .get()
192            .expect("IDBCursor.request should be set when cursor is opened")
193    }
194}
195
196/// A struct containing parameters for
197/// <https://www.w3.org/TR/IndexedDB-2/#iterate-a-cursor>
198#[derive(Clone)]
199pub(crate) struct IterationParam {
200    pub(crate) cursor: Trusted<IDBCursor>,
201    pub(crate) key: Option<IndexedDBKeyType>,
202    pub(crate) primary_key: Option<IndexedDBKeyType>,
203    pub(crate) count: Option<u32>,
204}
205
206/// <https://www.w3.org/TR/IndexedDB-2/#iterate-a-cursor>
207///
208/// NOTE: Be cautious: this part of the specification seems to assume the cursor’s source is an
209/// index. Therefore,
210///   "record’s key" means the key of the record,
211///   "record’s value" means the primary key of the record, and
212///   "record’s referenced value" means the value of the record.
213pub(crate) fn iterate_cursor(
214    global: &GlobalScope,
215    cx: SafeJSContext,
216    param: &IterationParam,
217    records: Vec<IndexedDBRecord>,
218    can_gc: CanGc,
219) -> Result<Option<DomRoot<IDBCursor>>, Error> {
220    // Unpack IterationParam
221    let cursor = param.cursor.root();
222    let key = param.key.clone();
223    let primary_key = param.primary_key.clone();
224    let count = param.count;
225
226    // Step 1. Let source be cursor’s source.
227    let source = &cursor.source;
228
229    // Step 2. Let direction be cursor’s direction.
230    let direction = cursor.direction;
231
232    // Step 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
233    if primary_key.is_some() {
234        assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
235        assert!(matches!(
236            direction,
237            IDBCursorDirection::Next | IDBCursorDirection::Prev
238        ));
239    }
240
241    // Step 4. Let records be the list of records in source.
242    // NOTE: It is given as a function parameter.
243
244    // Step 5. Let range be cursor’s range.
245    let range = &cursor.range;
246
247    // Step 6. Let position be cursor’s position.
248    let mut position = cursor.position.borrow().clone();
249
250    // Step 7. Let object store position be cursor’s object store position.
251    let object_store_position = cursor.object_store_position.borrow().clone();
252
253    // Step 8. If count is not given, let count be 1.
254    let mut count = count.unwrap_or(1);
255
256    let mut found_record: Option<&IndexedDBRecord> = None;
257
258    // Step 9. While count is greater than 0:
259    while count > 0 {
260        // Step 9.1. Switch on direction:
261        found_record = match direction {
262            // "next"
263            IDBCursorDirection::Next => records.iter().find(|record| {
264                // Let found record be the first record in records which satisfy all of the
265                // following requirements:
266
267                // If key is defined, the record’s key is greater than or equal to key.
268                let requirement1 = || match &key {
269                    Some(key) => &record.key >= key,
270                    None => true,
271                };
272
273                // If primaryKey is defined, the record’s key is equal to key and the record’s
274                // value is greater than or equal to primaryKey, or the record’s key is greater
275                // than key.
276                let requirement2 = || match &primary_key {
277                    Some(primary_key) => key.as_ref().is_some_and(|key| {
278                        (&record.key == key && &record.primary_key >= primary_key) ||
279                            &record.key > key
280                    }),
281                    _ => true,
282                };
283
284                // If position is defined, and source is an object store, the record’s key is
285                // greater than position.
286                let requirement3 = || match (&position, source) {
287                    (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
288                    _ => true,
289                };
290
291                // If position is defined, and source is an index, the record’s key is equal to
292                // position and the record’s value is greater than object store position or the
293                // record’s key is greater than position.
294                let requirement4 = || match (&position, source) {
295                    (Some(position), ObjectStoreOrIndex::Index(_)) => {
296                        (&record.key == position &&
297                            object_store_position.as_ref().is_some_and(
298                                |object_store_position| &record.primary_key > object_store_position,
299                            )) ||
300                            &record.key > position
301                    },
302                    _ => true,
303                };
304
305                // The record’s key is in range.
306                let requirement5 = || range.contains(&record.key);
307
308                // NOTE: Use closures here for lazy computation on requirements.
309                requirement1() &&
310                    requirement2() &&
311                    requirement3() &&
312                    requirement4() &&
313                    requirement5()
314            }),
315            // "nextunique"
316            IDBCursorDirection::Nextunique => records.iter().find(|record| {
317                // Let found record be the first record in records which satisfy all of the
318                // following requirements:
319
320                // If key is defined, the record’s key is greater than or equal to key.
321                let requirement1 = || match &key {
322                    Some(key) => &record.key >= key,
323                    None => true,
324                };
325
326                // If position is defined, the record’s key is greater than position.
327                let requirement2 = || match &position {
328                    Some(position) => &record.key > position,
329                    None => true,
330                };
331
332                // The record’s key is in range.
333                let requirement3 = || range.contains(&record.key);
334
335                // NOTE: Use closures here for lazy computation on requirements.
336                requirement1() && requirement2() && requirement3()
337            }),
338            // "prev"
339            IDBCursorDirection::Prev => {
340                records.iter().rev().find(|&record| {
341                    // Let found record be the last record in records which satisfy all of the
342                    // following requirements:
343
344                    // If key is defined, the record’s key is less than or equal to key.
345                    let requirement1 = || match &key {
346                        Some(key) => &record.key <= key,
347                        None => true,
348                    };
349
350                    // If primaryKey is defined, the record’s key is equal to key and the record’s
351                    // value is less than or equal to primaryKey, or the record’s key is less than
352                    // key.
353                    let requirement2 = || match &primary_key {
354                        Some(primary_key) => key.as_ref().is_some_and(|key| {
355                            (&record.key == key && &record.primary_key <= primary_key) ||
356                                &record.key < key
357                        }),
358                        _ => true,
359                    };
360
361                    // If position is defined, and source is an object store, the record’s key is
362                    // less than position.
363                    let requirement3 = || match (&position, source) {
364                        (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
365                            &record.key < position
366                        },
367                        _ => true,
368                    };
369
370                    // If position is defined, and source is an index, the record’s key is equal to
371                    // position and the record’s value is less than object store position or the
372                    // record’s key is less than position.
373                    let requirement4 = || match (&position, source) {
374                        (Some(position), ObjectStoreOrIndex::Index(_)) => {
375                            (&record.key == position &&
376                                object_store_position.as_ref().is_some_and(
377                                    |object_store_position| {
378                                        &record.primary_key < object_store_position
379                                    },
380                                )) ||
381                                &record.key < position
382                        },
383                        _ => true,
384                    };
385
386                    // The record’s key is in range.
387                    let requirement5 = || range.contains(&record.key);
388
389                    // NOTE: Use closures here for lazy computation on requirements.
390                    requirement1() &&
391                        requirement2() &&
392                        requirement3() &&
393                        requirement4() &&
394                        requirement5()
395                })
396            },
397            // "prevunique"
398            IDBCursorDirection::Prevunique => records
399                .iter()
400                .rev()
401                .find(|&record| {
402                    // Let temp record be the last record in records which satisfy all of the
403                    // following requirements:
404
405                    // If key is defined, the record’s key is less than or equal to key.
406                    let requirement1 = || match &key {
407                        Some(key) => &record.key <= key,
408                        None => true,
409                    };
410
411                    // If position is defined, the record’s key is less than position.
412                    let requirement2 = || match &position {
413                        Some(position) => &record.key < position,
414                        None => true,
415                    };
416
417                    // The record’s key is in range.
418                    let requirement3 = || range.contains(&record.key);
419
420                    // NOTE: Use closures here for lazy computation on requirements.
421                    requirement1() && requirement2() && requirement3()
422                })
423                // If temp record is defined, let found record be the first record in records
424                // whose key is equal to temp record’s key.
425                .map(|temp_record| {
426                    records
427                        .iter()
428                        .find(|&record| record.key == temp_record.key)
429                        .expect(
430                            "Record with key equal to temp record's key should exist in records",
431                        )
432                }),
433        };
434
435        match found_record {
436            // Step 9.2. If found record is not defined, then:
437            None => {
438                // Step 9.2.1. Set cursor’s key to undefined.
439                cursor.set_key(None);
440
441                // Step 9.2.2. If source is an index, set cursor’s object store position to undefined.
442                if matches!(source, ObjectStoreOrIndex::Index(_)) {
443                    cursor.set_object_store_position(None);
444                }
445
446                // Step 9.2.3. If cursor’s key only flag is unset, set cursor’s value to undefined.
447                if !cursor.key_only {
448                    cursor.value.set(UndefinedValue());
449                }
450
451                // Step 9.2.4. Return null.
452                return Ok(None);
453            },
454            Some(found_record) => {
455                // Step 9.3. Let position be found record’s key.
456                position = Some(found_record.key.clone());
457
458                // Step 9.4. If source is an index, let object store position be found record’s value.
459                if matches!(source, ObjectStoreOrIndex::Index(_)) {
460                    cursor.set_object_store_position(Some(found_record.primary_key.clone()));
461                }
462
463                // Step 9.5. Decrease count by 1.
464                count -= 1;
465            },
466        }
467    }
468    let found_record =
469        found_record.expect("The while loop above guarantees found_record is defined");
470
471    // Step 10. Set cursor’s position to position.
472    cursor.set_position(position);
473
474    // Step 11. If source is an index, set cursor’s object store position to object store position.
475    if let ObjectStoreOrIndex::Index(_) = source {
476        cursor.set_object_store_position(object_store_position);
477    }
478
479    // Step 12. Set cursor’s key to found record’s key.
480    cursor.set_key(Some(found_record.key.clone()));
481
482    // Step 13. If cursor’s key only flag is unset, then:
483    if !cursor.key_only {
484        // Step 13.1. Let serialized be found record’s referenced value.
485        // Step 13.2. Set cursor’s value to ! StructuredDeserialize(serialized, targetRealm)
486        rooted!(in(*cx) let mut new_cursor_value = UndefinedValue());
487        bincode::deserialize(&found_record.value)
488            .map_err(|_| Error::Data)
489            .and_then(|data| {
490                structuredclone::read(global, data, new_cursor_value.handle_mut(), can_gc)
491            })?;
492        cursor.value.set(new_cursor_value.get());
493    }
494
495    // Step 14. Set cursor’s got value flag.
496    cursor.got_value.set(true);
497
498    // Step 15. Return cursor.
499    Ok(Some(cursor))
500}