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