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