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;
13
14use crate::dom::bindings::cell::DomRefCell;
15use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
16    IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
17};
18use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
19use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::refcounted::Trusted;
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::event::{Event, EventBubbles, EventCancelable};
28use crate::dom::eventtarget::EventTarget;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
31use crate::dom::indexeddb::idbtransaction::IDBTransaction;
32use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
33use crate::indexeddb::is_valid_key_path;
34use crate::script_runtime::CanGc;
35
36#[dom_struct]
37pub struct IDBDatabase {
38    eventtarget: EventTarget,
39    /// <https://w3c.github.io/IndexedDB/#database-name>
40    name: DOMString,
41    /// <https://w3c.github.io/IndexedDB/#database-version>
42    version: Cell<u64>,
43    /// <https://w3c.github.io/IndexedDB/#object-store>
44    object_store_names: DomRefCell<Vec<DOMString>>,
45    /// <https://w3c.github.io/IndexedDB/#database-upgrade-transaction>
46    upgrade_transaction: MutNullableDom<IDBTransaction>,
47
48    // Flags
49    /// <https://w3c.github.io/IndexedDB/#connection-close-pending-flag>
50    closing: Cell<bool>,
51}
52
53impl IDBDatabase {
54    pub fn new_inherited(name: DOMString, version: u64) -> IDBDatabase {
55        IDBDatabase {
56            eventtarget: EventTarget::new_inherited(),
57            name,
58            version: Cell::new(version),
59            object_store_names: Default::default(),
60
61            upgrade_transaction: Default::default(),
62            closing: Cell::new(false),
63        }
64    }
65
66    pub fn new(
67        global: &GlobalScope,
68        name: DOMString,
69        version: u64,
70        can_gc: CanGc,
71    ) -> DomRoot<IDBDatabase> {
72        reflect_dom_object(
73            Box::new(IDBDatabase::new_inherited(name, version)),
74            global,
75            can_gc,
76        )
77    }
78
79    fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
80        self.global().storage_threads().sender()
81    }
82
83    pub fn get_name(&self) -> DOMString {
84        self.name.clone()
85    }
86
87    pub fn object_stores(&self) -> DomRoot<DOMStringList> {
88        DOMStringList::new(
89            &self.global(),
90            self.object_store_names.borrow().clone(),
91            CanGc::note(),
92        )
93    }
94
95    pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
96        self.object_store_names
97            .borrow()
98            .iter()
99            .any(|store_name| store_name == name)
100    }
101
102    pub fn version(&self) -> u64 {
103        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
104        let operation = SyncOperation::Version(
105            sender,
106            self.global().origin().immutable().clone(),
107            self.name.to_string(),
108        );
109
110        let _ = self
111            .get_idb_thread()
112            .send(IndexedDBThreadMsg::Sync(operation));
113
114        receiver.recv().unwrap().unwrap_or_else(|e| {
115            error!("{e:?}");
116            u64::MAX
117        })
118    }
119
120    pub fn set_transaction(&self, transaction: &IDBTransaction) {
121        self.upgrade_transaction.set(Some(transaction));
122    }
123
124    #[expect(dead_code)] // This will be used once we allow multiple concurrent connections
125    pub fn dispatch_versionchange(
126        &self,
127        old_version: u64,
128        new_version: Option<u64>,
129        _can_gc: CanGc,
130    ) {
131        let global = self.global();
132        let this = Trusted::new(self);
133        global.task_manager().database_access_task_source().queue(
134            task!(send_versionchange_notification: move || {
135                let this = this.root();
136                let global = this.global();
137                let event = IDBVersionChangeEvent::new(
138                    &global,
139                    Atom::from("versionchange"),
140                    EventBubbles::DoesNotBubble,
141                    EventCancelable::NotCancelable,
142                    old_version,
143                    new_version,
144                    CanGc::note()
145                );
146                event.upcast::<Event>().fire(this.upcast(), CanGc::note());
147            }),
148        );
149    }
150}
151
152impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
153    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction>
154    fn Transaction(
155        &self,
156        store_names: StringOrStringSequence,
157        mode: IDBTransactionMode,
158        _options: &IDBTransactionOptions,
159    ) -> Fallible<DomRoot<IDBTransaction>> {
160        // FIXIME:(arihant2math) use options
161        // Step 1: Check if upgrade transaction is running
162        // FIXME:(rasviitanen)
163
164        // Step 2: if close flag is set, throw error
165        if self.closing.get() {
166            return Err(Error::InvalidState(None));
167        }
168
169        // Step 3
170        Ok(match store_names {
171            StringOrStringSequence::String(name) => IDBTransaction::new(
172                &self.global(),
173                self,
174                mode,
175                &DOMStringList::new(&self.global(), vec![name], CanGc::note()),
176                CanGc::note(),
177            ),
178            StringOrStringSequence::StringSequence(sequence) => {
179                // FIXME:(rasviitanen) Remove eventual duplicated names
180                // from the sequence
181                IDBTransaction::new(
182                    &self.global(),
183                    self,
184                    mode,
185                    &DOMStringList::new(&self.global(), sequence, CanGc::note()),
186                    CanGc::note(),
187                )
188            },
189        })
190    }
191
192    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-createobjectstore>
193    fn CreateObjectStore(
194        &self,
195        cx: &mut JSContext,
196        name: DOMString,
197        options: &IDBObjectStoreParameters,
198    ) -> Fallible<DomRoot<IDBObjectStore>> {
199        // Step 2
200        let upgrade_transaction = match self.upgrade_transaction.get() {
201            Some(txn) => txn,
202            None => return Err(Error::InvalidState(None)),
203        };
204
205        // Step 3
206        if !upgrade_transaction.is_active() {
207            return Err(Error::TransactionInactive(None));
208        }
209
210        // Step 4
211        let key_path = options.keyPath.as_ref();
212
213        // Step 5
214        if let Some(path) = key_path {
215            if !is_valid_key_path(cx, path)? {
216                return Err(Error::Syntax(None));
217            }
218        }
219
220        // Step 6
221        if self.object_store_names.borrow().contains(&name) {
222            return Err(Error::Constraint(None));
223        }
224
225        // Step 7
226        let auto_increment = options.autoIncrement;
227
228        // Step 8
229        if auto_increment {
230            match key_path {
231                Some(StringOrStringSequence::String(path)) => {
232                    if path.is_empty() {
233                        return Err(Error::InvalidAccess(None));
234                    }
235                },
236                Some(StringOrStringSequence::StringSequence(_)) => {
237                    return Err(Error::InvalidAccess(None));
238                },
239                None => {},
240            }
241        }
242
243        // Step 9
244        let object_store = IDBObjectStore::new(
245            &self.global(),
246            self.name.clone(),
247            name.clone(),
248            Some(options),
249            CanGc::from_cx(cx),
250            &upgrade_transaction,
251        );
252
253        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
254
255        let key_paths = key_path.map(|p| match p {
256            StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
257            StringOrStringSequence::StringSequence(s) => {
258                KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
259            },
260        });
261        let operation = SyncOperation::CreateObjectStore(
262            sender,
263            self.global().origin().immutable().clone(),
264            self.name.to_string(),
265            name.to_string(),
266            key_paths,
267            auto_increment,
268        );
269
270        self.get_idb_thread()
271            .send(IndexedDBThreadMsg::Sync(operation))
272            .unwrap();
273
274        if receiver
275            .recv()
276            .expect("Could not receive object store creation status")
277            .is_err()
278        {
279            warn!("Object store creation failed in idb thread");
280            return Err(Error::InvalidState(None));
281        };
282
283        self.object_store_names.borrow_mut().push(name);
284        Ok(object_store)
285    }
286
287    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-deleteobjectstore>
288    fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
289        // Steps 1 & 2
290        let transaction = self.upgrade_transaction.get();
291        let transaction = match transaction {
292            Some(transaction) => transaction,
293            None => return Err(Error::InvalidState(None)),
294        };
295
296        // Step 3
297        if !transaction.is_active() {
298            return Err(Error::TransactionInactive(None));
299        }
300
301        // Step 4
302        if !self.object_store_names.borrow().contains(&name) {
303            return Err(Error::NotFound(None));
304        }
305
306        // Step 5
307        self.object_store_names
308            .borrow_mut()
309            .retain(|store_name| *store_name != name);
310
311        // Step 6
312        // FIXME:(arihant2math) Remove from index set ...
313
314        // Step 7
315        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
316
317        let operation = SyncOperation::DeleteObjectStore(
318            sender,
319            self.global().origin().immutable().clone(),
320            self.name.to_string(),
321            name.to_string(),
322        );
323
324        self.get_idb_thread()
325            .send(IndexedDBThreadMsg::Sync(operation))
326            .unwrap();
327
328        if receiver
329            .recv()
330            .expect("Could not receive object store deletion status")
331            .is_err()
332        {
333            warn!("Object store deletion failed in idb thread");
334            return Err(Error::InvalidState(None));
335        };
336        Ok(())
337    }
338
339    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-name>
340    fn Name(&self) -> DOMString {
341        self.name.clone()
342    }
343
344    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-version>
345    fn Version(&self) -> u64 {
346        self.version()
347    }
348
349    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-objectstorenames>
350    fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
351        // FIXME: (arihant2math) Sort the list of names, as per spec
352        DOMStringList::new(
353            &self.global(),
354            self.object_store_names.borrow().clone(),
355            CanGc::note(),
356        )
357    }
358
359    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-close>
360    fn Close(&self) {
361        // Step 1: Set the close pending flag of connection.
362        self.closing.set(true);
363
364        // Step 2: Handle force flag
365        // FIXME:(arihant2math)
366        // Step 3: Wait for all transactions by this db to finish
367        // FIXME:(arihant2math)
368        // Step 4: If force flag is set, fire a close event
369        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
370        let operation = SyncOperation::CloseDatabase(
371            sender,
372            self.global().origin().immutable().clone(),
373            self.name.to_string(),
374        );
375        let _ = self
376            .get_idb_thread()
377            .send(IndexedDBThreadMsg::Sync(operation));
378
379        if receiver.recv().is_err() {
380            warn!("Database close failed in idb thread");
381        };
382    }
383
384    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onabort
385    event_handler!(abort, GetOnabort, SetOnabort);
386
387    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onclose
388    event_handler!(close, GetOnclose, SetOnclose);
389
390    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onerror
391    event_handler!(error, GetOnerror, SetOnerror);
392
393    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onversionchange
394    event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
395}