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::codegen::GenericUnionTypes::StringOrStringSequence;
12use servo_base::generic_channel::{GenericSend, GenericSender};
13use servo_base::id::ScriptEventLoopId;
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::{
22 IDBObjectStoreParameters, IDBTransactionDurability,
23};
24use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBIndexParameters;
25use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
26 IDBTransactionMethods, IDBTransactionMode,
27};
28use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
29use crate::dom::bindings::inheritance::Castable;
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
32use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
33use crate::dom::bindings::str::DOMString;
34use crate::dom::domexception::DOMException;
35use crate::dom::domstringlist::DOMStringList;
36use crate::dom::event::{Event, EventBubbles, EventCancelable};
37use crate::dom::eventtarget::EventTarget;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::indexeddb::idbdatabase::IDBDatabase;
40use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
41use crate::dom::indexeddb::idbrequest::IDBRequest;
42use crate::script_runtime::CanGc;
43
44#[dom_struct]
45pub struct IDBTransaction {
46 eventtarget: EventTarget,
47 object_store_names: Dom<DOMStringList>,
48 mode: IDBTransactionMode,
49 durability: IDBTransactionDurability,
50 db: Dom<IDBDatabase>,
51 error: MutNullableDom<DOMException>,
52
53 store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
54 requests: DomRefCell<Vec<Dom<IDBRequest>>>,
56 active: Cell<bool>,
58 finished: Cell<bool>,
60 abort_initiated: Cell<bool>,
61 abort_requested: Cell<bool>,
62 committing: Cell<bool>,
63 commit_started: Cell<bool>,
64 version_change_old_version: Cell<Option<u64>>,
65 version_change_old_object_store_names: DomRefCell<Option<Vec<DOMString>>>,
68 #[no_trace]
71 cleanup_event_loop: Cell<Option<ScriptEventLoopId>>,
72 registered_in_global: Cell<bool>,
73 pending_request_count: Cell<usize>,
78 next_request_id: Cell<u64>,
79 next_unhandled_request_id: Cell<u64>,
81 handled_pending: DomRefCell<HashSet<u64>>,
82
83 serial_number: u64,
86}
87
88impl IDBTransaction {
89 fn new_inherited(
90 connection: &IDBDatabase,
91 mode: IDBTransactionMode,
92 durability: IDBTransactionDurability,
93 scope: &DOMStringList,
94 serial_number: u64,
95 ) -> IDBTransaction {
96 IDBTransaction {
97 eventtarget: EventTarget::new_inherited(),
98 object_store_names: Dom::from_ref(scope),
99 mode,
100 durability,
101 db: Dom::from_ref(connection),
102 error: Default::default(),
103
104 store_handles: Default::default(),
105 requests: Default::default(),
106 active: Cell::new(true),
107 finished: Cell::new(false),
108 abort_initiated: Cell::new(false),
109 abort_requested: Cell::new(false),
110 committing: Cell::new(false),
111 commit_started: Cell::new(false),
112 version_change_old_version: Cell::new(None),
113 version_change_old_object_store_names: DomRefCell::new(
114 (mode == IDBTransactionMode::Versionchange)
115 .then(|| connection.object_store_names_snapshot()),
116 ),
117 cleanup_event_loop: Cell::new(None),
118 registered_in_global: Cell::new(false),
119 pending_request_count: Cell::new(0),
120 next_request_id: Cell::new(0),
121 next_unhandled_request_id: Cell::new(0),
122 handled_pending: Default::default(),
123 serial_number,
124 }
125 }
126
127 pub fn new(
129 global: &GlobalScope,
130 connection: &IDBDatabase,
131 mode: IDBTransactionMode,
132 durability: IDBTransactionDurability,
133 scope: &DOMStringList,
134 can_gc: CanGc,
135 ) -> DomRoot<IDBTransaction> {
136 let serial_number =
137 IDBTransaction::create_transaction(global, connection.get_name(), mode, scope);
138 IDBTransaction::new_with_serial(
139 global,
140 connection,
141 mode,
142 durability,
143 scope,
144 serial_number,
145 can_gc,
146 )
147 }
148
149 pub(crate) fn new_with_serial(
150 global: &GlobalScope,
151 connection: &IDBDatabase,
152 mode: IDBTransactionMode,
153 durability: IDBTransactionDurability,
154 scope: &DOMStringList,
155 serial_number: u64,
156 can_gc: CanGc,
157 ) -> DomRoot<IDBTransaction> {
158 reflect_dom_object(
159 Box::new(IDBTransaction::new_inherited(
160 connection,
161 mode,
162 durability,
163 scope,
164 serial_number,
165 )),
166 global,
167 can_gc,
168 )
169 }
170
171 fn create_transaction(
172 global: &GlobalScope,
173 db_name: DOMString,
174 mode: IDBTransactionMode,
175 scope: &DOMStringList,
176 ) -> u64 {
177 let backend_mode = match mode {
178 IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
179 IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
180 IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
181 };
182 let scope: Vec<String> = (0..scope.Length())
183 .filter_map(|i| scope.Item(i))
184 .map(|name| name.to_string())
185 .collect();
186 let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
187
188 global
189 .storage_threads()
190 .send(IndexedDBThreadMsg::Sync(SyncOperation::CreateTransaction {
191 sender,
192 origin: global.origin().immutable().clone(),
193 db_name: db_name.to_string(),
194 mode: backend_mode,
195 scope,
196 }))
197 .expect("Failed to send IndexedDBThreadMsg::Sync");
198
199 receiver.recv().unwrap().expect("CreateTransaction failed")
200 }
201
202 pub fn set_active_flag(&self, status: bool) {
204 self.active.set(status);
209 }
210
211 pub fn is_active(&self) -> bool {
212 self.active.get()
213 }
214
215 pub(crate) fn is_usable(&self) -> bool {
217 !self.finished.get() && !self.abort_initiated.get() && !self.committing.get()
222 }
223
224 pub(crate) fn is_inactive(&self) -> bool {
225 !self.active.get() &&
226 !self.finished.get() &&
227 !self.abort_initiated.get() &&
228 !self.committing.get()
229 }
230
231 pub(crate) fn is_committing(&self) -> bool {
232 self.committing.get()
233 }
234
235 pub(crate) fn is_finished(&self) -> bool {
236 self.finished.get()
237 }
238
239 pub(crate) fn set_cleanup_event_loop(&self) {
240 self.cleanup_event_loop.set(ScriptEventLoopId::installed());
243 }
244
245 pub(crate) fn clear_cleanup_event_loop(&self) {
246 self.cleanup_event_loop.set(None);
249 }
250
251 pub(crate) fn cleanup_event_loop_matches_current(&self) -> bool {
252 match ScriptEventLoopId::installed() {
253 Some(current) => self.cleanup_event_loop.get() == Some(current),
254 None => false,
255 }
256 }
257
258 pub(crate) fn set_registered_in_global(&self) {
259 self.registered_in_global.set(true);
260 }
261
262 pub(crate) fn clear_registered_in_global(&self) {
263 self.registered_in_global.set(false);
264 }
265
266 pub(crate) fn set_versionchange_old_version(&self, version: u64) {
267 self.version_change_old_version.set(Some(version));
268 }
269
270 fn attempt_commit(&self) -> bool {
271 if self.commit_started.get() {
272 return true;
273 }
274 let this = Trusted::new(self);
275 let global = self.global();
276 let task_source = global
277 .task_manager()
278 .dom_manipulation_task_source()
279 .to_sendable();
280
281 let callback = GenericCallback::new(
284 global.time_profiler_chan().clone(),
285 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
286 let this = this.clone();
287 let task_source = task_source.clone();
288 task_source.queue(task!(handle_commit_result: move || {
289 let this = this.root();
290 let message = message.expect("Could not unwrap message");
291 match message.result {
292 Ok(()) => {
293 this.finalize_commit();
294 }
295 Err(_err) => {
296 this.initiate_abort(Error::Operation(None), CanGc::deprecated_note());
298
299 this.finalize_abort();
300 }
301 }
302 }));
305 },
306 )
307 .expect("Could not create callback");
308
309 let commit_operation = SyncOperation::Commit(
310 callback,
311 global.origin().immutable().clone(),
312 self.db.get_name().to_string(),
313 self.serial_number,
314 );
315
316 let send_result = self
319 .get_idb_thread()
320 .send(IndexedDBThreadMsg::Sync(commit_operation));
321 if send_result.is_err() {
322 return false;
323 }
324
325 self.committing.set(true);
326 self.commit_started.set(true);
327 true
328 }
329
330 pub(crate) fn maybe_commit(&self) {
331 let finished = self.finished.get();
337 let abort_initiated = self.abort_initiated.get();
338 let commit_started = self.commit_started.get();
339 let active = self.active.get();
340 let pending_request_count = self.pending_request_count.get();
341 let next_unhandled_request_id = self.next_unhandled_request_id.get();
342 let issued_count = self.issued_count();
343 if finished || abort_initiated || commit_started {
344 return;
345 }
346 if active || pending_request_count != 0 {
347 return;
348 }
349 if next_unhandled_request_id != issued_count {
350 return;
351 }
352 if !self.attempt_commit() {
353 self.initiate_abort(Error::InvalidState(None), CanGc::deprecated_note());
357 self.finalize_abort();
358 }
359 }
360
361 fn force_commit(&self) {
362 if self.finished.get() || self.abort_initiated.get() || self.commit_started.get() {
371 return;
372 }
373 if self.active.get() || self.pending_request_count.get() != 0 {
374 return;
375 }
376 self.attempt_commit();
377 }
378
379 pub fn get_mode(&self) -> IDBTransactionMode {
380 self.mode
381 }
382
383 pub fn get_db_name(&self) -> DOMString {
384 self.db.get_name()
385 }
386
387 pub(crate) fn get_db(&self) -> &IDBDatabase {
388 &self.db
389 }
390
391 pub fn get_serial_number(&self) -> u64 {
392 self.serial_number
393 }
394
395 pub(crate) fn issued_count(&self) -> u64 {
396 self.next_request_id.get()
397 }
398
399 pub(crate) fn allocate_request_id(&self) -> u64 {
402 let id = self.next_request_id.get();
403 self.next_request_id.set(id + 1);
404 id
405 }
406
407 pub(crate) fn mark_request_handled(&self, request_id: u64) {
408 let current = self.next_unhandled_request_id.get();
409 if request_id == current {
410 let mut next = current + 1;
411 {
412 let mut pending = self.handled_pending.borrow_mut();
413 while pending.remove(&next) {
414 next += 1;
415 }
416 }
417 self.next_unhandled_request_id.set(next);
418 } else if request_id > current {
419 self.handled_pending.borrow_mut().insert(request_id);
420 }
421 }
422
423 pub fn add_request(&self, request: &IDBRequest) {
424 self.requests.borrow_mut().push(Dom::from_ref(request));
425 self.pending_request_count
428 .set(self.pending_request_count.get() + 1);
429 }
430
431 pub fn request_finished(&self) {
432 if self.pending_request_count.get() == 0 {
437 return;
438 }
439 let remaining = self.pending_request_count.get() - 1;
440 self.pending_request_count.set(remaining);
441 }
442
443 pub(crate) fn initiate_abort(&self, error: Error, can_gc: CanGc) {
444 if self.finished.get() || self.abort_initiated.get() {
449 return;
450 }
451 if self.mode == IDBTransactionMode::Versionchange {
452 if let Some(names) = self
456 .version_change_old_object_store_names
457 .borrow()
458 .as_ref()
459 .cloned()
460 {
461 self.db.restore_object_store_names(names);
462 }
463 }
464 self.abort_initiated.set(true);
465 if self.error.get().is_none() {
469 if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
470 self.error.set(Some(&exception));
471 }
472 }
473 }
474
475 pub(crate) fn request_backend_abort(&self) {
476 if self.abort_requested.get() {
477 return;
478 }
479 self.abort_requested.set(true);
480 let this = Trusted::new(self);
481 let global = self.global();
482 let task_source = global
483 .task_manager()
484 .dom_manipulation_task_source()
485 .to_sendable();
486 let callback = GenericCallback::new(
487 global.time_profiler_chan().clone(),
488 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
489 let this = this.clone();
490 let task_source = task_source.clone();
491 task_source.queue(task!(handle_abort_result: move || {
492 let this = this.root();
493 let _ = message.expect("Could not unwrap message");
494 this.finalize_abort();
495 }));
496 },
497 )
498 .expect("Could not create callback");
499 let operation = SyncOperation::Abort(
500 callback,
501 global.origin().immutable().clone(),
502 self.db.get_name().to_string(),
503 self.serial_number,
504 );
505 let _ = self
506 .get_idb_thread()
507 .send(IndexedDBThreadMsg::Sync(operation));
508 }
509
510 fn notify_backend_transaction_finished(&self) {
511 let global = self.global();
512 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
513 SyncOperation::TransactionFinished {
514 origin: global.origin().immutable().clone(),
515 db_name: self.db.get_name().to_string(),
516 txn: self.serial_number,
517 },
518 ));
519 }
520
521 pub(crate) fn finalize_abort(&self) {
522 if self.finished.get() {
523 return;
524 }
525 self.committing.set(false);
526 self.commit_started.set(false);
527 let this = Trusted::new(self);
528 self.global()
529 .task_manager()
530 .dom_manipulation_task_source()
531 .queue(task!(send_abort_notification: move || {
532 let this = this.root();
533 this.active.set(false);
534 if this.mode == IDBTransactionMode::Versionchange {
535 if let Some(old_version) = this.version_change_old_version.get() {
536 this.db.set_version(old_version);
541 }
542 this.db.clear_upgrade_transaction(&this);
543 }
544 let global = this.global();
545 let event = Event::new(
546 &global,
547 Atom::from("abort"),
548 EventBubbles::DoesNotBubble,
549 EventCancelable::NotCancelable,
550 CanGc::deprecated_note(),
551 );
552 event.fire(this.upcast(), CanGc::deprecated_note());
553 if this.mode == IDBTransactionMode::Versionchange {
554 this.global()
555 .get_indexeddb()
556 .clear_open_request_transaction_for_txn(&this);
557 let origin = this.global().origin().immutable().clone();
558 let db_name = this.db.get_name().to_string();
559 let txn = this.serial_number;
560 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
561 SyncOperation::UpgradeTransactionFinished {
562 origin,
563 db_name,
564 txn,
565 committed: false,
566 },
567 ));
568 }
569 this.finished.set(true);
572 this.version_change_old_version.set(None);
573 this.version_change_old_object_store_names.borrow_mut().take();
574 this.notify_backend_transaction_finished();
575 if this.registered_in_global.get() {
576 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
577 }
578 }));
579 }
580
581 pub(crate) fn finalize_commit(&self) {
582 if self.finished.get() {
583 return;
584 }
585 self.dispatch_complete();
586 }
587
588 fn dispatch_complete(&self) {
589 let global = self.global();
590 let this = Trusted::new(self);
591 global.task_manager().database_access_task_source().queue(
592 task!(send_complete_notification: move || {
593 let this = this.root();
594 this.committing.set(false);
595 this.commit_started.set(false);
596 this.version_change_old_version.set(None);
597 this.version_change_old_object_store_names
598 .borrow_mut()
599 .take();
600 if this.mode == IDBTransactionMode::Versionchange {
601 this.db.clear_upgrade_transaction(&this);
605 }
606 this.finished.set(true);
609 let global = this.global();
610 let event = Event::new(
611 &global,
612 Atom::from("complete"),
613 EventBubbles::DoesNotBubble,
614 EventCancelable::NotCancelable,
615 CanGc::deprecated_note()
616 );
617 event.fire(this.upcast(), CanGc::deprecated_note());
620 if this.mode == IDBTransactionMode::Versionchange {
621 this.global()
625 .get_indexeddb()
626 .clear_open_request_transaction_for_txn(&this);
627 let origin = this.global().origin().immutable().clone();
628 let db_name = this.db.get_name().to_string();
629 let txn = this.serial_number;
630 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
631 SyncOperation::UpgradeTransactionFinished {
632 origin,
633 db_name,
634 txn,
635 committed: true,
636 },
637 ));
638 }
639 this.notify_backend_transaction_finished();
640 if this.registered_in_global.get() {
641 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
642 }
643 }),
644 );
645 }
646
647 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
648 self.global().storage_threads().sender()
649 }
650
651 fn object_store_parameters(
652 &self,
653 object_store_name: &DOMString,
654 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>, Option<i32>)> {
655 let global = self.global();
656 let idb_sender = global.storage_threads().sender();
657 let (sender, receiver) =
658 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
659
660 let origin = global.origin().immutable().clone();
661 let db_name = self.db.get_name().to_string();
662 let object_store_name = object_store_name.to_string();
663
664 let operation = SyncOperation::GetObjectStore(sender, origin, db_name, object_store_name);
665
666 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
667
668 let object_store = receiver.recv().ok()?.ok()?;
671
672 let key_path = object_store.key_path.map(|key_path| match key_path {
675 KeyPath::String(string) => StringOrStringSequence::String(string.into()),
676 KeyPath::Sequence(seq) => {
677 StringOrStringSequence::StringSequence(seq.into_iter().map(Into::into).collect())
678 },
679 });
680 Some((
681 IDBObjectStoreParameters {
682 autoIncrement: object_store.has_key_generator,
683 keyPath: key_path,
684 },
685 object_store.indexes,
686 object_store.key_generator_current_number,
687 ))
688 }
689}
690
691impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
692 fn Db(&self) -> DomRoot<IDBDatabase> {
694 DomRoot::from_ref(&*self.db)
695 }
696
697 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
699 if self.finished.get() || self.abort_initiated.get() {
701 return Err(Error::InvalidState(None));
702 }
703
704 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
708 self.db.object_store_exists(&name)
709 } else {
710 self.object_store_names.Contains(name.clone())
711 };
712 if !in_scope {
713 return Err(Error::NotFound(None));
714 }
715
716 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
720 return Ok(DomRoot::from_ref(store));
721 }
722
723 let parameters = self.object_store_parameters(&name);
724 let store = IDBObjectStore::new(
725 &self.global(),
726 self.db.get_name(),
727 name.clone(),
728 parameters.as_ref().map(|(params, _, _)| params),
729 parameters
730 .as_ref()
731 .and_then(|(_, _, key_generator_current_number)| *key_generator_current_number),
732 can_gc,
733 self,
734 );
735 if let Some(indexes) = parameters.map(|(_, indexes, _)| indexes) {
736 for index in indexes {
737 store.add_index(
738 index.name.into(),
739 &IDBIndexParameters {
740 multiEntry: index.multi_entry,
741 unique: index.unique,
742 },
743 index.key_path.into(),
744 can_gc,
745 );
746 }
747 }
748 self.store_handles
749 .borrow_mut()
750 .insert(name.to_string(), Dom::from_ref(&*store));
751 Ok(store)
752 }
753
754 fn Commit(&self) -> Fallible<()> {
756 if !self.active.get() {
758 return Err(Error::InvalidState(None));
759 }
760
761 self.set_active_flag(false);
763 self.committing.set(true);
764 self.force_commit();
765
766 Ok(())
767 }
768
769 fn Abort(&self) -> Fallible<()> {
771 if self.finished.get() || self.committing.get() {
772 return Err(Error::InvalidState(None));
773 }
774 self.active.set(false);
775 self.initiate_abort(Error::Abort(None), CanGc::deprecated_note());
776 self.request_backend_abort();
777
778 Ok(())
779 }
780
781 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
783 if self.mode == IDBTransactionMode::Versionchange {
784 self.db.object_stores()
785 } else {
786 self.object_store_names.as_rooted()
787 }
788 }
789
790 fn Mode(&self) -> IDBTransactionMode {
792 self.mode
793 }
794
795 fn Durability(&self) -> IDBTransactionDurability {
797 self.durability
798 }
799
800 fn GetError(&self) -> Option<DomRoot<DOMException>> {
802 self.error.get()
803 }
804
805 event_handler!(abort, GetOnabort, SetOnabort);
807
808 event_handler!(complete, GetOncomplete, SetOncomplete);
810
811 event_handler!(error, GetOnerror, SetOnerror);
813}