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