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