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    closing: 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            closing: 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_exists(&self, name: &DOMString) -> bool {
100        self.object_store_names
101            .borrow()
102            .iter()
103            .any(|store_name| store_name == name)
104    }
105
106    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-version>
107    pub(crate) fn version(&self) -> u64 {
108        // The version getter steps are to return this’s version.
109        self.version.get()
110    }
111
112    pub(crate) fn set_version(&self, version: u64) {
113        self.version.set(version);
114    }
115
116    pub fn set_transaction(&self, transaction: &IDBTransaction) {
117        self.upgrade_transaction.set(Some(transaction));
118    }
119
120    pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
121        let current = self
122            .upgrade_transaction
123            .get()
124            .expect("clear_upgrade_transaction called but no upgrade transaction is set");
125
126        debug_assert!(
127            &*current == transaction,
128            "clear_upgrade_transaction called with non-current transaction"
129        );
130
131        self.upgrade_transaction.set(None);
132    }
133
134    /// <https://w3c.github.io/IndexedDB/#eventdef-idbdatabase-versionchange>
135    pub fn dispatch_versionchange(
136        &self,
137        old_version: u64,
138        new_version: Option<u64>,
139        can_gc: CanGc,
140    ) {
141        let global = self.global();
142        let _ = IDBVersionChangeEvent::fire_version_change_event(
143            &global,
144            self.upcast(),
145            Atom::from("versionchange"),
146            old_version,
147            new_version,
148            can_gc,
149        );
150    }
151}
152
153impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
154    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction>
155    fn Transaction(
156        &self,
157        store_names: StringOrStringSequence,
158        mode: IDBTransactionMode,
159        _options: &IDBTransactionOptions,
160    ) -> Fallible<DomRoot<IDBTransaction>> {
161        // FIXIME:(arihant2math) use options
162        // Step 1: Check if upgrade transaction is running
163        // FIXME:(rasviitanen)
164
165        // Step 2: if close flag is set, throw error
166        if self.closing.get() {
167            return Err(Error::InvalidState(None));
168        }
169
170        // Step 3
171        let transaction = match store_names {
172            StringOrStringSequence::String(name) => IDBTransaction::new(
173                &self.global(),
174                self,
175                mode,
176                &DOMStringList::new(&self.global(), vec![name], CanGc::note()),
177                CanGc::note(),
178            ),
179            StringOrStringSequence::StringSequence(sequence) => {
180                // FIXME:(rasviitanen) Remove eventual duplicated names
181                // from the sequence
182                IDBTransaction::new(
183                    &self.global(),
184                    self,
185                    mode,
186                    &DOMStringList::new(&self.global(), sequence, CanGc::note()),
187                    CanGc::note(),
188                )
189            },
190        };
191
192        // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction
193        // Step 6: If mode is not "readonly" or "readwrite", throw a TypeError.
194        if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
195            return Err(Error::Type(c"Invalid transaction mode".to_owned()));
196        }
197
198        // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction
199        // Step 8: Set transaction’s cleanup event loop to the current event loop.
200        transaction.set_cleanup_event_loop();
201        // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions
202        // NOTE: These steps are invoked by [HTML]. They ensure that transactions created
203        // by a script call to transaction() are deactivated once the task that invoked
204        // the script has completed. The steps are run at most once for each transaction.
205        // https://w3c.github.io/IndexedDB/#transaction-concept
206        // A transaction optionally has a cleanup event loop which is an event loop.
207        self.global()
208            .get_indexeddb()
209            .register_indexeddb_transaction(&transaction);
210
211        Ok(transaction)
212    }
213
214    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-createobjectstore>
215    fn CreateObjectStore(
216        &self,
217        cx: &mut JSContext,
218        name: DOMString,
219        options: &IDBObjectStoreParameters,
220    ) -> Fallible<DomRoot<IDBObjectStore>> {
221        // Step 2
222        let upgrade_transaction = match self.upgrade_transaction.get() {
223            Some(txn) => txn,
224            None => return Err(Error::InvalidState(None)),
225        };
226
227        // Step 3
228        if !upgrade_transaction.is_active() {
229            return Err(Error::TransactionInactive(None));
230        }
231
232        // Step 4
233        let key_path = options.keyPath.as_ref();
234
235        // Step 5
236        if let Some(path) = key_path {
237            if !is_valid_key_path(cx, path)? {
238                return Err(Error::Syntax(None));
239            }
240        }
241
242        // Step 6
243        if self.object_store_names.borrow().contains(&name) {
244            return Err(Error::Constraint(None));
245        }
246
247        // Step 7
248        let auto_increment = options.autoIncrement;
249
250        // Step 8
251        if auto_increment {
252            match key_path {
253                Some(StringOrStringSequence::String(path)) => {
254                    if path.is_empty() {
255                        return Err(Error::InvalidAccess(None));
256                    }
257                },
258                Some(StringOrStringSequence::StringSequence(_)) => {
259                    return Err(Error::InvalidAccess(None));
260                },
261                None => {},
262            }
263        }
264
265        // Step 9
266        let object_store = IDBObjectStore::new(
267            &self.global(),
268            self.name.clone(),
269            name.clone(),
270            Some(options),
271            CanGc::from_cx(cx),
272            &upgrade_transaction,
273        );
274
275        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
276
277        let key_paths = key_path.map(|p| match p {
278            StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
279            StringOrStringSequence::StringSequence(s) => {
280                KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
281            },
282        });
283        let operation = SyncOperation::CreateObjectStore(
284            sender,
285            self.global().origin().immutable().clone(),
286            self.name.to_string(),
287            name.to_string(),
288            key_paths,
289            auto_increment,
290        );
291
292        self.get_idb_thread()
293            .send(IndexedDBThreadMsg::Sync(operation))
294            .unwrap();
295
296        if receiver
297            .recv()
298            .expect("Could not receive object store creation status")
299            .is_err()
300        {
301            warn!("Object store creation failed in idb thread");
302            return Err(Error::InvalidState(None));
303        };
304
305        self.object_store_names.borrow_mut().push(name);
306        Ok(object_store)
307    }
308
309    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-deleteobjectstore>
310    fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
311        // Steps 1 & 2
312        let transaction = self.upgrade_transaction.get();
313        let transaction = match transaction {
314            Some(transaction) => transaction,
315            None => return Err(Error::InvalidState(None)),
316        };
317
318        // Step 3
319        if !transaction.is_active() {
320            return Err(Error::TransactionInactive(None));
321        }
322
323        // Step 4
324        if !self.object_store_names.borrow().contains(&name) {
325            return Err(Error::NotFound(None));
326        }
327
328        // Step 5
329        self.object_store_names
330            .borrow_mut()
331            .retain(|store_name| *store_name != name);
332
333        // Step 6
334        // FIXME:(arihant2math) Remove from index set ...
335
336        // Step 7
337        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
338
339        let operation = SyncOperation::DeleteObjectStore(
340            sender,
341            self.global().origin().immutable().clone(),
342            self.name.to_string(),
343            name.to_string(),
344        );
345
346        self.get_idb_thread()
347            .send(IndexedDBThreadMsg::Sync(operation))
348            .unwrap();
349
350        if receiver
351            .recv()
352            .expect("Could not receive object store deletion status")
353            .is_err()
354        {
355            warn!("Object store deletion failed in idb thread");
356            return Err(Error::InvalidState(None));
357        };
358        Ok(())
359    }
360
361    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-name>
362    fn Name(&self) -> DOMString {
363        self.name.clone()
364    }
365
366    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-version>
367    fn Version(&self) -> u64 {
368        self.version()
369    }
370
371    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-objectstorenames>
372    fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
373        DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
374    }
375
376    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-close>
377    fn Close(&self) {
378        // Step 1: Run close a database connection with this connection.
379
380        // <https://w3c.github.io/IndexedDB/#close-a-database-connection>
381        // Step 1: Set connection’s close pending flag to true.
382        self.closing.set(true);
383
384        // Note: rest of algo runs in-parallel.
385        let operation = SyncOperation::CloseDatabase(
386            self.global().origin().immutable().clone(),
387            self.id,
388            self.name.to_string(),
389        );
390        let _ = self
391            .get_idb_thread()
392            .send(IndexedDBThreadMsg::Sync(operation));
393    }
394
395    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onabort
396    event_handler!(abort, GetOnabort, SetOnabort);
397
398    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onclose
399    event_handler!(close, GetOnclose, SetOnclose);
400
401    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onerror
402    event_handler!(error, GetOnerror, SetOnerror);
403
404    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onversionchange
405    event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
406}