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::cell::DomRefCell;
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
14use storage_traits::indexeddb::{IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord};
15
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::root::{Dom, DomRoot, MutNullableDom};
23use crate::dom::bindings::structuredclone;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::indexeddb::idbindex::IDBIndex;
26use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
27use crate::dom::indexeddb::idbrequest::IDBRequest;
28use crate::dom::indexeddb::idbtransaction::IDBTransaction;
29use crate::indexeddb::key_type_to_jsval;
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 transaction: Dom<IDBTransaction>,
45 #[no_trace]
47 range: IndexedDBKeyRange,
48 source: ObjectStoreOrIndex,
50 direction: IDBCursorDirection,
52 #[no_trace]
54 position: DomRefCell<Option<IndexedDBKeyType>>,
55 #[no_trace]
57 key: DomRefCell<Option<IndexedDBKeyType>>,
58 #[ignore_malloc_size_of = "mozjs"]
59 cached_key: DomRefCell<Option<Heap<JSVal>>>,
60 #[ignore_malloc_size_of = "mozjs"]
61 cached_primary_key: DomRefCell<Option<Heap<JSVal>>>,
62 #[ignore_malloc_size_of = "mozjs"]
64 value: Heap<JSVal>,
65 got_value: Cell<bool>,
67 #[no_trace]
69 object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
70 key_only: bool,
72
73 request: MutNullableDom<IDBRequest>,
75}
76
77impl IDBCursor {
78 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
79 pub(crate) fn new_inherited(
80 transaction: &IDBTransaction,
81 direction: IDBCursorDirection,
82 got_value: bool,
83 source: ObjectStoreOrIndex,
84 range: IndexedDBKeyRange,
85 key_only: bool,
86 ) -> IDBCursor {
87 IDBCursor {
88 reflector_: Reflector::new(),
89 transaction: Dom::from_ref(transaction),
90 range,
91 source,
92 direction,
93 position: DomRefCell::new(None),
94 key: DomRefCell::new(None),
95 cached_key: DomRefCell::new(None),
96 cached_primary_key: DomRefCell::new(None),
97 value: Heap::default(),
98 got_value: Cell::new(got_value),
99 object_store_position: DomRefCell::new(None),
100 key_only,
101 request: Default::default(),
102 }
103 }
104
105 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
106 #[allow(clippy::too_many_arguments)]
107 pub(crate) fn new(
108 cx: &mut JSContext,
109 global: &GlobalScope,
110 transaction: &IDBTransaction,
111 direction: IDBCursorDirection,
112 got_value: bool,
113 source: ObjectStoreOrIndex,
114 range: IndexedDBKeyRange,
115 key_only: bool,
116 ) -> DomRoot<IDBCursor> {
117 reflect_dom_object_with_cx(
118 Box::new(IDBCursor::new_inherited(
119 transaction,
120 direction,
121 got_value,
122 source,
123 range,
124 key_only,
125 )),
126 global,
127 cx,
128 )
129 }
130
131 fn set_position(&self, position: Option<IndexedDBKeyType>) {
132 let changed = *self.position.borrow() != position;
133 *self.position.borrow_mut() = position;
134 if changed {
135 *self.cached_primary_key.borrow_mut() = None;
136 }
137 }
138
139 fn set_key(&self, key: Option<IndexedDBKeyType>) {
140 let key_changed = {
141 let current_key = self.key.borrow();
142 current_key.as_ref() != key.as_ref()
143 };
144 *self.key.borrow_mut() = key;
145 if key_changed {
146 *self.cached_key.borrow_mut() = None;
147 }
148 }
149
150 fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
151 let changed = *self.object_store_position.borrow() != object_store_position;
152 *self.object_store_position.borrow_mut() = object_store_position;
153 if changed {
154 *self.cached_primary_key.borrow_mut() = None;
155 }
156 }
157
158 pub(crate) fn set_request(&self, request: &IDBRequest) {
159 self.request.set(Some(request));
160 }
161
162 pub(crate) fn value(&self, mut out: MutableHandleValue) {
163 out.set(self.value.get());
164 }
165
166 pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
168 match &self.source {
169 ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
170 ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
171 }
172 }
173}
174
175impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
176 fn Source(&self) -> IDBObjectStoreOrIDBIndex {
178 match &self.source {
179 ObjectStoreOrIndex::ObjectStore(source) => {
180 IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
181 },
182 ObjectStoreOrIndex::Index(source) => {
183 IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
184 },
185 }
186 }
187
188 fn Direction(&self) -> IDBCursorDirection {
190 self.direction
191 }
192
193 fn Key(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
195 if let Some(cached) = &*self.cached_key.borrow() {
203 value.set(cached.get());
204 return;
205 }
206
207 match self.key.borrow().as_ref() {
208 Some(key) => key_type_to_jsval(cx, key, value.reborrow()),
209 None => value.set(UndefinedValue()),
210 }
211
212 *self.cached_key.borrow_mut() = Some(Heap::default());
213 self.cached_key.borrow().as_ref().unwrap().set(value.get());
214 }
215
216 fn PrimaryKey(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
218 if let Some(cached) = &*self.cached_primary_key.borrow() {
224 value.set(cached.get());
225 return;
226 }
227
228 match self.effective_key() {
229 Some(effective_key) => key_type_to_jsval(cx, &effective_key, value.reborrow()),
230 None => value.set(UndefinedValue()),
231 }
232
233 *self.cached_primary_key.borrow_mut() = Some(Heap::default());
234 self.cached_primary_key
235 .borrow()
236 .as_ref()
237 .unwrap()
238 .set(value.get());
239 }
240
241 fn Request(&self) -> DomRoot<IDBRequest> {
243 self.request
244 .get()
245 .expect("IDBCursor.request should be set when cursor is opened")
246 }
247}
248
249#[derive(Clone)]
252pub(crate) struct IterationParam {
253 pub(crate) cursor: Trusted<IDBCursor>,
254 pub(crate) key: Option<IndexedDBKeyType>,
255 pub(crate) primary_key: Option<IndexedDBKeyType>,
256 pub(crate) count: Option<u32>,
257}
258
259pub(crate) fn iterate_cursor(
267 global: &GlobalScope,
268 cx: &mut JSContext,
269 param: &IterationParam,
270 records: Vec<IndexedDBRecord>,
271) -> Result<Option<DomRoot<IDBCursor>>, Error> {
272 let cursor = param.cursor.root();
274 let key = param.key.clone();
275 let primary_key = param.primary_key.clone();
276 let count = param.count;
277
278 let source = &cursor.source;
280
281 let direction = cursor.direction;
283
284 if primary_key.is_some() {
286 assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
287 assert!(matches!(
288 direction,
289 IDBCursorDirection::Next | IDBCursorDirection::Prev
290 ));
291 }
292
293 let range = &cursor.range;
298
299 let mut position = cursor.position.borrow().clone();
301
302 let object_store_position = cursor.object_store_position.borrow().clone();
304
305 let mut count = count.unwrap_or(1);
307
308 let mut found_record: Option<&IndexedDBRecord> = None;
309
310 while count > 0 {
312 found_record = match direction {
314 IDBCursorDirection::Next => records.iter().find(|record| {
316 let requirement1 = || match &key {
321 Some(key) => &record.key >= key,
322 None => true,
323 };
324
325 let requirement2 = || match &primary_key {
329 Some(primary_key) => key.as_ref().is_some_and(|key| {
330 (&record.key == key && &record.primary_key >= primary_key) ||
331 &record.key > key
332 }),
333 _ => true,
334 };
335
336 let requirement3 = || match (&position, source) {
339 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
340 _ => true,
341 };
342
343 let requirement4 = || match (&position, source) {
347 (Some(position), ObjectStoreOrIndex::Index(_)) => {
348 (&record.key == position &&
349 object_store_position.as_ref().is_some_and(
350 |object_store_position| &record.primary_key > object_store_position,
351 )) ||
352 &record.key > position
353 },
354 _ => true,
355 };
356
357 let requirement5 = || range.contains(&record.key);
359
360 requirement1() &&
362 requirement2() &&
363 requirement3() &&
364 requirement4() &&
365 requirement5()
366 }),
367 IDBCursorDirection::Nextunique => records.iter().find(|record| {
369 let requirement1 = || match &key {
374 Some(key) => &record.key >= key,
375 None => true,
376 };
377
378 let requirement2 = || match &position {
380 Some(position) => &record.key > position,
381 None => true,
382 };
383
384 let requirement3 = || range.contains(&record.key);
386
387 requirement1() && requirement2() && requirement3()
389 }),
390 IDBCursorDirection::Prev => {
392 records.iter().rev().find(|&record| {
393 let requirement1 = || match &key {
398 Some(key) => &record.key <= key,
399 None => true,
400 };
401
402 let requirement2 = || match &primary_key {
406 Some(primary_key) => key.as_ref().is_some_and(|key| {
407 (&record.key == key && &record.primary_key <= primary_key) ||
408 &record.key < key
409 }),
410 _ => true,
411 };
412
413 let requirement3 = || match (&position, source) {
416 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
417 &record.key < position
418 },
419 _ => true,
420 };
421
422 let requirement4 = || match (&position, source) {
426 (Some(position), ObjectStoreOrIndex::Index(_)) => {
427 (&record.key == position &&
428 object_store_position.as_ref().is_some_and(
429 |object_store_position| {
430 &record.primary_key < object_store_position
431 },
432 )) ||
433 &record.key < position
434 },
435 _ => true,
436 };
437
438 let requirement5 = || range.contains(&record.key);
440
441 requirement1() &&
443 requirement2() &&
444 requirement3() &&
445 requirement4() &&
446 requirement5()
447 })
448 },
449 IDBCursorDirection::Prevunique => records
451 .iter()
452 .rev()
453 .find(|&record| {
454 let requirement1 = || match &key {
459 Some(key) => &record.key <= key,
460 None => true,
461 };
462
463 let requirement2 = || match &position {
465 Some(position) => &record.key < position,
466 None => true,
467 };
468
469 let requirement3 = || range.contains(&record.key);
471
472 requirement1() && requirement2() && requirement3()
474 })
475 .map(|temp_record| {
478 records
479 .iter()
480 .find(|&record| record.key == temp_record.key)
481 .expect(
482 "Record with key equal to temp record's key should exist in records",
483 )
484 }),
485 };
486
487 match found_record {
488 None => {
490 cursor.set_key(None);
492
493 if matches!(source, ObjectStoreOrIndex::Index(_)) {
495 cursor.set_object_store_position(None);
496 }
497
498 if !cursor.key_only {
500 cursor.value.set(UndefinedValue());
501 }
502
503 return Ok(None);
505 },
506 Some(found_record) => {
507 position = Some(found_record.key.clone());
509
510 if matches!(source, ObjectStoreOrIndex::Index(_)) {
512 cursor.set_object_store_position(Some(found_record.primary_key.clone()));
513 }
514
515 count -= 1;
517 },
518 }
519 }
520 let found_record =
521 found_record.expect("The while loop above guarantees found_record is defined");
522
523 cursor.set_position(position);
525
526 if let ObjectStoreOrIndex::Index(_) = source {
528 cursor.set_object_store_position(object_store_position);
529 }
530
531 cursor.set_key(Some(found_record.key.clone()));
533
534 if !cursor.key_only {
536 rooted!(&in(cx) let mut new_cursor_value = UndefinedValue());
539 postcard::from_bytes(&found_record.value)
540 .map_err(|_| Error::Data(None))
541 .and_then(|data| {
542 structuredclone::read(cx, global, data, new_cursor_value.handle_mut())
543 })?;
544 cursor.value.set(new_cursor_value.get());
545 }
546
547 cursor.got_value.set(true);
549
550 Ok(Some(cursor))
552}