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