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, HashSet};
7
8use base::generic_channel::{GenericSend, GenericSender};
9use base::id::ScriptEventLoopId;
10use dom_struct::dom_struct;
11use profile_traits::generic_callback::GenericCallback;
12use profile_traits::generic_channel::channel;
13use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
14use storage_traits::indexeddb::{
15    IndexedDBIndex, IndexedDBThreadMsg, IndexedDBTxnMode, KeyPath, SyncOperation, TxnCompleteMsg,
16};
17use stylo_atoms::Atom;
18
19use crate::dom::bindings::cell::DomRefCell;
20use crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods;
21use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
22use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBIndexParameters;
23use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
24    IDBTransactionMethods, IDBTransactionMode,
25};
26use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
30use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::domexception::DOMException;
33use crate::dom::domstringlist::DOMStringList;
34use crate::dom::event::{Event, EventBubbles, EventCancelable};
35use crate::dom::eventtarget::EventTarget;
36use crate::dom::globalscope::GlobalScope;
37use crate::dom::indexeddb::idbdatabase::IDBDatabase;
38use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
39use crate::dom::indexeddb::idbrequest::IDBRequest;
40use crate::script_runtime::CanGc;
41
42#[dom_struct]
43pub struct IDBTransaction {
44    eventtarget: EventTarget,
45    object_store_names: Dom<DOMStringList>,
46    mode: IDBTransactionMode,
47    db: Dom<IDBDatabase>,
48    error: MutNullableDom<DOMException>,
49
50    store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
51    // https://www.w3.org/TR/IndexedDB-2/#transaction-request-list
52    requests: DomRefCell<Vec<Dom<IDBRequest>>>,
53    // https://www.w3.org/TR/IndexedDB-2/#transaction-active-flag
54    active: Cell<bool>,
55    // https://www.w3.org/TR/IndexedDB-2/#transaction-finish
56    finished: Cell<bool>,
57    abort_initiated: Cell<bool>,
58    abort_requested: Cell<bool>,
59    committing: Cell<bool>,
60    version_change_old_version: Cell<Option<u64>>,
61    // https://w3c.github.io/IndexedDB/#transaction-concept
62    // “A transaction optionally has a cleanup event loop which is an event loop.”
63    #[no_trace]
64    cleanup_event_loop: Cell<Option<ScriptEventLoopId>>,
65    registered_in_global: Cell<bool>,
66    // Tracks how many IDBRequest instances are still pending for this
67    // transaction. The value is incremented when a request is added to the
68    // transaction’s request list and decremented once the request has
69    // finished.
70    pending_request_count: Cell<usize>,
71    next_request_id: Cell<u64>,
72    // Smallest request_id that has not yet been marked handled (all < this are handled).
73    next_unhandled_request_id: Cell<u64>,
74    handled_pending: DomRefCell<HashSet<u64>>,
75
76    // An unique identifier, used to commit and revert this transaction
77    // FIXME:(rasviitanen) Replace this with a channel
78    serial_number: u64,
79}
80
81impl IDBTransaction {
82    fn new_inherited(
83        connection: &IDBDatabase,
84        mode: IDBTransactionMode,
85        scope: &DOMStringList,
86        serial_number: u64,
87    ) -> IDBTransaction {
88        IDBTransaction {
89            eventtarget: EventTarget::new_inherited(),
90            object_store_names: Dom::from_ref(scope),
91            mode,
92            db: Dom::from_ref(connection),
93            error: Default::default(),
94
95            store_handles: Default::default(),
96            requests: Default::default(),
97            active: Cell::new(true),
98            finished: Cell::new(false),
99            abort_initiated: Cell::new(false),
100            abort_requested: Cell::new(false),
101            committing: Cell::new(false),
102            version_change_old_version: Cell::new(None),
103            cleanup_event_loop: Cell::new(None),
104            registered_in_global: Cell::new(false),
105            pending_request_count: Cell::new(0),
106            next_request_id: Cell::new(0),
107            next_unhandled_request_id: Cell::new(0),
108            handled_pending: Default::default(),
109            serial_number,
110        }
111    }
112
113    /// Does a blocking call to create a backend transaction and get its id.
114    pub fn new(
115        global: &GlobalScope,
116        connection: &IDBDatabase,
117        mode: IDBTransactionMode,
118        scope: &DOMStringList,
119        can_gc: CanGc,
120    ) -> DomRoot<IDBTransaction> {
121        let serial_number =
122            IDBTransaction::create_transaction(global, connection.get_name(), mode, scope);
123        IDBTransaction::new_with_serial(global, connection, mode, scope, serial_number, can_gc)
124    }
125
126    pub(crate) fn new_with_serial(
127        global: &GlobalScope,
128        connection: &IDBDatabase,
129        mode: IDBTransactionMode,
130        scope: &DOMStringList,
131        serial_number: u64,
132        can_gc: CanGc,
133    ) -> DomRoot<IDBTransaction> {
134        reflect_dom_object(
135            Box::new(IDBTransaction::new_inherited(
136                connection,
137                mode,
138                scope,
139                serial_number,
140            )),
141            global,
142            can_gc,
143        )
144    }
145
146    fn create_transaction(
147        global: &GlobalScope,
148        db_name: DOMString,
149        mode: IDBTransactionMode,
150        scope: &DOMStringList,
151    ) -> u64 {
152        let backend_mode = match mode {
153            IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
154            IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
155            IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
156        };
157        let scope: Vec<String> = (0..scope.Length())
158            .filter_map(|i| scope.Item(i))
159            .map(|name| name.to_string())
160            .collect();
161        let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
162
163        global
164            .storage_threads()
165            .send(IndexedDBThreadMsg::Sync(SyncOperation::CreateTransaction {
166                sender,
167                origin: global.origin().immutable().clone(),
168                db_name: db_name.to_string(),
169                mode: backend_mode,
170                scope,
171            }))
172            .expect("Failed to send IndexedDBThreadMsg::Sync");
173
174        receiver.recv().unwrap().expect("CreateTransaction failed")
175    }
176
177    /// <https://w3c.github.io/IndexedDB/#transaction-lifecycle>
178    pub fn set_active_flag(&self, status: bool) {
179        // inactive
180        // A transaction is in this state after control returns to the event loop after its creation,
181        //  and when events are not being dispatched.
182        // No requests can be made against the transaction when it is in this state.
183        self.active.set(status);
184    }
185
186    pub fn is_active(&self) -> bool {
187        self.active.get()
188    }
189
190    /// <https://w3c.github.io/IndexedDB/#transaction-lifetime>
191    pub(crate) fn is_usable(&self) -> bool {
192        // A transaction can be aborted at any time before it is finished,
193        //  even if the transaction isn’t currently active or hasn’t yet started.
194        // An explicit call to abort() will initiate an abort.
195        // An abort will also be initiated following a failed request that is not handled by script.
196        !self.finished.get() && !self.abort_initiated.get() && !self.committing.get()
197    }
198
199    pub(crate) fn is_finished(&self) -> bool {
200        self.finished.get()
201    }
202
203    pub(crate) fn set_cleanup_event_loop(&self) {
204        // https://w3c.github.io/IndexedDB/#transaction-concept
205        // A transaction optionally has a cleanup event loop which is an event loop.
206        self.cleanup_event_loop.set(ScriptEventLoopId::installed());
207    }
208
209    pub(crate) fn clear_cleanup_event_loop(&self) {
210        // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions
211        // Clear transaction’s cleanup event loop.
212        self.cleanup_event_loop.set(None);
213    }
214
215    pub(crate) fn cleanup_event_loop_matches_current(&self) -> bool {
216        match ScriptEventLoopId::installed() {
217            Some(current) => self.cleanup_event_loop.get() == Some(current),
218            None => false,
219        }
220    }
221
222    pub(crate) fn set_registered_in_global(&self) {
223        self.registered_in_global.set(true);
224    }
225
226    pub(crate) fn clear_registered_in_global(&self) {
227        self.registered_in_global.set(false);
228    }
229
230    pub(crate) fn set_versionchange_old_version(&self, version: u64) {
231        self.version_change_old_version.set(Some(version));
232    }
233
234    fn attempt_commit(&self) -> bool {
235        let this = Trusted::new(self);
236        let global = self.global();
237        let task_source = global
238            .task_manager()
239            .dom_manipulation_task_source()
240            .to_sendable();
241
242        // TODO: Reuse a shared transaction callback path (similar to IDBFactory
243        // connection callbacks) instead of creating one per transaction operation.
244        let callback = GenericCallback::new(
245            global.time_profiler_chan().clone(),
246            move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
247                let this = this.clone();
248                let task_source = task_source.clone();
249                task_source.queue(task!(handle_commit_result: move || {
250                    let this = this.root();
251                    let message = message.expect("Could not unwrap message");
252                    match message.result {
253                        Ok(()) => {
254                            this.finalize_commit();
255                        }
256                        Err(_err) => {
257                             // TODO: Map backend commit/rollback failure to an appropriate DOMException
258                            this.initiate_abort(Error::Operation(None), CanGc::note());
259
260                            this.finalize_abort();
261                        }
262                    }
263                    // TODO: https://w3c.github.io/IndexedDB/#commit-a-transaction
264                    // Backend commit/rollback is not yet atomic.
265                }));
266            },
267        )
268        .expect("Could not create callback");
269
270        let commit_operation = SyncOperation::Commit(
271            callback,
272            global.origin().immutable().clone(),
273            self.db.get_name().to_string(),
274            self.serial_number,
275        );
276
277        // https://w3c.github.io/IndexedDB/#transaction-lifecycle
278        // When committing, the transaction state is set to committing.
279        let send_result = self
280            .get_idb_thread()
281            .send(IndexedDBThreadMsg::Sync(commit_operation));
282        if send_result.is_err() {
283            return false;
284        }
285
286        self.committing.set(true);
287        true
288    }
289
290    pub(crate) fn maybe_commit(&self) {
291        // https://w3c.github.io/IndexedDB/#transaction-lifetime
292        // Step 5: transaction when all requests
293        //  placed against the transaction have completed and their returned results handled,
294        //  no new requests have been placed against the transaction, and the transaction has
295        //  not been aborted.
296        let finished = self.finished.get();
297        let abort_initiated = self.abort_initiated.get();
298        let committing = self.committing.get();
299        let active = self.active.get();
300        let pending_request_count = self.pending_request_count.get();
301        let next_unhandled_request_id = self.next_unhandled_request_id.get();
302        let issued_count = self.issued_count();
303        if finished || abort_initiated || committing {
304            return;
305        }
306        if active || pending_request_count != 0 {
307            return;
308        }
309        if next_unhandled_request_id != issued_count {
310            return;
311        }
312        if !self.attempt_commit() {
313            // We failed to initiate the commit algorithm (backend task could not be queued),
314            // so the transaction cannot progress to a successful "complete".
315            // Choose the most appropriate DOMException mapping for Servo here.
316            self.initiate_abort(Error::InvalidState(None), CanGc::note());
317            self.finalize_abort();
318        }
319    }
320
321    fn force_commit(&self) {
322        // https://w3c.github.io/IndexedDB/#transaction-lifetime
323        // An explicit call to commit() will initiate a commit without waiting for request results
324        //  to be handled by script.
325        //
326        // This differs from automatic commit:
327        // The implementation must attempt to commit an inactive transaction when all requests
328        // placed against the transaction have completed and their returned results handled,
329        // no new requests have been placed against the transaction, and the transaction has not been aborted
330        if self.finished.get() || self.abort_initiated.get() || self.committing.get() {
331            return;
332        }
333        if self.active.get() || self.pending_request_count.get() != 0 {
334            return;
335        }
336        self.attempt_commit();
337    }
338
339    pub fn get_mode(&self) -> IDBTransactionMode {
340        self.mode
341    }
342
343    pub fn get_db_name(&self) -> DOMString {
344        self.db.get_name()
345    }
346
347    pub fn get_serial_number(&self) -> u64 {
348        self.serial_number
349    }
350
351    pub(crate) fn issued_count(&self) -> u64 {
352        self.next_request_id.get()
353    }
354
355    /// request_id is only required to be unique within this transaction.
356    /// The backend keys “handled” state by (txn, request_id).
357    pub(crate) fn allocate_request_id(&self) -> u64 {
358        let id = self.next_request_id.get();
359        self.next_request_id.set(id + 1);
360        id
361    }
362
363    pub(crate) fn mark_request_handled(&self, request_id: u64) {
364        let current = self.next_unhandled_request_id.get();
365        if request_id == current {
366            let mut next = current + 1;
367            {
368                let mut pending = self.handled_pending.borrow_mut();
369                while pending.remove(&next) {
370                    next += 1;
371                }
372            }
373            self.next_unhandled_request_id.set(next);
374        } else if request_id > current {
375            self.handled_pending.borrow_mut().insert(request_id);
376        }
377    }
378
379    pub fn add_request(&self, request: &IDBRequest) {
380        self.requests.borrow_mut().push(Dom::from_ref(request));
381        // Increase the number of outstanding requests so that we can detect when
382        // the transaction is allowed to finish.
383        self.pending_request_count
384            .set(self.pending_request_count.get() + 1);
385    }
386
387    pub fn request_finished(&self) {
388        // https://w3c.github.io/IndexedDB/#transaction-lifecycle
389        // finished
390        // Once a transaction has committed or aborted, it enters this state.
391        // No requests can be made against the transaction when it is in this state.
392        if self.pending_request_count.get() == 0 {
393            return;
394        }
395        let remaining = self.pending_request_count.get() - 1;
396        self.pending_request_count.set(remaining);
397    }
398
399    pub(crate) fn initiate_abort(&self, error: Error, can_gc: CanGc) {
400        // https://w3c.github.io/IndexedDB/#transaction-lifetime
401        // Step 4: An abort will also be initiated following a failed request that is not handled by script.
402        // A transaction can be aborted at any time before it is finished,
403        // even if the transaction isn’t currently active or hasn’t yet started.
404        if self.finished.get() || self.abort_initiated.get() {
405            return;
406        }
407        self.abort_initiated.set(true);
408        // https://w3c.github.io/IndexedDB/#transaction-concept
409        // A transaction has a error which is set if the transaction is aborted.
410        // NOTE: Implementors need to keep in mind that the value "null" is considered an error, as it is set from abort()
411        if self.error.get().is_none() {
412            if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
413                self.error.set(Some(&exception));
414            }
415        }
416    }
417
418    pub(crate) fn request_backend_abort(&self) {
419        if self.abort_requested.get() {
420            return;
421        }
422        self.abort_requested.set(true);
423        let this = Trusted::new(self);
424        let global = self.global();
425        let task_source = global
426            .task_manager()
427            .dom_manipulation_task_source()
428            .to_sendable();
429        let callback = GenericCallback::new(
430            global.time_profiler_chan().clone(),
431            move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
432                let this = this.clone();
433                let task_source = task_source.clone();
434                task_source.queue(task!(handle_abort_result: move || {
435                    let this = this.root();
436                    let _ = message.expect("Could not unwrap message");
437                    this.finalize_abort();
438                }));
439            },
440        )
441        .expect("Could not create callback");
442        let operation = SyncOperation::Abort(
443            callback,
444            global.origin().immutable().clone(),
445            self.db.get_name().to_string(),
446            self.serial_number,
447        );
448        let _ = self
449            .get_idb_thread()
450            .send(IndexedDBThreadMsg::Sync(operation));
451    }
452
453    fn notify_backend_transaction_finished(&self) {
454        let global = self.global();
455        let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
456            SyncOperation::TransactionFinished {
457                origin: global.origin().immutable().clone(),
458                db_name: self.db.get_name().to_string(),
459                txn: self.serial_number,
460            },
461        ));
462    }
463
464    pub(crate) fn finalize_abort(&self) {
465        if self.finished.get() {
466            return;
467        }
468        self.committing.set(false);
469        let this = Trusted::new(self);
470        self.global()
471            .task_manager()
472            .dom_manipulation_task_source()
473            .queue(task!(send_abort_notification: move || {
474                let this = this.root();
475                this.active.set(false);
476                if this.mode == IDBTransactionMode::Versionchange {
477                    if let Some(old_version) = this.version_change_old_version.get() {
478                        // IndexedDB §5.8 "Aborting an upgrade transaction":
479                        // set connection's version to database's version (or 0 if newly created).
480                        // Spec note: this reverts the value of `IDBDatabase.version`.
481                        // https://w3c.github.io/IndexedDB/#abort-an-upgrade-transaction
482                        this.db.set_version(old_version);
483                    }
484                    this.db.clear_upgrade_transaction(&this);
485                }
486                let global = this.global();
487                let event = Event::new(
488                    &global,
489                    Atom::from("abort"),
490                    EventBubbles::DoesNotBubble,
491                    EventCancelable::NotCancelable,
492                    CanGc::note(),
493                );
494                event.fire(this.upcast(), CanGc::note());
495                if this.mode == IDBTransactionMode::Versionchange {
496                    this.global()
497                        .get_indexeddb()
498                        .clear_open_request_transaction_for_txn(&this);
499                    let origin = this.global().origin().immutable().clone();
500                    let db_name = this.db.get_name().to_string();
501                    let txn = this.serial_number;
502                    let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
503                        SyncOperation::UpgradeTransactionFinished {
504                            origin,
505                            db_name,
506                            txn,
507                            committed: false,
508                        },
509                    ));
510                }
511                // https://w3c.github.io/IndexedDB/#transaction-lifetime
512                // Step 6: When a transaction is committed or aborted, its state is set to finished.
513                this.finished.set(true);
514                this.version_change_old_version.set(None);
515                this.notify_backend_transaction_finished();
516                if this.registered_in_global.get() {
517                    this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
518                }
519            }));
520    }
521
522    pub(crate) fn finalize_commit(&self) {
523        if self.finished.get() {
524            return;
525        }
526        self.committing.set(false);
527        self.version_change_old_version.set(None);
528        // https://w3c.github.io/IndexedDB/#transaction-lifetime
529        // Step 6: When a transaction is committed or aborted, its state is set to finished.
530        self.finished.set(true);
531        if self.mode == IDBTransactionMode::Versionchange {
532            self.db.clear_upgrade_transaction(self);
533        }
534        self.notify_backend_transaction_finished();
535        self.dispatch_complete();
536        if self.registered_in_global.get() {
537            self.global()
538                .get_indexeddb()
539                .unregister_indexeddb_transaction(self);
540        }
541    }
542
543    fn dispatch_complete(&self) {
544        let global = self.global();
545        let this = Trusted::new(self);
546        global.task_manager().database_access_task_source().queue(
547            task!(send_complete_notification: move || {
548                let this = this.root();
549                let global = this.global();
550                let event = Event::new(
551                    &global,
552                    Atom::from("complete"),
553                    EventBubbles::DoesNotBubble,
554                    EventCancelable::NotCancelable,
555                    CanGc::note()
556                );
557                event.fire(this.upcast(), CanGc::note());
558                if this.mode == IDBTransactionMode::Versionchange {
559                    this.global()
560                        .get_indexeddb()
561                        .clear_open_request_transaction_for_txn(&this);
562                    let origin = this.global().origin().immutable().clone();
563                    let db_name = this.db.get_name().to_string();
564                    let txn = this.serial_number;
565                    let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
566                        SyncOperation::UpgradeTransactionFinished {
567                            origin,
568                            db_name,
569                            txn,
570                            committed: true,
571                        },
572                    ));
573                }
574            }),
575        );
576    }
577
578    fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
579        self.global().storage_threads().sender()
580    }
581
582    fn object_store_parameters(
583        &self,
584        object_store_name: &DOMString,
585    ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>)> {
586        let global = self.global();
587        let idb_sender = global.storage_threads().sender();
588        let (sender, receiver) =
589            channel(global.time_profiler_chan().clone()).expect("failed to create channel");
590
591        let origin = global.origin().immutable().clone();
592        let db_name = self.db.get_name().to_string();
593        let object_store_name = object_store_name.to_string();
594
595        let operation = SyncOperation::GetObjectStore(
596            sender,
597            origin.clone(),
598            db_name.clone(),
599            object_store_name.clone(),
600        );
601
602        let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
603
604        // First unwrap for ipc
605        // Second unwrap will never happen unless this db gets manually deleted somehow
606        let object_store = receiver.recv().ok()?.ok()?;
607
608        // First unwrap for ipc
609        // Second unwrap will never happen unless this db gets manually deleted somehow
610        let key_path = object_store.key_path.map(|key_path| match key_path {
611            KeyPath::String(s) => StringOrStringSequence::String(DOMString::from_string(s)),
612            KeyPath::Sequence(seq) => StringOrStringSequence::StringSequence(
613                seq.into_iter().map(DOMString::from_string).collect(),
614            ),
615        });
616        Some((
617            IDBObjectStoreParameters {
618                autoIncrement: object_store.has_key_generator,
619                keyPath: key_path,
620            },
621            object_store.indexes,
622        ))
623    }
624}
625
626impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
627    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-db>
628    fn Db(&self) -> DomRoot<IDBDatabase> {
629        DomRoot::from_ref(&*self.db)
630    }
631
632    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstore>
633    fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
634        // Step 1: If transaction has finished, throw an "InvalidStateError" DOMException.
635        if self.finished.get() || self.abort_initiated.get() {
636            return Err(Error::InvalidState(None));
637        }
638
639        // Step 2: Check that the object store exists in this transaction's scope.
640        // For versionchange transactions, the scope tracks object store changes
641        // performed during the upgrade.
642        let in_scope = if self.mode == IDBTransactionMode::Versionchange {
643            self.db.object_store_exists(&name)
644        } else {
645            self.object_store_names.Contains(name.clone())
646        };
647        if !in_scope {
648            return Err(Error::NotFound(None));
649        }
650
651        // Step 3: Each call to this method on the same
652        // IDBTransaction instance with the same name
653        // returns the same IDBObjectStore instance.
654        if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
655            return Ok(DomRoot::from_ref(store));
656        }
657
658        let parameters = self.object_store_parameters(&name);
659        let store = IDBObjectStore::new(
660            &self.global(),
661            self.db.get_name(),
662            name.clone(),
663            parameters.as_ref().map(|(params, _)| params),
664            can_gc,
665            self,
666        );
667        if let Some(indexes) = parameters.map(|(_, indexes)| indexes) {
668            for index in indexes {
669                store.add_index(
670                    DOMString::from_string(index.name),
671                    &IDBIndexParameters {
672                        multiEntry: index.multi_entry,
673                        unique: index.unique,
674                    },
675                    index.key_path.into(),
676                    can_gc,
677                );
678            }
679        }
680        self.store_handles
681            .borrow_mut()
682            .insert(name.to_string(), Dom::from_ref(&*store));
683        Ok(store)
684    }
685
686    /// <https://www.w3.org/TR/IndexedDB-2/#commit-transaction>
687    fn Commit(&self) -> Fallible<()> {
688        // Step 1
689        if self.finished.get() {
690            return Err(Error::InvalidState(None));
691        }
692
693        // https://w3c.github.io/IndexedDB/#transaction-lifetime
694        // Step 5: The implementation must attempt to commit an inactive transaction when all requests placed against
695        // the transaction have completed and their returned results handled, no new requests have been placed against the transaction, and the transaction has not been aborted
696        // An explicit call to commit() will initiate a commit without waiting for request results to be handled by script.
697        // When committing, the transaction state is set to committing. The implementation must atomically write any changes
698        // to the database made by requests placed against the transaction. That is, either all of the changes must be written,
699        // or if an error occurs, such as a disk write error, the implementation must not write any of the changes to the database, and the steps to abort a transaction will be followed.
700        self.set_active_flag(false);
701        self.force_commit();
702
703        Ok(())
704    }
705
706    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-abort>
707    fn Abort(&self) -> Fallible<()> {
708        if self.finished.get() || self.committing.get() {
709            return Err(Error::InvalidState(None));
710        }
711        self.active.set(false);
712        self.initiate_abort(Error::Abort(None), CanGc::note());
713        self.request_backend_abort();
714
715        Ok(())
716    }
717
718    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstorenames>
719    fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
720        if self.mode == IDBTransactionMode::Versionchange {
721            self.db.object_stores()
722        } else {
723            self.object_store_names.as_rooted()
724        }
725    }
726
727    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode>
728    fn Mode(&self) -> IDBTransactionMode {
729        self.mode
730    }
731
732    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
733    // fn Durability(&self) -> IDBTransactionDurability {
734    //     // FIXME:(arihant2math) Durability is not implemented at all
735    //     unimplemented!();
736    // }
737
738    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-error>
739    fn GetError(&self) -> Option<DomRoot<DOMException>> {
740        self.error.get()
741    }
742
743    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onabort
744    event_handler!(abort, GetOnabort, SetOnabort);
745
746    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-oncomplete
747    event_handler!(complete, GetOncomplete, SetOncomplete);
748
749    // https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onerror
750    event_handler!(error, GetOnerror, SetOnerror);
751}