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
5#![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, NullValue, UndefinedValue};
13use js::rust::{HandleValue, MutableHandleValue};
14use script_bindings::cell::DomRefCell;
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16use storage_traits::indexeddb::{
17 AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyRange,
18 IndexedDBKeyType, IndexedDBRecord,
19};
20
21use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
22 IDBCursorDirection, IDBCursorMethods,
23};
24use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
25use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
26use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
29use crate::dom::bindings::structuredclone;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::indexeddb::idbindex::IDBIndex;
32use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
33use crate::dom::indexeddb::idbrequest::IDBRequest;
34use crate::dom::indexeddb::idbtransaction::IDBTransaction;
35use crate::dom::indexeddb::key::{
36 ExtractionResult, convert_value_to_key, extract_key, key_type_to_jsval,
37};
38
39#[derive(JSTraceable, MallocSizeOf)]
40#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41pub(crate) enum ObjectStoreOrIndex {
42 ObjectStore(Dom<IDBObjectStore>),
43 Index(Dom<IDBIndex>),
44}
45
46#[dom_struct]
47pub(crate) struct IDBCursor {
48 reflector_: Reflector,
49
50 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-transaction>
51 transaction: Dom<IDBTransaction>,
52 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-range>
53 #[no_trace]
54 range: IndexedDBKeyRange,
55 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-source>
56 source: ObjectStoreOrIndex,
57 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-direction>
58 direction: IDBCursorDirection,
59 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-position>
60 #[no_trace]
61 position: DomRefCell<Option<IndexedDBKeyType>>,
62 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-key>
63 #[no_trace]
64 key: DomRefCell<Option<IndexedDBKeyType>>,
65 #[ignore_malloc_size_of = "mozjs"]
66 cached_key: DomRefCell<Option<Heap<JSVal>>>,
67 #[ignore_malloc_size_of = "mozjs"]
68 cached_primary_key: DomRefCell<Option<Heap<JSVal>>>,
69 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-value>
70 #[ignore_malloc_size_of = "mozjs"]
71 value: Heap<JSVal>,
72 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-got-value-flag>
73 got_value: Cell<bool>,
74 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-object-store-position>
75 #[no_trace]
76 object_store_position: DomRefCell<Option<IndexedDBKeyType>>,
77 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-key-only-flag>
78 key_only: bool,
79
80 /// <https://w3c.github.io/IndexedDB/#cursor-request>
81 request: MutNullableDom<IDBRequest>,
82}
83
84impl IDBCursor {
85 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
86 pub(crate) fn new_inherited(
87 transaction: &IDBTransaction,
88 direction: IDBCursorDirection,
89 got_value: bool,
90 source: ObjectStoreOrIndex,
91 range: IndexedDBKeyRange,
92 key_only: bool,
93 ) -> IDBCursor {
94 IDBCursor {
95 reflector_: Reflector::new(),
96 transaction: Dom::from_ref(transaction),
97 range,
98 source,
99 direction,
100 position: DomRefCell::new(None),
101 key: DomRefCell::new(None),
102 cached_key: DomRefCell::new(None),
103 cached_primary_key: DomRefCell::new(None),
104 value: Heap::default(),
105 got_value: Cell::new(got_value),
106 object_store_position: DomRefCell::new(None),
107 key_only,
108 request: Default::default(),
109 }
110 }
111
112 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
113 #[allow(clippy::too_many_arguments)]
114 pub(crate) fn new(
115 cx: &mut JSContext,
116 global: &GlobalScope,
117 transaction: &IDBTransaction,
118 direction: IDBCursorDirection,
119 got_value: bool,
120 source: ObjectStoreOrIndex,
121 range: IndexedDBKeyRange,
122 key_only: bool,
123 ) -> DomRoot<IDBCursor> {
124 reflect_dom_object(
125 cx,
126 Box::new(IDBCursor::new_inherited(
127 transaction,
128 direction,
129 got_value,
130 source,
131 range,
132 key_only,
133 )),
134 global,
135 )
136 }
137
138 fn set_position(&self, position: Option<IndexedDBKeyType>) {
139 let changed = *self.position.borrow() != position;
140 *self.position.borrow_mut() = position;
141 if changed {
142 *self.cached_primary_key.borrow_mut() = None;
143 }
144 }
145
146 fn set_key(&self, key: Option<IndexedDBKeyType>) {
147 let key_changed = {
148 let current_key = self.key.borrow();
149 current_key.as_ref() != key.as_ref()
150 };
151 *self.key.borrow_mut() = key;
152 if key_changed {
153 *self.cached_key.borrow_mut() = None;
154 }
155 }
156
157 fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
158 let changed = *self.object_store_position.borrow() != object_store_position;
159 *self.object_store_position.borrow_mut() = object_store_position;
160 if changed {
161 *self.cached_primary_key.borrow_mut() = None;
162 }
163 }
164
165 pub(crate) fn set_request(&self, request: &IDBRequest) {
166 self.request.set(Some(request));
167 }
168
169 pub(crate) fn value(&self, mut out: MutableHandleValue) {
170 out.set(self.value.get());
171 }
172
173 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-effective-key>
174 pub(crate) fn effective_key(&self) -> Option<IndexedDBKeyType> {
175 match &self.source {
176 ObjectStoreOrIndex::ObjectStore(_) => self.position.borrow().clone(),
177 ObjectStoreOrIndex::Index(_) => self.object_store_position.borrow().clone(),
178 }
179 }
180
181 /// <https://www.w3.org/TR/IndexedDB-3/#cursor-effective-object-store>
182 pub(crate) fn effective_object_store(&self) -> DomRoot<IDBObjectStore> {
183 match &self.source {
184 ObjectStoreOrIndex::ObjectStore(store) => store.as_rooted(),
185 ObjectStoreOrIndex::Index(index) => index.object_store(),
186 }
187 }
188
189 pub(crate) fn verify_not_deleted(&self) -> ErrorResult {
190 match &self.source {
191 ObjectStoreOrIndex::ObjectStore(store) => store.verify_not_deleted(),
192 ObjectStoreOrIndex::Index(index) => index.verify_not_deleted(),
193 }
194 }
195
196 pub(crate) fn check_transaction_active(&self) -> ErrorResult {
197 if !self.transaction.is_active() || !self.transaction.is_usable() {
198 Err(Error::TransactionInactive(Some(
199 "Transaction is not active".to_owned(),
200 )))
201 } else {
202 Ok(())
203 }
204 }
205
206 pub(crate) fn check_transaction_readwrite(&self) -> ErrorResult {
207 if let IDBTransactionMode::Readonly = self.transaction.get_mode() {
208 return Err(Error::ReadOnly(Some(
209 "Transaction is a read-only transaction".to_owned(),
210 )));
211 }
212 Ok(())
213 }
214}
215
216impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
217 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-source>
218 fn Source(&self) -> IDBObjectStoreOrIDBIndex {
219 match &self.source {
220 ObjectStoreOrIndex::ObjectStore(source) => {
221 IDBObjectStoreOrIDBIndex::IDBObjectStore(source.as_rooted())
222 },
223 ObjectStoreOrIndex::Index(source) => {
224 IDBObjectStoreOrIDBIndex::IDBIndex(source.as_rooted())
225 },
226 }
227 }
228
229 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-direction>
230 fn Direction(&self) -> IDBCursorDirection {
231 self.direction
232 }
233
234 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-key>
235 fn Key(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
236 // The key getter steps are to return the result of converting a key to a value with the cursor’s current key.
237 //
238 // NOTE: If key returns an object (e.g. a Date or Array), it returns the
239 // same object instance every time it is inspected, until the cursor’s key is changed.
240 // This means that if the object is modified, those modifications will be seen by
241 // anyone inspecting the value of the cursor. However modifying such an object does not
242 // modify the contents of the database.
243 if let Some(cached) = &*self.cached_key.borrow() {
244 value.set(cached.get());
245 return;
246 }
247
248 match self.key.borrow().as_ref() {
249 Some(key) => key_type_to_jsval(cx, key, value.reborrow()),
250 None => value.set(UndefinedValue()),
251 }
252
253 *self.cached_key.borrow_mut() = Some(Heap::default());
254 self.cached_key.borrow().as_ref().unwrap().set(value.get());
255 }
256
257 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-primarykey>
258 fn PrimaryKey(&self, cx: &mut JSContext, mut value: MutableHandleValue) {
259 // NOTE: If primaryKey returns an object (e.g. a Date or Array),
260 // it returns the same object instance every time it is inspected,
261 // until the cursor’s effective key is changed. This means that if the object is modified,
262 // those modifications will be seen by anyone inspecting the value of the cursor.
263 // However modifying such an object does not modify the contents of the database.
264 if let Some(cached) = &*self.cached_primary_key.borrow() {
265 value.set(cached.get());
266 return;
267 }
268
269 match self.effective_key() {
270 Some(effective_key) => key_type_to_jsval(cx, &effective_key, value.reborrow()),
271 None => value.set(UndefinedValue()),
272 }
273
274 *self.cached_primary_key.borrow_mut() = Some(Heap::default());
275 self.cached_primary_key
276 .borrow()
277 .as_ref()
278 .unwrap()
279 .set(value.get());
280 }
281
282 /// <https://w3c.github.io/IndexedDB/#dom-idbcursor-request>
283 fn Request(&self) -> DomRoot<IDBRequest> {
284 self.request
285 .get()
286 .expect("IDBCursor.request should be set when cursor is opened")
287 }
288
289 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-advance>
290 fn Advance(&self, cx: &mut JSContext, count: u32) -> ErrorResult {
291 // Step 1: If count is 0 (zero), throw a TypeError.
292 if count == 0 {
293 return Err(Error::Type(c"Count cannot be zero".to_owned()));
294 }
295
296 // Step 2: Let transaction be this cursor's transaction.
297 // Step 3: If transaction is not active, throw a "TransactionInactiveError" DOMException.
298 self.check_transaction_active()?;
299
300 // Step 4: If the cursor's source or effective object store has been deleted, throw an
301 // "InvalidStateError" DOMException.
302 self.verify_not_deleted()?;
303
304 // Step 5: If this cursor's got value flag is unset, indicating that the cursor is being
305 // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
306 if !self.got_value.get() {
307 return Err(Error::InvalidState(Some(
308 "The cursor is being iterated or has iterated past its end".to_owned(),
309 )));
310 }
311
312 // Step 7: Unset the got value flag on the cursor.
313 self.got_value.set(false);
314
315 // Step 8: Let request be the request created when this cursor was created.
316 let request = self.Request();
317
318 // Step 9: Unset the request's done flag on request.
319 request.unset_done_flag();
320
321 // Step 10: Run the steps to asynchronously execute a request with the cursor's source as
322 // source, the steps to iterate a cursor as operation and request, using the current Realm
323 // as targetRealm, this cursor and count.
324 let iteration_param = IterationParam {
325 cursor: Trusted::new(self),
326 key: None,
327 primary_key: None,
328 count: Some(count),
329 };
330
331 let range = self.range.clone();
332 // NOTE: According to the spec, execute_async should take the cursor's source
333 // (which may be an index). Using effective_object_store here is a temporary
334 // workaround until execute_async supports ObjectStoreOrIndex as source.
335 IDBRequest::execute_async(
336 cx,
337 &self.effective_object_store(),
338 |callback| {
339 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
340 callback,
341 key_range: range,
342 })
343 },
344 Some(request),
345 Some(iteration_param),
346 )?;
347
348 Ok(())
349 }
350
351 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-continue>
352 fn Continue(&self, cx: &mut JSContext, key: HandleValue) -> ErrorResult {
353 // Step 1: Let transaction be this cursor's transaction.
354 // Step 2: If transaction is not active, throw a "TransactionInactiveError" DOMException.
355 self.check_transaction_active()?;
356
357 // Step 3: If the cursor's source or effective object store has been deleted, throw an
358 // "InvalidStateError" DOMException.
359 self.verify_not_deleted()?;
360
361 // Step 4: If this cursor's got value flag is unset, indicating that the cursor is being
362 // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
363 if !self.got_value.get() {
364 return Err(Error::InvalidState(Some(
365 "The cursor is being iterated or has iterated past its end".to_owned(),
366 )));
367 }
368
369 // Step 5: If key is given, then:
370 let key = if !key.is_undefined() {
371 // Step 5.1: Let r be the result of running the steps to convert a value to a key with
372 // key. Rethrow any exceptions.
373 // Step 5.2: If r is invalid, throw a "DataError" DOMException.
374 // Step 5.3: Set key to r.
375 let key = convert_value_to_key(cx, key, None)?.into_result()?;
376
377 // Step 5.4: If key is less than or equal to this cursor's position and this cursor's
378 // direction is "next" or "nextunique", throw a "DataError" DOMException.
379 if let Some(position) = self.position.borrow().as_ref() {
380 match self.direction {
381 IDBCursorDirection::Next | IDBCursorDirection::Nextunique => {
382 if &key <= position {
383 return Err(Error::Data(Some(
384 "Key is less than or equal to this cursor's position".to_owned(),
385 )));
386 }
387 },
388 // Step 5.5: If key is greater than or equal to this cursor's position
389 // and this cursor's direction is "prev" or "prevunique", throw a
390 // "DataError" DOMException.
391 IDBCursorDirection::Prev | IDBCursorDirection::Prevunique => {
392 if &key >= position {
393 return Err(Error::Data(Some(
394 "Key is greater than or equal to this cursor's position".to_owned(),
395 )));
396 }
397 },
398 }
399 }
400
401 Some(key)
402 } else {
403 None
404 };
405
406 // Step 6: Unset the got value flag on the cursor.
407 self.got_value.set(false);
408
409 // Step 7: Let request be the request created when this cursor was created.
410 let request = self.Request();
411
412 // Step 8: Unset the request's done flag on request.
413 request.unset_done_flag();
414
415 // Step 9: Run the steps to asynchronously execute a request with the cursor's source as
416 // source, the steps to iterate a cursor as operation and request, using the current Realm
417 // as targetRealm, this cursor and key (if given).
418 let iteration_param = IterationParam {
419 cursor: Trusted::new(self),
420 key,
421 primary_key: None,
422 count: None,
423 };
424
425 let range = self.range.clone();
426 // NOTE: According to the spec, execute_async should take the cursor's source
427 // (which may be an index). Using effective_object_store here is a temporary
428 // workaround until execute_async supports ObjectStoreOrIndex as source.
429 IDBRequest::execute_async(
430 cx,
431 &self.effective_object_store(),
432 |callback| {
433 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
434 callback,
435 key_range: range,
436 })
437 },
438 Some(request),
439 Some(iteration_param),
440 )?;
441
442 Ok(())
443 }
444
445 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-continueprimarykey>
446 fn ContinuePrimaryKey(
447 &self,
448 cx: &mut JSContext,
449 key: HandleValue,
450 primary_key: HandleValue,
451 ) -> ErrorResult {
452 // Step 1: Let transaction be this cursor's transaction.
453 // Step 2: If transaction is not active, throw a "TransactionInactiveError" DOMException.
454 self.check_transaction_active()?;
455
456 // Step 3: If the cursor's source or effective object store has been deleted, throw an
457 // "InvalidStateError" DOMException.
458 self.verify_not_deleted()?;
459
460 // Step 4: If this cursor's source is not an index, throw an "InvalidAccessError"
461 // DOMException.
462 if !matches!(self.source, ObjectStoreOrIndex::Index(_)) {
463 return Err(Error::InvalidAccess(Some(
464 "The cursor's source is not an index".to_owned(),
465 )));
466 }
467
468 // Step 5: If this cursor's direction is not "next" or "prev", throw an "InvalidAccessError"
469 // DOMException.
470 if !matches!(
471 self.direction,
472 IDBCursorDirection::Next | IDBCursorDirection::Prev
473 ) {
474 return Err(Error::InvalidAccess(Some(
475 "The cursor's direction is not 'next' or 'prev'".to_owned(),
476 )));
477 }
478
479 // Step 6: If this cursor's got value flag is unset, indicating that the cursor is being
480 // iterated or has iterated past its end, throw an "InvalidStateError" DOMException.
481 if !self.got_value.get() {
482 return Err(Error::InvalidState(Some(
483 "The cursor is being iterated or has iterated past its end".to_owned(),
484 )));
485 }
486
487 // Step 7: Let r be the result of running the steps to convert a value to a key with key.
488 // Step 8: If r is invalid, throw a "DataError" DOMException.
489 // Step 9: Set key to r.
490 let key = convert_value_to_key(cx, key, None)?.into_result()?;
491
492 // Step 10: Let r be the result of running the steps to convert a value to a key with
493 // primaryKey.
494 // Step 11: If r is invalid, throw a "DataError" DOMException.
495 // Step 12: Set primaryKey to r.
496 let primary_key = convert_value_to_key(cx, primary_key, None)?.into_result()?;
497
498 let position = self.position.borrow().clone();
499 let object_store_position = self.object_store_position.borrow().clone();
500
501 match self.direction {
502 IDBCursorDirection::Next => {
503 // Step 13: If key is less than this cursor's position and this cursor's direction
504 // is "next", throw a "DataError" DOMException.
505 if let Some(pos) = &position {
506 if &key < pos {
507 return Err(Error::Data(Some(
508 "Key is less than this cursor's position".to_owned(),
509 )));
510 }
511 // Step 15: If key is equal to this cursor's position and primaryKey is less
512 // than or equal to this cursor's object store position and this cursor's
513 // direction is "next", throw a "DataError" DOMException.
514 if &key == pos &&
515 object_store_position
516 .as_ref()
517 .is_some_and(|osp| &primary_key <= osp)
518 {
519 return Err(Error::Data(Some(
520 "Primary key is less than or equal to object store position".to_owned(),
521 )));
522 }
523 }
524 },
525 IDBCursorDirection::Prev => {
526 // Step 14: If key is greater than this cursor's position and this cursor's
527 // direction is "prev", throw a "DataError" DOMException.
528 if let Some(pos) = &position {
529 if &key > pos {
530 return Err(Error::Data(Some(
531 "Key is greater than this cursor's position".to_owned(),
532 )));
533 }
534 // Step 16: If key is equal to this cursor's position and primaryKey is greater
535 // than or equal to this cursor's object store position and this cursor's
536 // direction is "prev", throw a "DataError" DOMException.
537 if &key == pos &&
538 object_store_position
539 .as_ref()
540 .is_some_and(|osp| &primary_key >= osp)
541 {
542 return Err(Error::Data(Some(
543 "Primary key is greater than or equal to object store position"
544 .to_owned(),
545 )));
546 }
547 }
548 },
549 _ => unreachable!("Guaranteed by Step 5"),
550 }
551
552 // Step 17: Unset the got value flag on the cursor.
553 self.got_value.set(false);
554
555 // Step 18: Let request be the request created when this cursor was created.
556 let request = self.Request();
557
558 // Step 19: Unset the request's done flag on request.
559 request.unset_done_flag();
560
561 // Step 20: Run the steps to asynchronously execute a request with the cursor's source as
562 // source, the steps to iterate a cursor as operation and request, using the current Realm
563 // as targetRealm, this cursor, key and primaryKey.
564 let iteration_param = IterationParam {
565 cursor: Trusted::new(self),
566 key: Some(key),
567 primary_key: Some(primary_key),
568 count: None,
569 };
570
571 let range = self.range.clone();
572 // NOTE: According to the spec, execute_async should take the cursor's source
573 // (which may be an index). Using effective_object_store here is a temporary
574 // workaround until execute_async supports ObjectStoreOrIndex as source.
575 IDBRequest::execute_async(
576 cx,
577 &self.effective_object_store(),
578 |callback| {
579 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
580 callback,
581 key_range: range,
582 })
583 },
584 Some(request),
585 Some(iteration_param),
586 )?;
587
588 Ok(())
589 }
590
591 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-update>
592 fn Update(&self, cx: &mut JSContext, value: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
593 // Step 1. Let transaction be this's transaction.
594 // Step 2. If transaction's state is not active, then throw a "TransactionInactiveError"
595 // DOMException.
596 self.check_transaction_active()?;
597
598 // Step 3. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
599 self.check_transaction_readwrite()?;
600
601 // Step 4. If this's source or effective object store has been deleted, throw an
602 // "InvalidStateError" DOMException.
603 self.verify_not_deleted()?;
604 let effective_object_store = self.effective_object_store();
605 effective_object_store.verify_not_deleted()?;
606
607 // Step 5. If this's got value flag is false, indicating that the cursor is being iterated
608 // or has iterated past its end, throw an "InvalidStateError" DOMException.
609 if !self.got_value.get() {
610 return Err(Error::InvalidState(Some(
611 "The cursor is being iterated or has iterated past its end".to_owned(),
612 )));
613 }
614
615 // Step 6. If this's key only flag is true, throw an "InvalidStateError" DOMException.
616 if self.key_only {
617 return Err(Error::InvalidState(Some(
618 "This cursor is a key-only cursor".to_owned(),
619 )));
620 }
621
622 // Step 7. Let targetRealm be a user-agent defined Realm.
623 // Step 8. Let clone be a clone of value in targetRealm during transaction. Rethrow any
624 // exceptions.
625 rooted!(&in(cx) let mut cloned_js_value = NullValue());
626 effective_object_store.clone_value_in_target_realm(
627 cx,
628 value,
629 cloned_js_value.handle_mut(),
630 )?;
631
632 // Step 9. If this's effective object store uses in-line keys, then:
633 if effective_object_store.uses_inline_keys() {
634 // Step 9.1. Let kpk be the result of extracting a key from a value using a key path
635 // with clone and the key path of this's effective object store. Rethrow any exceptions.
636 let key_path = effective_object_store
637 .key_path()
638 .expect("Uses in-line keys requires key_path to be Some");
639 let kpk = extract_key(cx, cloned_js_value.handle(), key_path, None)?;
640
641 // Step 9.2. If kpk is failure, invalid, or not equal to this's effective key, throw a
642 // "DataError" DOMException.
643 match kpk {
644 ExtractionResult::Failure | ExtractionResult::Invalid => {
645 return Err(Error::Data(Some(
646 "Failed to extract key from value using key path".to_owned(),
647 )));
648 },
649 ExtractionResult::Key(key) => {
650 if !self
651 .effective_key()
652 .is_some_and(|effective_key| effective_key == key)
653 {
654 return Err(Error::Data(Some(
655 "Extracted key does not match the effective key".to_owned(),
656 )));
657 }
658 },
659 }
660 }
661
662 let cloned_value = structuredclone::write(cx, cloned_js_value.handle(), None)?;
663 let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
664 return Err(Error::InvalidState(Some(
665 "Failed to serialize cloned value".to_owned(),
666 )));
667 };
668
669 // Step 10. Let operation be an algorithm to run store a record into an object store with
670 // this's effective object store, clone, this's effective key, and false.
671 // Step 11. Return the result (an IDBRequest) of running asynchronously execute a request
672 // with this and operation.
673 IDBRequest::execute_async(
674 cx,
675 &effective_object_store,
676 |callback| {
677 AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
678 callback,
679 key: self.effective_key(),
680 value: serialized_value,
681 should_overwrite: true,
682 key_generator_current_number: None,
683 })
684 },
685 None,
686 None,
687 )
688 }
689
690 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbcursor-delete>
691 fn Delete(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
692 // Step 1. Let transaction be this's transaction.
693 // Step 2. If transaction's state is not active, then throw a "TransactionInactiveError"
694 // DOMException.
695 self.check_transaction_active()?;
696
697 // Step 3. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
698 self.check_transaction_readwrite()?;
699
700 // Step 4. If this's source or effective object store has been deleted, throw an
701 // "InvalidStateError" DOMException.
702 self.verify_not_deleted()?;
703 let effective_object_store = self.effective_object_store();
704 effective_object_store.verify_not_deleted()?;
705
706 // Step 5. If this's got value flag is false, indicating that the cursor is being iterated
707 // or has iterated past its end, throw an "InvalidStateError" DOMException.
708 if !self.got_value.get() {
709 return Err(Error::InvalidState(Some(
710 "The cursor is being iterated or has iterated past its end".to_owned(),
711 )));
712 }
713
714 // Step 6. If this's key only flag is true, throw an "InvalidStateError" DOMException.
715 if self.key_only {
716 return Err(Error::InvalidState(Some(
717 "This cursor is a key-only cursor".to_owned(),
718 )));
719 }
720
721 // Step 7. Let operation be an algorithm to run delete records from an object store with
722 // this's effective object store and this's effective key.
723 // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request
724 // with this and operation.
725 let effective_key = self.effective_key().ok_or_else(|| {
726 Error::InvalidState(Some("Missing effective key on cursor".to_owned()))
727 })?;
728
729 IDBRequest::execute_async(
730 cx,
731 &effective_object_store,
732 |callback| {
733 AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
734 callback,
735 key_range: IndexedDBKeyRange::only(effective_key),
736 })
737 },
738 None,
739 None,
740 )
741 }
742}
743
744/// A struct containing parameters for
745/// <https://www.w3.org/TR/IndexedDB-3/#iterate-a-cursor>
746#[derive(Clone)]
747pub(crate) struct IterationParam {
748 pub(crate) cursor: Trusted<IDBCursor>,
749 pub(crate) key: Option<IndexedDBKeyType>,
750 pub(crate) primary_key: Option<IndexedDBKeyType>,
751 pub(crate) count: Option<u32>,
752}
753
754/// <https://www.w3.org/TR/IndexedDB-3/#iterate-a-cursor>
755///
756/// NOTE: Be cautious: this part of the specification seems to assume the cursor’s source is an
757/// index. Therefore,
758/// "record’s key" means the key of the record,
759/// "record’s value" means the primary key of the record, and
760/// "record’s referenced value" means the value of the record.
761pub(crate) fn iterate_cursor(
762 global: &GlobalScope,
763 cx: &mut JSContext,
764 param: &IterationParam,
765 records: Vec<IndexedDBRecord>,
766) -> Result<Option<DomRoot<IDBCursor>>, Error> {
767 // Unpack IterationParam
768 let cursor = param.cursor.root();
769 let key = param.key.clone();
770 let primary_key = param.primary_key.clone();
771 let count = param.count;
772
773 // Step 1. Let source be cursor’s source.
774 let source = &cursor.source;
775
776 // Step 2. Let direction be cursor’s direction.
777 let direction = cursor.direction;
778
779 // Step 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
780 if primary_key.is_some() {
781 assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
782 assert!(matches!(
783 direction,
784 IDBCursorDirection::Next | IDBCursorDirection::Prev
785 ));
786 }
787
788 // Step 4. Let records be the list of records in source.
789 // NOTE: It is given as a function parameter.
790
791 // Step 5. Let range be cursor’s range.
792 let range = &cursor.range;
793
794 // Step 6. Let position be cursor’s position.
795 let mut position = cursor.position.borrow().clone();
796
797 // Step 7. Let object store position be cursor’s object store position.
798 let mut object_store_position = cursor.object_store_position.borrow().clone();
799
800 // Step 8. If count is not given, let count be 1.
801 let mut count = count.unwrap_or(1);
802
803 let mut found_record: Option<&IndexedDBRecord> = None;
804
805 // Step 9. While count is greater than 0:
806 while count > 0 {
807 // Step 9.1. Switch on direction:
808 found_record = match direction {
809 // "next"
810 IDBCursorDirection::Next => records.iter().find(|record| {
811 // Let found record be the first record in records which satisfy all of the
812 // following requirements:
813
814 // If key is defined, the record’s key is greater than or equal to key.
815 let requirement1 = || match &key {
816 Some(key) => &record.key >= key,
817 None => true,
818 };
819
820 // If primaryKey is defined, the record’s key is equal to key and the record’s
821 // value is greater than or equal to primaryKey, or the record’s key is greater
822 // than key.
823 let requirement2 = || match &primary_key {
824 Some(primary_key) => key.as_ref().is_some_and(|key| {
825 (&record.key == key && &record.primary_key >= primary_key) ||
826 &record.key > key
827 }),
828 _ => true,
829 };
830
831 // If position is defined, and source is an object store, the record’s key is
832 // greater than position.
833 let requirement3 = || match (&position, source) {
834 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
835 _ => true,
836 };
837
838 // If position is defined, and source is an index, the record’s key is equal to
839 // position and the record’s value is greater than object store position or the
840 // record’s key is greater than position.
841 let requirement4 = || match (&position, source) {
842 (Some(position), ObjectStoreOrIndex::Index(_)) => {
843 (&record.key == position &&
844 object_store_position.as_ref().is_some_and(
845 |object_store_position| &record.primary_key > object_store_position,
846 )) ||
847 &record.key > position
848 },
849 _ => true,
850 };
851
852 // The record’s key is in range.
853 let requirement5 = || range.contains(&record.key);
854
855 // NOTE: Use closures here for lazy computation on requirements.
856 requirement1() &&
857 requirement2() &&
858 requirement3() &&
859 requirement4() &&
860 requirement5()
861 }),
862 // "nextunique"
863 IDBCursorDirection::Nextunique => records.iter().find(|record| {
864 // Let found record be the first record in records which satisfy all of the
865 // following requirements:
866
867 // If key is defined, the record’s key is greater than or equal to key.
868 let requirement1 = || match &key {
869 Some(key) => &record.key >= key,
870 None => true,
871 };
872
873 // If position is defined, the record’s key is greater than position.
874 let requirement2 = || match &position {
875 Some(position) => &record.key > position,
876 None => true,
877 };
878
879 // The record’s key is in range.
880 let requirement3 = || range.contains(&record.key);
881
882 // NOTE: Use closures here for lazy computation on requirements.
883 requirement1() && requirement2() && requirement3()
884 }),
885 // "prev"
886 IDBCursorDirection::Prev => {
887 records.iter().rev().find(|&record| {
888 // Let found record be the last record in records which satisfy all of the
889 // following requirements:
890
891 // If key is defined, the record’s key is less than or equal to key.
892 let requirement1 = || match &key {
893 Some(key) => &record.key <= key,
894 None => true,
895 };
896
897 // If primaryKey is defined, the record’s key is equal to key and the record’s
898 // value is less than or equal to primaryKey, or the record’s key is less than
899 // key.
900 let requirement2 = || match &primary_key {
901 Some(primary_key) => key.as_ref().is_some_and(|key| {
902 (&record.key == key && &record.primary_key <= primary_key) ||
903 &record.key < key
904 }),
905 _ => true,
906 };
907
908 // If position is defined, and source is an object store, the record’s key is
909 // less than position.
910 let requirement3 = || match (&position, source) {
911 (Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
912 &record.key < position
913 },
914 _ => true,
915 };
916
917 // If position is defined, and source is an index, the record’s key is equal to
918 // position and the record’s value is less than object store position or the
919 // record’s key is less than position.
920 let requirement4 = || match (&position, source) {
921 (Some(position), ObjectStoreOrIndex::Index(_)) => {
922 (&record.key == position &&
923 object_store_position.as_ref().is_some_and(
924 |object_store_position| {
925 &record.primary_key < object_store_position
926 },
927 )) ||
928 &record.key < position
929 },
930 _ => true,
931 };
932
933 // The record’s key is in range.
934 let requirement5 = || range.contains(&record.key);
935
936 // NOTE: Use closures here for lazy computation on requirements.
937 requirement1() &&
938 requirement2() &&
939 requirement3() &&
940 requirement4() &&
941 requirement5()
942 })
943 },
944 // "prevunique"
945 IDBCursorDirection::Prevunique => records
946 .iter()
947 .rev()
948 .find(|&record| {
949 // Let temp record be the last record in records which satisfy all of the
950 // following requirements:
951
952 // If key is defined, the record’s key is less than or equal to key.
953 let requirement1 = || match &key {
954 Some(key) => &record.key <= key,
955 None => true,
956 };
957
958 // If position is defined, the record’s key is less than position.
959 let requirement2 = || match &position {
960 Some(position) => &record.key < position,
961 None => true,
962 };
963
964 // The record’s key is in range.
965 let requirement3 = || range.contains(&record.key);
966
967 // NOTE: Use closures here for lazy computation on requirements.
968 requirement1() && requirement2() && requirement3()
969 })
970 // If temp record is defined, let found record be the first record in records
971 // whose key is equal to temp record’s key.
972 .map(|temp_record| {
973 records
974 .iter()
975 .find(|&record| record.key == temp_record.key)
976 .expect(
977 "Record with key equal to temp record's key should exist in records",
978 )
979 }),
980 };
981
982 match found_record {
983 // Step 9.2. If found record is not defined, then:
984 None => {
985 // Step 9.2.1. Set cursor’s key to undefined.
986 cursor.set_key(None);
987
988 // Step 9.2.2. If source is an index, set cursor’s object store position to undefined.
989 if matches!(source, ObjectStoreOrIndex::Index(_)) {
990 cursor.set_object_store_position(None);
991 }
992
993 // Step 9.2.3. If cursor’s key only flag is unset, set cursor’s value to undefined.
994 if !cursor.key_only {
995 cursor.value.set(UndefinedValue());
996 }
997
998 // Step 9.2.4. Return null.
999 return Ok(None);
1000 },
1001 Some(found_record) => {
1002 // Step 9.3. Let position be found record’s key.
1003 position = Some(found_record.key.clone());
1004
1005 // Step 9.4. If source is an index, let object store position be found record’s value.
1006 if matches!(source, ObjectStoreOrIndex::Index(_)) {
1007 object_store_position = Some(found_record.primary_key.clone());
1008 }
1009
1010 // Step 9.5. Decrease count by 1.
1011 count -= 1;
1012 },
1013 }
1014 }
1015 let found_record =
1016 found_record.expect("The while loop above guarantees found_record is defined");
1017
1018 // Step 10. Set cursor’s position to position.
1019 cursor.set_position(position);
1020
1021 // Step 11. If source is an index, set cursor’s object store position to object store position.
1022 if let ObjectStoreOrIndex::Index(_) = source {
1023 cursor.set_object_store_position(object_store_position);
1024 }
1025
1026 // Step 12. Set cursor’s key to found record’s key.
1027 cursor.set_key(Some(found_record.key.clone()));
1028
1029 // Step 13. If cursor’s key only flag is unset, then:
1030 if !cursor.key_only {
1031 // Step 13.1. Let serialized be found record’s referenced value.
1032 // Step 13.2. Set cursor’s value to ! StructuredDeserialize(serialized, targetRealm)
1033 rooted!(&in(cx) let mut new_cursor_value = UndefinedValue());
1034 postcard::from_bytes(&found_record.value)
1035 .map_err(|_| Error::Data(None))
1036 .and_then(|data| {
1037 structuredclone::read(cx, global, data, new_cursor_value.handle_mut())
1038 })?;
1039 cursor.value.set(new_cursor_value.get());
1040 }
1041
1042 // Step 14. Set cursor’s got value flag.
1043 cursor.got_value.set(true);
1044
1045 // Step 15. Return cursor.
1046 Ok(Some(cursor))
1047}