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