1#![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, UndefinedValue};
13use js::rust::MutableHandleValue;
14use script_bindings::cell::DomRefCell;
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16use storage_traits::indexeddb::{IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord};
17
18use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
19 IDBCursorDirection, IDBCursorMethods,
20};
21use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
22use crate::dom::bindings::error::Error;
23use crate::dom::bindings::refcounted::Trusted;
24use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
25use crate::dom::bindings::structuredclone;
26use crate::dom::globalscope::GlobalScope;
27use crate::dom::indexeddb::idbindex::IDBIndex;
28use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
29use crate::dom::indexeddb::idbrequest::IDBRequest;
30use crate::dom::indexeddb::idbtransaction::IDBTransaction;
31use crate::dom::indexeddb::key::key_type_to_jsval;
32
33#[derive(JSTraceable, MallocSizeOf)]
34#[expect(unused)]
35#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
36pub(crate) enum ObjectStoreOrIndex {
37 ObjectStore(Dom<IDBObjectStore>),
38 Index(Dom<IDBIndex>),
39}
40
41#[dom_struct]
42pub(crate) struct IDBCursor {
43 reflector_: Reflector,
44
45 transaction: Dom<IDBTransaction>,
47 #[no_trace]
49 range: IndexedDBKeyRange,
50 source: ObjectStoreOrIndex,
52 direction: IDBCursorDirection,
54 #[no_trace]
56 position: DomRefCell<Option<IndexedDBKeyType>>,
57 #[no_trace]
59 key: DomRefCell<Option<IndexedDBKeyType>>,
60 #[ignore_malloc_size_of = "mozjs"]
61 cached_key: DomRefCell<Option<Heap<JSVal>>>,
62 #[ignore_malloc_size_of = "mozjs"]
63 cached_primary_key: DomRefCell<Option<Heap<JSVal>>>,
64 #[ignore_malloc_size_of = "mozjs"]
66 value: Heap<JSVal>,
67 got_value: Cell<bool>,
69 #[no_trace]
71 object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
72 key_only: bool,
74
75 request: MutNullableDom<IDBRequest>,
77}
78
79impl IDBCursor {
80 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
81 pub(crate) fn new_inherited(
82 transaction: &IDBTransaction,
83 direction: IDBCursorDirection,
84 got_value: bool,
85 source: ObjectStoreOrIndex,
86 range: IndexedDBKeyRange,
87 key_only: bool,
88 ) -> IDBCursor {
89 IDBCursor {
90 reflector_: Reflector::new(),
91 transaction: Dom::from_ref(transaction),
92 range,
93 source,
94 direction,
95 position: DomRefCell::new(None),
96 key: DomRefCell::new(None),
97 cached_key: DomRefCell::new(None),
98 cached_primary_key: DomRefCell::new(None),
99 value: Heap::default(),
100 got_value: Cell::new(got_value),
101 object_store_position: DomRefCell::new(None),
102 key_only,
103 request: Default::default(),
104 }
105 }
106
107 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
108 #[allow(clippy::too_many_arguments)]
109 pub(crate) fn new(
110 cx: &mut JSContext,
111 global: &GlobalScope,
112 transaction: &IDBTransaction,
113 direction: IDBCursorDirection,
114 got_value: bool,
115 source: ObjectStoreOrIndex,
116 range: IndexedDBKeyRange,
117 key_only: bool,
118 ) -> DomRoot<IDBCursor> {
119 reflect_dom_object(
120 cx,
121 Box::new(IDBCursor::new_inherited(
122 transaction,
123 direction,
124 got_value,
125 source,
126 range,
127 key_only,
128 )),
129 global,
130 )
131 }
132
133 fn set_position(&self, position: Option<IndexedDBKeyType>) {
134 let changed = *self.position.borrow() != position;
135 *self.position.borrow_mut() = position;
136 if changed {
137 *self.cached_primary_key.borrow_mut() = None;
138 }
139 }
140
141 fn set_key(&self, key: Option<IndexedDBKeyType>) {
142 let key_changed = {
143 let current_key = self.key.borrow();
144 current_key.as_ref() != key.as_ref()
145 };
146 *self.key.borrow_mut() = key;
147 if key_changed {
148 *self.cached_key.borrow_mut() = None;
149 }
150 }
151
152 fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
153 let changed = *self.object_store_position.borrow() != object_store_position;
154 *self.object_store_position.borrow_mut() = object_store_position;
155 if changed {
156 *self.cached_primary_key.borrow_mut() = None;
157 }
158 }
159
160 pub(crate) fn set_request(&self, request: &IDBRequest) {
161 self.request.set(Some(request));
162 }
163
164 pub(crate) fn value(&self, mut out: MutableHandleValue) {
165 out.set(self.value.get());
166 }
167
168 pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
170 match &self.source {
171 ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
172 ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
173 }
174 }
175}
176
177impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
178 fn Source(&self) -> IDBObjectStoreOrIDBIndex {
180 match &self.source {
181 ObjectStoreOrIndex::ObjectStore(source) => {
182 IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
183 },
184 ObjectStoreOrIndex::Index(source) => {
185 IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
186 },
187 }
188 }
189
190 fn Direction(&self) -> IDBCursorDirection {
192 self.direction
193 }
194
195 fn Key(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
197 if let Some(cached) = &*self.cached_key.borrow() {
205 value.set(cached.get());
206 return;
207 }
208
209 match self.key.borrow().as_ref() {
210 Some(key) => key_type_to_jsval(cx, key, value.reborrow()),
211 None => value.set(UndefinedValue()),
212 }
213
214 *self.cached_key.borrow_mut() = Some(Heap::default());
215 self.cached_key.borrow().as_ref().unwrap().set(value.get());
216 }
217
218 fn PrimaryKey(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
220 if let Some(cached) = &*self.cached_primary_key.borrow() {
226 value.set(cached.get());
227 return;
228 }
229
230 match self.effective_key() {
231 Some(effective_key) => key_type_to_jsval(cx, &effective_key, value.reborrow()),
232 None => value.set(UndefinedValue()),
233 }
234
235 *self.cached_primary_key.borrow_mut() = Some(Heap::default());
236 self.cached_primary_key
237 .borrow()
238 .as_ref()
239 .unwrap()
240 .set(value.get());
241 }
242
243 fn Request(&self) -> DomRoot<IDBRequest> {
245 self.request
246 .get()
247 .expect("IDBCursor.request should be set when cursor is opened")
248 }
249}
250
251#[derive(Clone)]
254pub(crate) struct IterationParam {
255 pub(crate) cursor: Trusted<IDBCursor>,
256 pub(crate) key: Option<IndexedDBKeyType>,
257 pub(crate) primary_key: Option<IndexedDBKeyType>,
258 pub(crate) count: Option<u32>,
259}
260
261pub(crate) fn iterate_cursor(
269 global: &GlobalScope,
270 cx: &mut JSContext,
271 param: &IterationParam,
272 records: Vec<IndexedDBRecord>,
273) -> Result<Option<DomRoot<IDBCursor>>, Error> {
274 let cursor = param.cursor.root();
276 let key = param.key.clone();
277 let primary_key = param.primary_key.clone();
278 let count = param.count;
279
280 let source = &cursor.source;
282
283 let direction = cursor.direction;
285
286 if primary_key.is_some() {
288 assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
289 assert!(matches!(
290 direction,
291 IDBCursorDirection::Next | IDBCursorDirection::Prev
292 ));
293 }
294
295 let range = &cursor.range;
300
301 let mut position = cursor.position.borrow().clone();
303
304 let object_store_position = cursor.object_store_position.borrow().clone();
306
307 let mut count = count.unwrap_or(1);
309
310 let mut found_record: Option<&IndexedDBRecord> = None;
311
312 while count > 0 {
314 found_record = match direction {
316 IDBCursorDirection::Next => records.iter().find(|record| {
318 let requirement1 = || match &key {
323 Some(key) => &record.key >= key,
324 None => true,
325 };
326
327 let requirement2 = || match &primary_key {
331 Some(primary_key) => key.as_ref().is_some_and(|key| {
332 (&record.key == key && &record.primary_key >= primary_key) ||
333 &record.key > key
334 }),
335 _ => true,
336 };
337
338 let requirement3 = || match (&position, source) {
341 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
342 _ => true,
343 };
344
345 let requirement4 = || match (&position, source) {
349 (Some(position), ObjectStoreOrIndex::Index(_)) => {
350 (&record.key == position &&
351 object_store_position.as_ref().is_some_and(
352 |object_store_position| &record.primary_key > object_store_position,
353 )) ||
354 &record.key > position
355 },
356 _ => true,
357 };
358
359 let requirement5 = || range.contains(&record.key);
361
362 requirement1() &&
364 requirement2() &&
365 requirement3() &&
366 requirement4() &&
367 requirement5()
368 }),
369 IDBCursorDirection::Nextunique => records.iter().find(|record| {
371 let requirement1 = || match &key {
376 Some(key) => &record.key >= key,
377 None => true,
378 };
379
380 let requirement2 = || match &position {
382 Some(position) => &record.key > position,
383 None => true,
384 };
385
386 let requirement3 = || range.contains(&record.key);
388
389 requirement1() && requirement2() && requirement3()
391 }),
392 IDBCursorDirection::Prev => {
394 records.iter().rev().find(|&record| {
395 let requirement1 = || match &key {
400 Some(key) => &record.key <= key,
401 None => true,
402 };
403
404 let requirement2 = || match &primary_key {
408 Some(primary_key) => key.as_ref().is_some_and(|key| {
409 (&record.key == key && &record.primary_key <= primary_key) ||
410 &record.key < key
411 }),
412 _ => true,
413 };
414
415 let requirement3 = || match (&position, source) {
418 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
419 &record.key < position
420 },
421 _ => true,
422 };
423
424 let requirement4 = || match (&position, source) {
428 (Some(position), ObjectStoreOrIndex::Index(_)) => {
429 (&record.key == position &&
430 object_store_position.as_ref().is_some_and(
431 |object_store_position| {
432 &record.primary_key < object_store_position
433 },
434 )) ||
435 &record.key < position
436 },
437 _ => true,
438 };
439
440 let requirement5 = || range.contains(&record.key);
442
443 requirement1() &&
445 requirement2() &&
446 requirement3() &&
447 requirement4() &&
448 requirement5()
449 })
450 },
451 IDBCursorDirection::Prevunique => records
453 .iter()
454 .rev()
455 .find(|&record| {
456 let requirement1 = || match &key {
461 Some(key) => &record.key <= key,
462 None => true,
463 };
464
465 let requirement2 = || match &position {
467 Some(position) => &record.key < position,
468 None => true,
469 };
470
471 let requirement3 = || range.contains(&record.key);
473
474 requirement1() && requirement2() && requirement3()
476 })
477 .map(|temp_record| {
480 records
481 .iter()
482 .find(|&record| record.key == temp_record.key)
483 .expect(
484 "Record with key equal to temp record's key should exist in records",
485 )
486 }),
487 };
488
489 match found_record {
490 None => {
492 cursor.set_key(None);
494
495 if matches!(source, ObjectStoreOrIndex::Index(_)) {
497 cursor.set_object_store_position(None);
498 }
499
500 if !cursor.key_only {
502 cursor.value.set(UndefinedValue());
503 }
504
505 return Ok(None);
507 },
508 Some(found_record) => {
509 position = Some(found_record.key.clone());
511
512 if matches!(source, ObjectStoreOrIndex::Index(_)) {
514 cursor.set_object_store_position(Some(found_record.primary_key.clone()));
515 }
516
517 count -= 1;
519 },
520 }
521 }
522 let found_record =
523 found_record.expect("The while loop above guarantees found_record is defined");
524
525 cursor.set_position(position);
527
528 if let ObjectStoreOrIndex::Index(_) = source {
530 cursor.set_object_store_position(object_store_position);
531 }
532
533 cursor.set_key(Some(found_record.key.clone()));
535
536 if !cursor.key_only {
538 rooted!(&in(cx) let mut new_cursor_value = UndefinedValue());
541 postcard::from_bytes(&found_record.value)
542 .map_err(|_| Error::Data(None))
543 .and_then(|data| {
544 structuredclone::read(cx, global, data, new_cursor_value.handle_mut())
545 })?;
546 cursor.value.set(new_cursor_value.get());
547 }
548
549 cursor.got_value.set(true);
551
552 Ok(Some(cursor))
554}