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