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}