script/dom/indexeddb/
idbdatabase.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 std::cell::Cell;
6
7use base::generic_channel::{GenericSend, GenericSender};
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use profile_traits::generic_channel::channel;
11use storage_traits::indexeddb::{IndexedDBThreadMsg, KeyPath, SyncOperation};
12use stylo_atoms::Atom;
13use uuid::Uuid;
14
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
17    IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
18};
19use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
20use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
24use crate::dom::bindings::root::{DomRoot, MutNullableDom};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::domstringlist::DOMStringList;
27use crate::dom::eventtarget::EventTarget;
28use crate::dom::globalscope::GlobalScope;
29use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
30use crate::dom::indexeddb::idbtransaction::IDBTransaction;
31use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
32use crate::indexeddb::is_valid_key_path;
33use crate::script_runtime::CanGc;
34
35#[dom_struct]
36pub struct IDBDatabase {
37    eventtarget: EventTarget,
38    /// <https://w3c.github.io/IndexedDB/#database-name>
39    name: DOMString,
40    /// <https://w3c.github.io/IndexedDB/#database-version>
41    version: Cell<u64>,
42    /// <https://w3c.github.io/IndexedDB/#object-store>
43    object_store_names: DomRefCell<Vec<DOMString>>,
44    /// <https://w3c.github.io/IndexedDB/#database-upgrade-transaction>
45    upgrade_transaction: MutNullableDom<IDBTransaction>,
46
47    #[no_trace]
48    #[ignore_malloc_size_of = "Uuid"]
49    id: Uuid,
50
51    // Flags
52    /// <https://w3c.github.io/IndexedDB/#connection-close-pending-flag>
53    close_pending: Cell<bool>,
54}
55
56impl IDBDatabase {
57    pub fn new_inherited(name: DOMString, id: Uuid, version: u64) -> IDBDatabase {
58        IDBDatabase {
59            eventtarget: EventTarget::new_inherited(),
60            name,
61            id,
62            version: Cell::new(version),
63            object_store_names: Default::default(),
64            upgrade_transaction: Default::default(),
65            close_pending: Cell::new(false),
66        }
67    }
68
69    pub fn new(
70        global: &GlobalScope,
71        name: DOMString,
72        id: Uuid,
73        version: u64,
74        can_gc: CanGc,
75    ) -> DomRoot<IDBDatabase> {
76        reflect_dom_object(
77            Box::new(IDBDatabase::new_inherited(name, id, version)),
78            global,
79            can_gc,
80        )
81    }
82
83    fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
84        self.global().storage_threads().sender()
85    }
86
87    pub fn get_name(&self) -> DOMString {
88        self.name.clone()
89    }
90
91    pub fn object_stores(&self) -> DomRoot<DOMStringList> {
92        DOMStringList::new(
93            &self.global(),
94            self.object_store_names.borrow().clone(),
95            CanGc::note(),
96        )
97    }
98
99    pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
100        // https://w3c.github.io/IndexedDB/#abort-upgrade-transaction
101        // Step 4. Set connection’s object store set to the set of object stores in database if database previously existed,
102        // or the empty set if database was newly created.
103        self.object_store_names.borrow().clone()
104    }
105
106    pub(crate) fn set_object_store_names_from_backend(&self, names: Vec<String>) {
107        // https://w3c.github.io/IndexedDB/#abort-upgrade-transaction
108        // Step 4. NOTE: This reverts the value of objectStoreNames returned by the IDBDatabase object.
109        *self.object_store_names.borrow_mut() = names.into_iter().map(Into::into).collect();
110    }
111
112    pub(crate) fn restore_object_store_names(&self, names: Vec<DOMString>) {
113        // https://w3c.github.io/IndexedDB/#abort-upgrade-transaction
114        // Step 4. NOTE: This reverts the value of objectStoreNames returned by the IDBDatabase object.
115        *self.object_store_names.borrow_mut() = names;
116    }
117
118    pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
119        self.object_store_names
120            .borrow()
121            .iter()
122            .any(|store_name| store_name == name)
123    }
124
125    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-version>
126    pub(crate) fn version(&self) -> u64 {
127        // The version getter steps are to return this’s version.
128        self.version.get()
129    }
130
131    pub(crate) fn set_version(&self, version: u64) {
132        self.version.set(version);
133    }
134
135    pub fn set_transaction(&self, transaction: &IDBTransaction) {
136        self.upgrade_transaction.set(Some(transaction));
137    }
138
139    pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
140        let current = self
141            .upgrade_transaction
142            .get()
143            .expect("clear_upgrade_transaction called but no upgrade transaction is set");
144
145        debug_assert!(
146            &*current == transaction,
147            "clear_upgrade_transaction called with non-current transaction"
148        );
149
150        self.upgrade_transaction.set(None);
151    }
152
153    /// <https://w3c.github.io/IndexedDB/#eventdef-idbdatabase-versionchange>
154    pub fn dispatch_versionchange(
155        &self,
156        old_version: u64,
157        new_version: Option<u64>,
158        can_gc: CanGc,
159    ) {
160        let global = self.global();
161        let _ = IDBVersionChangeEvent::fire_version_change_event(
162            &global,
163            self.upcast(),
164            Atom::from("versionchange"),
165            old_version,
166            new_version,
167            can_gc,
168        );
169    }
170}
171
172impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
173    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction>
174    fn Transaction(
175        &self,
176        store_names: StringOrStringSequence,
177        mode: IDBTransactionMode,
178        _options: &IDBTransactionOptions,
179    ) -> Fallible<DomRoot<IDBTransaction>> {
180        // FIXIME:(arihant2math) use options
181        // Step 1: Check if upgrade transaction is running
182        // FIXME:(rasviitanen)
183
184        // Step 2: if close pending flag is set, throw error
185        if self.close_pending.get() {
186            return Err(Error::InvalidState(None));
187        }
188
189        // Step 3
190        let transaction = match store_names {
191            StringOrStringSequence::String(name) => IDBTransaction::new(
192                &self.global(),
193                self,
194                mode,
195                &DOMStringList::new(&self.global(), vec![name], CanGc::note()),
196                CanGc::note(),
197            ),
198            StringOrStringSequence::StringSequence(sequence) => {
199                // FIXME:(rasviitanen) Remove eventual duplicated names
200                // from the sequence
201                IDBTransaction::new(
202                    &self.global(),
203                    self,
204                    mode,
205                    &DOMStringList::new(&self.global(), sequence, CanGc::note()),
206                    CanGc::note(),
207                )
208            },
209        };
210
211        // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction
212        // Step 6: If mode is not "readonly" or "readwrite", throw a TypeError.
213        if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
214            return Err(Error::Type(c"Invalid transaction mode".to_owned()));
215        }
216
217        // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction
218        // Step 8: Set transaction’s cleanup event loop to the current event loop.
219        transaction.set_cleanup_event_loop();
220        // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions
221        // NOTE: These steps are invoked by [HTML]. They ensure that transactions created
222        // by a script call to transaction() are deactivated once the task that invoked
223        // the script has completed. The steps are run at most once for each transaction.
224        // https://w3c.github.io/IndexedDB/#transaction-concept
225        // A transaction optionally has a cleanup event loop which is an event loop.
226        self.global()
227            .get_indexeddb()
228            .register_indexeddb_transaction(&transaction);
229
230        Ok(transaction)
231    }
232
233    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-createobjectstore>
234    fn CreateObjectStore(
235        &self,
236        cx: &mut JSContext,
237        name: DOMString,
238        options: &IDBObjectStoreParameters,
239    ) -> Fallible<DomRoot<IDBObjectStore>> {
240        // Step 2
241        let upgrade_transaction = match self.upgrade_transaction.get() {
242            Some(txn) => txn,
243            None => return Err(Error::InvalidState(None)),
244        };
245
246        // Step 3
247        if !upgrade_transaction.is_active() {
248            return Err(Error::TransactionInactive(None));
249        }
250
251        // Step 4
252        let key_path = options.keyPath.as_ref();
253
254        // Step 5
255        if let Some(path) = key_path {
256            if !is_valid_key_path(cx, path)? {
257                return Err(Error::Syntax(None));
258            }
259        }
260
261        // Step 6
262        if self.object_store_names.borrow().contains(&name) {
263            return Err(Error::Constraint(None));
264        }
265
266        // Step 7
267        let auto_increment = options.autoIncrement;
268
269        // Step 8
270        if auto_increment {
271            match key_path {
272                Some(StringOrStringSequence::String(path)) => {
273                    if path.is_empty() {
274                        return Err(Error::InvalidAccess(None));
275                    }
276                },
277                Some(StringOrStringSequence::StringSequence(_)) => {
278                    return Err(Error::InvalidAccess(None));
279                },
280                None => {},
281            }
282        }
283
284        // Step 9
285        let object_store = IDBObjectStore::new(
286            &self.global(),
287            self.name.clone(),
288            name.clone(),
289            Some(options),
290            if auto_increment { Some(1) } else { None },
291            CanGc::from_cx(cx),
292            &upgrade_transaction,
293        );
294
295        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
296
297        let key_paths = key_path.map(|p| match p {
298            StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
299            StringOrStringSequence::StringSequence(s) => {
300                KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
301            },
302        });
303        let operation = SyncOperation::CreateObjectStore(
304            sender,
305            self.global().origin().immutable().clone(),
306            self.name.to_string(),
307            name.to_string(),
308            key_paths,
309            auto_increment,
310        );
311
312        self.get_idb_thread()
313            .send(IndexedDBThreadMsg::Sync(operation))
314            .unwrap();
315
316        if receiver
317            .recv()
318            .expect("Could not receive object store creation status")
319            .is_err()
320        {
321            warn!("Object store creation failed in idb thread");
322            return Err(Error::InvalidState(None));
323        };
324
325        self.object_store_names.borrow_mut().push(name);
326        Ok(object_store)
327    }
328
329    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-deleteobjectstore>
330    fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
331        // Steps 1 & 2
332        let transaction = self.upgrade_transaction.get();
333        let transaction = match transaction {
334            Some(transaction) => transaction,
335            None => return Err(Error::InvalidState(None)),
336        };
337
338        // Step 3
339        if !transaction.is_active() {
340            return Err(Error::TransactionInactive(None));
341        }
342
343        // Step 4
344        if !self.object_store_names.borrow().contains(&name) {
345            return Err(Error::NotFound(None));
346        }
347
348        // Step 5
349        self.object_store_names
350            .borrow_mut()
351            .retain(|store_name| *store_name != name);
352
353        // Step 6
354        // FIXME:(arihant2math) Remove from index set ...
355
356        // Step 7
357        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
358
359        let operation = SyncOperation::DeleteObjectStore(
360            sender,
361            self.global().origin().immutable().clone(),
362            self.name.to_string(),
363            name.to_string(),
364        );
365
366        self.get_idb_thread()
367            .send(IndexedDBThreadMsg::Sync(operation))
368            .unwrap();
369
370        if receiver
371            .recv()
372            .expect("Could not receive object store deletion status")
373            .is_err()
374        {
375            warn!("Object store deletion failed in idb thread");
376            return Err(Error::InvalidState(None));
377        };
378        Ok(())
379    }
380
381    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-name>
382    fn Name(&self) -> DOMString {
383        self.name.clone()
384    }
385
386    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-version>
387    fn Version(&self) -> u64 {
388        self.version()
389    }
390
391    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-objectstorenames>
392    fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
393        DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
394    }
395
396    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-close>
397    fn Close(&self) {
398        // Step 1: Run close a database connection with this connection.
399
400        // <https://w3c.github.io/IndexedDB/#close-a-database-connection>
401        // Step 1: Set connection’s close pending flag to true.
402        self.close_pending.set(true);
403
404        // Note: rest of algo runs in-parallel.
405        let operation = SyncOperation::CloseDatabase(
406            self.global().origin().immutable().clone(),
407            self.id,
408            self.name.to_string(),
409        );
410        let _ = self
411            .get_idb_thread()
412            .send(IndexedDBThreadMsg::Sync(operation));
413    }
414
415    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onabort
416    event_handler!(abort, GetOnabort, SetOnabort);
417
418    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onclose
419    event_handler!(close, GetOnclose, SetOnclose);
420
421    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onerror
422    event_handler!(error, GetOnerror, SetOnerror);
423
424    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onversionchange
425    event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
426}