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 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::idbdatabase::IDBDatabase;
34use crate::dom::idbobjectstore::IDBObjectStore;
35use crate::dom::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) -> 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        let mut store_handles = self.store_handles.borrow_mut();
278        let store = store_handles.entry(name.to_string()).or_insert_with(|| {
279            let parameters = self.object_store_parameters(&name);
280            let store = IDBObjectStore::new(
281                &self.global(),
282                self.db.get_name(),
283                name,
284                parameters.as_ref(),
285                CanGc::note(),
286                self,
287            );
288            Dom::from_ref(&*store)
289        });
290
291        Ok(DomRoot::from_ref(&*store))
292    }
293
294    // https://www.w3.org/TR/IndexedDB-2/#commit-transaction
295    fn Commit(&self) -> Fallible<()> {
296        // Step 1
297        let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
298        let start_operation = SyncOperation::Commit(
299            sender,
300            self.global().origin().immutable().clone(),
301            self.db.get_name().to_string(),
302            self.serial_number,
303        );
304
305        self.get_idb_thread()
306            .send(IndexedDBThreadMsg::Sync(start_operation))
307            .unwrap();
308
309        let result = receiver.recv().unwrap();
310
311        // Step 2
312        if let Err(_result) = result {
313            // FIXME:(rasviitanen) also support Unknown error
314            return Err(Error::QuotaExceeded {
315                quota: None,
316                requested: None,
317            });
318        }
319
320        // Step 3
321        // FIXME:(rasviitanen) https://www.w3.org/TR/IndexedDB-2/#commit-a-transaction
322
323        // Steps 3.1 and 3.3
324        self.dispatch_complete();
325
326        Ok(())
327    }
328
329    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-abort
330    fn Abort(&self) -> Fallible<()> {
331        // FIXME:(rasviitanen)
332        // This only sets the flags, and does not abort the transaction
333        // see https://www.w3.org/TR/IndexedDB-2/#abort-a-transaction
334        if self.finished.get() {
335            return Err(Error::InvalidState(None));
336        }
337
338        self.active.set(false);
339
340        Ok(())
341    }
342
343    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstorenames
344    fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
345        self.object_store_names.as_rooted()
346    }
347
348    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
349    fn Mode(&self) -> IDBTransactionMode {
350        self.mode
351    }
352
353    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
354    // fn Durability(&self) -> IDBTransactionDurability {
355    //     // FIXME:(arihant2math) Durability is not implemented at all
356    //     unimplemented!();
357    // }
358
359    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-error
360    fn GetError(&self) -> Option<DomRoot<DOMException>> {
361        self.error.get()
362    }
363
364    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onabort
365    event_handler!(abort, GetOnabort, SetOnabort);
366
367    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-oncomplete
368    event_handler!(complete, GetOncomplete, SetOncomplete);
369
370    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onerror
371    event_handler!(error, GetOnerror, SetOnerror);
372}