script/dom/indexeddb/
idbtransaction.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;
6use std::collections::HashMap;
7
8use base::IpcSend;
9use dom_struct::dom_struct;
10use ipc_channel::ipc::IpcSender;
11use profile_traits::ipc;
12use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
13use storage_traits::indexeddb_thread::{IndexedDBThreadMsg, KeyPath, SyncOperation};
14use stylo_atoms::Atom;
15
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods;
18use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
19use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
20    IDBTransactionMethods, IDBTransactionMode,
21};
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
26use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
27use crate::dom::bindings::str::DOMString;
28use crate::dom::domexception::DOMException;
29use crate::dom::domstringlist::DOMStringList;
30use crate::dom::event::{Event, EventBubbles, EventCancelable};
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::indexeddb::idbdatabase::IDBDatabase;
34use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
35use crate::dom::indexeddb::idbrequest::IDBRequest;
36use crate::script_runtime::CanGc;
37
38#[dom_struct]
39pub struct IDBTransaction {
40    eventtarget: EventTarget,
41    object_store_names: Dom<DOMStringList>,
42    mode: IDBTransactionMode,
43    db: Dom<IDBDatabase>,
44    error: MutNullableDom<DOMException>,
45
46    store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
47    // https://www.w3.org/TR/IndexedDB-2/#transaction-request-list
48    requests: DomRefCell<Vec<Dom<IDBRequest>>>,
49    // https://www.w3.org/TR/IndexedDB-2/#transaction-active-flag
50    active: Cell<bool>,
51    // https://www.w3.org/TR/IndexedDB-2/#transaction-finish
52    finished: Cell<bool>,
53    // An unique identifier, used to commit and revert this transaction
54    // FIXME:(rasviitanen) Replace this with a channel
55    serial_number: u64,
56}
57
58impl IDBTransaction {
59    fn new_inherited(
60        connection: &IDBDatabase,
61        mode: IDBTransactionMode,
62        scope: &DOMStringList,
63        serial_number: u64,
64    ) -> IDBTransaction {
65        IDBTransaction {
66            eventtarget: EventTarget::new_inherited(),
67            object_store_names: Dom::from_ref(scope),
68            mode,
69            db: Dom::from_ref(connection),
70            error: Default::default(),
71
72            store_handles: Default::default(),
73            requests: Default::default(),
74            active: Cell::new(true),
75            finished: Cell::new(false),
76            serial_number,
77        }
78    }
79
80    pub fn new(
81        global: &GlobalScope,
82        connection: &IDBDatabase,
83        mode: IDBTransactionMode,
84        scope: &DOMStringList,
85        can_gc: CanGc,
86    ) -> DomRoot<IDBTransaction> {
87        let serial_number = IDBTransaction::register_new(global, connection.get_name());
88        reflect_dom_object(
89            Box::new(IDBTransaction::new_inherited(
90                connection,
91                mode,
92                scope,
93                serial_number,
94            )),
95            global,
96            can_gc,
97        )
98    }
99
100    // Registers a new transaction in the idb thread, and gets an unique serial number in return.
101    // The serial number is used when placing requests against a transaction
102    // and allows us to commit/abort transactions running in our idb thread.
103    // FIXME:(rasviitanen) We could probably replace this with a channel instead,
104    // and queue requests directly to that channel.
105    fn register_new(global: &GlobalScope, db_name: DOMString) -> u64 {
106        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
107
108        global
109            .storage_threads()
110            .send(IndexedDBThreadMsg::Sync(SyncOperation::RegisterNewTxn(
111                sender,
112                global.origin().immutable().clone(),
113                db_name.to_string(),
114            )))
115            .unwrap();
116
117        receiver.recv().unwrap()
118    }
119
120    // Runs the transaction and waits for it to finish
121    pub fn wait(&self) {
122        // Start the transaction
123        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
124
125        let start_operation = SyncOperation::StartTransaction(
126            sender,
127            self.global().origin().immutable().clone(),
128            self.db.get_name().to_string(),
129            self.serial_number,
130        );
131
132        self.get_idb_thread()
133            .send(IndexedDBThreadMsg::Sync(start_operation))
134            .unwrap();
135
136        // Wait for transaction to complete
137        if receiver.recv().is_err() {
138            warn!("IDBtransaction failed to run");
139        };
140    }
141
142    pub fn set_active_flag(&self, status: bool) {
143        self.active.set(status)
144    }
145
146    pub fn is_active(&self) -> bool {
147        self.active.get()
148    }
149
150    pub fn get_mode(&self) -> IDBTransactionMode {
151        self.mode
152    }
153
154    pub fn get_db_name(&self) -> DOMString {
155        self.db.get_name()
156    }
157
158    pub fn get_serial_number(&self) -> u64 {
159        self.serial_number
160    }
161
162    pub fn add_request(&self, request: &IDBRequest) {
163        self.requests.borrow_mut().push(Dom::from_ref(request));
164    }
165
166    pub fn upgrade_db_version(&self, version: u64) {
167        // Runs the previous request and waits for them to finish
168        self.wait();
169        // Queue a request to upgrade the db version
170        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
171        let upgrade_version_operation = SyncOperation::UpgradeVersion(
172            sender,
173            self.global().origin().immutable().clone(),
174            self.db.get_name().to_string(),
175            self.serial_number,
176            version,
177        );
178        self.get_idb_thread()
179            .send(IndexedDBThreadMsg::Sync(upgrade_version_operation))
180            .unwrap();
181        // Wait for the version to be updated
182        // TODO(jdm): This returns a Result; what do we do with an error?
183        let _ = receiver.recv().unwrap();
184    }
185
186    fn dispatch_complete(&self) {
187        let global = self.global();
188        let this = Trusted::new(self);
189        global.task_manager().database_access_task_source().queue(
190            task!(send_complete_notification: move || {
191                let this = this.root();
192                let global = this.global();
193                let event = Event::new(
194                    &global,
195                    Atom::from("complete"),
196                    EventBubbles::DoesNotBubble,
197                    EventCancelable::NotCancelable,
198                    CanGc::note()
199                );
200                event.fire(this.upcast(), CanGc::note());
201            }),
202        );
203    }
204
205    fn get_idb_thread(&self) -> IpcSender<IndexedDBThreadMsg> {
206        self.global().storage_threads().sender()
207    }
208
209    fn object_store_parameters(
210        &self,
211        object_store_name: &DOMString,
212    ) -> Option<IDBObjectStoreParameters> {
213        let global = self.global();
214        let idb_sender = global.storage_threads().sender();
215        let (sender, receiver) =
216            ipc::channel(global.time_profiler_chan().clone()).expect("failed to create channel");
217
218        let origin = global.origin().immutable().clone();
219        let db_name = self.db.get_name().to_string();
220        let object_store_name = object_store_name.to_string();
221
222        let operation = SyncOperation::HasKeyGenerator(
223            sender,
224            origin.clone(),
225            db_name.clone(),
226            object_store_name.clone(),
227        );
228
229        let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
230
231        // First unwrap for ipc
232        // Second unwrap will never happen unless this db gets manually deleted somehow
233        let auto_increment = receiver.recv().ok()?.ok()?;
234
235        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).ok()?;
236        let operation = SyncOperation::KeyPath(sender, origin, db_name, object_store_name);
237
238        let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
239
240        // First unwrap for ipc
241        // Second unwrap will never happen unless this db gets manually deleted somehow
242        let key_path = receiver.recv().unwrap().ok()?;
243        let key_path = key_path.map(|key_path| match key_path {
244            KeyPath::String(s) => StringOrStringSequence::String(DOMString::from_string(s)),
245            KeyPath::Sequence(seq) => StringOrStringSequence::StringSequence(
246                seq.into_iter().map(DOMString::from_string).collect(),
247            ),
248        });
249        Some(IDBObjectStoreParameters {
250            autoIncrement: auto_increment,
251            keyPath: key_path,
252        })
253    }
254}
255
256impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
257    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-db
258    fn Db(&self) -> DomRoot<IDBDatabase> {
259        DomRoot::from_ref(&*self.db)
260    }
261
262    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstore
263    fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
264        // Step 1: If transaction has finished, throw an "InvalidStateError" DOMException.
265        if self.finished.get() {
266            return Err(Error::InvalidState(None));
267        }
268
269        // Step 2: Check that the object store exists
270        if !self.object_store_names.Contains(name.clone()) {
271            return Err(Error::NotFound(None));
272        }
273
274        // Step 3: Each call to this method on the same
275        // IDBTransaction instance with the same name
276        // returns the same IDBObjectStore instance.
277        if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
278            return Ok(DomRoot::from_ref(store));
279        }
280
281        let parameters = self.object_store_parameters(&name);
282        let store = IDBObjectStore::new(
283            &self.global(),
284            self.db.get_name(),
285            name.clone(),
286            parameters.as_ref(),
287            can_gc,
288            self,
289        );
290        self.store_handles
291            .borrow_mut()
292            .insert(name.to_string(), Dom::from_ref(&*store));
293        Ok(store)
294    }
295
296    // https://www.w3.org/TR/IndexedDB-2/#commit-transaction
297    fn Commit(&self) -> Fallible<()> {
298        // Step 1
299        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
300        let start_operation = SyncOperation::Commit(
301            sender,
302            self.global().origin().immutable().clone(),
303            self.db.get_name().to_string(),
304            self.serial_number,
305        );
306
307        self.get_idb_thread()
308            .send(IndexedDBThreadMsg::Sync(start_operation))
309            .unwrap();
310
311        let result = receiver.recv().unwrap();
312
313        // Step 2
314        if let Err(_result) = result {
315            // FIXME:(rasviitanen) also support Unknown error
316            return Err(Error::QuotaExceeded {
317                quota: None,
318                requested: None,
319            });
320        }
321
322        // Step 3
323        // FIXME:(rasviitanen) https://www.w3.org/TR/IndexedDB-2/#commit-a-transaction
324
325        // Steps 3.1 and 3.3
326        self.dispatch_complete();
327
328        Ok(())
329    }
330
331    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-abort
332    fn Abort(&self) -> Fallible<()> {
333        // FIXME:(rasviitanen)
334        // This only sets the flags, and does not abort the transaction
335        // see https://www.w3.org/TR/IndexedDB-2/#abort-a-transaction
336        if self.finished.get() {
337            return Err(Error::InvalidState(None));
338        }
339
340        self.active.set(false);
341
342        Ok(())
343    }
344
345    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstorenames
346    fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
347        self.object_store_names.as_rooted()
348    }
349
350    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
351    fn Mode(&self) -> IDBTransactionMode {
352        self.mode
353    }
354
355    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
356    // fn Durability(&self) -> IDBTransactionDurability {
357    //     // FIXME:(arihant2math) Durability is not implemented at all
358    //     unimplemented!();
359    // }
360
361    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-error
362    fn GetError(&self) -> Option<DomRoot<DOMException>> {
363        self.error.get()
364    }
365
366    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onabort
367    event_handler!(abort, GetOnabort, SetOnabort);
368
369    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-oncomplete
370    event_handler!(complete, GetOncomplete, SetOncomplete);
371
372    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onerror
373    event_handler!(error, GetOnerror, SetOnerror);
374}