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, IndexedDBKeyType,
21 IndexedDBThreadMsg, SyncOperation,
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::indexeddb::{
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};
49use crate::script_runtime::CanGc;
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<DOMString>,
101 key_path: Option<KeyPath>,
102 index_set: DomRefCell<HashMap<DOMString, 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: DOMString,
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),
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,
163 }
164 }
165
166 pub fn new(
167 global: &GlobalScope,
168 db_name: DOMString,
169 name: DOMString,
170 options: Option<&IDBObjectStoreParameters>,
171 abort_state: IDBObjectStoreAbortState,
172 can_gc: CanGc,
173 transaction: &IDBTransaction,
174 ) -> DomRoot<IDBObjectStore> {
175 reflect_dom_object(
176 Box::new(IDBObjectStore::new_inherited(
177 db_name,
178 name,
179 options,
180 abort_state,
181 transaction,
182 )),
183 global,
184 can_gc,
185 )
186 }
187
188 pub fn get_name(&self) -> DOMString {
189 self.name.borrow().clone()
190 }
191
192 /// <https://w3c.github.io/IndexedDB/#abort-an-upgrade-transaction>
193 pub(crate) fn restore_metadata_after_abort(&self, can_gc: CanGc) {
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.borrow_mut() = name;
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.borrow_mut().clear();
209 for index in abort_state.rollback_indexes {
210 self.add_index(
211 index.name.clone().into(),
212 &IDBIndexParameters {
213 multiEntry: index.multi_entry,
214 unique: index.unique,
215 },
216 index.key_path.clone().into(),
217 can_gc,
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 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 fn uses_inline_keys(&self) -> bool {
323 self.key_path.is_some()
324 }
325
326 fn verify_not_deleted(&self) -> ErrorResult {
327 let db = self.transaction.Db();
328 if !db.object_store_exists(&self.name.borrow()) {
329 return Err(Error::InvalidState(None));
330 }
331 Ok(())
332 }
333
334 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
335 fn check_transaction_active(&self) -> Fallible<()> {
336 // Let transaction be this object store handle's transaction.
337 let transaction = &self.transaction;
338
339 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
340 // https://w3c.github.io/IndexedDB/#transaction-inactive
341 // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
342 // No requests can be made against the transaction when it is in this state.
343 if !transaction.is_active() || !transaction.is_usable() {
344 return Err(Error::TransactionInactive(None));
345 }
346
347 Ok(())
348 }
349
350 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
351 /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
352 fn check_readwrite_transaction_active(&self) -> Fallible<()> {
353 // Let transaction be this object store handle's transaction.
354 let transaction = &self.transaction;
355
356 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
357 self.check_transaction_active()?;
358
359 if let IDBTransactionMode::Readonly = transaction.get_mode() {
360 return Err(Error::ReadOnly(None));
361 }
362 Ok(())
363 }
364
365 /// <https://www.w3.org/TR/IndexedDB-3/#add-or-put>
366 fn put(
367 &self,
368 cx: &mut JSContext,
369 value: HandleValue,
370 key: HandleValue,
371 no_overwrite: bool,
372 ) -> Fallible<DomRoot<IDBRequest>> {
373 // Step 1. Let transaction be handle’s transaction.
374 // Step 2. Let store be handle’s object store.
375 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
376 self.verify_not_deleted()?;
377
378 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
379 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
380 self.check_readwrite_transaction_active()?;
381
382 // Step 6. If store uses in-line keys and key was given, throw a "DataError"
383 // DOMException.
384 if !key.is_undefined() && self.uses_inline_keys() {
385 return Err(Error::Data(None));
386 }
387
388 // Step 7. If store uses out-of-line keys and has no key generator and key
389 // was not given, throw a "DataError" DOMException.
390 if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
391 return Err(Error::Data(None));
392 }
393
394 // Step 8. If key was given, then:
395 let mut serialized_key = None;
396 let mut key_generator_current_number_for_put = None;
397
398 if !key.is_undefined() {
399 // Step 8.1. Let r be the result of converting a value to a key with key.
400 // Rethrow any exceptions.
401 let key = convert_value_to_key(cx, key, None)?.into_result()?;
402 // Step 8.2. If r is "invalid value" or "invalid type", throw a
403 // "DataError" DOMException.
404 // Handled by `into_result()` above.
405 // Step 8.3. Let key be r.
406 key_generator_current_number_for_put = self.possibly_update_the_key_generator(&key);
407 serialized_key = Some(key);
408 }
409
410 // Step 9. Let targetRealm be a user-agent defined Realm.
411 // Step 10. Let clone be a clone of value in targetRealm during transaction.
412 // Rethrow any exceptions.
413 rooted!(&in(cx) let mut cloned_js_value = NullValue());
414 self.clone_value_in_target_realm(cx, value, cloned_js_value.handle_mut())?;
415
416 // Step 11. If store uses in-line keys, then:
417 let cloned_value = match self.key_path.as_ref() {
418 Some(key_path) => {
419 // Step 11.1. Let kpk be the result of extracting a key from a value using a key
420 // path with clone and store’s key path. Rethrow any exceptions.
421 match extract_key(cx, cloned_js_value.handle(), key_path, None)? {
422 // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
423 ExtractionResult::Invalid => return Err(Error::Data(None)),
424 // Step 11.3. If kpk is not failure, let key be kpk.
425 ExtractionResult::Key(kpk) => {
426 key_generator_current_number_for_put =
427 self.possibly_update_the_key_generator(&kpk);
428 serialized_key = Some(kpk);
429 },
430 // Step 11.4. Otherwise (kpk is failure):
431 ExtractionResult::Failure => {
432 // Step 11.4.1. If store does not have a key generator, throw a
433 // "DataError" DOMException.
434 if !self.has_key_generator() {
435 return Err(Error::Data(None));
436 }
437 let KeyPath::String(key_path) = key_path else {
438 return Err(Error::Data(None));
439 };
440 // Step 11.4.2. If check that a key could be injected into a value with
441 // clone and store’s key path return false, throw a "DataError"
442 // DOMException.
443 if !can_inject_key_into_value(cx, cloned_js_value.handle(), key_path)? {
444 return Err(Error::Data(None));
445 }
446
447 // Prepares the generated key and injected clone here so Step 12 can
448 // pass the final key/value pair to the storage backend.
449 let (generated_key, next_current_number) = self.generate_key_for_put()?;
450 if !inject_key_into_value(
451 cx,
452 cloned_js_value.handle(),
453 &generated_key,
454 key_path,
455 )? {
456 return Err(Error::Data(None));
457 }
458 serialized_key = Some(generated_key);
459 key_generator_current_number_for_put = Some(next_current_number);
460 },
461 }
462
463 structuredclone::write(cx, cloned_js_value.handle(), None)?
464 },
465 None => structuredclone::write(cx, cloned_js_value.handle(), None)?,
466 };
467 let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
468 return Err(Error::InvalidState(None));
469 };
470 // Step 12. Let operation be an algorithm to run store a record into an object store with
471 // store, clone, key, and no-overwrite flag.
472 let request = IDBRequest::execute_async(
473 self,
474 |callback| {
475 AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
476 callback,
477 key: serialized_key,
478 value: serialized_value,
479 should_overwrite: !no_overwrite,
480 key_generator_current_number: key_generator_current_number_for_put,
481 })
482 },
483 None,
484 None,
485 CanGc::from_cx(cx),
486 )?;
487 // Keep the in-memory key generator in sync with the queued put request.
488 if let Some(next_key_generator_current_number) = key_generator_current_number_for_put {
489 self.key_generator_current_number
490 .set(Some(next_key_generator_current_number));
491 }
492 // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request
493 // with handle and operation.
494 Ok(request)
495 }
496
497 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
498 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
499 fn open_cursor(
500 &self,
501 cx: &mut JSContext,
502 query: HandleValue,
503 direction: IDBCursorDirection,
504 key_only: bool,
505 ) -> Fallible<DomRoot<IDBRequest>> {
506 // Step 1. Let transaction be this object store handle's transaction.
507 // Step 2. Let store be this object store handle's object store.
508
509 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
510 self.verify_not_deleted()?;
511
512 // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
513 self.check_transaction_active()?;
514
515 // Step 5. Let range be the result of running the steps to convert a value to a key range
516 // with query. Rethrow any exceptions.
517 //
518 // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
519 // or not given, an unbounded key range is used.
520 let range = convert_value_to_key_range(cx, query, Some(false))?;
521
522 // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
523 // position, direction set to direction, got value flag unset, and undefined key and value.
524 // The source of cursor is store. The range of cursor is range.
525 //
526 // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
527 // interface as well.
528 let cursor = if key_only {
529 IDBCursor::new(
530 &self.global(),
531 &self.transaction,
532 direction,
533 false,
534 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
535 range.clone(),
536 key_only,
537 CanGc::from_cx(cx),
538 )
539 } else {
540 DomRoot::upcast(IDBCursorWithValue::new(
541 &self.global(),
542 &self.transaction,
543 direction,
544 false,
545 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
546 range.clone(),
547 key_only,
548 CanGc::from_cx(cx),
549 ))
550 };
551
552 // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
553 // created by these steps. The steps are run with this object store handle as source and
554 // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
555 // cursor.
556 let iteration_param = IterationParam {
557 cursor: Trusted::new(&cursor),
558 key: None,
559 primary_key: None,
560 count: None,
561 };
562
563 IDBRequest::execute_async(
564 self,
565 |callback| {
566 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
567 callback,
568 key_range: range,
569 })
570 },
571 None,
572 Some(iteration_param),
573 CanGc::from_cx(cx),
574 )
575 .inspect(|request| cursor.set_request(request))
576 }
577
578 pub(crate) fn add_index(
579 &self,
580 name: DOMString,
581 options: &IDBIndexParameters,
582 key_path: KeyPath,
583 can_gc: CanGc,
584 ) -> DomRoot<IDBIndex> {
585 let index = IDBIndex::new(
586 &self.global(),
587 DomRoot::from_ref(self),
588 name.clone(),
589 options.multiEntry,
590 options.unique,
591 key_path,
592 can_gc,
593 );
594 self.index_set
595 .borrow_mut()
596 .insert(name, Dom::from_ref(&index));
597 index
598 }
599}
600
601impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
602 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-put>
603 fn Put(
604 &self,
605 cx: &mut JSContext,
606 value: HandleValue,
607 key: HandleValue,
608 ) -> Fallible<DomRoot<IDBRequest>> {
609 // Step 1. Return the result of running add or put with this, value, key and the
610 // no-overwrite flag false.
611 self.put(cx, value, key, false)
612 }
613
614 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-add>
615 fn Add(
616 &self,
617 cx: &mut JSContext,
618 value: HandleValue,
619 key: HandleValue,
620 ) -> Fallible<DomRoot<IDBRequest>> {
621 // Step 1. Return the result of running add or put with this, value, key and the
622 // no-overwrite flag true.
623 self.put(cx, value, key, true)
624 }
625
626 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-delete>
627 fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
628 // Step 1. Let transaction be this’s transaction.
629 // Step 2. Let store be this's object store.
630 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
631 self.verify_not_deleted()?;
632
633 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
634 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
635 self.check_readwrite_transaction_active()?;
636
637 // 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.
638 let serialized_query = convert_value_to_key_range(cx, query, Some(true));
639 // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
640 // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
641 serialized_query.and_then(|key_range| {
642 IDBRequest::execute_async(
643 self,
644 |callback| {
645 AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
646 callback,
647 key_range,
648 })
649 },
650 None,
651 None,
652 CanGc::from_cx(cx),
653 )
654 })
655 }
656
657 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-clear>
658 fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
659 // Step 1. Let transaction be this’s transaction.
660 // Step 2. Let store be this's object store.
661 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
662 self.verify_not_deleted()?;
663
664 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
665 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
666 self.check_readwrite_transaction_active()?;
667
668 // Step 6. Let operation be an algorithm to run clear an object store with store.
669 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
670 IDBRequest::execute_async(
671 self,
672 |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
673 None,
674 None,
675 CanGc::from_cx(cx),
676 )
677 }
678
679 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-get>
680 fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
681 // Step 1. Let transaction be this’s transaction.
682 // Step 2. Let store be this's object store.
683 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
684 self.verify_not_deleted()?;
685
686 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
687 self.check_transaction_active()?;
688
689 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
690 let serialized_query = convert_value_to_key_range(cx, query, Some(true));
691
692 // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
693 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
694 serialized_query.and_then(|q| {
695 IDBRequest::execute_async(
696 self,
697 |callback| {
698 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
699 callback,
700 key_range: q,
701 })
702 },
703 None,
704 None,
705 CanGc::from_cx(cx),
706 )
707 })
708 }
709
710 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getkey>
711 fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
712 // Step 1. Let transaction be this’s transaction.
713 // Step 2. Let store be this's object store.
714 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
715 self.verify_not_deleted()?;
716
717 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
718 self.check_transaction_active()?;
719
720 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
721 let serialized_query = convert_value_to_key_range(cx, query, Some(true));
722
723 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
724 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
725 // store as operation, using store and range.
726 serialized_query.and_then(|q| {
727 IDBRequest::execute_async(
728 self,
729 |callback| {
730 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
731 callback,
732 key_range: q,
733 })
734 },
735 None,
736 None,
737 CanGc::from_cx(cx),
738 )
739 })
740 }
741
742 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getall>
743 fn GetAll(
744 &self,
745 cx: &mut JSContext,
746 query: HandleValue,
747 count: Option<u32>,
748 ) -> Fallible<DomRoot<IDBRequest>> {
749 // Step 1. Let transaction be this’s transaction.
750 // Step 2. Let store be this's object store.
751 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
752 self.verify_not_deleted()?;
753
754 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
755 self.check_transaction_active()?;
756
757 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
758 let serialized_query = convert_value_to_key_range(cx, query, None);
759
760 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
761 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
762 // store as operation, using store and range.
763 serialized_query.and_then(|q| {
764 IDBRequest::execute_async(
765 self,
766 |callback| {
767 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
768 callback,
769 key_range: q,
770 count,
771 })
772 },
773 None,
774 None,
775 CanGc::from_cx(cx),
776 )
777 })
778 }
779
780 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-getallkeys>
781 fn GetAllKeys(
782 &self,
783 cx: &mut JSContext,
784 query: HandleValue,
785 count: Option<u32>,
786 ) -> Fallible<DomRoot<IDBRequest>> {
787 // Step 1. Let transaction be this’s transaction.
788 // Step 2. Let store be this's object store.
789 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
790 self.verify_not_deleted()?;
791
792 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
793 self.check_transaction_active()?;
794
795 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
796 let serialized_query = convert_value_to_key_range(cx, query, None);
797
798 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
799 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
800 // store as operation, using store and range.
801 serialized_query.and_then(|q| {
802 IDBRequest::execute_async(
803 self,
804 |callback| {
805 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
806 callback,
807 key_range: q,
808 count,
809 })
810 },
811 None,
812 None,
813 CanGc::from_cx(cx),
814 )
815 })
816 }
817
818 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-count>
819 fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
820 // Step 1. Let transaction be this’s transaction.
821 // Step 2. Let store be this's object store.
822 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
823 self.verify_not_deleted()?;
824
825 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
826 self.check_transaction_active()?;
827
828 // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
829 let serialized_query = convert_value_to_key_range(cx, query, None);
830
831 // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
832 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
833 serialized_query.and_then(|q| {
834 IDBRequest::execute_async(
835 self,
836 |callback| {
837 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
838 callback,
839 key_range: q,
840 })
841 },
842 None,
843 None,
844 CanGc::from_cx(cx),
845 )
846 })
847 }
848
849 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-opencursor>
850 fn OpenCursor(
851 &self,
852 cx: &mut JSContext,
853 query: HandleValue,
854 direction: IDBCursorDirection,
855 ) -> Fallible<DomRoot<IDBRequest>> {
856 self.open_cursor(cx, query, direction, false)
857 }
858
859 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-openkeycursor>
860 fn OpenKeyCursor(
861 &self,
862 cx: &mut JSContext,
863 query: HandleValue,
864 direction: IDBCursorDirection,
865 ) -> Fallible<DomRoot<IDBRequest>> {
866 self.open_cursor(cx, query, direction, true)
867 }
868
869 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
870 fn Name(&self) -> DOMString {
871 self.name.borrow().clone()
872 }
873
874 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-name>
875 fn SetName(&self, value: DOMString) -> ErrorResult {
876 // Step 1. Let name be the given value.
877 let name = value;
878
879 // Step 2. Let transaction be this’s transaction.
880 let transaction = &self.transaction;
881
882 // Step 3. Let store be this’s object store.
883 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
884 self.verify_not_deleted()?;
885
886 // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
887 if transaction.Mode() != IDBTransactionMode::Versionchange {
888 return Err(Error::InvalidState(None));
889 }
890 // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
891 self.check_transaction_active()?;
892
893 // Step 7. If store’s name is equal to name, terminate these steps.
894 if *self.name.borrow() == name {
895 return Ok(());
896 }
897
898 // Step 8. If an object store named name already exists in store’s database,
899 // throw a "ConstraintError" DOMException.
900 if transaction.Db().object_store_exists(&name) {
901 return Err(Error::Constraint(None));
902 }
903
904 let old_name = self.name.borrow().clone();
905 if let Some(abort_state) = self.abort_state_on_abort.borrow_mut().as_mut() &&
906 abort_state.rollback_name.is_none()
907 {
908 abort_state.rollback_name = Some(old_name.clone());
909 }
910
911 // Step 9. Set store’s name to name.
912 transaction
913 .Db()
914 .rename_object_store_name(&old_name, name.clone());
915 // Step 10. Set this’s name to name.
916 *self.name.borrow_mut() = name.clone();
917 transaction.rename_object_store_handle_cache(&old_name, &name, self);
918 Ok(())
919 }
920
921 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-keypath>
922 fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
923 match &self.key_path {
924 Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
925 Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
926 None => ret_val.set(NullValue()),
927 }
928 }
929
930 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-indexnames>
931 fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
932 DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
933 }
934
935 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-transaction>
936 fn Transaction(&self) -> DomRoot<IDBTransaction> {
937 self.transaction()
938 }
939
940 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-autoincrement>
941 fn AutoIncrement(&self) -> bool {
942 self.has_key_generator()
943 }
944
945 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-createindex>
946 fn CreateIndex(
947 &self,
948 cx: &mut JSContext,
949 name: DOMString,
950 key_path: StringOrStringSequence,
951 options: &IDBIndexParameters,
952 ) -> Fallible<DomRoot<IDBIndex>> {
953 let key_path: KeyPath = key_path.into();
954 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
955 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
956 return Err(Error::InvalidState(None));
957 }
958
959 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
960 self.verify_not_deleted()?;
961 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
962 self.check_transaction_active()?;
963
964 // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
965 if self.index_set.borrow().contains_key(&name) {
966 return Err(Error::Constraint(None));
967 }
968
969 let js_key_path = match key_path.clone() {
970 KeyPath::String(s) => StringOrStringSequence::String(s),
971 KeyPath::StringSequence(s) => StringOrStringSequence::StringSequence(s),
972 };
973
974 // Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
975 if !is_valid_key_path(cx, &js_key_path)? {
976 return Err(Error::Syntax(None));
977 }
978 // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
979 // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
980 // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
981 if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
982 return Err(Error::InvalidAccess(None));
983 }
984
985 // Step 11. Let index be a new index in store.
986 // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
987 // If multiEntry is set, set index’s multiEntry flag.
988 let create_index_operation = SyncOperation::CreateIndex(
989 self.global().origin().immutable().clone(),
990 self.db_name.to_string(),
991 self.name.borrow().to_string(),
992 name.to_string(),
993 key_path.clone().into(),
994 options.unique,
995 options.multiEntry,
996 );
997 if self
998 .get_idb_thread()
999 .send(IndexedDBThreadMsg::Sync(create_index_operation))
1000 .is_err()
1001 {
1002 return Err(Error::Operation(None));
1003 }
1004
1005 // Step 12. Add index to this object store handle's index set.
1006 let index = self.add_index(name, options, key_path, CanGc::from_cx(cx));
1007
1008 // Step 13. Return a new index handle associated with index and this object store handle.
1009 Ok(index)
1010 }
1011
1012 /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbobjectstore-deleteindex>
1013 fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
1014 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
1015 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
1016 return Err(Error::InvalidState(None));
1017 }
1018 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
1019 self.verify_not_deleted()?;
1020 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
1021 self.check_transaction_active()?;
1022 // Step 6. Let index be the index named name in store if one exists,
1023 // or throw a "NotFoundError" DOMException otherwise.
1024 if !self.index_set.borrow().contains_key(&name) {
1025 return Err(Error::NotFound(None));
1026 }
1027 // Step 7. Remove index from this object store handle's index set.
1028 self.index_set.borrow_mut().retain(|n, _| n != &name);
1029 // Step 8. Destroy index.
1030 let delete_index_operation = SyncOperation::DeleteIndex(
1031 self.global().origin().immutable().clone(),
1032 self.db_name.to_string(),
1033 self.name.borrow().to_string(),
1034 String::from(name),
1035 );
1036 self.get_idb_thread()
1037 .send(IndexedDBThreadMsg::Sync(delete_index_operation))
1038 .unwrap();
1039 Ok(())
1040 }
1041
1042 /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
1043 fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
1044 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
1045 self.verify_not_deleted()?;
1046
1047 // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
1048 if self.transaction.is_finished() {
1049 return Err(Error::InvalidState(None));
1050 }
1051
1052 // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
1053 let index_set = self.index_set.borrow();
1054 let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
1055
1056 // Step 6. Return an index handle associated with index and this.
1057 Ok(index.as_rooted())
1058 }
1059}