script/dom/
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 dom_struct::dom_struct;
8use ipc_channel::ipc::IpcSender;
9use net_traits::IpcSend;
10use net_traits::indexeddb_thread::{IndexedDBThreadMsg, KeyPath, SyncOperation};
11use profile_traits::ipc;
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::idbobjectstore::IDBObjectStore;
31use crate::dom::idbtransaction::IDBTransaction;
32use crate::dom::idbversionchangeevent::IDBVersionChangeEvent;
33use crate::indexed_db::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) -> IpcSender<IndexedDBThreadMsg> {
80        self.global().resource_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) = ipc::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    #[allow(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);
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        name: DOMString,
196        options: &IDBObjectStoreParameters,
197    ) -> Fallible<DomRoot<IDBObjectStore>> {
198        // Step 2
199        let upgrade_transaction = match self.upgrade_transaction.get() {
200            Some(txn) => txn,
201            None => return Err(Error::InvalidState),
202        };
203
204        // Step 3
205        if !upgrade_transaction.is_active() {
206            return Err(Error::TransactionInactive);
207        }
208
209        // Step 4
210        let key_path = options.keyPath.as_ref();
211
212        // Step 5
213        if let Some(path) = key_path {
214            if !is_valid_key_path(path) {
215                return Err(Error::Syntax(None));
216            }
217        }
218
219        // Step 6
220        if self
221            .object_store_names
222            .borrow()
223            .iter()
224            .any(|store_name| store_name.to_string() == name.to_string())
225        {
226            return Err(Error::Constraint);
227        }
228
229        // Step 7
230        let auto_increment = options.autoIncrement;
231
232        // Step 8
233        if auto_increment {
234            match key_path {
235                Some(StringOrStringSequence::String(path)) => {
236                    if path.is_empty() {
237                        return Err(Error::InvalidAccess);
238                    }
239                },
240                Some(StringOrStringSequence::StringSequence(_)) => {
241                    return Err(Error::InvalidAccess);
242                },
243                None => {},
244            }
245        }
246
247        // Step 9
248        let object_store = IDBObjectStore::new(
249            &self.global(),
250            self.name.clone(),
251            name.clone(),
252            Some(options),
253            CanGc::note(),
254            &upgrade_transaction,
255        );
256
257        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
258
259        let key_paths = key_path.map(|p| match p {
260            StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
261            StringOrStringSequence::StringSequence(s) => {
262                KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
263            },
264        });
265        let operation = SyncOperation::CreateObjectStore(
266            sender,
267            self.global().origin().immutable().clone(),
268            self.name.to_string(),
269            name.to_string(),
270            key_paths,
271            auto_increment,
272        );
273
274        self.get_idb_thread()
275            .send(IndexedDBThreadMsg::Sync(operation))
276            .unwrap();
277
278        if receiver
279            .recv()
280            .expect("Could not receive object store creation status")
281            .is_err()
282        {
283            warn!("Object store creation failed in idb thread");
284            return Err(Error::InvalidState);
285        };
286
287        self.object_store_names.borrow_mut().push(name);
288        Ok(object_store)
289    }
290
291    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-deleteobjectstore
292    fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
293        // Steps 1 & 2
294        let transaction = self.upgrade_transaction.get();
295        let transaction = match transaction {
296            Some(transaction) => transaction,
297            None => return Err(Error::InvalidState),
298        };
299
300        // Step 3
301        if !transaction.is_active() {
302            return Err(Error::TransactionInactive);
303        }
304
305        // Step 4
306        if !self
307            .object_store_names
308            .borrow()
309            .iter()
310            .any(|store_name| store_name.to_string() == name.to_string())
311        {
312            return Err(Error::NotFound);
313        }
314
315        // Step 5
316        self.object_store_names
317            .borrow_mut()
318            .retain(|store_name| store_name.to_string() != name.to_string());
319
320        // Step 6
321        // FIXME:(arihant2math) Remove from index set ...
322
323        // Step 7
324        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
325
326        let operation = SyncOperation::DeleteObjectStore(
327            sender,
328            self.global().origin().immutable().clone(),
329            self.name.to_string(),
330            name.to_string(),
331        );
332
333        self.get_idb_thread()
334            .send(IndexedDBThreadMsg::Sync(operation))
335            .unwrap();
336
337        if receiver
338            .recv()
339            .expect("Could not receive object store deletion status")
340            .is_err()
341        {
342            warn!("Object store deletion failed in idb thread");
343            return Err(Error::InvalidState);
344        };
345        Ok(())
346    }
347
348    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-name
349    fn Name(&self) -> DOMString {
350        self.name.clone()
351    }
352
353    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-version
354    fn Version(&self) -> u64 {
355        self.version()
356    }
357
358    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-objectstorenames
359    fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
360        // FIXME: (arihant2math) Sort the list of names, as per spec
361        DOMStringList::new(
362            &self.global(),
363            self.object_store_names.borrow().clone(),
364            CanGc::note(),
365        )
366    }
367
368    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-close
369    fn Close(&self) {
370        // Step 1: Set the close pending flag of connection.
371        self.closing.set(true);
372
373        // Step 2: Handle force flag
374        // FIXME:(arihant2math)
375        // Step 3: Wait for all transactions by this db to finish
376        // FIXME:(arihant2math)
377        // Step 4: If force flag is set, fire a close event
378        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
379        let operation = SyncOperation::CloseDatabase(
380            sender,
381            self.global().origin().immutable().clone(),
382            self.name.to_string(),
383        );
384        let _ = self
385            .get_idb_thread()
386            .send(IndexedDBThreadMsg::Sync(operation));
387
388        if receiver.recv().is_err() {
389            warn!("Database close failed in idb thread");
390        };
391    }
392
393    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onabort
394    event_handler!(abort, GetOnabort, SetOnabort);
395
396    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onclose
397    event_handler!(close, GetOnclose, SetOnclose);
398
399    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onerror
400    event_handler!(error, GetOnerror, SetOnerror);
401
402    // https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onversionchange
403    event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
404}