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