1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::jsapi::Heap;
10use js::jsval::{JSVal, UndefinedValue};
11use js::rust::MutableHandleValue;
12use script_bindings::script_runtime::CanGc;
13use storage_traits::indexeddb::{IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord};
14
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
17 IDBCursorDirection, IDBCursorMethods,
18};
19use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
20use crate::dom::bindings::error::Error;
21use crate::dom::bindings::refcounted::Trusted;
22use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
23use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
24use crate::dom::bindings::structuredclone;
25use crate::dom::globalscope::GlobalScope;
26use crate::dom::indexeddb::idbindex::IDBIndex;
27use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
28use crate::dom::indexeddb::idbrequest::IDBRequest;
29use crate::dom::indexeddb::idbtransaction::IDBTransaction;
30use crate::indexeddb::key_type_to_jsval;
31
32#[derive(JSTraceable, MallocSizeOf)]
33#[expect(unused)]
34#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
35pub(crate) enum ObjectStoreOrIndex {
36 ObjectStore(Dom<IDBObjectStore>),
37 Index(Dom<IDBIndex>),
38}
39
40#[dom_struct]
41pub(crate) struct IDBCursor {
42 reflector_: Reflector,
43
44 transaction: Dom<IDBTransaction>,
46 #[no_trace]
48 range: IndexedDBKeyRange,
49 source: ObjectStoreOrIndex,
51 direction: IDBCursorDirection,
53 #[no_trace]
55 position: DomRefCell<Option<IndexedDBKeyType>>,
56 #[no_trace]
58 key: DomRefCell<Option<IndexedDBKeyType>>,
59 #[ignore_malloc_size_of = "mozjs"]
60 cached_key: DomRefCell<Option<Heap<JSVal>>>,
61 #[ignore_malloc_size_of = "mozjs"]
62 cached_primary_key: DomRefCell<Option<Heap<JSVal>>>,
63 #[ignore_malloc_size_of = "mozjs"]
65 value: Heap<JSVal>,
66 got_value: Cell<bool>,
68 #[no_trace]
70 object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
71 key_only: bool,
73
74 request: MutNullableDom<IDBRequest>,
76}
77
78impl IDBCursor {
79 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
80 pub(crate) fn new_inherited(
81 transaction: &IDBTransaction,
82 direction: IDBCursorDirection,
83 got_value: bool,
84 source: ObjectStoreOrIndex,
85 range: IndexedDBKeyRange,
86 key_only: bool,
87 ) -> IDBCursor {
88 IDBCursor {
89 reflector_: Reflector::new(),
90 transaction: Dom::from_ref(transaction),
91 range,
92 source,
93 direction,
94 position: DomRefCell::new(None),
95 key: DomRefCell::new(None),
96 cached_key: DomRefCell::new(None),
97 cached_primary_key: DomRefCell::new(None),
98 value: Heap::default(),
99 got_value: Cell::new(got_value),
100 object_store_position: DomRefCell::new(None),
101 key_only,
102 request: Default::default(),
103 }
104 }
105
106 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
107 #[allow(clippy::too_many_arguments)]
108 pub(crate) fn new(
109 global: &GlobalScope,
110 transaction: &IDBTransaction,
111 direction: IDBCursorDirection,
112 got_value: bool,
113 source: ObjectStoreOrIndex,
114 range: IndexedDBKeyRange,
115 key_only: bool,
116 can_gc: CanGc,
117 ) -> DomRoot<IDBCursor> {
118 reflect_dom_object(
119 Box::new(IDBCursor::new_inherited(
120 transaction,
121 direction,
122 got_value,
123 source,
124 range,
125 key_only,
126 )),
127 global,
128 can_gc,
129 )
130 }
131
132 fn set_position(&self, position: Option<IndexedDBKeyType>) {
133 let changed = *self.position.borrow() != position;
134 *self.position.borrow_mut() = position;
135 if changed {
136 *self.cached_primary_key.borrow_mut() = None;
137 }
138 }
139
140 fn set_key(&self, key: Option<IndexedDBKeyType>) {
141 let key_changed = {
142 let current_key = self.key.borrow();
143 current_key.as_ref() != key.as_ref()
144 };
145 *self.key.borrow_mut() = key;
146 if key_changed {
147 *self.cached_key.borrow_mut() = None;
148 }
149 }
150
151 fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
152 let changed = *self.object_store_position.borrow() != object_store_position;
153 *self.object_store_position.borrow_mut() = object_store_position;
154 if changed {
155 *self.cached_primary_key.borrow_mut() = None;
156 }
157 }
158
159 pub(crate) fn set_request(&self, request: &IDBRequest) {
160 self.request.set(Some(request));
161 }
162
163 pub(crate) fn value(&self, mut out: MutableHandleValue) {
164 out.set(self.value.get());
165 }
166
167 pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
169 match &self.source {
170 ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
171 ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
172 }
173 }
174}
175
176impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
177 fn Source(&self) -> IDBObjectStoreOrIDBIndex {
179 match &self.source {
180 ObjectStoreOrIndex::ObjectStore(source) => {
181 IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
182 },
183 ObjectStoreOrIndex::Index(source) => {
184 IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
185 },
186 }
187 }
188
189 fn Direction(&self) -> IDBCursorDirection {
191 self.direction
192 }
193
194 fn Key(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
196 if let Some(cached) = &*self.cached_key.borrow() {
204 value.set(cached.get());
205 return;
206 }
207
208 match self.key.borrow().as_ref() {
209 Some(key) => key_type_to_jsval(cx, key, value.reborrow()),
210 None => value.set(UndefinedValue()),
211 }
212
213 *self.cached_key.borrow_mut() = Some(Heap::default());
214 self.cached_key.borrow().as_ref().unwrap().set(value.get());
215 }
216
217 fn PrimaryKey(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
219 if let Some(cached) = &*self.cached_primary_key.borrow() {
225 value.set(cached.get());
226 return;
227 }
228
229 match self.effective_key() {
230 Some(effective_key) => key_type_to_jsval(cx, &effective_key, value.reborrow()),
231 None => value.set(UndefinedValue()),
232 }
233
234 *self.cached_primary_key.borrow_mut() = Some(Heap::default());
235 self.cached_primary_key
236 .borrow()
237 .as_ref()
238 .unwrap()
239 .set(value.get());
240 }
241
242 fn Request(&self) -> DomRoot<IDBRequest> {
244 self.request
245 .get()
246 .expect("IDBCursor.request should be set when cursor is opened")
247 }
248}
249
250#[derive(Clone)]
253pub(crate) struct IterationParam {
254 pub(crate) cursor: Trusted<IDBCursor>,
255 pub(crate) key: Option<IndexedDBKeyType>,
256 pub(crate) primary_key: Option<IndexedDBKeyType>,
257 pub(crate) count: Option<u32>,
258}
259
260pub(crate) fn iterate_cursor(
268 global: &GlobalScope,
269 cx: &mut JSContext,
270 param: &IterationParam,
271 records: Vec<IndexedDBRecord>,
272) -> Result<Option<DomRoot<IDBCursor>>, Error> {
273 let cursor = param.cursor.root();
275 let key = param.key.clone();
276 let primary_key = param.primary_key.clone();
277 let count = param.count;
278
279 let source = &cursor.source;
281
282 let direction = cursor.direction;
284
285 if primary_key.is_some() {
287 assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
288 assert!(matches!(
289 direction,
290 IDBCursorDirection::Next | IDBCursorDirection::Prev
291 ));
292 }
293
294 let range = &cursor.range;
299
300 let mut position = cursor.position.borrow().clone();
302
303 let object_store_position = cursor.object_store_position.borrow().clone();
305
306 let mut count = count.unwrap_or(1);
308
309 let mut found_record: Option<&IndexedDBRecord> = None;
310
311 while count > 0 {
313 found_record = match direction {
315 IDBCursorDirection::Next => records.iter().find(|record| {
317 let requirement1 = || match &key {
322 Some(key) => &record.key >= key,
323 None => true,
324 };
325
326 let requirement2 = || match &primary_key {
330 Some(primary_key) => key.as_ref().is_some_and(|key| {
331 (&record.key == key && &record.primary_key >= primary_key) ||
332 &record.key > key
333 }),
334 _ => true,
335 };
336
337 let requirement3 = || match (&position, source) {
340 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
341 _ => true,
342 };
343
344 let requirement4 = || match (&position, source) {
348 (Some(position), ObjectStoreOrIndex::Index(_)) => {
349 (&record.key == position &&
350 object_store_position.as_ref().is_some_and(
351 |object_store_position| &record.primary_key > object_store_position,
352 )) ||
353 &record.key > position
354 },
355 _ => true,
356 };
357
358 let requirement5 = || range.contains(&record.key);
360
361 requirement1() &&
363 requirement2() &&
364 requirement3() &&
365 requirement4() &&
366 requirement5()
367 }),
368 IDBCursorDirection::Nextunique => records.iter().find(|record| {
370 let requirement1 = || match &key {
375 Some(key) => &record.key >= key,
376 None => true,
377 };
378
379 let requirement2 = || match &position {
381 Some(position) => &record.key > position,
382 None => true,
383 };
384
385 let requirement3 = || range.contains(&record.key);
387
388 requirement1() && requirement2() && requirement3()
390 }),
391 IDBCursorDirection::Prev => {
393 records.iter().rev().find(|&record| {
394 let requirement1 = || match &key {
399 Some(key) => &record.key <= key,
400 None => true,
401 };
402
403 let requirement2 = || match &primary_key {
407 Some(primary_key) => key.as_ref().is_some_and(|key| {
408 (&record.key == key && &record.primary_key <= primary_key) ||
409 &record.key < key
410 }),
411 _ => true,
412 };
413
414 let requirement3 = || match (&position, source) {
417 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
418 &record.key < position
419 },
420 _ => true,
421 };
422
423 let requirement4 = || match (&position, source) {
427 (Some(position), ObjectStoreOrIndex::Index(_)) => {
428 (&record.key == position &&
429 object_store_position.as_ref().is_some_and(
430 |object_store_position| {
431 &record.primary_key < object_store_position
432 },
433 )) ||
434 &record.key < position
435 },
436 _ => true,
437 };
438
439 let requirement5 = || range.contains(&record.key);
441
442 requirement1() &&
444 requirement2() &&
445 requirement3() &&
446 requirement4() &&
447 requirement5()
448 })
449 },
450 IDBCursorDirection::Prevunique => records
452 .iter()
453 .rev()
454 .find(|&record| {
455 let requirement1 = || match &key {
460 Some(key) => &record.key <= key,
461 None => true,
462 };
463
464 let requirement2 = || match &position {
466 Some(position) => &record.key < position,
467 None => true,
468 };
469
470 let requirement3 = || range.contains(&record.key);
472
473 requirement1() && requirement2() && requirement3()
475 })
476 .map(|temp_record| {
479 records
480 .iter()
481 .find(|&record| record.key == temp_record.key)
482 .expect(
483 "Record with key equal to temp record's key should exist in records",
484 )
485 }),
486 };
487
488 match found_record {
489 None => {
491 cursor.set_key(None);
493
494 if matches!(source, ObjectStoreOrIndex::Index(_)) {
496 cursor.set_object_store_position(None);
497 }
498
499 if !cursor.key_only {
501 cursor.value.set(UndefinedValue());
502 }
503
504 return Ok(None);
506 },
507 Some(found_record) => {
508 position = Some(found_record.key.clone());
510
511 if matches!(source, ObjectStoreOrIndex::Index(_)) {
513 cursor.set_object_store_position(Some(found_record.primary_key.clone()));
514 }
515
516 count -= 1;
518 },
519 }
520 }
521 let found_record =
522 found_record.expect("The while loop above guarantees found_record is defined");
523
524 cursor.set_position(position);
526
527 if let ObjectStoreOrIndex::Index(_) = source {
529 cursor.set_object_store_position(object_store_position);
530 }
531
532 cursor.set_key(Some(found_record.key.clone()));
534
535 if !cursor.key_only {
537 rooted!(&in(cx) let mut new_cursor_value = UndefinedValue());
540 postcard::from_bytes(&found_record.value)
541 .map_err(|_| Error::Data(None))
542 .and_then(|data| {
543 structuredclone::read(
544 global,
545 data,
546 new_cursor_value.handle_mut(),
547 CanGc::from_cx(cx),
548 )
549 })?;
550 cursor.value.set(new_cursor_value.get());
551 }
552
553 cursor.got_value.set(true);
555
556 Ok(Some(cursor))
558}