1use std::cell::Cell;
6use std::collections::{HashMap, HashSet};
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use profile_traits::generic_callback::GenericCallback;
11use profile_traits::generic_channel::channel;
12use script_bindings::cell::DomRefCell;
13use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
14use script_bindings::domstring::TracedDOMString;
15use script_bindings::reflector::reflect_dom_object;
16use servo_base::generic_channel::{GenericSend, GenericSender, SendError};
17use servo_base::id::ScriptEventLoopId;
18use storage_traits::indexeddb::{
19 BackendError, IndexedDBIndex, IndexedDBThreadMsg, IndexedDBTxnMode, KeyPath, SyncOperation,
20 TxnCompleteMsg,
21};
22use stylo_atoms::Atom;
23
24use crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods;
25use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
26 IDBObjectStoreParameters, IDBTransactionDurability,
27};
28use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBIndexParameters;
29use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
30 IDBTransactionMethods, IDBTransactionMode,
31};
32use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::refcounted::Trusted;
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
37use crate::dom::bindings::str::DOMString;
38use crate::dom::domexception::DOMException;
39use crate::dom::domstringlist::DOMStringList;
40use crate::dom::event::{Event, EventBubbles, EventCancelable};
41use crate::dom::eventtarget::EventTarget;
42use crate::dom::globalscope::GlobalScope;
43use crate::dom::indexeddb::idbdatabase::IDBDatabase;
44use crate::dom::indexeddb::idbobjectstore::{IDBObjectStore, IDBObjectStoreAbortState};
45use crate::dom::indexeddb::idbrequest::IDBRequest;
46use crate::dom::indexeddb::key::map_backend_error_to_dom_error;
47
48#[dom_struct]
49pub struct IDBTransaction {
50 eventtarget: EventTarget,
51 object_store_names: Dom<DOMStringList>,
52 mode: IDBTransactionMode,
53 durability: IDBTransactionDurability,
54 db: Dom<IDBDatabase>,
55 error: MutNullableDom<DOMException>,
56
57 store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
58 requests: DomRefCell<Vec<Dom<IDBRequest>>>,
60 active: Cell<bool>,
62 finished: Cell<bool>,
64 abort_initiated: Cell<bool>,
65 abort_requested: Cell<bool>,
66 committing: Cell<bool>,
67 commit_started: Cell<bool>,
68 version_change_old_version: Cell<Option<u64>>,
69 version_change_old_object_store_names: DomRefCell<Option<Vec<TracedDOMString>>>,
72 #[no_trace]
75 cleanup_event_loop: Cell<Option<ScriptEventLoopId>>,
76 registered_in_global: Cell<bool>,
77 pending_request_count: Cell<usize>,
82 next_request_id: Cell<u64>,
83 next_unhandled_request_id: Cell<u64>,
85 handled_pending: DomRefCell<HashSet<u64>>,
86
87 serial_number: u64,
90}
91
92impl IDBTransaction {
93 fn new_inherited(
94 connection: &IDBDatabase,
95 mode: IDBTransactionMode,
96 durability: IDBTransactionDurability,
97 scope: &DOMStringList,
98 serial_number: u64,
99 ) -> IDBTransaction {
100 IDBTransaction {
101 eventtarget: EventTarget::new_inherited(),
102 object_store_names: Dom::from_ref(scope),
103 mode,
104 durability,
105 db: Dom::from_ref(connection),
106 error: Default::default(),
107
108 store_handles: Default::default(),
109 requests: Default::default(),
110 active: Cell::new(true),
111 finished: Cell::new(false),
112 abort_initiated: Cell::new(false),
113 abort_requested: Cell::new(false),
114 committing: Cell::new(false),
115 commit_started: Cell::new(false),
116 version_change_old_version: Cell::new(None),
117 version_change_old_object_store_names: DomRefCell::new(
118 (mode == IDBTransactionMode::Versionchange).then(|| {
119 connection
120 .object_store_names_snapshot()
121 .into_iter()
122 .map(|domstring| domstring.traced())
123 .collect()
124 }),
125 ),
126 cleanup_event_loop: Cell::new(None),
127 registered_in_global: Cell::new(false),
128 pending_request_count: Cell::new(0),
129 next_request_id: Cell::new(0),
130 next_unhandled_request_id: Cell::new(0),
131 handled_pending: Default::default(),
132 serial_number,
133 }
134 }
135
136 pub fn new(
138 cx: &mut JSContext,
139 global: &GlobalScope,
140 connection: &IDBDatabase,
141 mode: IDBTransactionMode,
142 durability: IDBTransactionDurability,
143 scope: &DOMStringList,
144 ) -> DomRoot<IDBTransaction> {
145 let serial_number =
146 IDBTransaction::create_transaction(global, connection.get_name(), mode, scope);
147 IDBTransaction::new_with_serial(
148 cx,
149 global,
150 connection,
151 mode,
152 durability,
153 scope,
154 serial_number,
155 )
156 }
157
158 pub(crate) fn new_with_serial(
159 cx: &mut JSContext,
160 global: &GlobalScope,
161 connection: &IDBDatabase,
162 mode: IDBTransactionMode,
163 durability: IDBTransactionDurability,
164 scope: &DOMStringList,
165 serial_number: u64,
166 ) -> DomRoot<IDBTransaction> {
167 reflect_dom_object(
168 cx,
169 Box::new(IDBTransaction::new_inherited(
170 connection,
171 mode,
172 durability,
173 scope,
174 serial_number,
175 )),
176 global,
177 )
178 }
179
180 fn create_transaction(
181 global: &GlobalScope,
182 db_name: DOMString,
183 mode: IDBTransactionMode,
184 scope: &DOMStringList,
185 ) -> u64 {
186 let backend_mode = match mode {
187 IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
188 IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
189 IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
190 };
191 let scope: Vec<String> = (0..scope.Length())
192 .filter_map(|i| scope.Item(i))
193 .map(String::from)
194 .collect();
195 let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
196
197 global
198 .storage_threads()
199 .send(IndexedDBThreadMsg::Sync(SyncOperation::CreateTransaction {
200 sender,
201 origin: global.origin().immutable().clone(),
202 db_name: String::from(db_name),
203 mode: backend_mode,
204 scope,
205 }))
206 .expect("Failed to send IndexedDBThreadMsg::Sync");
207
208 receiver.recv().unwrap().expect("CreateTransaction failed")
209 }
210
211 pub fn set_active_flag(&self, status: bool) {
213 self.active.set(status);
218 }
219
220 pub fn is_active(&self) -> bool {
221 self.active.get()
222 }
223
224 pub(crate) fn is_usable(&self) -> bool {
226 !self.finished.get() && !self.abort_initiated.get() && !self.committing.get()
231 }
232
233 pub(crate) fn is_inactive(&self) -> bool {
234 !self.active.get() &&
235 !self.finished.get() &&
236 !self.abort_initiated.get() &&
237 !self.committing.get()
238 }
239
240 pub(crate) fn is_committing(&self) -> bool {
241 self.committing.get()
242 }
243
244 pub(crate) fn is_finished(&self) -> bool {
245 self.finished.get()
246 }
247
248 pub(crate) fn set_cleanup_event_loop(&self) {
249 self.cleanup_event_loop.set(ScriptEventLoopId::installed());
252 }
253
254 pub(crate) fn clear_cleanup_event_loop(&self) {
255 self.cleanup_event_loop.set(None);
258 }
259
260 pub(crate) fn cleanup_event_loop_matches_current(&self) -> bool {
261 match ScriptEventLoopId::installed() {
262 Some(current) => self.cleanup_event_loop.get() == Some(current),
263 None => false,
264 }
265 }
266
267 pub(crate) fn set_registered_in_global(&self) {
268 self.registered_in_global.set(true);
269 }
270
271 pub(crate) fn clear_registered_in_global(&self) {
272 self.registered_in_global.set(false);
273 }
274
275 pub(crate) fn set_versionchange_old_version(&self, version: u64) {
276 self.version_change_old_version.set(Some(version));
277 }
278
279 pub(crate) fn register_object_store_handle(&self, name: &DOMString, store: &IDBObjectStore) {
280 self.store_handles
281 .borrow_mut()
282 .insert(name.to_string(), Dom::from_ref(store));
283 }
284
285 pub(crate) fn rename_object_store_handle_cache(
286 &self,
287 old_name: &TracedDOMString,
288 new_name: &TracedDOMString,
289 store: &IDBObjectStore,
290 ) {
291 let mut store_handles = self.store_handles.borrow_mut();
292 store_handles.remove(&old_name.to_string());
293 store_handles.insert(new_name.to_string(), Dom::from_ref(store));
294 }
295
296 fn restore_associated_object_store_handles_after_abort(&self, cx: &mut JSContext) {
298 let stores = self
302 .store_handles
303 .borrow()
304 .values()
305 .map(|store| DomRoot::from_ref(&**store))
306 .collect::<Vec<_>>();
307 for store in stores {
308 store.restore_metadata_after_abort(cx);
309 }
310 }
311
312 fn attempt_commit(&self) -> bool {
313 if self.commit_started.get() {
314 return true;
315 }
316 let this = Trusted::new(self);
317 let global = self.global();
318 let task_source = global
319 .task_manager()
320 .dom_manipulation_task_source()
321 .to_sendable();
322
323 let callback = GenericCallback::new(move |message: Result<TxnCompleteMsg, SendError>| {
326 let this = this.clone();
327 let task_source = task_source.clone();
328 task_source.queue(task!(handle_commit_result: move |cx| {
329 let this = this.root();
330 let message = message.expect("Could not unwrap message");
331 match message.result {
332 Ok(()) => {
333 this.finalize_commit();
334 }
335 Err(_err) => {
336 this.initiate_abort(cx, Error::Operation(None));
338
339 this.finalize_abort();
340 }
341 }
342 }));
345 })
346 .expect("Could not create callback");
347
348 let commit_operation = SyncOperation::Commit(
349 callback,
350 global.origin().immutable().clone(),
351 String::from(self.db.get_name()),
352 self.serial_number,
353 );
354
355 let send_result = self
358 .get_idb_thread()
359 .send(IndexedDBThreadMsg::Sync(commit_operation));
360 if send_result.is_err() {
361 return false;
362 }
363
364 self.committing.set(true);
365 self.commit_started.set(true);
366 true
367 }
368
369 pub(crate) fn maybe_commit(&self, cx: &mut JSContext) {
370 let finished = self.finished.get();
376 let abort_initiated = self.abort_initiated.get();
377 let commit_started = self.commit_started.get();
378 let active = self.active.get();
379 let pending_request_count = self.pending_request_count.get();
380 let next_unhandled_request_id = self.next_unhandled_request_id.get();
381 let issued_count = self.issued_count();
382 if finished || abort_initiated || commit_started {
383 return;
384 }
385 if active || pending_request_count != 0 {
386 return;
387 }
388 if next_unhandled_request_id != issued_count {
389 return;
390 }
391 if !self.attempt_commit() {
392 self.initiate_abort(cx, Error::InvalidState(None));
396 self.finalize_abort();
397 }
398 }
399
400 fn force_commit(&self) {
401 if self.finished.get() || self.abort_initiated.get() || self.commit_started.get() {
410 return;
411 }
412 if self.active.get() || self.pending_request_count.get() != 0 {
413 return;
414 }
415 self.attempt_commit();
416 }
417
418 pub fn get_mode(&self) -> IDBTransactionMode {
419 self.mode
420 }
421
422 pub fn get_db_name(&self) -> DOMString {
423 self.db.get_name()
424 }
425
426 pub(crate) fn get_db(&self) -> &IDBDatabase {
427 &self.db
428 }
429
430 pub fn get_serial_number(&self) -> u64 {
431 self.serial_number
432 }
433
434 pub(crate) fn issued_count(&self) -> u64 {
435 self.next_request_id.get()
436 }
437
438 pub(crate) fn allocate_request_id(&self) -> u64 {
441 let id = self.next_request_id.get();
442 self.next_request_id.set(id + 1);
443 id
444 }
445
446 pub(crate) fn mark_request_handled(&self, request_id: u64) {
447 let current = self.next_unhandled_request_id.get();
448 if request_id == current {
449 let mut next = current + 1;
450 {
451 let mut pending = self.handled_pending.borrow_mut();
452 while pending.remove(&next) {
453 next += 1;
454 }
455 }
456 self.next_unhandled_request_id.set(next);
457 } else if request_id > current {
458 self.handled_pending.borrow_mut().insert(request_id);
459 }
460 }
461
462 pub fn add_request(&self, request: &IDBRequest) {
463 self.requests.borrow_mut().push(Dom::from_ref(request));
464 self.pending_request_count
467 .set(self.pending_request_count.get() + 1);
468 }
469
470 pub fn request_finished(&self) {
471 if self.pending_request_count.get() == 0 {
476 return;
477 }
478 let remaining = self.pending_request_count.get() - 1;
479 self.pending_request_count.set(remaining);
480 }
481
482 pub(crate) fn initiate_abort(&self, cx: &mut JSContext, error: Error) {
483 if self.finished.get() || self.abort_initiated.get() {
488 return;
489 }
490 if self.mode == IDBTransactionMode::Versionchange {
491 if let Some(names) = self
495 .version_change_old_object_store_names
496 .borrow()
497 .as_ref()
498 .cloned()
499 .map(|domstring_vec| {
500 domstring_vec
501 .into_iter()
502 .map(|domstring| domstring.root())
503 .collect::<Vec<_>>()
504 })
505 {
506 self.db.restore_object_store_names(names);
507 }
508 self.restore_associated_object_store_handles_after_abort(cx);
509 }
510 self.abort_initiated.set(true);
511 if self.error.get().is_none() &&
515 let Ok(exception) = create_dom_exception(cx, &self.global(), error)
516 {
517 self.error.set(Some(&exception));
518 }
519 }
520
521 pub(crate) fn request_backend_abort(&self) {
522 if self.abort_requested.get() {
523 return;
524 }
525 self.abort_requested.set(true);
526 let this = Trusted::new(self);
527 let global = self.global();
528 let task_source = global
529 .task_manager()
530 .dom_manipulation_task_source()
531 .to_sendable();
532 let callback = GenericCallback::new(move |message: Result<TxnCompleteMsg, SendError>| {
533 let this = this.clone();
534 let task_source = task_source.clone();
535 task_source.queue(task!(handle_abort_result: move || {
536 let this = this.root();
537 let _ = message.expect("Could not unwrap message");
538 this.finalize_abort();
539 }));
540 })
541 .expect("Could not create callback");
542 let operation = SyncOperation::Abort(
543 callback,
544 global.origin().immutable().clone(),
545 String::from(self.db.get_name()),
546 self.serial_number,
547 );
548 let _ = self
549 .get_idb_thread()
550 .send(IndexedDBThreadMsg::Sync(operation));
551 }
552
553 fn notify_backend_transaction_finished(&self) {
554 let global = self.global();
555 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
556 SyncOperation::TransactionFinished {
557 origin: global.origin().immutable().clone(),
558 db_name: String::from(self.db.get_name()),
559 txn: self.serial_number,
560 },
561 ));
562 }
563
564 pub(crate) fn finalize_abort(&self) {
565 if self.finished.get() {
566 return;
567 }
568 self.committing.set(false);
569 self.commit_started.set(false);
570 let this = Trusted::new(self);
571 self.global()
572 .task_manager()
573 .dom_manipulation_task_source()
574 .queue(task!(send_abort_notification: move |cx| {
575 let this = this.root();
576 this.active.set(false);
577 if this.mode == IDBTransactionMode::Versionchange {
578 if let Some(old_version) = this.version_change_old_version.get() {
579 this.db.set_version(old_version);
584 }
585 this.db.clear_upgrade_transaction(&this);
586 }
587 let global = this.global();
588 let event = Event::new(
589 cx,
590 &global,
591 Atom::from("abort"),
592 EventBubbles::DoesNotBubble,
593 EventCancelable::NotCancelable,
594 );
595 event.fire(cx, this.upcast());
596 if this.mode == IDBTransactionMode::Versionchange {
597 this.global()
598 .ensure_indexeddb_factory(cx)
599 .clear_open_request_transaction_for_txn(&this);
600 let origin = this.global().origin().immutable().clone();
601 let db_name = String::from(this.db.get_name());
602 let txn = this.serial_number;
603 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
604 SyncOperation::UpgradeTransactionFinished {
605 origin,
606 db_name,
607 txn,
608 committed: false,
609 },
610 ));
611 }
612 this.finished.set(true);
615 this.version_change_old_version.set(None);
616 this.version_change_old_object_store_names.safe_borrow_mut(cx.no_gc()).take();
617 this.notify_backend_transaction_finished();
618 if this.registered_in_global.get() {
619 this.global().ensure_indexeddb_factory(cx).unregister_indexeddb_transaction(&this);
620 }
621 }));
622 }
623
624 pub(crate) fn finalize_commit(&self) {
625 if self.finished.get() {
626 return;
627 }
628 self.dispatch_complete();
629 }
630
631 fn dispatch_complete(&self) {
632 let global = self.global();
633 let this = Trusted::new(self);
634 global.task_manager().database_access_task_source().queue(
635 task!(send_complete_notification: move |cx| {
636 let this = this.root();
637 this.committing.set(false);
638 this.commit_started.set(false);
639 this.version_change_old_version.set(None);
640 this.version_change_old_object_store_names
641 .safe_borrow_mut(cx.no_gc())
642 .take();
643 if this.mode == IDBTransactionMode::Versionchange {
644 this.db.clear_upgrade_transaction(&this);
648 }
649 this.finished.set(true);
652 let global = this.global();
653 let event = Event::new(
654 cx,
655 &global,
656 Atom::from("complete"),
657 EventBubbles::DoesNotBubble,
658 EventCancelable::NotCancelable,
659 );
660 event.fire(cx, this.upcast());
663 if this.mode == IDBTransactionMode::Versionchange {
664 this.global()
668 .ensure_indexeddb_factory(cx)
669 .clear_open_request_transaction_for_txn(&this);
670 let origin = this.global().origin().immutable().clone();
671 let db_name = String::from(this.db.get_name());
672 let txn = this.serial_number;
673 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
674 SyncOperation::UpgradeTransactionFinished {
675 origin,
676 db_name,
677 txn,
678 committed: true,
679 },
680 ));
681 }
682 this.notify_backend_transaction_finished();
683 if this.registered_in_global.get() {
684 this.global().ensure_indexeddb_factory(cx).unregister_indexeddb_transaction(&this);
685 }
686 }),
687 );
688 }
689
690 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
691 self.global().storage_threads().sender()
692 }
693
694 fn object_store_parameters(
695 &self,
696 object_store_name: &DOMString,
697 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>, Option<i64>)> {
698 let global = self.global();
699 let idb_sender = global.storage_threads().sender();
700 let (sender, receiver) =
701 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
702
703 let origin = global.origin().immutable().clone();
704 let db_name = String::from(self.db.get_name());
705 let object_store_name = object_store_name.to_string();
706
707 let operation = SyncOperation::GetObjectStore(sender, origin, db_name, object_store_name);
708
709 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
710
711 let object_store = receiver.recv().ok()?.ok()?;
714
715 let key_path = object_store.key_path.map(|key_path| match key_path {
718 KeyPath::String(string) => StringOrStringSequence::String(string.into()),
719 KeyPath::Sequence(seq) => {
720 StringOrStringSequence::StringSequence(seq.into_iter().map(Into::into).collect())
721 },
722 });
723 Some((
724 IDBObjectStoreParameters {
725 autoIncrement: object_store.has_key_generator,
726 keyPath: key_path,
727 },
728 object_store.indexes,
729 object_store.key_generator_current_number,
730 ))
731 }
732
733 pub(crate) fn create_abort_callback(&self) -> GenericCallback<BackendError> {
734 let trusted_transaction = Trusted::new(self);
735 let task_source = self
736 .global()
737 .task_manager()
738 .storage_task_source()
739 .to_sendable();
740 GenericCallback::new(move |error: Result<BackendError, SendError>| {
741 let Ok(error) = error else {
742 return;
743 };
744 let trusted_transaction = trusted_transaction.clone();
745 task_source.queue(task!(delete_failed: move |cx| {
746 let transaction = trusted_transaction.root();
747 transaction.initiate_abort(cx, map_backend_error_to_dom_error(error));
748 transaction.request_backend_abort();
749 }));
750 })
751 .expect("Could not create GenericCallback")
752 }
753}
754
755impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
756 fn Db(&self) -> DomRoot<IDBDatabase> {
758 DomRoot::from_ref(&*self.db)
759 }
760
761 fn ObjectStore(
763 &self,
764 cx: &mut JSContext,
765 name: DOMString,
766 ) -> Fallible<DomRoot<IDBObjectStore>> {
767 if self.finished.get() || self.abort_initiated.get() {
769 return Err(Error::InvalidState(None));
770 }
771
772 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
776 self.db.object_store_exists(&name)
777 } else {
778 self.object_store_names.Contains(name.clone())
779 };
780 if !in_scope {
781 return Err(Error::NotFound(None));
782 }
783
784 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
788 return Ok(DomRoot::from_ref(store));
789 }
790
791 let parameters = self.object_store_parameters(&name);
792 let store = IDBObjectStore::new(
793 cx,
794 &self.global(),
795 self.db.get_name(),
796 name.clone(),
797 parameters.as_ref().map(|(params, _, _)| params),
798 IDBObjectStoreAbortState {
799 newly_created_during_transaction: false,
800 rollback_indexes_on_abort: if self.mode == IDBTransactionMode::Versionchange {
801 parameters
802 .as_ref()
803 .map(|(_, indexes, _)| indexes.clone())
804 .unwrap_or_default()
805 } else {
806 Vec::new()
807 },
808 key_generator_current_number: parameters
809 .as_ref()
810 .and_then(|(_, _, key_generator_current_number)| *key_generator_current_number),
811 },
812 self,
813 );
814 if let Some(indexes) = parameters.map(|(_, indexes, _)| indexes) {
815 for index in indexes {
816 store.add_index(
817 cx,
818 index.name.into(),
819 &IDBIndexParameters {
820 multiEntry: index.multi_entry,
821 unique: index.unique,
822 },
823 index.key_path.into(),
824 );
825 }
826 }
827 self.register_object_store_handle(&name, &store);
828 Ok(store)
829 }
830
831 fn Commit(&self) -> Fallible<()> {
833 if !self.active.get() {
835 return Err(Error::InvalidState(None));
836 }
837
838 self.set_active_flag(false);
840 self.committing.set(true);
841 self.force_commit();
842
843 Ok(())
844 }
845
846 fn Abort(&self, cx: &mut JSContext) -> Fallible<()> {
848 if self.finished.get() || self.committing.get() {
849 return Err(Error::InvalidState(None));
850 }
851 self.active.set(false);
852 self.initiate_abort(cx, Error::Abort(None));
853 self.request_backend_abort();
854
855 Ok(())
856 }
857
858 fn ObjectStoreNames(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
860 if self.mode == IDBTransactionMode::Versionchange {
861 self.db.object_stores(cx)
862 } else {
863 self.object_store_names.as_rooted()
864 }
865 }
866
867 fn Mode(&self) -> IDBTransactionMode {
869 self.mode
870 }
871
872 fn Durability(&self) -> IDBTransactionDurability {
874 self.durability
875 }
876
877 fn GetError(&self) -> Option<DomRoot<DOMException>> {
879 self.error.get()
880 }
881
882 event_handler!(abort, GetOnabort, SetOnabort);
884
885 event_handler!(complete, GetOncomplete, SetOncomplete);
887
888 event_handler!(error, GetOnerror, SetOnerror);
890}