script/dom/
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::idbindex::IDBIndex;
25use crate::dom::idbobjectstore::IDBObjectStore;
26use crate::dom::idbrequest::IDBRequest;
27use crate::dom::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, mut value: MutableHandleValue) {
174        match self.key.borrow().as_ref() {
175            Some(key) => key_type_to_jsval(cx, key, value),
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, mut value: MutableHandleValue) {
182        match self.effective_key() {
183            Some(effective_key) => key_type_to_jsval(cx, &effective_key, value),
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) -> Result<Option<DomRoot<IDBCursor>>, Error> {
219    // Unpack IterationParam
220    let cursor = param.cursor.root();
221    let key = param.key.clone();
222    let primary_key = param.primary_key.clone();
223    let count = param.count;
224
225    // Step 1. Let source be cursor’s source.
226    let source = &cursor.source;
227
228    // Step 2. Let direction be cursor’s direction.
229    let direction = cursor.direction;
230
231    // Step 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
232    if primary_key.is_some() {
233        assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
234        assert!(matches!(
235            direction,
236            IDBCursorDirection::Next | IDBCursorDirection::Prev
237        ));
238    }
239
240    // Step 4. Let records be the list of records in source.
241    // NOTE: It is given as a function parameter.
242
243    // Step 5. Let range be cursor’s range.
244    let range = &cursor.range;
245
246    // Step 6. Let position be cursor’s position.
247    let mut position = cursor.position.borrow().clone();
248
249    // Step 7. Let object store position be cursor’s object store position.
250    let object_store_position = cursor.object_store_position.borrow().clone();
251
252    // Step 8. If count is not given, let count be 1.
253    let mut count = count.unwrap_or(1);
254
255    let mut found_record: Option<&IndexedDBRecord> = None;
256
257    // Step 9. While count is greater than 0:
258    while count > 0 {
259        // Step 9.1. Switch on direction:
260        found_record = match direction {
261            // "next"
262            IDBCursorDirection::Next => records.iter().find(|record| {
263                // Let found record be the first record in records which satisfy all of the
264                // following requirements:
265
266                // If key is defined, the record’s key is greater than or equal to key.
267                let requirement1 = || match &key {
268                    Some(key) => &record.key >= key,
269                    None => true,
270                };
271
272                // If primaryKey is defined, the record’s key is equal to key and the record’s
273                // value is greater than or equal to primaryKey, or the record’s key is greater
274                // than key.
275                let requirement2 = || match &primary_key {
276                    Some(primary_key) => key.as_ref().is_some_and(|key| {
277                        (&record.key == key && &record.primary_key >= primary_key) ||
278                            &record.key > key
279                    }),
280                    _ => true,
281                };
282
283                // If position is defined, and source is an object store, the record’s key is
284                // greater than position.
285                let requirement3 = || match (&position, source) {
286                    (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
287                    _ => true,
288                };
289
290                // If position is defined, and source is an index, the record’s key is equal to
291                // position and the record’s value is greater than object store position or the
292                // record’s key is greater than position.
293                let requirement4 = || match (&position, source) {
294                    (Some(position), ObjectStoreOrIndex::Index(_)) => {
295                        (&record.key == position &&
296                            object_store_position.as_ref().is_some_and(
297                                |object_store_position| &record.primary_key > object_store_position,
298                            )) ||
299                            &record.key > position
300                    },
301                    _ => true,
302                };
303
304                // The record’s key is in range.
305                let requirement5 = || range.contains(&record.key);
306
307                // NOTE: Use closures here for lazy computation on requirements.
308                requirement1() &&
309                    requirement2() &&
310                    requirement3() &&
311                    requirement4() &&
312                    requirement5()
313            }),
314            // "nextunique"
315            IDBCursorDirection::Nextunique => records.iter().find(|record| {
316                // Let found record be the first record in records which satisfy all of the
317                // following requirements:
318
319                // If key is defined, the record’s key is greater than or equal to key.
320                let requirement1 = || match &key {
321                    Some(key) => &record.key >= key,
322                    None => true,
323                };
324
325                // If position is defined, the record’s key is greater than position.
326                let requirement2 = || match &position {
327                    Some(position) => &record.key > position,
328                    None => true,
329                };
330
331                // The record’s key is in range.
332                let requirement3 = || range.contains(&record.key);
333
334                // NOTE: Use closures here for lazy computation on requirements.
335                requirement1() && requirement2() && requirement3()
336            }),
337            // "prev"
338            IDBCursorDirection::Prev => {
339                records.iter().rev().find(|&record| {
340                    // Let found record be the last record in records which satisfy all of the
341                    // following requirements:
342
343                    // If key is defined, the record’s key is less than or equal to key.
344                    let requirement1 = || match &key {
345                        Some(key) => &record.key <= key,
346                        None => true,
347                    };
348
349                    // If primaryKey is defined, the record’s key is equal to key and the record’s
350                    // value is less than or equal to primaryKey, or the record’s key is less than
351                    // key.
352                    let requirement2 = || match &primary_key {
353                        Some(primary_key) => key.as_ref().is_some_and(|key| {
354                            (&record.key == key && &record.primary_key <= primary_key) ||
355                                &record.key < key
356                        }),
357                        _ => true,
358                    };
359
360                    // If position is defined, and source is an object store, the record’s key is
361                    // less than position.
362                    let requirement3 = || match (&position, source) {
363                        (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
364                            &record.key < position
365                        },
366                        _ => true,
367                    };
368
369                    // If position is defined, and source is an index, the record’s key is equal to
370                    // position and the record’s value is less than object store position or the
371                    // record’s key is less than position.
372                    let requirement4 = || match (&position, source) {
373                        (Some(position), ObjectStoreOrIndex::Index(_)) => {
374                            (&record.key == position &&
375                                object_store_position.as_ref().is_some_and(
376                                    |object_store_position| {
377                                        &record.primary_key < object_store_position
378                                    },
379                                )) ||
380                                &record.key < position
381                        },
382                        _ => true,
383                    };
384
385                    // The record’s key is in range.
386                    let requirement5 = || range.contains(&record.key);
387
388                    // NOTE: Use closures here for lazy computation on requirements.
389                    requirement1() &&
390                        requirement2() &&
391                        requirement3() &&
392                        requirement4() &&
393                        requirement5()
394                })
395            },
396            // "prevunique"
397            IDBCursorDirection::Prevunique => records
398                .iter()
399                .rev()
400                .find(|&record| {
401                    // Let temp record be the last record in records which satisfy all of the
402                    // following requirements:
403
404                    // If key is defined, the record’s key is less than or equal to key.
405                    let requirement1 = || match &key {
406                        Some(key) => &record.key <= key,
407                        None => true,
408                    };
409
410                    // If position is defined, the record’s key is less than position.
411                    let requirement2 = || match &position {
412                        Some(position) => &record.key < position,
413                        None => true,
414                    };
415
416                    // The record’s key is in range.
417                    let requirement3 = || range.contains(&record.key);
418
419                    // NOTE: Use closures here for lazy computation on requirements.
420                    requirement1() && requirement2() && requirement3()
421                })
422                // If temp record is defined, let found record be the first record in records
423                // whose key is equal to temp record’s key.
424                .map(|temp_record| {
425                    records
426                        .iter()
427                        .find(|&record| record.key == temp_record.key)
428                        .expect(
429                            "Record with key equal to temp record's key should exist in records",
430                        )
431                }),
432        };
433
434        match found_record {
435            // Step 9.2. If found record is not defined, then:
436            None => {
437                // Step 9.2.1. Set cursor’s key to undefined.
438                cursor.set_key(None);
439
440                // Step 9.2.2. If source is an index, set cursor’s object store position to undefined.
441                if matches!(source, ObjectStoreOrIndex::Index(_)) {
442                    cursor.set_object_store_position(None);
443                }
444
445                // Step 9.2.3. If cursor’s key only flag is unset, set cursor’s value to undefined.
446                if !cursor.key_only {
447                    cursor.value.set(UndefinedValue());
448                }
449
450                // Step 9.2.4. Return null.
451                return Ok(None);
452            },
453            Some(found_record) => {
454                // Step 9.3. Let position be found record’s key.
455                position = Some(found_record.key.clone());
456
457                // Step 9.4. If source is an index, let object store position be found record’s value.
458                if matches!(source, ObjectStoreOrIndex::Index(_)) {
459                    cursor.set_object_store_position(Some(found_record.primary_key.clone()));
460                }
461
462                // Step 9.5. Decrease count by 1.
463                count -= 1;
464            },
465        }
466    }
467    let found_record =
468        found_record.expect("The while loop above guarantees found_record is defined");
469
470    // Step 10. Set cursor’s position to position.
471    cursor.set_position(position);
472
473    // Step 11. If source is an index, set cursor’s object store position to object store position.
474    if let ObjectStoreOrIndex::Index(_) = source {
475        cursor.set_object_store_position(object_store_position);
476    }
477
478    // Step 12. Set cursor’s key to found record’s key.
479    cursor.set_key(Some(found_record.key.clone()));
480
481    // Step 13. If cursor’s key only flag is unset, then:
482    if !cursor.key_only {
483        // Step 13.1. Let serialized be found record’s referenced value.
484        // Step 13.2. Set cursor’s value to ! StructuredDeserialize(serialized, targetRealm)
485        rooted!(in(*cx) let mut new_cursor_value = UndefinedValue());
486        bincode::deserialize(&found_record.value)
487            .map_err(|_| Error::Data)
488            .and_then(|data| structuredclone::read(global, data, new_cursor_value.handle_mut()))?;
489        cursor.value.set(new_cursor_value.get());
490    }
491
492    // Step 14. Set cursor’s got value flag.
493    cursor.got_value.set(true);
494
495    // Step 15. Return cursor.
496    Ok(Some(cursor))
497}