script/dom/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/. */
4
5use dom_struct::dom_struct;
6use js::rust::HandleValue;
7use net_traits::IpcSend;
8use net_traits::indexeddb_thread::{
9 AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyType,
10 IndexedDBThreadMsg, SyncOperation,
11};
12use profile_traits::ipc;
13use script_bindings::error::ErrorResult;
14
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
17use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
18use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
19 IDBTransactionMethods, IDBTransactionMode,
20};
21// We need to alias this name, otherwise test-tidy complains at &String reference.
22use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::bindings::str::DOMString;
27use crate::dom::bindings::structuredclone;
28use crate::dom::domstringlist::DOMStringList;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::idbrequest::IDBRequest;
31use crate::dom::idbtransaction::IDBTransaction;
32use crate::indexed_db;
33use crate::indexed_db::{convert_value_to_key, convert_value_to_key_range, extract_key};
34use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
35
36#[derive(JSTraceable, MallocSizeOf)]
37pub enum KeyPath {
38 String(DOMString),
39 StringSequence(Vec<DOMString>),
40}
41
42#[dom_struct]
43pub struct IDBObjectStore {
44 reflector_: Reflector,
45 name: DomRefCell<DOMString>,
46 key_path: Option<KeyPath>,
47 index_names: DomRoot<DOMStringList>,
48 transaction: Dom<IDBTransaction>,
49
50 // We store the db name in the object store to be able to find the correct
51 // store in the idb thread when checking if we have a key generator
52 db_name: DOMString,
53}
54
55impl IDBObjectStore {
56 pub fn new_inherited(
57 global: &GlobalScope,
58 db_name: DOMString,
59 name: DOMString,
60 options: Option<&IDBObjectStoreParameters>,
61 can_gc: CanGc,
62 transaction: &IDBTransaction,
63 ) -> IDBObjectStore {
64 let key_path: Option<KeyPath> = match options {
65 Some(options) => options.keyPath.as_ref().map(|path| match path {
66 StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
67 StrOrStringSequence::StringSequence(inner) => {
68 KeyPath::StringSequence(inner.clone())
69 },
70 }),
71 None => None,
72 };
73
74 IDBObjectStore {
75 reflector_: Reflector::new(),
76 name: DomRefCell::new(name),
77 key_path,
78
79 index_names: DOMStringList::new(global, Vec::new(), can_gc),
80 transaction: Dom::from_ref(transaction),
81 db_name,
82 }
83 }
84
85 pub fn new(
86 global: &GlobalScope,
87 db_name: DOMString,
88 name: DOMString,
89 options: Option<&IDBObjectStoreParameters>,
90 can_gc: CanGc,
91 transaction: &IDBTransaction,
92 ) -> DomRoot<IDBObjectStore> {
93 reflect_dom_object(
94 Box::new(IDBObjectStore::new_inherited(
95 global,
96 db_name,
97 name,
98 options,
99 can_gc,
100 transaction,
101 )),
102 global,
103 can_gc,
104 )
105 }
106
107 pub fn get_name(&self) -> DOMString {
108 self.name.borrow().clone()
109 }
110
111 pub fn transaction(&self) -> DomRoot<IDBTransaction> {
112 self.transaction.as_rooted()
113 }
114
115 fn has_key_generator(&self) -> bool {
116 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
117
118 let operation = SyncOperation::HasKeyGenerator(
119 sender,
120 self.global().origin().immutable().clone(),
121 self.db_name.to_string(),
122 self.name.borrow().to_string(),
123 );
124
125 self.global()
126 .resource_threads()
127 .send(IndexedDBThreadMsg::Sync(operation))
128 .unwrap();
129
130 // First unwrap for ipc
131 // Second unwrap will never happen unless this db gets manually deleted somehow
132 receiver.recv().unwrap().unwrap()
133 }
134
135 // fn get_stored_key_path(&mut self) -> Option<KeyPath> {
136 // let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
137 //
138 // let operation = SyncOperation::KeyPath(
139 // sender,
140 // self.global().origin().immutable().clone(),
141 // self.db_name.to_string(),
142 // self.name.borrow().to_string(),
143 // );
144 //
145 // self.global()
146 // .resource_threads()
147 // .sender()
148 // .send(IndexedDBThreadMsg::Sync(operation))
149 // .unwrap();
150 //
151 // // First unwrap for ipc
152 // // Second unwrap will never happen unless this db gets manually deleted somehow
153 // let key_path = receiver.recv().unwrap().unwrap();
154 // key_path.map(|p| {
155 // // TODO: have separate storage for string sequence of len 1 and signle string
156 // if p.len() == 1 {
157 // KeyPath::String(DOMString::from_string(p[0].clone()))
158 // } else {
159 // let strings: Vec<_> = p.into_iter().map(|s| {
160 // DOMString::from_string(s)
161 // }).collect();
162 // KeyPath::StringSequence(strings)
163 // }
164 // })
165 // }
166
167 // https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys
168 fn uses_inline_keys(&self) -> bool {
169 self.key_path.is_some()
170 }
171
172 fn verify_not_deleted(&self) -> ErrorResult {
173 let db = self.transaction.Db();
174 if !db.object_store_exists(&self.name.borrow()) {
175 return Err(Error::InvalidState);
176 }
177 Ok(())
178 }
179
180 /// Checks if the transation is active, throwing a "TransactionInactiveError" DOMException if not.
181 fn check_transaction_active(&self) -> Fallible<()> {
182 // Let transaction be this object store handle's transaction.
183 let transaction = &self.transaction;
184
185 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
186 if !transaction.is_active() {
187 return Err(Error::TransactionInactive);
188 }
189
190 Ok(())
191 }
192
193 /// Checks if the transation is active, throwing a "TransactionInactiveError" DOMException if not.
194 /// it then checks if the transaction is a read-only transaction, throwing a "ReadOnlyError" DOMException if so.
195 fn check_readwrite_transaction_active(&self) -> Fallible<()> {
196 // Let transaction be this object store handle's transaction.
197 let transaction = &self.transaction;
198
199 // If transaction is not active, throw a "TransactionInactiveError" DOMException.
200 self.check_transaction_active()?;
201
202 if let IDBTransactionMode::Readonly = transaction.get_mode() {
203 return Err(Error::ReadOnly);
204 }
205 Ok(())
206 }
207
208 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
209 fn put(
210 &self,
211 cx: SafeJSContext,
212 value: HandleValue,
213 key: HandleValue,
214 overwrite: bool,
215 can_gc: CanGc,
216 ) -> Fallible<DomRoot<IDBRequest>> {
217 // Step 1. Let transaction be handle’s transaction.
218 // Step 2: Let store be this object store handle's object store.
219 // This is resolved in the `execute_async` function.
220 // Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
221 self.verify_not_deleted()?;
222
223 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
224 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
225 self.check_readwrite_transaction_active()?;
226
227 // Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
228 if !key.is_undefined() && self.uses_inline_keys() {
229 return Err(Error::Data);
230 }
231
232 // Step 7: If store uses out-of-line keys and has no key generator
233 // and key was not given, throw a "DataError" DOMException.
234 if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
235 return Err(Error::Data);
236 }
237
238 // Step 8: If key was given, then: convert a value to a key with key
239 let serialized_key: Option<IndexedDBKeyType>;
240
241 if !key.is_undefined() {
242 serialized_key = Some(convert_value_to_key(cx, key, None)?);
243 } else {
244 // Step 11: We should use in-line keys instead
245 if let Some(Ok(kpk)) = self
246 .key_path
247 .as_ref()
248 .map(|p| extract_key(cx, value, p, None))
249 {
250 serialized_key = Some(kpk);
251 } else {
252 if !self.has_key_generator() {
253 return Err(Error::Data);
254 }
255 serialized_key = None;
256 }
257 }
258
259 // Step 10. Let clone be a clone of value in targetRealm during transaction. Rethrow any exceptions.
260 let cloned_value = structuredclone::write(cx, value, None)?;
261 let Ok(serialized_value) = bincode::serialize(&cloned_value) else {
262 return Err(Error::InvalidState);
263 };
264
265 let (sender, receiver) = indexed_db::create_channel(self.global());
266
267 // Step 12. Let operation be an algorithm to run store a record into an object store with store, clone, key, and no-overwrite flag.
268 // Step 13. Return the result (an IDBRequest) of running asynchronously execute a request with handle and operation.
269 IDBRequest::execute_async(
270 self,
271 AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
272 sender,
273 key: serialized_key,
274 value: serialized_value,
275 should_overwrite: overwrite,
276 }),
277 receiver,
278 None,
279 can_gc,
280 )
281 }
282}
283
284impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
285 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
286 fn Put(
287 &self,
288 cx: SafeJSContext,
289 value: HandleValue,
290 key: HandleValue,
291 ) -> Fallible<DomRoot<IDBRequest>> {
292 self.put(cx, value, key, true, CanGc::note())
293 }
294
295 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add
296 fn Add(
297 &self,
298 cx: SafeJSContext,
299 value: HandleValue,
300 key: HandleValue,
301 ) -> Fallible<DomRoot<IDBRequest>> {
302 self.put(cx, value, key, false, CanGc::note())
303 }
304
305 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete
306 fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
307 // Step 1. Let transaction be this’s transaction.
308 // Step 2. Let store be this's object store.
309 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
310 self.verify_not_deleted()?;
311
312 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
313 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
314 self.check_readwrite_transaction_active()?;
315
316 // Step 6
317 // TODO: Convert to key range instead
318 let serialized_query = convert_value_to_key(cx, query, None);
319 // Step 7. Let operation be an algorithm to run delete records from an object store with store and range.
320 // Stpe 8. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
321 let (sender, receiver) = indexed_db::create_channel(self.global());
322 serialized_query.and_then(|q| {
323 IDBRequest::execute_async(
324 self,
325 AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem { sender, key: q }),
326 receiver,
327 None,
328 CanGc::note(),
329 )
330 })
331 }
332
333 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear
334 fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> {
335 // Step 1. Let transaction be this’s transaction.
336 // Step 2. Let store be this's object store.
337 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
338 self.verify_not_deleted()?;
339
340 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
341 // Step 5. If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
342 self.check_readwrite_transaction_active()?;
343
344 // Step 6. Let operation be an algorithm to run clear an object store with store.
345 // Stpe 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
346 let (sender, receiver) = indexed_db::create_channel(self.global());
347
348 IDBRequest::execute_async(
349 self,
350 AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(sender)),
351 receiver,
352 None,
353 CanGc::note(),
354 )
355 }
356
357 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get
358 fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
359 // Step 1. Let transaction be this’s transaction.
360 // Step 2. Let store be this's object store.
361 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
362 self.verify_not_deleted()?;
363
364 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
365 self.check_transaction_active()?;
366
367 // Step 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
368 let serialized_query = convert_value_to_key_range(cx, query, None);
369
370 // Step 6. Let operation be an algorithm to run retrieve a value from an object store with the current Realm record, store, and range.
371 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
372 let (sender, receiver) = indexed_db::create_channel(self.global());
373 serialized_query.and_then(|q| {
374 IDBRequest::execute_async(
375 self,
376 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
377 sender,
378 key_range: q,
379 }),
380 receiver,
381 None,
382 CanGc::note(),
383 )
384 })
385 }
386
387 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey
388 fn GetKey(&self, cx: SafeJSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
389 // Step 1. Let transaction be this’s transaction.
390 // Step 2. Let store be this's object store.
391 // Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
392 self.verify_not_deleted()?;
393
394 // Step 4. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
395 self.check_transaction_active()?;
396
397 // 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.
398 let serialized_query = convert_value_to_key_range(cx, query, None);
399
400 // Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
401 // The steps are run with this object store handle as source and the steps to retrieve a key from an object
402 // store as operation, using store and range.
403 let (sender, receiver) = indexed_db::create_channel(self.global());
404 serialized_query.and_then(|q| {
405 IDBRequest::execute_async(
406 self,
407 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetKey {
408 sender,
409 key_range: q,
410 }),
411 receiver,
412 None,
413 CanGc::note(),
414 )
415 })
416 }
417
418 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall
419 // fn GetAll(
420 // &self,
421 // _cx: SafeJSContext,
422 // _query: HandleValue,
423 // _count: Option<u32>,
424 // ) -> DomRoot<IDBRequest> {
425 // unimplemented!();
426 // }
427
428 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys
429 // fn GetAllKeys(
430 // &self,
431 // _cx: SafeJSContext,
432 // _query: HandleValue,
433 // _count: Option<u32>,
434 // ) -> DomRoot<IDBRequest> {
435 // unimplemented!();
436 // }
437
438 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count
439 fn Count(&self, cx: SafeJSContext, 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 self.check_transaction_active()?;
447
448 // Step 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
449 let serialized_query = convert_value_to_key_range(cx, query, None);
450
451 // Step 6. Let operation be an algorithm to run count the records in a range with store and range.
452 // Step 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
453 let (sender, receiver) = indexed_db::create_channel(self.global());
454 serialized_query.and_then(|q| {
455 IDBRequest::execute_async(
456 self,
457 AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
458 sender,
459 key_range: q,
460 }),
461 receiver,
462 None,
463 CanGc::note(),
464 )
465 })
466 }
467
468 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name
469 fn Name(&self) -> DOMString {
470 self.name.borrow().clone()
471 }
472
473 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname
474 fn SetName(&self, value: DOMString) -> ErrorResult {
475 // Step 2. Let transaction be this’s transaction.
476 let transaction = &self.transaction;
477
478 // Step 3. Let store be this's object store.
479 // Step 4. If store has been deleted, throw an "InvalidStateError" DOMException.
480 self.verify_not_deleted()?;
481
482 // Step 5. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
483 if transaction.Mode() != IDBTransactionMode::Versionchange {
484 return Err(Error::InvalidState);
485 }
486 // Step 6. If transaction’s state is not active, throw a "TransactionInactiveError" DOMException.
487 self.check_transaction_active()?;
488
489 *self.name.borrow_mut() = value;
490 Ok(())
491 }
492
493 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath
494 // fn KeyPath(&self, _cx: SafeJSContext, _val: MutableHandleValue) {
495 // unimplemented!();
496 // }
497
498 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames
499 // fn IndexNames(&self) -> DomRoot<DOMStringList> {
500 // unimplemented!();
501 // }
502
503 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction
504 fn Transaction(&self) -> DomRoot<IDBTransaction> {
505 self.transaction()
506 }
507
508 // https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement
509 fn AutoIncrement(&self) -> bool {
510 self.has_key_generator()
511 }
512}