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;
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(|name| name.to_string())
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: db_name.to_string(),
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 fn attempt_commit(&self) -> bool {
272 if self.commit_started.get() {
273 return true;
274 }
275 let this = Trusted::new(self);
276 let global = self.global();
277 let task_source = global
278 .task_manager()
279 .dom_manipulation_task_source()
280 .to_sendable();
281
282 let callback = GenericCallback::new(
285 global.time_profiler_chan().clone(),
286 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
287 let this = this.clone();
288 let task_source = task_source.clone();
289 task_source.queue(task!(handle_commit_result: move || {
290 let this = this.root();
291 let message = message.expect("Could not unwrap message");
292 match message.result {
293 Ok(()) => {
294 this.finalize_commit();
295 }
296 Err(_err) => {
297 this.initiate_abort(Error::Operation(None), CanGc::deprecated_note());
299
300 this.finalize_abort();
301 }
302 }
303 }));
306 },
307 )
308 .expect("Could not create callback");
309
310 let commit_operation = SyncOperation::Commit(
311 callback,
312 global.origin().immutable().clone(),
313 self.db.get_name().to_string(),
314 self.serial_number,
315 );
316
317 let send_result = self
320 .get_idb_thread()
321 .send(IndexedDBThreadMsg::Sync(commit_operation));
322 if send_result.is_err() {
323 return false;
324 }
325
326 self.committing.set(true);
327 self.commit_started.set(true);
328 true
329 }
330
331 pub(crate) fn maybe_commit(&self) {
332 let finished = self.finished.get();
338 let abort_initiated = self.abort_initiated.get();
339 let commit_started = self.commit_started.get();
340 let active = self.active.get();
341 let pending_request_count = self.pending_request_count.get();
342 let next_unhandled_request_id = self.next_unhandled_request_id.get();
343 let issued_count = self.issued_count();
344 if finished || abort_initiated || commit_started {
345 return;
346 }
347 if active || pending_request_count != 0 {
348 return;
349 }
350 if next_unhandled_request_id != issued_count {
351 return;
352 }
353 if !self.attempt_commit() {
354 self.initiate_abort(Error::InvalidState(None), CanGc::deprecated_note());
358 self.finalize_abort();
359 }
360 }
361
362 fn force_commit(&self) {
363 if self.finished.get() || self.abort_initiated.get() || self.commit_started.get() {
372 return;
373 }
374 if self.active.get() || self.pending_request_count.get() != 0 {
375 return;
376 }
377 self.attempt_commit();
378 }
379
380 pub fn get_mode(&self) -> IDBTransactionMode {
381 self.mode
382 }
383
384 pub fn get_db_name(&self) -> DOMString {
385 self.db.get_name()
386 }
387
388 pub(crate) fn get_db(&self) -> &IDBDatabase {
389 &self.db
390 }
391
392 pub fn get_serial_number(&self) -> u64 {
393 self.serial_number
394 }
395
396 pub(crate) fn issued_count(&self) -> u64 {
397 self.next_request_id.get()
398 }
399
400 pub(crate) fn allocate_request_id(&self) -> u64 {
403 let id = self.next_request_id.get();
404 self.next_request_id.set(id + 1);
405 id
406 }
407
408 pub(crate) fn mark_request_handled(&self, request_id: u64) {
409 let current = self.next_unhandled_request_id.get();
410 if request_id == current {
411 let mut next = current + 1;
412 {
413 let mut pending = self.handled_pending.borrow_mut();
414 while pending.remove(&next) {
415 next += 1;
416 }
417 }
418 self.next_unhandled_request_id.set(next);
419 } else if request_id > current {
420 self.handled_pending.borrow_mut().insert(request_id);
421 }
422 }
423
424 pub fn add_request(&self, request: &IDBRequest) {
425 self.requests.borrow_mut().push(Dom::from_ref(request));
426 self.pending_request_count
429 .set(self.pending_request_count.get() + 1);
430 }
431
432 pub fn request_finished(&self) {
433 if self.pending_request_count.get() == 0 {
438 return;
439 }
440 let remaining = self.pending_request_count.get() - 1;
441 self.pending_request_count.set(remaining);
442 }
443
444 pub(crate) fn initiate_abort(&self, error: Error, can_gc: CanGc) {
445 if self.finished.get() || self.abort_initiated.get() {
450 return;
451 }
452 if self.mode == IDBTransactionMode::Versionchange {
453 if let Some(names) = self
457 .version_change_old_object_store_names
458 .borrow()
459 .as_ref()
460 .cloned()
461 {
462 self.db.restore_object_store_names(names);
463 }
464 }
465 self.abort_initiated.set(true);
466 if self.error.get().is_none() &&
470 let Ok(exception) = create_dom_exception(&self.global(), error, can_gc)
471 {
472 self.error.set(Some(&exception));
473 }
474 }
475
476 pub(crate) fn request_backend_abort(&self) {
477 if self.abort_requested.get() {
478 return;
479 }
480 self.abort_requested.set(true);
481 let this = Trusted::new(self);
482 let global = self.global();
483 let task_source = global
484 .task_manager()
485 .dom_manipulation_task_source()
486 .to_sendable();
487 let callback = GenericCallback::new(
488 global.time_profiler_chan().clone(),
489 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
490 let this = this.clone();
491 let task_source = task_source.clone();
492 task_source.queue(task!(handle_abort_result: move || {
493 let this = this.root();
494 let _ = message.expect("Could not unwrap message");
495 this.finalize_abort();
496 }));
497 },
498 )
499 .expect("Could not create callback");
500 let operation = SyncOperation::Abort(
501 callback,
502 global.origin().immutable().clone(),
503 self.db.get_name().to_string(),
504 self.serial_number,
505 );
506 let _ = self
507 .get_idb_thread()
508 .send(IndexedDBThreadMsg::Sync(operation));
509 }
510
511 fn notify_backend_transaction_finished(&self) {
512 let global = self.global();
513 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
514 SyncOperation::TransactionFinished {
515 origin: global.origin().immutable().clone(),
516 db_name: self.db.get_name().to_string(),
517 txn: self.serial_number,
518 },
519 ));
520 }
521
522 pub(crate) fn finalize_abort(&self) {
523 if self.finished.get() {
524 return;
525 }
526 self.committing.set(false);
527 self.commit_started.set(false);
528 let this = Trusted::new(self);
529 self.global()
530 .task_manager()
531 .dom_manipulation_task_source()
532 .queue(task!(send_abort_notification: move || {
533 let this = this.root();
534 this.active.set(false);
535 if this.mode == IDBTransactionMode::Versionchange {
536 if let Some(old_version) = this.version_change_old_version.get() {
537 this.db.set_version(old_version);
542 }
543 this.db.clear_upgrade_transaction(&this);
544 }
545 let global = this.global();
546 let event = Event::new(
547 &global,
548 Atom::from("abort"),
549 EventBubbles::DoesNotBubble,
550 EventCancelable::NotCancelable,
551 CanGc::deprecated_note(),
552 );
553 event.fire(this.upcast(), CanGc::deprecated_note());
554 if this.mode == IDBTransactionMode::Versionchange {
555 this.global()
556 .get_indexeddb()
557 .clear_open_request_transaction_for_txn(&this);
558 let origin = this.global().origin().immutable().clone();
559 let db_name = this.db.get_name().to_string();
560 let txn = this.serial_number;
561 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
562 SyncOperation::UpgradeTransactionFinished {
563 origin,
564 db_name,
565 txn,
566 committed: false,
567 },
568 ));
569 }
570 this.finished.set(true);
573 this.version_change_old_version.set(None);
574 this.version_change_old_object_store_names.borrow_mut().take();
575 this.notify_backend_transaction_finished();
576 if this.registered_in_global.get() {
577 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
578 }
579 }));
580 }
581
582 pub(crate) fn finalize_commit(&self) {
583 if self.finished.get() {
584 return;
585 }
586 self.dispatch_complete();
587 }
588
589 fn dispatch_complete(&self) {
590 let global = self.global();
591 let this = Trusted::new(self);
592 global.task_manager().database_access_task_source().queue(
593 task!(send_complete_notification: move || {
594 let this = this.root();
595 this.committing.set(false);
596 this.commit_started.set(false);
597 this.version_change_old_version.set(None);
598 this.version_change_old_object_store_names
599 .borrow_mut()
600 .take();
601 if this.mode == IDBTransactionMode::Versionchange {
602 this.db.clear_upgrade_transaction(&this);
606 }
607 this.finished.set(true);
610 let global = this.global();
611 let event = Event::new(
612 &global,
613 Atom::from("complete"),
614 EventBubbles::DoesNotBubble,
615 EventCancelable::NotCancelable,
616 CanGc::deprecated_note()
617 );
618 event.fire(this.upcast(), CanGc::deprecated_note());
621 if this.mode == IDBTransactionMode::Versionchange {
622 this.global()
626 .get_indexeddb()
627 .clear_open_request_transaction_for_txn(&this);
628 let origin = this.global().origin().immutable().clone();
629 let db_name = this.db.get_name().to_string();
630 let txn = this.serial_number;
631 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
632 SyncOperation::UpgradeTransactionFinished {
633 origin,
634 db_name,
635 txn,
636 committed: true,
637 },
638 ));
639 }
640 this.notify_backend_transaction_finished();
641 if this.registered_in_global.get() {
642 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
643 }
644 }),
645 );
646 }
647
648 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
649 self.global().storage_threads().sender()
650 }
651
652 fn object_store_parameters(
653 &self,
654 object_store_name: &DOMString,
655 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>, Option<i32>)> {
656 let global = self.global();
657 let idb_sender = global.storage_threads().sender();
658 let (sender, receiver) =
659 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
660
661 let origin = global.origin().immutable().clone();
662 let db_name = self.db.get_name().to_string();
663 let object_store_name = object_store_name.to_string();
664
665 let operation = SyncOperation::GetObjectStore(sender, origin, db_name, object_store_name);
666
667 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
668
669 let object_store = receiver.recv().ok()?.ok()?;
672
673 let key_path = object_store.key_path.map(|key_path| match key_path {
676 KeyPath::String(string) => StringOrStringSequence::String(string.into()),
677 KeyPath::Sequence(seq) => {
678 StringOrStringSequence::StringSequence(seq.into_iter().map(Into::into).collect())
679 },
680 });
681 Some((
682 IDBObjectStoreParameters {
683 autoIncrement: object_store.has_key_generator,
684 keyPath: key_path,
685 },
686 object_store.indexes,
687 object_store.key_generator_current_number,
688 ))
689 }
690}
691
692impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
693 fn Db(&self) -> DomRoot<IDBDatabase> {
695 DomRoot::from_ref(&*self.db)
696 }
697
698 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
700 if self.finished.get() || self.abort_initiated.get() {
702 return Err(Error::InvalidState(None));
703 }
704
705 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
709 self.db.object_store_exists(&name)
710 } else {
711 self.object_store_names.Contains(name.clone())
712 };
713 if !in_scope {
714 return Err(Error::NotFound(None));
715 }
716
717 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
721 return Ok(DomRoot::from_ref(store));
722 }
723
724 let parameters = self.object_store_parameters(&name);
725 let store = IDBObjectStore::new(
726 &self.global(),
727 self.db.get_name(),
728 name.clone(),
729 parameters.as_ref().map(|(params, _, _)| params),
730 parameters
731 .as_ref()
732 .and_then(|(_, _, key_generator_current_number)| *key_generator_current_number),
733 can_gc,
734 self,
735 );
736 if let Some(indexes) = parameters.map(|(_, indexes, _)| indexes) {
737 for index in indexes {
738 store.add_index(
739 index.name.into(),
740 &IDBIndexParameters {
741 multiEntry: index.multi_entry,
742 unique: index.unique,
743 },
744 index.key_path.into(),
745 can_gc,
746 );
747 }
748 }
749 self.store_handles
750 .borrow_mut()
751 .insert(name.to_string(), Dom::from_ref(&*store));
752 Ok(store)
753 }
754
755 fn Commit(&self) -> Fallible<()> {
757 if !self.active.get() {
759 return Err(Error::InvalidState(None));
760 }
761
762 self.set_active_flag(false);
764 self.committing.set(true);
765 self.force_commit();
766
767 Ok(())
768 }
769
770 fn Abort(&self) -> Fallible<()> {
772 if self.finished.get() || self.committing.get() {
773 return Err(Error::InvalidState(None));
774 }
775 self.active.set(false);
776 self.initiate_abort(Error::Abort(None), CanGc::deprecated_note());
777 self.request_backend_abort();
778
779 Ok(())
780 }
781
782 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
784 if self.mode == IDBTransactionMode::Versionchange {
785 self.db.object_stores()
786 } else {
787 self.object_store_names.as_rooted()
788 }
789 }
790
791 fn Mode(&self) -> IDBTransactionMode {
793 self.mode
794 }
795
796 fn Durability(&self) -> IDBTransactionDurability {
798 self.durability
799 }
800
801 fn GetError(&self) -> Option<DomRoot<DOMException>> {
803 self.error.get()
804 }
805
806 event_handler!(abort, GetOnabort, SetOnabort);
808
809 event_handler!(complete, GetOncomplete, SetOncomplete);
811
812 event_handler!(error, GetOnerror, SetOnerror);
814}