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