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;
15use servo_base::generic_channel::{GenericSend, GenericSender, SendError};
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::dom::indexeddb::key::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(
162 cx,
163 Box::new(IDBTransaction::new_inherited(
164 connection,
165 mode,
166 durability,
167 scope,
168 serial_number,
169 )),
170 global,
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(move |message: Result<TxnCompleteMsg, SendError>| {
320 let this = this.clone();
321 let task_source = task_source.clone();
322 task_source.queue(task!(handle_commit_result: move |cx| {
323 let this = this.root();
324 let message = message.expect("Could not unwrap message");
325 match message.result {
326 Ok(()) => {
327 this.finalize_commit();
328 }
329 Err(_err) => {
330 this.initiate_abort(cx, Error::Operation(None));
332
333 this.finalize_abort();
334 }
335 }
336 }));
339 })
340 .expect("Could not create callback");
341
342 let commit_operation = SyncOperation::Commit(
343 callback,
344 global.origin().immutable().clone(),
345 String::from(self.db.get_name()),
346 self.serial_number,
347 );
348
349 let send_result = self
352 .get_idb_thread()
353 .send(IndexedDBThreadMsg::Sync(commit_operation));
354 if send_result.is_err() {
355 return false;
356 }
357
358 self.committing.set(true);
359 self.commit_started.set(true);
360 true
361 }
362
363 pub(crate) fn maybe_commit(&self, cx: &mut JSContext) {
364 let finished = self.finished.get();
370 let abort_initiated = self.abort_initiated.get();
371 let commit_started = self.commit_started.get();
372 let active = self.active.get();
373 let pending_request_count = self.pending_request_count.get();
374 let next_unhandled_request_id = self.next_unhandled_request_id.get();
375 let issued_count = self.issued_count();
376 if finished || abort_initiated || commit_started {
377 return;
378 }
379 if active || pending_request_count != 0 {
380 return;
381 }
382 if next_unhandled_request_id != issued_count {
383 return;
384 }
385 if !self.attempt_commit() {
386 self.initiate_abort(cx, Error::InvalidState(None));
390 self.finalize_abort();
391 }
392 }
393
394 fn force_commit(&self) {
395 if self.finished.get() || self.abort_initiated.get() || self.commit_started.get() {
404 return;
405 }
406 if self.active.get() || self.pending_request_count.get() != 0 {
407 return;
408 }
409 self.attempt_commit();
410 }
411
412 pub fn get_mode(&self) -> IDBTransactionMode {
413 self.mode
414 }
415
416 pub fn get_db_name(&self) -> DOMString {
417 self.db.get_name()
418 }
419
420 pub(crate) fn get_db(&self) -> &IDBDatabase {
421 &self.db
422 }
423
424 pub fn get_serial_number(&self) -> u64 {
425 self.serial_number
426 }
427
428 pub(crate) fn issued_count(&self) -> u64 {
429 self.next_request_id.get()
430 }
431
432 pub(crate) fn allocate_request_id(&self) -> u64 {
435 let id = self.next_request_id.get();
436 self.next_request_id.set(id + 1);
437 id
438 }
439
440 pub(crate) fn mark_request_handled(&self, request_id: u64) {
441 let current = self.next_unhandled_request_id.get();
442 if request_id == current {
443 let mut next = current + 1;
444 {
445 let mut pending = self.handled_pending.borrow_mut();
446 while pending.remove(&next) {
447 next += 1;
448 }
449 }
450 self.next_unhandled_request_id.set(next);
451 } else if request_id > current {
452 self.handled_pending.borrow_mut().insert(request_id);
453 }
454 }
455
456 pub fn add_request(&self, request: &IDBRequest) {
457 self.requests.borrow_mut().push(Dom::from_ref(request));
458 self.pending_request_count
461 .set(self.pending_request_count.get() + 1);
462 }
463
464 pub fn request_finished(&self) {
465 if self.pending_request_count.get() == 0 {
470 return;
471 }
472 let remaining = self.pending_request_count.get() - 1;
473 self.pending_request_count.set(remaining);
474 }
475
476 pub(crate) fn initiate_abort(&self, cx: &mut JSContext, error: Error) {
477 if self.finished.get() || self.abort_initiated.get() {
482 return;
483 }
484 if self.mode == IDBTransactionMode::Versionchange {
485 if let Some(names) = self
489 .version_change_old_object_store_names
490 .borrow()
491 .as_ref()
492 .cloned()
493 {
494 self.db.restore_object_store_names(names);
495 }
496 self.restore_associated_object_store_handles_after_abort(cx);
497 }
498 self.abort_initiated.set(true);
499 if self.error.get().is_none() &&
503 let Ok(exception) = create_dom_exception(cx, &self.global(), error)
504 {
505 self.error.set(Some(&exception));
506 }
507 }
508
509 pub(crate) fn request_backend_abort(&self) {
510 if self.abort_requested.get() {
511 return;
512 }
513 self.abort_requested.set(true);
514 let this = Trusted::new(self);
515 let global = self.global();
516 let task_source = global
517 .task_manager()
518 .dom_manipulation_task_source()
519 .to_sendable();
520 let callback = GenericCallback::new(move |message: Result<TxnCompleteMsg, SendError>| {
521 let this = this.clone();
522 let task_source = task_source.clone();
523 task_source.queue(task!(handle_abort_result: move || {
524 let this = this.root();
525 let _ = message.expect("Could not unwrap message");
526 this.finalize_abort();
527 }));
528 })
529 .expect("Could not create callback");
530 let operation = SyncOperation::Abort(
531 callback,
532 global.origin().immutable().clone(),
533 String::from(self.db.get_name()),
534 self.serial_number,
535 );
536 let _ = self
537 .get_idb_thread()
538 .send(IndexedDBThreadMsg::Sync(operation));
539 }
540
541 fn notify_backend_transaction_finished(&self) {
542 let global = self.global();
543 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
544 SyncOperation::TransactionFinished {
545 origin: global.origin().immutable().clone(),
546 db_name: String::from(self.db.get_name()),
547 txn: self.serial_number,
548 },
549 ));
550 }
551
552 pub(crate) fn finalize_abort(&self) {
553 if self.finished.get() {
554 return;
555 }
556 self.committing.set(false);
557 self.commit_started.set(false);
558 let this = Trusted::new(self);
559 self.global()
560 .task_manager()
561 .dom_manipulation_task_source()
562 .queue(task!(send_abort_notification: move |cx| {
563 let this = this.root();
564 this.active.set(false);
565 if this.mode == IDBTransactionMode::Versionchange {
566 if let Some(old_version) = this.version_change_old_version.get() {
567 this.db.set_version(old_version);
572 }
573 this.db.clear_upgrade_transaction(&this);
574 }
575 let global = this.global();
576 let event = Event::new(
577 cx,
578 &global,
579 Atom::from("abort"),
580 EventBubbles::DoesNotBubble,
581 EventCancelable::NotCancelable,
582 );
583 event.fire(cx, this.upcast());
584 if this.mode == IDBTransactionMode::Versionchange {
585 this.global()
586 .ensure_indexeddb_factory(cx)
587 .clear_open_request_transaction_for_txn(&this);
588 let origin = this.global().origin().immutable().clone();
589 let db_name = String::from(this.db.get_name());
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: false,
597 },
598 ));
599 }
600 this.finished.set(true);
603 this.version_change_old_version.set(None);
604 this.version_change_old_object_store_names.borrow_mut().take();
605 this.notify_backend_transaction_finished();
606 if this.registered_in_global.get() {
607 this.global().ensure_indexeddb_factory(cx).unregister_indexeddb_transaction(&this);
608 }
609 }));
610 }
611
612 pub(crate) fn finalize_commit(&self) {
613 if self.finished.get() {
614 return;
615 }
616 self.dispatch_complete();
617 }
618
619 fn dispatch_complete(&self) {
620 let global = self.global();
621 let this = Trusted::new(self);
622 global.task_manager().database_access_task_source().queue(
623 task!(send_complete_notification: move |cx| {
624 let this = this.root();
625 this.committing.set(false);
626 this.commit_started.set(false);
627 this.version_change_old_version.set(None);
628 this.version_change_old_object_store_names
629 .borrow_mut()
630 .take();
631 if this.mode == IDBTransactionMode::Versionchange {
632 this.db.clear_upgrade_transaction(&this);
636 }
637 this.finished.set(true);
640 let global = this.global();
641 let event = Event::new(
642 cx,
643 &global,
644 Atom::from("complete"),
645 EventBubbles::DoesNotBubble,
646 EventCancelable::NotCancelable,
647 );
648 event.fire(cx, this.upcast());
651 if this.mode == IDBTransactionMode::Versionchange {
652 this.global()
656 .ensure_indexeddb_factory(cx)
657 .clear_open_request_transaction_for_txn(&this);
658 let origin = this.global().origin().immutable().clone();
659 let db_name = String::from(this.db.get_name());
660 let txn = this.serial_number;
661 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
662 SyncOperation::UpgradeTransactionFinished {
663 origin,
664 db_name,
665 txn,
666 committed: true,
667 },
668 ));
669 }
670 this.notify_backend_transaction_finished();
671 if this.registered_in_global.get() {
672 this.global().ensure_indexeddb_factory(cx).unregister_indexeddb_transaction(&this);
673 }
674 }),
675 );
676 }
677
678 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
679 self.global().storage_threads().sender()
680 }
681
682 fn object_store_parameters(
683 &self,
684 object_store_name: &DOMString,
685 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>, Option<i64>)> {
686 let global = self.global();
687 let idb_sender = global.storage_threads().sender();
688 let (sender, receiver) =
689 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
690
691 let origin = global.origin().immutable().clone();
692 let db_name = String::from(self.db.get_name());
693 let object_store_name = object_store_name.to_string();
694
695 let operation = SyncOperation::GetObjectStore(sender, origin, db_name, object_store_name);
696
697 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
698
699 let object_store = receiver.recv().ok()?.ok()?;
702
703 let key_path = object_store.key_path.map(|key_path| match key_path {
706 KeyPath::String(string) => StringOrStringSequence::String(string.into()),
707 KeyPath::Sequence(seq) => {
708 StringOrStringSequence::StringSequence(seq.into_iter().map(Into::into).collect())
709 },
710 });
711 Some((
712 IDBObjectStoreParameters {
713 autoIncrement: object_store.has_key_generator,
714 keyPath: key_path,
715 },
716 object_store.indexes,
717 object_store.key_generator_current_number,
718 ))
719 }
720
721 pub(crate) fn create_abort_callback(&self) -> GenericCallback<BackendError> {
722 let trusted_transaction = Trusted::new(self);
723 let task_source = self
724 .global()
725 .task_manager()
726 .storage_task_source()
727 .to_sendable();
728 GenericCallback::new(move |error: Result<BackendError, SendError>| {
729 let Ok(error) = error else {
730 return;
731 };
732 let trusted_transaction = trusted_transaction.clone();
733 task_source.queue(task!(delete_failed: move |cx| {
734 let transaction = trusted_transaction.root();
735 transaction.initiate_abort(cx, map_backend_error_to_dom_error(error));
736 transaction.request_backend_abort();
737 }));
738 })
739 .expect("Could not create GenericCallback")
740 }
741}
742
743impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
744 fn Db(&self) -> DomRoot<IDBDatabase> {
746 DomRoot::from_ref(&*self.db)
747 }
748
749 fn ObjectStore(
751 &self,
752 cx: &mut JSContext,
753 name: DOMString,
754 ) -> Fallible<DomRoot<IDBObjectStore>> {
755 if self.finished.get() || self.abort_initiated.get() {
757 return Err(Error::InvalidState(None));
758 }
759
760 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
764 self.db.object_store_exists(&name)
765 } else {
766 self.object_store_names.Contains(name.clone())
767 };
768 if !in_scope {
769 return Err(Error::NotFound(None));
770 }
771
772 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
776 return Ok(DomRoot::from_ref(store));
777 }
778
779 let parameters = self.object_store_parameters(&name);
780 let store = IDBObjectStore::new(
781 cx,
782 &self.global(),
783 self.db.get_name(),
784 name.clone(),
785 parameters.as_ref().map(|(params, _, _)| params),
786 IDBObjectStoreAbortState {
787 newly_created_during_transaction: false,
788 rollback_indexes_on_abort: if self.mode == IDBTransactionMode::Versionchange {
789 parameters
790 .as_ref()
791 .map(|(_, indexes, _)| indexes.clone())
792 .unwrap_or_default()
793 } else {
794 Vec::new()
795 },
796 key_generator_current_number: parameters
797 .as_ref()
798 .and_then(|(_, _, key_generator_current_number)| *key_generator_current_number),
799 },
800 self,
801 );
802 if let Some(indexes) = parameters.map(|(_, indexes, _)| indexes) {
803 for index in indexes {
804 store.add_index(
805 cx,
806 index.name.into(),
807 &IDBIndexParameters {
808 multiEntry: index.multi_entry,
809 unique: index.unique,
810 },
811 index.key_path.into(),
812 );
813 }
814 }
815 self.register_object_store_handle(&name, &store);
816 Ok(store)
817 }
818
819 fn Commit(&self) -> Fallible<()> {
821 if !self.active.get() {
823 return Err(Error::InvalidState(None));
824 }
825
826 self.set_active_flag(false);
828 self.committing.set(true);
829 self.force_commit();
830
831 Ok(())
832 }
833
834 fn Abort(&self, cx: &mut JSContext) -> Fallible<()> {
836 if self.finished.get() || self.committing.get() {
837 return Err(Error::InvalidState(None));
838 }
839 self.active.set(false);
840 self.initiate_abort(cx, Error::Abort(None));
841 self.request_backend_abort();
842
843 Ok(())
844 }
845
846 fn ObjectStoreNames(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
848 if self.mode == IDBTransactionMode::Versionchange {
849 self.db.object_stores(cx)
850 } else {
851 self.object_store_names.as_rooted()
852 }
853 }
854
855 fn Mode(&self) -> IDBTransactionMode {
857 self.mode
858 }
859
860 fn Durability(&self) -> IDBTransactionDurability {
862 self.durability
863 }
864
865 fn GetError(&self) -> Option<DomRoot<DOMException>> {
867 self.error.get()
868 }
869
870 event_handler!(abort, GetOnabort, SetOnabort);
872
873 event_handler!(complete, GetOncomplete, SetOncomplete);
875
876 event_handler!(error, GetOnerror, SetOnerror);
878}