1use std::cell::Cell;
6use std::collections::{HashMap, HashSet};
7
8use base::generic_channel::{GenericSend, GenericSender};
9use base::id::ScriptEventLoopId;
10use dom_struct::dom_struct;
11use profile_traits::generic_callback::GenericCallback;
12use profile_traits::generic_channel::channel;
13use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
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::IDBObjectStoreParameters;
22use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBIndexParameters;
23use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
24 IDBTransactionMethods, IDBTransactionMode,
25};
26use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
30use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::domexception::DOMException;
33use crate::dom::domstringlist::DOMStringList;
34use crate::dom::event::{Event, EventBubbles, EventCancelable};
35use crate::dom::eventtarget::EventTarget;
36use crate::dom::globalscope::GlobalScope;
37use crate::dom::indexeddb::idbdatabase::IDBDatabase;
38use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
39use crate::dom::indexeddb::idbrequest::IDBRequest;
40use crate::script_runtime::CanGc;
41
42#[dom_struct]
43pub struct IDBTransaction {
44 eventtarget: EventTarget,
45 object_store_names: Dom<DOMStringList>,
46 mode: IDBTransactionMode,
47 db: Dom<IDBDatabase>,
48 error: MutNullableDom<DOMException>,
49
50 store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
51 requests: DomRefCell<Vec<Dom<IDBRequest>>>,
53 active: Cell<bool>,
55 finished: Cell<bool>,
57 abort_initiated: Cell<bool>,
58 abort_requested: Cell<bool>,
59 committing: Cell<bool>,
60 version_change_old_version: Cell<Option<u64>>,
61 #[no_trace]
64 cleanup_event_loop: Cell<Option<ScriptEventLoopId>>,
65 registered_in_global: Cell<bool>,
66 pending_request_count: Cell<usize>,
71 next_request_id: Cell<u64>,
72 next_unhandled_request_id: Cell<u64>,
74 handled_pending: DomRefCell<HashSet<u64>>,
75
76 serial_number: u64,
79}
80
81impl IDBTransaction {
82 fn new_inherited(
83 connection: &IDBDatabase,
84 mode: IDBTransactionMode,
85 scope: &DOMStringList,
86 serial_number: u64,
87 ) -> IDBTransaction {
88 IDBTransaction {
89 eventtarget: EventTarget::new_inherited(),
90 object_store_names: Dom::from_ref(scope),
91 mode,
92 db: Dom::from_ref(connection),
93 error: Default::default(),
94
95 store_handles: Default::default(),
96 requests: Default::default(),
97 active: Cell::new(true),
98 finished: Cell::new(false),
99 abort_initiated: Cell::new(false),
100 abort_requested: Cell::new(false),
101 committing: Cell::new(false),
102 version_change_old_version: Cell::new(None),
103 cleanup_event_loop: Cell::new(None),
104 registered_in_global: Cell::new(false),
105 pending_request_count: Cell::new(0),
106 next_request_id: Cell::new(0),
107 next_unhandled_request_id: Cell::new(0),
108 handled_pending: Default::default(),
109 serial_number,
110 }
111 }
112
113 pub fn new(
115 global: &GlobalScope,
116 connection: &IDBDatabase,
117 mode: IDBTransactionMode,
118 scope: &DOMStringList,
119 can_gc: CanGc,
120 ) -> DomRoot<IDBTransaction> {
121 let serial_number =
122 IDBTransaction::create_transaction(global, connection.get_name(), mode, scope);
123 IDBTransaction::new_with_serial(global, connection, mode, scope, serial_number, can_gc)
124 }
125
126 pub(crate) fn new_with_serial(
127 global: &GlobalScope,
128 connection: &IDBDatabase,
129 mode: IDBTransactionMode,
130 scope: &DOMStringList,
131 serial_number: u64,
132 can_gc: CanGc,
133 ) -> DomRoot<IDBTransaction> {
134 reflect_dom_object(
135 Box::new(IDBTransaction::new_inherited(
136 connection,
137 mode,
138 scope,
139 serial_number,
140 )),
141 global,
142 can_gc,
143 )
144 }
145
146 fn create_transaction(
147 global: &GlobalScope,
148 db_name: DOMString,
149 mode: IDBTransactionMode,
150 scope: &DOMStringList,
151 ) -> u64 {
152 let backend_mode = match mode {
153 IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
154 IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
155 IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
156 };
157 let scope: Vec<String> = (0..scope.Length())
158 .filter_map(|i| scope.Item(i))
159 .map(|name| name.to_string())
160 .collect();
161 let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
162
163 global
164 .storage_threads()
165 .send(IndexedDBThreadMsg::Sync(SyncOperation::CreateTransaction {
166 sender,
167 origin: global.origin().immutable().clone(),
168 db_name: db_name.to_string(),
169 mode: backend_mode,
170 scope,
171 }))
172 .expect("Failed to send IndexedDBThreadMsg::Sync");
173
174 receiver.recv().unwrap().expect("CreateTransaction failed")
175 }
176
177 pub fn set_active_flag(&self, status: bool) {
179 self.active.set(status);
184 }
185
186 pub fn is_active(&self) -> bool {
187 self.active.get()
188 }
189
190 pub(crate) fn is_usable(&self) -> bool {
192 !self.finished.get() && !self.abort_initiated.get() && !self.committing.get()
197 }
198
199 pub(crate) fn is_finished(&self) -> bool {
200 self.finished.get()
201 }
202
203 pub(crate) fn set_cleanup_event_loop(&self) {
204 self.cleanup_event_loop.set(ScriptEventLoopId::installed());
207 }
208
209 pub(crate) fn clear_cleanup_event_loop(&self) {
210 self.cleanup_event_loop.set(None);
213 }
214
215 pub(crate) fn cleanup_event_loop_matches_current(&self) -> bool {
216 match ScriptEventLoopId::installed() {
217 Some(current) => self.cleanup_event_loop.get() == Some(current),
218 None => false,
219 }
220 }
221
222 pub(crate) fn set_registered_in_global(&self) {
223 self.registered_in_global.set(true);
224 }
225
226 pub(crate) fn clear_registered_in_global(&self) {
227 self.registered_in_global.set(false);
228 }
229
230 pub(crate) fn set_versionchange_old_version(&self, version: u64) {
231 self.version_change_old_version.set(Some(version));
232 }
233
234 fn attempt_commit(&self) -> bool {
235 let this = Trusted::new(self);
236 let global = self.global();
237 let task_source = global
238 .task_manager()
239 .dom_manipulation_task_source()
240 .to_sendable();
241
242 let callback = GenericCallback::new(
245 global.time_profiler_chan().clone(),
246 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
247 let this = this.clone();
248 let task_source = task_source.clone();
249 task_source.queue(task!(handle_commit_result: move || {
250 let this = this.root();
251 let message = message.expect("Could not unwrap message");
252 match message.result {
253 Ok(()) => {
254 this.finalize_commit();
255 }
256 Err(_err) => {
257 this.initiate_abort(Error::Operation(None), CanGc::note());
259
260 this.finalize_abort();
261 }
262 }
263 }));
266 },
267 )
268 .expect("Could not create callback");
269
270 let commit_operation = SyncOperation::Commit(
271 callback,
272 global.origin().immutable().clone(),
273 self.db.get_name().to_string(),
274 self.serial_number,
275 );
276
277 let send_result = self
280 .get_idb_thread()
281 .send(IndexedDBThreadMsg::Sync(commit_operation));
282 if send_result.is_err() {
283 return false;
284 }
285
286 self.committing.set(true);
287 true
288 }
289
290 pub(crate) fn maybe_commit(&self) {
291 let finished = self.finished.get();
297 let abort_initiated = self.abort_initiated.get();
298 let committing = self.committing.get();
299 let active = self.active.get();
300 let pending_request_count = self.pending_request_count.get();
301 let next_unhandled_request_id = self.next_unhandled_request_id.get();
302 let issued_count = self.issued_count();
303 if finished || abort_initiated || committing {
304 return;
305 }
306 if active || pending_request_count != 0 {
307 return;
308 }
309 if next_unhandled_request_id != issued_count {
310 return;
311 }
312 if !self.attempt_commit() {
313 self.initiate_abort(Error::InvalidState(None), CanGc::note());
317 self.finalize_abort();
318 }
319 }
320
321 fn force_commit(&self) {
322 if self.finished.get() || self.abort_initiated.get() || self.committing.get() {
331 return;
332 }
333 if self.active.get() || self.pending_request_count.get() != 0 {
334 return;
335 }
336 self.attempt_commit();
337 }
338
339 pub fn get_mode(&self) -> IDBTransactionMode {
340 self.mode
341 }
342
343 pub fn get_db_name(&self) -> DOMString {
344 self.db.get_name()
345 }
346
347 pub fn get_serial_number(&self) -> u64 {
348 self.serial_number
349 }
350
351 pub(crate) fn issued_count(&self) -> u64 {
352 self.next_request_id.get()
353 }
354
355 pub(crate) fn allocate_request_id(&self) -> u64 {
358 let id = self.next_request_id.get();
359 self.next_request_id.set(id + 1);
360 id
361 }
362
363 pub(crate) fn mark_request_handled(&self, request_id: u64) {
364 let current = self.next_unhandled_request_id.get();
365 if request_id == current {
366 let mut next = current + 1;
367 {
368 let mut pending = self.handled_pending.borrow_mut();
369 while pending.remove(&next) {
370 next += 1;
371 }
372 }
373 self.next_unhandled_request_id.set(next);
374 } else if request_id > current {
375 self.handled_pending.borrow_mut().insert(request_id);
376 }
377 }
378
379 pub fn add_request(&self, request: &IDBRequest) {
380 self.requests.borrow_mut().push(Dom::from_ref(request));
381 self.pending_request_count
384 .set(self.pending_request_count.get() + 1);
385 }
386
387 pub fn request_finished(&self) {
388 if self.pending_request_count.get() == 0 {
393 return;
394 }
395 let remaining = self.pending_request_count.get() - 1;
396 self.pending_request_count.set(remaining);
397 }
398
399 pub(crate) fn initiate_abort(&self, error: Error, can_gc: CanGc) {
400 if self.finished.get() || self.abort_initiated.get() {
405 return;
406 }
407 self.abort_initiated.set(true);
408 if self.error.get().is_none() {
412 if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
413 self.error.set(Some(&exception));
414 }
415 }
416 }
417
418 pub(crate) fn request_backend_abort(&self) {
419 if self.abort_requested.get() {
420 return;
421 }
422 self.abort_requested.set(true);
423 let this = Trusted::new(self);
424 let global = self.global();
425 let task_source = global
426 .task_manager()
427 .dom_manipulation_task_source()
428 .to_sendable();
429 let callback = GenericCallback::new(
430 global.time_profiler_chan().clone(),
431 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
432 let this = this.clone();
433 let task_source = task_source.clone();
434 task_source.queue(task!(handle_abort_result: move || {
435 let this = this.root();
436 let _ = message.expect("Could not unwrap message");
437 this.finalize_abort();
438 }));
439 },
440 )
441 .expect("Could not create callback");
442 let operation = SyncOperation::Abort(
443 callback,
444 global.origin().immutable().clone(),
445 self.db.get_name().to_string(),
446 self.serial_number,
447 );
448 let _ = self
449 .get_idb_thread()
450 .send(IndexedDBThreadMsg::Sync(operation));
451 }
452
453 fn notify_backend_transaction_finished(&self) {
454 let global = self.global();
455 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
456 SyncOperation::TransactionFinished {
457 origin: global.origin().immutable().clone(),
458 db_name: self.db.get_name().to_string(),
459 txn: self.serial_number,
460 },
461 ));
462 }
463
464 pub(crate) fn finalize_abort(&self) {
465 if self.finished.get() {
466 return;
467 }
468 self.committing.set(false);
469 let this = Trusted::new(self);
470 self.global()
471 .task_manager()
472 .dom_manipulation_task_source()
473 .queue(task!(send_abort_notification: move || {
474 let this = this.root();
475 this.active.set(false);
476 if this.mode == IDBTransactionMode::Versionchange {
477 if let Some(old_version) = this.version_change_old_version.get() {
478 this.db.set_version(old_version);
483 }
484 this.db.clear_upgrade_transaction(&this);
485 }
486 let global = this.global();
487 let event = Event::new(
488 &global,
489 Atom::from("abort"),
490 EventBubbles::DoesNotBubble,
491 EventCancelable::NotCancelable,
492 CanGc::note(),
493 );
494 event.fire(this.upcast(), CanGc::note());
495 if this.mode == IDBTransactionMode::Versionchange {
496 this.global()
497 .get_indexeddb()
498 .clear_open_request_transaction_for_txn(&this);
499 let origin = this.global().origin().immutable().clone();
500 let db_name = this.db.get_name().to_string();
501 let txn = this.serial_number;
502 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
503 SyncOperation::UpgradeTransactionFinished {
504 origin,
505 db_name,
506 txn,
507 committed: false,
508 },
509 ));
510 }
511 this.finished.set(true);
514 this.version_change_old_version.set(None);
515 this.notify_backend_transaction_finished();
516 if this.registered_in_global.get() {
517 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
518 }
519 }));
520 }
521
522 pub(crate) fn finalize_commit(&self) {
523 if self.finished.get() {
524 return;
525 }
526 self.committing.set(false);
527 self.version_change_old_version.set(None);
528 self.finished.set(true);
531 if self.mode == IDBTransactionMode::Versionchange {
532 self.db.clear_upgrade_transaction(self);
533 }
534 self.notify_backend_transaction_finished();
535 self.dispatch_complete();
536 if self.registered_in_global.get() {
537 self.global()
538 .get_indexeddb()
539 .unregister_indexeddb_transaction(self);
540 }
541 }
542
543 fn dispatch_complete(&self) {
544 let global = self.global();
545 let this = Trusted::new(self);
546 global.task_manager().database_access_task_source().queue(
547 task!(send_complete_notification: move || {
548 let this = this.root();
549 let global = this.global();
550 let event = Event::new(
551 &global,
552 Atom::from("complete"),
553 EventBubbles::DoesNotBubble,
554 EventCancelable::NotCancelable,
555 CanGc::note()
556 );
557 event.fire(this.upcast(), CanGc::note());
558 if this.mode == IDBTransactionMode::Versionchange {
559 this.global()
560 .get_indexeddb()
561 .clear_open_request_transaction_for_txn(&this);
562 let origin = this.global().origin().immutable().clone();
563 let db_name = this.db.get_name().to_string();
564 let txn = this.serial_number;
565 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
566 SyncOperation::UpgradeTransactionFinished {
567 origin,
568 db_name,
569 txn,
570 committed: true,
571 },
572 ));
573 }
574 }),
575 );
576 }
577
578 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
579 self.global().storage_threads().sender()
580 }
581
582 fn object_store_parameters(
583 &self,
584 object_store_name: &DOMString,
585 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>)> {
586 let global = self.global();
587 let idb_sender = global.storage_threads().sender();
588 let (sender, receiver) =
589 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
590
591 let origin = global.origin().immutable().clone();
592 let db_name = self.db.get_name().to_string();
593 let object_store_name = object_store_name.to_string();
594
595 let operation = SyncOperation::GetObjectStore(
596 sender,
597 origin.clone(),
598 db_name.clone(),
599 object_store_name.clone(),
600 );
601
602 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
603
604 let object_store = receiver.recv().ok()?.ok()?;
607
608 let key_path = object_store.key_path.map(|key_path| match key_path {
611 KeyPath::String(s) => StringOrStringSequence::String(DOMString::from_string(s)),
612 KeyPath::Sequence(seq) => StringOrStringSequence::StringSequence(
613 seq.into_iter().map(DOMString::from_string).collect(),
614 ),
615 });
616 Some((
617 IDBObjectStoreParameters {
618 autoIncrement: object_store.has_key_generator,
619 keyPath: key_path,
620 },
621 object_store.indexes,
622 ))
623 }
624}
625
626impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
627 fn Db(&self) -> DomRoot<IDBDatabase> {
629 DomRoot::from_ref(&*self.db)
630 }
631
632 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
634 if self.finished.get() || self.abort_initiated.get() {
636 return Err(Error::InvalidState(None));
637 }
638
639 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
643 self.db.object_store_exists(&name)
644 } else {
645 self.object_store_names.Contains(name.clone())
646 };
647 if !in_scope {
648 return Err(Error::NotFound(None));
649 }
650
651 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
655 return Ok(DomRoot::from_ref(store));
656 }
657
658 let parameters = self.object_store_parameters(&name);
659 let store = IDBObjectStore::new(
660 &self.global(),
661 self.db.get_name(),
662 name.clone(),
663 parameters.as_ref().map(|(params, _)| params),
664 can_gc,
665 self,
666 );
667 if let Some(indexes) = parameters.map(|(_, indexes)| indexes) {
668 for index in indexes {
669 store.add_index(
670 DOMString::from_string(index.name),
671 &IDBIndexParameters {
672 multiEntry: index.multi_entry,
673 unique: index.unique,
674 },
675 index.key_path.into(),
676 can_gc,
677 );
678 }
679 }
680 self.store_handles
681 .borrow_mut()
682 .insert(name.to_string(), Dom::from_ref(&*store));
683 Ok(store)
684 }
685
686 fn Commit(&self) -> Fallible<()> {
688 if self.finished.get() {
690 return Err(Error::InvalidState(None));
691 }
692
693 self.set_active_flag(false);
701 self.force_commit();
702
703 Ok(())
704 }
705
706 fn Abort(&self) -> Fallible<()> {
708 if self.finished.get() || self.committing.get() {
709 return Err(Error::InvalidState(None));
710 }
711 self.active.set(false);
712 self.initiate_abort(Error::Abort(None), CanGc::note());
713 self.request_backend_abort();
714
715 Ok(())
716 }
717
718 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
720 if self.mode == IDBTransactionMode::Versionchange {
721 self.db.object_stores()
722 } else {
723 self.object_store_names.as_rooted()
724 }
725 }
726
727 fn Mode(&self) -> IDBTransactionMode {
729 self.mode
730 }
731
732 fn GetError(&self) -> Option<DomRoot<DOMException>> {
740 self.error.get()
741 }
742
743 event_handler!(abort, GetOnabort, SetOnabort);
745
746 event_handler!(complete, GetOncomplete, SetOncomplete);
748
749 event_handler!(error, GetOnerror, SetOnerror);
751}