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 dom_struct::dom_struct;
8use js::context::JSContext;
9use profile_traits::generic_channel::channel;
10use servo_base::generic_channel::{GenericSend, GenericSender};
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 1. Let database be this’s associated database.
241
242        // Step 2. Let transaction be database’s upgrade transaction if it is not null,
243        // or throw an "InvalidStateError" DOMException otherwise.
244        let transaction = match self.upgrade_transaction.get() {
245            Some(txn) => txn,
246            None => return Err(Error::InvalidState(None)),
247        };
248
249        // Step 3. If transaction’s state is not active, then throw a
250        // "TransactionInactiveError" DOMException.
251        if !transaction.is_active() {
252            return Err(Error::TransactionInactive(None));
253        }
254
255        // Step 4. Let keyPath be options’s keyPath member if it is not undefined
256        // or null, or null otherwise.
257        let key_path = options.keyPath.as_ref();
258
259        // Step 5. If keyPath is not null and is not a valid key path, throw a
260        // "SyntaxError" DOMException.
261        if let Some(path) = key_path {
262            if !is_valid_key_path(cx, path)? {
263                return Err(Error::Syntax(None));
264            }
265        }
266
267        // Step 6. If an object store named name already exists in database throw
268        // a "ConstraintError" DOMException.
269        if self.object_store_names.borrow().contains(&name) {
270            return Err(Error::Constraint(None));
271        }
272
273        // Step 7. Let autoIncrement be options’s autoIncrement member.
274        let auto_increment = options.autoIncrement;
275
276        // Step 8. If autoIncrement is true and keyPath is an empty string or any
277        // sequence (empty or otherwise), throw an "InvalidAccessError" DOMException.
278        if auto_increment {
279            match key_path {
280                Some(StringOrStringSequence::String(path)) => {
281                    if path.is_empty() {
282                        return Err(Error::InvalidAccess(None));
283                    }
284                },
285                Some(StringOrStringSequence::StringSequence(_)) => {
286                    return Err(Error::InvalidAccess(None));
287                },
288                None => {},
289            }
290        }
291
292        // Step 9. Let store be a new object store in database. Set the created
293        // object store’s name to name. If autoIncrement is true, then the
294        // created object store uses a key generator. If keyPath is not null,
295        // set the created object store’s key path to keyPath.
296        let object_store = IDBObjectStore::new(
297            &self.global(),
298            self.name.clone(),
299            name.clone(),
300            Some(options),
301            if auto_increment { Some(1) } else { None },
302            CanGc::from_cx(cx),
303            &transaction,
304        );
305
306        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
307
308        let key_paths = key_path.map(|p| match p {
309            StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
310            StringOrStringSequence::StringSequence(s) => {
311                KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
312            },
313        });
314        let operation = SyncOperation::CreateObjectStore(
315            sender,
316            self.global().origin().immutable().clone(),
317            self.name.to_string(),
318            name.to_string(),
319            key_paths,
320            auto_increment,
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 creation status")
330            .is_err()
331        {
332            warn!("Object store creation failed in idb thread");
333            return Err(Error::InvalidState(None));
334        };
335
336        self.object_store_names.borrow_mut().push(name);
337
338        // Step 10. Return a new object store handle associated with store and transaction.
339        Ok(object_store)
340    }
341
342    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-deleteobjectstore>
343    fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
344        // Steps 1 & 2
345        let transaction = self.upgrade_transaction.get();
346        let transaction = match transaction {
347            Some(transaction) => transaction,
348            None => return Err(Error::InvalidState(None)),
349        };
350
351        // Step 3
352        if !transaction.is_active() {
353            return Err(Error::TransactionInactive(None));
354        }
355
356        // Step 4
357        if !self.object_store_names.borrow().contains(&name) {
358            return Err(Error::NotFound(None));
359        }
360
361        // Step 5
362        self.object_store_names
363            .borrow_mut()
364            .retain(|store_name| *store_name != name);
365
366        // Step 6
367        // FIXME:(arihant2math) Remove from index set ...
368
369        // Step 7
370        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
371
372        let operation = SyncOperation::DeleteObjectStore(
373            sender,
374            self.global().origin().immutable().clone(),
375            self.name.to_string(),
376            name.to_string(),
377        );
378
379        self.get_idb_thread()
380            .send(IndexedDBThreadMsg::Sync(operation))
381            .unwrap();
382
383        if receiver
384            .recv()
385            .expect("Could not receive object store deletion status")
386            .is_err()
387        {
388            warn!("Object store deletion failed in idb thread");
389            return Err(Error::InvalidState(None));
390        };
391        Ok(())
392    }
393
394    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-name>
395    fn Name(&self) -> DOMString {
396        self.name.clone()
397    }
398
399    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-version>
400    fn Version(&self) -> u64 {
401        self.version()
402    }
403
404    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-objectstorenames>
405    fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
406        DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
407    }
408
409    /// <https://w3c.github.io/IndexedDB/#dom-idbdatabase-close>
410    fn Close(&self) {
411        // Step 1: Run close a database connection with this connection.
412
413        // <https://w3c.github.io/IndexedDB/#close-a-database-connection>
414        // Step 1: Set connection’s close pending flag to true.
415        self.close_pending.set(true);
416
417        // Note: rest of algo runs in-parallel.
418        let operation = SyncOperation::CloseDatabase(
419            self.global().origin().immutable().clone(),
420            self.id,
421            self.name.to_string(),
422        );
423        let _ = self
424            .get_idb_thread()
425            .send(IndexedDBThreadMsg::Sync(operation));
426    }
427
428    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onabort
429    event_handler!(abort, GetOnabort, SetOnabort);
430
431    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onclose
432    event_handler!(close, GetOnclose, SetOnclose);
433
434    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onerror
435    event_handler!(error, GetOnerror, SetOnerror);
436
437    // https://www.w3.org/TR/IndexedDB-3/#dom-idbdatabase-onversionchange
438    event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
439}