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::collections::HashMap;
5
6use base::generic_channel::{GenericSend, GenericSender};
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 profile_traits::generic_channel::channel;
14use script_bindings::codegen::GenericBindings::IDBObjectStoreBinding::IDBIndexParameters;
15use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
16use script_bindings::error::ErrorResult;
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, convert_value_to_key, convert_value_to_key_range, extract_key,
46};
47use crate::script_runtime::CanGc;
48
49#[derive(Clone, JSTraceable, MallocSizeOf)]
50pub enum KeyPath {
51 String(DOMString),
52 StringSequence(Vec<DOMString>),
53}
54
55impl From<StringOrStringSequence> for KeyPath {
56 fn from(value: StringOrStringSequence) -> Self {
57 match value {
58 StringOrStringSequence::String(s) => KeyPath::String(s),
59 StringOrStringSequence::StringSequence(ss) => KeyPath::StringSequence(ss),
60 }
61 }
62}
63
64impl From<indexeddb::KeyPath> for KeyPath {
65 fn from(value: indexeddb::KeyPath) -> Self {
66 match value {
67 indexeddb::KeyPath::String(s) => KeyPath::String(DOMString::from_string(s)),
68 indexeddb::KeyPath::Sequence(ss) => {
69 KeyPath::StringSequence(ss.into_iter().map(DOMString::from_string).collect())
70 },
71 }
72 }
73}
74
75impl From<KeyPath> for indexeddb::KeyPath {
76 fn from(item: KeyPath) -> Self {
77 match item {
78 KeyPath::String(s) => Self::String(s.to_string()),
79 KeyPath::StringSequence(ss) => {
80 Self::Sequence(ss.into_iter().map(|s| s.to_string()).collect())
81 },
82 }
83 }
84}
85
86#[dom_struct]
87pub struct IDBObjectStore {
88 reflector_: Reflector,
89 name: DomRefCell<DOMString>,
90 key_path: Option<KeyPath>,
91 index_set: DomRefCell<HashMap<DOMString, Dom<IDBIndex>>>,
92 transaction: Dom<IDBTransaction>,
93
94 // We store the db name in the object store to be able to find the correct
95 // store in the idb thread when checking if we have a key generator
96 db_name: DOMString,
97}
98
99impl IDBObjectStore {
100 pub fn new_inherited(
101 db_name: DOMString,
102 name: DOMString,
103 options: Option<&IDBObjectStoreParameters>,
104 transaction: &IDBTransaction,
105 ) -> IDBObjectStore {
106 let key_path: Option<KeyPath> = match options {
107 Some(options) => options.keyPath.as_ref().map(|path| match path {
108 StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
109 StrOrStringSequence::StringSequence(inner) => {
110 KeyPath::StringSequence(inner.clone())
111 },
112 }),
113 None => None,
114 };
115
116 IDBObjectStore {
117 reflector_: Reflector::new(),
118 name: DomRefCell::new(name),
119 key_path,
120 index_set: DomRefCell::new(HashMap::new()),
121 transaction: Dom::from_ref(transaction),
122 db_name,
123 }
124 }
125
126 pub fn new(
127 global: &GlobalScope,
128 db_name: DOMString,
129 name: DOMString,
130 options: Option<&IDBObjectStoreParameters>,
131 can_gc: CanGc,
132 transaction: &IDBTransaction,
133 ) -> DomRoot<IDBObjectStore> {
134 reflect_dom_object(
135 Box::new(IDBObjectStore::new_inherited(
136 db_name,
137 name,
138 options,
139 transaction,
140 )),
141 global,
142 can_gc,
143 )
144 }
145
146 pub fn get_name(&self) -> DOMString {
147 self.name.borrow().clone()
148 }
149
150 pub(crate) fn transaction(&self) -> DomRoot<IDBTransaction> {
151 self.transaction.as_rooted()
152 }
153
154 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
155 self.global().storage_threads().sender()
156 }
157
158 fn has_key_generator(&self) -> bool {
159 // FIXME: blocking IPC call
160 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
161
162 let operation = SyncOperation::GetObjectStore(
163 sender,
164 self.global().origin().immutable().clone(),
165 self.db_name.to_string(),
166 self.name.borrow().to_string(),
167 );
168
169 self.get_idb_thread()
170 .send(IndexedDBThreadMsg::Sync(operation))
171 .unwrap();
172
173 // First unwrap for ipc
174 // Second unwrap will never happen unless this db gets manually deleted somehow
175 receiver.recv().unwrap().unwrap().has_key_generator
176 }
177
178 /// <https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys>
179 fn uses_inline_keys(&self) -> bool {
180 self.key_path.is_some()
181 }
182
183 fn verify_not_deleted(&self) -> ErrorResult {
184 let db = self.transaction.Db();
185 if !db.object_store_exists(&self.name.borrow()) {
186 return Err(Error::InvalidState(None));
187 }
188 Ok(())
189 }
190
191 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
192 fn check_transaction_active(&self) -> Fallible<()> {
193 // Let transaction be this object store handle's transaction.
194 let transaction = &self.transaction;
195
196 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
197 // https://w3c.github.io/IndexedDB/#transaction-inactive
198 // A transaction is in this state after control returns to the event loop after its creation, and when events are not being dispatched.
199 // No requests can be made against the transaction when it is in this state.
200 if !transaction.is_active() || !transaction.is_usable() {
201 return Err(Error::TransactionInactive(None));
202 }
203
204 Ok(())
205 }
206
207 /// Checks if the transaction is active, throwing a "TransactionInactiveError" DOMException if not.
208 /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
209 fn check_readwrite_transaction_active(&self) -> Fallible<()> {
210 // Let transaction be this object store handle's transaction.
211 let transaction = &self.transaction;
212
213 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
214 self.check_transaction_active()?;
215
216 if let IDBTransactionMode::Readonly = transaction.get_mode() {
217 return Err(Error::ReadOnly(None));
218 }
219 Ok(())
220 }
221
222 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
223 fn put(
224 &self,
225 cx: &mut JSContext,
226 value: HandleValue,
227 key: HandleValue,
228 overwrite: bool,
229 ) -> Fallible<DomRoot<IDBRequest>> {
230 // Step 1. Let transaction be handle’s transaction.
231 // Step 2: Let store be this object store handle's object store.
232 // This is resolved in the `execute_async` function.
233 // Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
234 self.verify_not_deleted()?;
235
236 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
237 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
238 self.check_readwrite_transaction_active()?;
239
240 // Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
241 if !key.is_undefined() && self.uses_inline_keys() {
242 return Err(Error::Data(None));
243 }
244
245 // Step 7: If store uses out-of-line keys and has no key generator
246 // and key was not given, throw a "DataError" DOMException.
247 if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
248 return Err(Error::Data(None));
249 }
250
251 // Step 8: If key was given, then: convert a value to a key with key
252 let serialized_key: Option<IndexedDBKeyType>;
253
254 if !key.is_undefined() {
255 serialized_key = Some(convert_value_to_key(cx, key, None)?.into_result()?);
256 } else {
257 // Step 11: We should use in-line keys instead
258 // Step 11.1: Let kpk be the result of running the steps to extract a
259 // key from a value using a key path with clone and store’s key path.
260 let extraction_result = self
261 .key_path
262 .as_ref()
263 .map(|p| extract_key(cx, value, p, None));
264
265 match extraction_result {
266 Some(Ok(ExtractionResult::Failure)) | None => {
267 // Step 11.4. Otherwise:
268 // Step 11.4.1. If store does not have a key generator, throw
269 // a "DataError" DOMException.
270 if !self.has_key_generator() {
271 return Err(Error::Data(None));
272 }
273 // Step 11.4.2. Otherwise, if the steps to check that a key could
274 // be injected into a value with clone and store’s key path return
275 // false, throw a "DataError" DOMException.
276 // TODO
277 serialized_key = None;
278 },
279 // Step 11.1. Rethrow any exceptions.
280 Some(extraction_result) => match extraction_result? {
281 // Step 11.2. If kpk is invalid, throw a "DataError" DOMException.
282 ExtractionResult::Invalid => return Err(Error::Data(None)),
283 // Step 11.3. If kpk is not failure, let key be kpk.
284 ExtractionResult::Key(kpk) => serialized_key = Some(kpk),
285 ExtractionResult::Failure => unreachable!(),
286 },
287 }
288 }
289
290 // Step 10. Let clone be a clone of value in targetRealm during transaction. Rethrow any exceptions.
291 let cloned_value = structuredclone::write(cx.into(), value, None)?;
292 let Ok(serialized_value) = postcard::to_stdvec(&cloned_value) else {
293 return Err(Error::InvalidState(None));
294 };
295 // Step 12. Let operation be an algorithm to run store a record into an object store with store, clone, key, and no-overwrite flag.
296 // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request with handle and operation.
297 IDBRequest::execute_async(
298 self,
299 |callback| {
300 AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
301 callback,
302 key: serialized_key,
303 value: serialized_value,
304 should_overwrite: overwrite,
305 })
306 },
307 None,
308 None,
309 CanGc::from_cx(cx),
310 )
311 }
312
313 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
314 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
315 fn open_cursor(
316 &self,
317 cx: &mut JSContext,
318 query: HandleValue,
319 direction: IDBCursorDirection,
320 key_only: bool,
321 ) -> Fallible<DomRoot<IDBRequest>> {
322 // Step 1. Let transaction be this object store handle's transaction.
323 // Step 2. Let store be this object store handle's object store.
324
325 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
326 self.verify_not_deleted()?;
327
328 // Step 4. If transaction is not active, throw a "TransactionInactiveError" DOMException.
329 self.check_transaction_active()?;
330
331 // Step 5. Let range be the result of running the steps to convert a value to a key range
332 // with query. Rethrow any exceptions.
333 //
334 // The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
335 // or not given, an unbounded key range is used.
336 let range = convert_value_to_key_range(cx, query, Some(false))?;
337
338 // Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
339 // position, direction set to direction, got value flag unset, and undefined key and value.
340 // The source of cursor is store. The range of cursor is range.
341 //
342 // NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
343 // interface as well.
344 let cursor = if key_only {
345 IDBCursor::new(
346 &self.global(),
347 &self.transaction,
348 direction,
349 false,
350 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
351 range.clone(),
352 key_only,
353 CanGc::from_cx(cx),
354 )
355 } else {
356 DomRoot::upcast(IDBCursorWithValue::new(
357 &self.global(),
358 &self.transaction,
359 direction,
360 false,
361 ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
362 range.clone(),
363 key_only,
364 CanGc::from_cx(cx),
365 ))
366 };
367
368 // Step 7. Run the steps to asynchronously execute a request and return the IDBRequest
369 // created by these steps. The steps are run with this object store handle as source and
370 // the steps to iterate a cursor as operation, using the current Realm as targetRealm, and
371 // cursor.
372 let iteration_param = IterationParam {
373 cursor: Trusted::new(&cursor),
374 key: None,
375 primary_key: None,
376 count: None,
377 };
378
379 IDBRequest::execute_async(
380 self,
381 |callback| {
382 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
383 callback,
384 key_range: range,
385 })
386 },
387 None,
388 Some(iteration_param),
389 CanGc::from_cx(cx),
390 )
391 .inspect(|request| cursor.set_request(request))
392 }
393
394 pub(crate) fn add_index(
395 &self,
396 name: DOMString,
397 options: &IDBIndexParameters,
398 key_path: KeyPath,
399 can_gc: CanGc,
400 ) -> DomRoot<IDBIndex> {
401 let index = IDBIndex::new(
402 &self.global(),
403 DomRoot::from_ref(self),
404 name.clone(),
405 options.multiEntry,
406 options.unique,
407 key_path,
408 can_gc,
409 );
410 self.index_set
411 .borrow_mut()
412 .insert(name, Dom::from_ref(&index));
413 index
414 }
415}
416
417impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
418 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
419 fn Put(
420 &self,
421 cx: &mut JSContext,
422 value: HandleValue,
423 key: HandleValue,
424 ) -> Fallible<DomRoot<IDBRequest>> {
425 self.put(cx, value, key, true)
426 }
427
428 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add>
429 fn Add(
430 &self,
431 cx: &mut JSContext,
432 value: HandleValue,
433 key: HandleValue,
434 ) -> Fallible<DomRoot<IDBRequest>> {
435 self.put(cx, value, key, false)
436 }
437
438 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete>
439 fn Delete(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
440 // Step 1. Let transaction be this’s transaction.
441 // Step 2. Let store be this's object store.
442 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
443 self.verify_not_deleted()?;
444
445 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
446 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
447 self.check_readwrite_transaction_active()?;
448
449 // 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.
450 let serialized_query = convert_value_to_key_range(cx, query, Some(true));
451 // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
452 // Step 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
453 serialized_query.and_then(|key_range| {
454 IDBRequest::execute_async(
455 self,
456 |callback| {
457 AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
458 callback,
459 key_range,
460 })
461 },
462 None,
463 None,
464 CanGc::from_cx(cx),
465 )
466 })
467 }
468
469 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear>
470 fn Clear(&self, cx: &mut JSContext) -> Fallible<DomRoot<IDBRequest>> {
471 // Step 1. Let transaction be this’s transaction.
472 // Step 2. Let store be this's object store.
473 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
474 self.verify_not_deleted()?;
475
476 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
477 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
478 self.check_readwrite_transaction_active()?;
479
480 // Step 6. Let operation be an algorithm to run clear an object store with store.
481 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
482 IDBRequest::execute_async(
483 self,
484 |callback| AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(callback)),
485 None,
486 None,
487 CanGc::from_cx(cx),
488 )
489 }
490
491 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get>
492 fn Get(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
493 // Step 1. Let transaction be this’s transaction.
494 // Step 2. Let store be this's object store.
495 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
496 self.verify_not_deleted()?;
497
498 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
499 self.check_transaction_active()?;
500
501 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
502 let serialized_query = convert_value_to_key_range(cx, query, None);
503
504 // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
505 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
506 serialized_query.and_then(|q| {
507 IDBRequest::execute_async(
508 self,
509 |callback| {
510 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
511 callback,
512 key_range: q,
513 })
514 },
515 None,
516 None,
517 CanGc::from_cx(cx),
518 )
519 })
520 }
521
522 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey>
523 fn GetKey(&self, cx: &mut JSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
524 // Step 1. Let transaction be this’s transaction.
525 // Step 2. Let store be this's object store.
526 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
527 self.verify_not_deleted()?;
528
529 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
530 self.check_transaction_active()?;
531
532 // Step 5. 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.
533 let serialized_query = convert_value_to_key_range(cx, query, None);
534
535 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
536 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
537 // store as operation, using store and range.
538 serialized_query.and_then(|q| {
539 IDBRequest::execute_async(
540 self,
541 |callback| {
542 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
543 callback,
544 key_range: q,
545 })
546 },
547 None,
548 None,
549 CanGc::from_cx(cx),
550 )
551 })
552 }
553
554 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall>
555 fn GetAll(
556 &self,
557 cx: &mut JSContext,
558 query: HandleValue,
559 count: Option<u32>,
560 ) -> Fallible<DomRoot<IDBRequest>> {
561 // Step 1. Let transaction be this’s transaction.
562 // Step 2. Let store be this's object store.
563 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
564 self.verify_not_deleted()?;
565
566 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
567 self.check_transaction_active()?;
568
569 // Step 5. 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.
570 let serialized_query = convert_value_to_key_range(cx, query, None);
571
572 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
573 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
574 // store as operation, using store and range.
575 serialized_query.and_then(|q| {
576 IDBRequest::execute_async(
577 self,
578 |callback| {
579 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
580 callback,
581 key_range: q,
582 count,
583 })
584 },
585 None,
586 None,
587 CanGc::from_cx(cx),
588 )
589 })
590 }
591
592 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys>
593 fn GetAllKeys(
594 &self,
595 cx: &mut JSContext,
596 query: HandleValue,
597 count: Option<u32>,
598 ) -> Fallible<DomRoot<IDBRequest>> {
599 // Step 1. Let transaction be this’s transaction.
600 // Step 2. Let store be this's object store.
601 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
602 self.verify_not_deleted()?;
603
604 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
605 self.check_transaction_active()?;
606
607 // Step 5. 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.
608 let serialized_query = convert_value_to_key_range(cx, query, None);
609
610 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
611 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
612 // store as operation, using store and range.
613 serialized_query.and_then(|q| {
614 IDBRequest::execute_async(
615 self,
616 |callback| {
617 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
618 callback,
619 key_range: q,
620 count,
621 })
622 },
623 None,
624 None,
625 CanGc::from_cx(cx),
626 )
627 })
628 }
629
630 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count>
631 fn Count(&self, cx: &mut JSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
632 // Step 1. Let transaction be this’s transaction.
633 // Step 2. Let store be this's object store.
634 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
635 self.verify_not_deleted()?;
636
637 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
638 self.check_transaction_active()?;
639
640 // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
641 let serialized_query = convert_value_to_key_range(cx, query, None);
642
643 // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
644 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
645 serialized_query.and_then(|q| {
646 IDBRequest::execute_async(
647 self,
648 |callback| {
649 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
650 callback,
651 key_range: q,
652 })
653 },
654 None,
655 None,
656 CanGc::from_cx(cx),
657 )
658 })
659 }
660
661 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
662 fn OpenCursor(
663 &self,
664 cx: &mut JSContext,
665 query: HandleValue,
666 direction: IDBCursorDirection,
667 ) -> Fallible<DomRoot<IDBRequest>> {
668 self.open_cursor(cx, query, direction, false)
669 }
670
671 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
672 fn OpenKeyCursor(
673 &self,
674 cx: &mut JSContext,
675 query: HandleValue,
676 direction: IDBCursorDirection,
677 ) -> Fallible<DomRoot<IDBRequest>> {
678 self.open_cursor(cx, query, direction, true)
679 }
680
681 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name>
682 fn Name(&self) -> DOMString {
683 self.name.borrow().clone()
684 }
685
686 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname>
687 fn SetName(&self, value: DOMString) -> ErrorResult {
688 // Step 2. Let transaction be this’s transaction.
689 let transaction = &self.transaction;
690
691 // Step 3. Let store be this's object store.
692 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
693 self.verify_not_deleted()?;
694
695 // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
696 if transaction.Mode() != IDBTransactionMode::Versionchange {
697 return Err(Error::InvalidState(None));
698 }
699 // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
700 self.check_transaction_active()?;
701
702 *self.name.borrow_mut() = value;
703 Ok(())
704 }
705
706 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath>
707 fn KeyPath(&self, cx: &mut JSContext, mut ret_val: MutableHandleValue) {
708 match &self.key_path {
709 Some(KeyPath::String(path)) => path.safe_to_jsval(cx, ret_val),
710 Some(KeyPath::StringSequence(paths)) => paths.safe_to_jsval(cx, ret_val),
711 None => ret_val.set(NullValue()),
712 }
713 }
714
715 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames>
716 fn IndexNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
717 DOMStringList::new_sorted(&self.global(), self.index_set.borrow().keys(), can_gc)
718 }
719
720 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction>
721 fn Transaction(&self) -> DomRoot<IDBTransaction> {
722 self.transaction()
723 }
724
725 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement>
726 fn AutoIncrement(&self) -> bool {
727 self.has_key_generator()
728 }
729
730 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-createindex>
731 fn CreateIndex(
732 &self,
733 name: DOMString,
734 key_path: StringOrStringSequence,
735 options: &IDBIndexParameters,
736 can_gc: CanGc,
737 ) -> Fallible<DomRoot<IDBIndex>> {
738 let key_path: KeyPath = key_path.into();
739 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
740 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
741 return Err(Error::InvalidState(None));
742 }
743
744 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
745 self.verify_not_deleted()?;
746 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
747 self.check_transaction_active()?;
748
749 // Step 6. If an index named name already exists in store, throw a "ConstraintError" DOMException.
750 if self.index_set.borrow().contains_key(&name) {
751 return Err(Error::Constraint(None));
752 }
753
754 // TODO: Step 7. If keyPath is not a valid key path, throw a "SyntaxError" DOMException.
755 // Step 8. Let unique be set if options’s unique member is true, and unset otherwise.
756 // Step 9. Let multiEntry be set if options’s multiEntry member is true, and unset otherwise.
757 // Step 10. If keyPath is a sequence and multiEntry is set, throw an "InvalidAccessError" DOMException.
758 if matches!(key_path, KeyPath::StringSequence(_)) && options.multiEntry {
759 return Err(Error::InvalidAccess(None));
760 }
761
762 // Step 11. Let index be a new index in store.
763 // Set index’s name to name and key path to keyPath. If unique is set, set index’s unique flag.
764 // If multiEntry is set, set index’s multiEntry flag.
765 let create_index_operation = SyncOperation::CreateIndex(
766 self.global().origin().immutable().clone(),
767 self.db_name.to_string(),
768 self.name.borrow().to_string(),
769 name.to_string(),
770 key_path.clone().into(),
771 options.unique,
772 options.multiEntry,
773 );
774 if self
775 .get_idb_thread()
776 .send(IndexedDBThreadMsg::Sync(create_index_operation))
777 .is_err()
778 {
779 return Err(Error::Operation(None));
780 }
781
782 // Step 12. Add index to this object store handle's index set.
783 let index = self.add_index(name, options, key_path, can_gc);
784
785 // Step 13. Return a new index handle associated with index and this object store handle.
786 Ok(index)
787 }
788
789 /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-deleteindex>
790 fn DeleteIndex(&self, name: DOMString) -> Fallible<()> {
791 // Step 3. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
792 if self.transaction.Mode() != IDBTransactionMode::Versionchange {
793 return Err(Error::InvalidState(None));
794 }
795 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
796 self.verify_not_deleted()?;
797 // Step 5. If transaction is not active, throw a "TransactionInactiveError" DOMException.
798 self.check_transaction_active()?;
799 // Step 6. Let index be the index named name in store if one exists,
800 // or throw a "NotFoundError" DOMException otherwise.
801 if !self.index_set.borrow().contains_key(&name) {
802 return Err(Error::NotFound(None));
803 }
804 // Step 7. Remove index from this object store handle's index set.
805 self.index_set.borrow_mut().retain(|n, _| n != &name);
806 // Step 8. Destroy index.
807 let delete_index_operation = SyncOperation::DeleteIndex(
808 self.global().origin().immutable().clone(),
809 self.db_name.to_string(),
810 self.name.borrow().to_string(),
811 name.to_string(),
812 );
813 self.get_idb_thread()
814 .send(IndexedDBThreadMsg::Sync(delete_index_operation))
815 .unwrap();
816 Ok(())
817 }
818
819 /// <https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index>
820 fn Index(&self, name: DOMString) -> Fallible<DomRoot<IDBIndex>> {
821 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
822 self.verify_not_deleted()?;
823
824 // Step 4. If the transaction's state is finished, then throw an "InvalidStateError" DOMException.
825 if self.transaction.is_finished() {
826 return Err(Error::InvalidState(None));
827 }
828
829 // Step 5. Let index be the index named name in this’s index set if one exists, or throw a "NotFoundError" DOMException otherwise.
830 let index_set = self.index_set.borrow();
831 let index = index_set.get(&name).ok_or(Error::NotFound(None))?;
832
833 // Step 6. Return an index handle associated with index and this.
834 Ok(index.as_rooted())
835 }
836}