script/dom/indexeddb/idbobjectstore.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/. */
4use std::cell::Cell;
5use std::collections::HashMap;
6
7use base::generic_channel::{GenericSend, GenericSender};
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::conversions::ToJSValConvertible;
11use js::gc::MutableHandleValue;
12use js::jsval::NullValue;
13use js::rust::HandleValue;
14use script_bindings::codegen::GenericBindings::IDBObjectStoreBinding::IDBIndexParameters;
15use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
16use script_bindings::error::ErrorResult;
17use storage_traits::indexeddb::{
18 self, AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyType,
19 IndexedDBThreadMsg, SyncOperation,
20};
21
22use crate::dom::bindings::cell::DomRefCell;
23use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::IDBCursorDirection;
24use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
25use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
26use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
27 IDBTransactionMethods, IDBTransactionMode,
28};
29// We need to alias this name, otherwise test-tidy complains at &String reference.
30use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
31use crate::dom::bindings::error::{Error, Fallible};
32use crate::dom::bindings::refcounted::Trusted;
33use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
34use crate::dom::bindings::root::{Dom, DomRoot};
35use crate::dom::bindings::str::DOMString;
36use crate::dom::bindings::structuredclone;
37use crate::dom::domstringlist::DOMStringList;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::indexeddb::idbcursor::{IDBCursor, IterationParam, ObjectStoreOrIndex};
40use crate::dom::indexeddb::idbcursorwithvalue::IDBCursorWithValue;
41use crate::dom::indexeddb::idbindex::IDBIndex;
42use crate::dom::indexeddb::idbrequest::IDBRequest;
43use crate::dom::indexeddb::idbtransaction::IDBTransaction;
44use crate::indexeddb::{
45 ExtractionResult, can_inject_key_into_value, convert_value_to_key, convert_value_to_key_range,
46 extract_key, inject_key_into_value,
47};
48use crate::script_runtime::CanGc;
49
50#[derive(Clone, JSTraceable, MallocSizeOf)]
51pub enum KeyPath {
52 String(DOMString),
53 StringSequence(Vec<DOMString>),
54}
55
56impl From<StringOrStringSequence> for KeyPath {
57 fn from(value: StringOrStringSequence) -> Self {
58 match value {
59 StringOrStringSequence::String(s) => KeyPath::String(s),
60 StringOrStringSequence::StringSequence(ss) => KeyPath::StringSequence(ss),
61 }
62 }
63}
64
65impl From<indexeddb::KeyPath> for KeyPath {
66 fn from(value: indexeddb::KeyPath) -> Self {
67 match value {
68 indexeddb::KeyPath::String(string) => KeyPath::String(string.into()),
69 indexeddb::KeyPath::Sequence(ss) => {
70 KeyPath::StringSequence(ss.into_iter().map(Into::into).collect())
71 },
72 }
73 }
74}
75
76impl From<KeyPath> for indexeddb::KeyPath {
77 fn from(item: KeyPath) -> Self {
78 match item {
79 KeyPath::String(s) => Self::String(s.to_string()),
80 KeyPath::StringSequence(ss) => {
81 Self::Sequence(ss.into_iter().map(|s| s.to_string()).collect())
82 },
83 }
84 }
85}
86
87#[dom_struct]
88pub struct IDBObjectStore {
89 reflector_: Reflector,
90 name: DomRefCell<DOMString>,
91 key_path: Option<KeyPath>,
92 index_set: DomRefCell<HashMap<DOMString, Dom<IDBIndex>>>,
93 transaction: Dom<IDBTransaction>,
94 has_key_generator: bool,
95 key_generator_current_number: Cell<Option<i32>>,
96
97 // We store the db name in the object store to address backend operations
98 // that are keyed by (origin, database name, object store name).
99 db_name: DOMString,
100}
101
102impl IDBObjectStore {
103 pub fn new_inherited(
104 db_name: DOMString,
105 name: DOMString,
106 options: Option<&IDBObjectStoreParameters>,
107 key_generator_current_number: Option<i32>,
108 transaction: &IDBTransaction,
109 ) -> IDBObjectStore {
110 let key_path: Option<KeyPath> = match options {
111 Some(options) => options.keyPath.as_ref().map(|path| match path {
112 StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
113 StrOrStringSequence::StringSequence(inner) => {
114 KeyPath::StringSequence(inner.clone())
115 },
116 }),
117 None => None,
118 };
119 let has_key_generator = options.is_some_and(|options| options.autoIncrement);
120 let key_generator_current_number = if has_key_generator {
121 Some(key_generator_current_number.unwrap_or(1))
122 } else {
123 None
124 };
125
126 IDBObjectStore {
127 reflector_: Reflector::new(),
128 name: DomRefCell::new(name),
129 key_path,
130 index_set: DomRefCell::new(HashMap::new()),
131 transaction: Dom::from_ref(transaction),
132 has_key_generator,
133 key_generator_current_number: Cell::new(key_generator_current_number),
134 db_name,
135 }
136 }
137
138 pub fn new(
139 global: &GlobalScope,
140 db_name: DOMString,
141 name: DOMString,
142 options: Option<&IDBObjectStoreParameters>,
143 key_generator_current_number: Option<i32>,
144 can_gc: CanGc,
145 transaction: &IDBTransaction,
146 ) -> DomRoot<IDBObjectStore> {
147 reflect_dom_object(
148 Box::new(IDBObjectStore::new_inherited(
149 db_name,
150 name,
151 options,
152 key_generator_current_number,
153 transaction,
154 )),
155 global,
156 can_gc,
157 )
158 }
159
160 pub fn get_name(&self) -> DOMString {
161 self.name.borrow().clone()
162 }
163
164 pub(crate) fn transaction(&self) -> DomRoot<IDBTransaction> {
165 self.transaction.as_rooted()
166 }
167
168 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
169 self.global().storage_threads().sender()
170 }
171
172 fn has_key_generator(&self) -> bool {
173 self.has_key_generator
174 }
175
176 /// <https://w3c.github.io/IndexedDB/#generate-a-key>
177 fn generate_key_for_put(&self) -> Fallible<(IndexedDBKeyType, i32)> {
178 // Step 1. Let generator be store's key generator.
179 let Some(current_number) = self.key_generator_current_number.get() else {
180 return Err(Error::Data(None));
181 };
182 // Step 2. Let key be generator's current number.
183 let key = current_number as f64;
184 // Step 3. If key is greater than 2^53 (9007199254740992), then return failure.
185 if key > 9_007_199_254_740_992.0 {
186 return Err(Error::Constraint(None));
187 }
188 // Step 4. Increase generator's current number by 1.
189 let next_current_number = current_number
190 .checked_add(1)
191 .ok_or(Error::Constraint(None))?;
192 // Step 5. Return key.
193 Ok((IndexedDBKeyType::Number(key), next_current_number))
194 }
195
196 /// <https://w3c.github.io/IndexedDB/#possibly-update-the-key-generator>
197 fn possibly_update_the_key_generator(&self, key: &IndexedDBKeyType) -> Option<i32> {
198 // Step 1. If the type of key is not number, abort these steps.
199 let IndexedDBKeyType::Number(number) = key else {
200 return None;
201 };
202
203 // Step 2. Let value be the value of key.
204 let mut value = *number;
205 // Step 3. Set value to the minimum of value and 2^53 (9007199254740992).
206 value = value.min(9_007_199_254_740_992.0);
207 // Step 4. Set value to the largest integer not greater than value.
208 value = value.floor();
209 // Step 5. Let generator be store's key generator.
210 let current_number = self.key_generator_current_number.get()?;
211 // Step 6. If value is greater than or equal to generator's current number,
212 // then set generator's current number to value + 1.
213 if value < current_number as f64 {
214 return None;
215 }
216
217 let next = value + 1.0;
218 // Servo currently stores the key generator current number as i32.
219 // Saturate to keep "no more generated keys" behavior when this overflows.
220 if next >= i32::MAX as f64 {
221 return Some(i32::MAX);
222 }
223 Some(next as i32)
224 }
225
226 /// <https://www.w3.org/TR/IndexedDB-3/#object-store-in-line-keys>
227 fn uses_inline_keys(&self) -> bool {
228 self.key_path.is_some()
229 }
230
231 fn verify_not_deleted(&self) -> ErrorResult {
232 let db = self.transaction.Db();
233 if !db.object_store_exists(&self.name.borrow()) {
234 return Err(Error::InvalidState(None));
235 }
236 Ok(())
237 }
238
239 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
240 fn check_transaction_active(&self) -> Fallible<()> {
241 // Let transaction be this object store handle's transaction.
242 let transaction = &self.transaction;
243
244 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
245 // https://w3c.github.io/IndexedDB/#transaction-inactive
246 // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
247 // No requests can be made against the transaction when it is in this state.
248 if !transaction.is_active() || !transaction.is_usable() {
249 return Err(Error::TransactionInactive(None));
250 }
251
252 Ok(())
253 }
254
255 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
256 /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
257 fn check_readwrite_transaction_active(&self) -> Fallible<()> {
258 // Let transaction be this object store handle's transaction.
259 let transaction = &self.transaction;
260
261 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
262 self.check_transaction_active()?;
263
264 if let IDBTransactionMode::Readonly = transaction.get_mode() {
265 return Err(Error::ReadOnly(None));
266 }
267 Ok(())
268 }
269
270 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
271 fn put(
272 &self,
273 cx: &mut JSContext,
274 value: HandleValue,
275 key: HandleValue,
276 overwrite: bool,
277 ) -> Fallible<DomRoot<IDBRequest>> {
278 // Step 1. Let transaction be handle’s transaction.
279 // Step 2: Let store be this object store handle's object store.
280 // This is resolved in the `execute_async` function.
281 // Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
282 self.verify_not_deleted()?;
283
284 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
285 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
286 self.check_readwrite_transaction_active()?;
287
288 // Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
289 if !key.is_undefined() && self.uses_inline_keys() {
290 return Err(Error::Data(None));
291 }
292
293 // Step 7: If store uses out-of-line keys and has no key generator
294 // and key was not given, throw a "DataError" DOMException.
295 if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
296 return Err(Error::Data(None));
297 }
298
299 // Step 8. If key was given, then: convert a value to a key with key.
300 let serialized_key: Option<IndexedDBKeyType>;
301 let maybe_modified_cloned_value;
302 let key_generator_current_number_for_put: Option<i32>;
303
304 if !key.is_undefined() {
305 let key = convert_value_to_key(cx, key, None)?.into_result()?;
306 key_generator_current_number_for_put = self.possibly_update_the_key_generator(&key);
307 serialized_key = Some(key);
308 maybe_modified_cloned_value = None;
309 } else {
310 match self.key_path.as_ref() {
311 Some(key_path) => {
312 // Step 10. Let clone be a clone of value in targetRealm during transaction.
313 // Rethrow any exceptions.
314 let cloned_value = structuredclone::write(cx.into(), value, None)?;
315
316 rooted!(&in(cx) let mut cloned_js_value = NullValue());
317 let _ = structuredclone::read(
318 &self.global(),
319 cloned_value,
320 cloned_js_value.handle_mut(),
321 CanGc::from_cx(cx),
322 )?;
323
324 // TODO: Avoid this deserialize/re-serialize round-trip once extract/inject can
325 // operate directly on the structured clone payload.
326
327 // Step 11: We should use in-line keys instead
328 // Step 11.1: Let kpk be the result of running the steps to extract a
329 // key from a value using a key path with clone and store’s key path.
330 match extract_key(cx, cloned_js_value.handle(), key_path, None)? {
331 // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
332 ExtractionResult::Invalid => return Err(Error::Data(None)),
333 // Step 11.3. If kpk is not failure, let key be kpk.
334 ExtractionResult::Key(kpk) => {
335 key_generator_current_number_for_put =
336 self.possibly_update_the_key_generator(&kpk);
337 serialized_key = Some(kpk);
338 },
339 ExtractionResult::Failure => {
340 // Step 11.4. Otherwise:
341 // Step 11.4.1. If store does not have a key generator, throw
342 // a "DataError" DOMException.
343 if !self.has_key_generator() {
344 return Err(Error::Data(None));
345 }
346 let KeyPath::String(key_path) = key_path else {
347 return Err(Error::Data(None));
348 };
349 // Step 11.4.2. If the steps to check that a key could be injected
350 // into a value with clone and store’s key path return false, throw a
351 // "DataError" DOMException.
352 if !can_inject_key_into_value(cx, cloned_js_value.handle(), key_path)? {
353 return Err(Error::Data(None));
354 }
355 // Step 11.4.3. Let key be the result of generating a key for store.
356 let (generated_key, next_current_number) =
357 self.generate_key_for_put()?;
358 // Step 11.4.4. Inject key into value.
359 if !inject_key_into_value(
360 cx,
361 cloned_js_value.handle(),
362 &generated_key,
363 key_path,
364 )? {
365 return Err(Error::Data(None));
366 }
367 serialized_key = Some(generated_key);
368 key_generator_current_number_for_put = Some(next_current_number);
369 },
370 }
371
372 // Store the clone (possibly with an injected key path), without mutating
373 // the original JS value that was passed to add()/put().
374 maybe_modified_cloned_value = Some(structuredclone::write(
375 cx.into(),
376 cloned_js_value.handle(),
377 None,
378 )?);
379 },
380 None => {
381 if !self.has_key_generator() {
382 return Err(Error::Data(None));
383 }
384 // Out-of-line key generation happens in the backend as part of executing
385 // the put request, so script does not reserve a key here.
386 serialized_key = None;
387 maybe_modified_cloned_value = None;
388 key_generator_current_number_for_put = None;
389 },
390 }
391 }
392 // For paths that did not need key-path extraction/injection above, clone `value` here.
393 let cloned_value = match maybe_modified_cloned_value {
394 Some(cloned_value) => cloned_value,
395 None => structuredclone::write(cx.into(), value, None)?,
396 };
397 let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
398 return Err(Error::InvalidState(None));
399 };
400 // Step 12. Let operation be an algorithm to run store a record into an object store with store, clone, key, and no-overwrite flag.
401 // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request with handle and operation.
402 let request = IDBRequest::execute_async(
403 self,
404 |callback| {
405 AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
406 callback,
407 key: serialized_key,
408 value: serialized_value,
409 should_overwrite: overwrite,
410 key_generator_current_number: key_generator_current_number_for_put,
411 })
412 },
413 None,
414 None,
415 CanGc::from_cx(cx),
416 )?;
417 if let Some(next_key_generator_current_number) = key_generator_current_number_for_put {
418 self.key_generator_current_number
419 .set(Some(next_key_generator_current_number));
420 }
421 Ok(request)
422 }
423
424 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
425 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
426 fn open_cursor(
427 &self,
428 cx: &mut JSContext,
429 query: HandleValue,
430 direction: IDBCursorDirection,
431 key_only: bool,
432 ) -> Fallible<DomRoot<IDBRequest>> {
433 // Step 1. Let transaction be this object store handle's transaction.
434 // Step 2. Let store be this object store handle's object store.
435
436 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
437 self.verify_not_deleted()?;
438
439 // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
440 self.check_transaction_active()?;
441
442 // Step 5. Let range be the result of running the steps to convert a value to a key range
443 // with query. Rethrow any exceptions.
444 //
445 // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
446 // or not given, an unbounded key range is used.
447 let range = convert_value_to_key_range(cx, query, Some(false))?;
448
449 // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
450 // position, direction set to direction, got value flag unset, and undefined key and value.
451 // The source of cursor is store. The range of cursor is range.
452 //
453 // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
454 // interface as well.
455 let cursor = if key_only {
456 IDBCursor::new(
457 &self.global(),
458 &self.transaction,
459 direction,
460 false,
461 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
462 range.clone(),
463 key_only,
464 CanGc::from_cx(cx),
465 )
466 } else {
467 DomRoot::upcast(IDBCursorWithValue::new(
468 &self.global(),
469 &self.transaction,
470 direction,
471 false,
472 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
473 range.clone(),
474 key_only,
475 CanGc::from_cx(cx),
476 ))
477 };
478
479 // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
480 // created by these steps. The steps are run with this object store handle as source and
481 // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
482 // cursor.
483 let iteration_param = IterationParam {
484 cursor: Trusted::new(&cursor),
485 key: None,
486 primary_key: None,
487 count: None,
488 };
489
490 IDBRequest::execute_async(
491 self,
492 |callback| {
493 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
494 callback,
495 key_range: range,
496 })
497 },
498 None,
499 Some(iteration_param),
500 CanGc::from_cx(cx),
501 )
502 .inspect(|request| cursor.set_request(request))
503 }
504
505 pub(crate) fn add_index(
506 &self,
507 name: DOMString,
508 options: &IDBIndexParameters,
509 key_path: KeyPath,
510 can_gc: CanGc,
511 ) -> DomRoot<IDBIndex> {
512 let index = IDBIndex::new(
513 &self.global(),
514 DomRoot::from_ref(self),
515 name.clone(),
516 options.multiEntry,
517 options.unique,
518 key_path,
519 can_gc,
520 );
521 self.index_set
522 .borrow_mut()
523 .insert(name, Dom::from_ref(&index));
524 index
525 }
526}
527
528impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
529 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
530 fn Put(
531 &self,
532 cx: &mut JSContext,
533 value: HandleValue,
534 key: HandleValue,
535 ) -> Fallible<DomRoot<IDBRequest>> {
536 self.put(cx, value, key, true)
537 }
538
539 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-add>
540 fn Add(
541 &self,
542 cx: &mut JSContext,
543 value: HandleValue,
544 key: HandleValue,
545 ) -> Fallible<DomRoot<IDBRequest>> {
546 self.put(cx, value, key, false)
547 }
548
549 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-delete>
550 fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
551 // Step 1. Let transaction be this’s transaction.
552 // Step 2. Let store be this's object store.
553 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
554 self.verify_not_deleted()?;
555
556 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
557 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
558 self.check_readwrite_transaction_active()?;
559
560 // Step 6. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
561 let serialized_query = convert_value_to_key_range(cx, query, Some(true));
562 // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
563 // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
564 serialized_query.and_then(|key_range| {
565 IDBRequest::execute_async(
566 self,
567 |callback| {
568 AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
569 callback,
570 key_range,
571 })
572 },
573 None,
574 None,
575 CanGc::from_cx(cx),
576 )
577 })
578 }
579
580 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-clear>
581 fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
582 // Step 1. Let transaction be this’s transaction.
583 // Step 2. Let store be this's object store.
584 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
585 self.verify_not_deleted()?;
586
587 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
588 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
589 self.check_readwrite_transaction_active()?;
590
591 // Step 6. Let operation be an algorithm to run clear an object store with store.
592 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
593 IDBRequest::execute_async(
594 self,
595 |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
596 None,
597 None,
598 CanGc::from_cx(cx),
599 )
600 }
601
602 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-get>
603 fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
604 // Step 1. Let transaction be this’s transaction.
605 // Step 2. Let store be this's object store.
606 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
607 self.verify_not_deleted()?;
608
609 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
610 self.check_transaction_active()?;
611
612 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
613 let serialized_query = convert_value_to_key_range(cx, query, None);
614
615 // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
616 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
617 serialized_query.and_then(|q| {
618 IDBRequest::execute_async(
619 self,
620 |callback| {
621 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
622 callback,
623 key_range: q,
624 })
625 },
626 None,
627 None,
628 CanGc::from_cx(cx),
629 )
630 })
631 }
632
633 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getkey>
634 fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
635 // Step 1. Let transaction be this’s transaction.
636 // Step 2. Let store be this's object store.
637 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
638 self.verify_not_deleted()?;
639
640 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
641 self.check_transaction_active()?;
642
643 // Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
644 let serialized_query = convert_value_to_key_range(cx, query, None);
645
646 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
647 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
648 // store as operation, using store and range.
649 serialized_query.and_then(|q| {
650 IDBRequest::execute_async(
651 self,
652 |callback| {
653 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
654 callback,
655 key_range: q,
656 })
657 },
658 None,
659 None,
660 CanGc::from_cx(cx),
661 )
662 })
663 }
664
665 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getall>
666 fn GetAll(
667 &self,
668 cx: &mut JSContext,
669 query: HandleValue,
670 count: Option<u32>,
671 ) -> Fallible<DomRoot<IDBRequest>> {
672 // Step 1. Let transaction be this’s transaction.
673 // Step 2. Let store be this's object store.
674 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
675 self.verify_not_deleted()?;
676
677 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
678 self.check_transaction_active()?;
679
680 // Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
681 let serialized_query = convert_value_to_key_range(cx, query, None);
682
683 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
684 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
685 // store as operation, using store and range.
686 serialized_query.and_then(|q| {
687 IDBRequest::execute_async(
688 self,
689 |callback| {
690 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
691 callback,
692 key_range: q,
693 count,
694 })
695 },
696 None,
697 None,
698 CanGc::from_cx(cx),
699 )
700 })
701 }
702
703 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getallkeys>
704 fn GetAllKeys(
705 &self,
706 cx: &mut JSContext,
707 query: HandleValue,
708 count: Option<u32>,
709 ) -> Fallible<DomRoot<IDBRequest>> {
710 // Step 1. Let transaction be this’s transaction.
711 // Step 2. Let store be this's object store.
712 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
713 self.verify_not_deleted()?;
714
715 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
716 self.check_transaction_active()?;
717
718 // Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
719 let serialized_query = convert_value_to_key_range(cx, query, None);
720
721 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
722 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
723 // store as operation, using store and range.
724 serialized_query.and_then(|q| {
725 IDBRequest::execute_async(
726 self,
727 |callback| {
728 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
729 callback,
730 key_range: q,
731 count,
732 })
733 },
734 None,
735 None,
736 CanGc::from_cx(cx),
737 )
738 })
739 }
740
741 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-count>
742 fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
743 // Step 1. Let transaction be this’s transaction.
744 // Step 2. Let store be this's object store.
745 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
746 self.verify_not_deleted()?;
747
748 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
749 self.check_transaction_active()?;
750
751 // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
752 let serialized_query = convert_value_to_key_range(cx, query, None);
753
754 // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
755 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
756 serialized_query.and_then(|q| {
757 IDBRequest::execute_async(
758 self,
759 |callback| {
760 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
761 callback,
762 key_range: q,
763 })
764 },
765 None,
766 None,
767 CanGc::from_cx(cx),
768 )
769 })
770 }
771
772 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
773 fn OpenCursor(
774 &self,
775 cx: &mut JSContext,
776 query: HandleValue,
777 direction: IDBCursorDirection,
778 ) -> Fallible<DomRoot<IDBRequest>> {
779 self.open_cursor(cx, query, direction, false)
780 }
781
782 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
783 fn OpenKeyCursor(
784 &self,
785 cx: &mut JSContext,
786 query: HandleValue,
787 direction: IDBCursorDirection,
788 ) -> Fallible<DomRoot<IDBRequest>> {
789 self.open_cursor(cx, query, direction, true)
790 }
791
792 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
793 fn Name(&self) -> DOMString {
794 self.name.borrow().clone()
795 }
796
797 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-setname>
798 fn SetName(&self, value: DOMString) -> ErrorResult {
799 // Step 2. Let transaction be this’s transaction.
800 let transaction = &self.transaction;
801
802 // Step 3. Let store be this's object store.
803 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
804 self.verify_not_deleted()?;
805
806 // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
807 if transaction.Mode() != IDBTransactionMode::Versionchange {
808 return Err(Error::InvalidState(None));
809 }
810 // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
811 self.check_transaction_active()?;
812
813 *self.name.borrow_mut() = value;
814 Ok(())
815 }
816
817 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-keypath>
818 fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
819 match &self.key_path {
820 Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
821 Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
822 None => ret_val.set(NullValue()),
823 }
824 }
825
826 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-indexnames>
827 fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
828 DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
829 }
830
831 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-transaction>
832 fn Transaction(&self) -> DomRoot<IDBTransaction> {
833 self.transaction()
834 }
835
836 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-autoincrement>
837 fn AutoIncrement(&self) -> bool {
838 self.has_key_generator()
839 }
840
841 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-createindex>
842 fn CreateIndex(
843 &self,
844 name: DOMString,
845 key_path: StringOrStringSequence,
846 options: &IDBIndexParameters,
847 can_gc: CanGc,
848 ) -> Fallible<DomRoot<IDBIndex>> {
849 let key_path: KeyPath = key_path.into();
850 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
851 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
852 return Err(Error::InvalidState(None));
853 }
854
855 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
856 self.verify_not_deleted()?;
857 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
858 self.check_transaction_active()?;
859
860 // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
861 if self.index_set.borrow().contains_key(&name) {
862 return Err(Error::Constraint(None));
863 }
864
865 // TODO: Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
866 // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
867 // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
868 // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
869 if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
870 return Err(Error::InvalidAccess(None));
871 }
872
873 // Step 11. Let index be a new index in store.
874 // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
875 // If multiEntry is set, set index’s multiEntry flag.
876 let create_index_operation = SyncOperation::CreateIndex(
877 self.global().origin().immutable().clone(),
878 self.db_name.to_string(),
879 self.name.borrow().to_string(),
880 name.to_string(),
881 key_path.clone().into(),
882 options.unique,
883 options.multiEntry,
884 );
885 if self
886 .get_idb_thread()
887 .send(IndexedDBThreadMsg::Sync(create_index_operation))
888 .is_err()
889 {
890 return Err(Error::Operation(None));
891 }
892
893 // Step 12. Add index to this object store handle's index set.
894 let index = self.add_index(name, options, key_path, can_gc);
895
896 // Step 13. Return a new index handle associated with index and this object store handle.
897 Ok(index)
898 }
899
900 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-deleteindex>
901 fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
902 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
903 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
904 return Err(Error::InvalidState(None));
905 }
906 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
907 self.verify_not_deleted()?;
908 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
909 self.check_transaction_active()?;
910 // Step 6. Let index be the index named name in store if one exists,
911 // or throw a "NotFoundError" DOMException otherwise.
912 if !self.index_set.borrow().contains_key(&name) {
913 return Err(Error::NotFound(None));
914 }
915 // Step 7. Remove index from this object store handle's index set.
916 self.index_set.borrow_mut().retain(|n, _| n != &name);
917 // Step 8. Destroy index.
918 let delete_index_operation = SyncOperation::DeleteIndex(
919 self.global().origin().immutable().clone(),
920 self.db_name.to_string(),
921 self.name.borrow().to_string(),
922 name.to_string(),
923 );
924 self.get_idb_thread()
925 .send(IndexedDBThreadMsg::Sync(delete_index_operation))
926 .unwrap();
927 Ok(())
928 }
929
930 /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
931 fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
932 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
933 self.verify_not_deleted()?;
934
935 // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
936 if self.transaction.is_finished() {
937 return Err(Error::InvalidState(None));
938 }
939
940 // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
941 let index_set = self.index_set.borrow();
942 let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
943
944 // Step 6. Return an index handle associated with index and this.
945 Ok(index.as_rooted())
946 }
947}