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 version_change_old_object_store_names: DomRefCell<Option<Vec<DOMString>>>,
64 #[no_trace]
67 cleanup_event_loop: Cell<Option<ScriptEventLoopId>>,
68 registered_in_global: Cell<bool>,
69 pending_request_count: Cell<usize>,
74 next_request_id: Cell<u64>,
75 next_unhandled_request_id: Cell<u64>,
77 handled_pending: DomRefCell<HashSet<u64>>,
78
79 serial_number: u64,
82}
83
84impl IDBTransaction {
85 fn new_inherited(
86 connection: &IDBDatabase,
87 mode: IDBTransactionMode,
88 scope: &DOMStringList,
89 serial_number: u64,
90 ) -> IDBTransaction {
91 IDBTransaction {
92 eventtarget: EventTarget::new_inherited(),
93 object_store_names: Dom::from_ref(scope),
94 mode,
95 db: Dom::from_ref(connection),
96 error: Default::default(),
97
98 store_handles: Default::default(),
99 requests: Default::default(),
100 active: Cell::new(true),
101 finished: Cell::new(false),
102 abort_initiated: Cell::new(false),
103 abort_requested: Cell::new(false),
104 committing: Cell::new(false),
105 version_change_old_version: Cell::new(None),
106 version_change_old_object_store_names: DomRefCell::new(
107 (mode == IDBTransactionMode::Versionchange)
108 .then(|| connection.object_store_names_snapshot()),
109 ),
110 cleanup_event_loop: Cell::new(None),
111 registered_in_global: Cell::new(false),
112 pending_request_count: Cell::new(0),
113 next_request_id: Cell::new(0),
114 next_unhandled_request_id: Cell::new(0),
115 handled_pending: Default::default(),
116 serial_number,
117 }
118 }
119
120 pub fn new(
122 global: &GlobalScope,
123 connection: &IDBDatabase,
124 mode: IDBTransactionMode,
125 scope: &DOMStringList,
126 can_gc: CanGc,
127 ) -> DomRoot<IDBTransaction> {
128 let serial_number =
129 IDBTransaction::create_transaction(global, connection.get_name(), mode, scope);
130 IDBTransaction::new_with_serial(global, connection, mode, scope, serial_number, can_gc)
131 }
132
133 pub(crate) fn new_with_serial(
134 global: &GlobalScope,
135 connection: &IDBDatabase,
136 mode: IDBTransactionMode,
137 scope: &DOMStringList,
138 serial_number: u64,
139 can_gc: CanGc,
140 ) -> DomRoot<IDBTransaction> {
141 reflect_dom_object(
142 Box::new(IDBTransaction::new_inherited(
143 connection,
144 mode,
145 scope,
146 serial_number,
147 )),
148 global,
149 can_gc,
150 )
151 }
152
153 fn create_transaction(
154 global: &GlobalScope,
155 db_name: DOMString,
156 mode: IDBTransactionMode,
157 scope: &DOMStringList,
158 ) -> u64 {
159 let backend_mode = match mode {
160 IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
161 IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
162 IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
163 };
164 let scope: Vec<String> = (0..scope.Length())
165 .filter_map(|i| scope.Item(i))
166 .map(|name| name.to_string())
167 .collect();
168 let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
169
170 global
171 .storage_threads()
172 .send(IndexedDBThreadMsg::Sync(SyncOperation::CreateTransaction {
173 sender,
174 origin: global.origin().immutable().clone(),
175 db_name: db_name.to_string(),
176 mode: backend_mode,
177 scope,
178 }))
179 .expect("Failed to send IndexedDBThreadMsg::Sync");
180
181 receiver.recv().unwrap().expect("CreateTransaction failed")
182 }
183
184 pub fn set_active_flag(&self, status: bool) {
186 self.active.set(status);
191 }
192
193 pub fn is_active(&self) -> bool {
194 self.active.get()
195 }
196
197 pub(crate) fn is_usable(&self) -> bool {
199 !self.finished.get() && !self.abort_initiated.get() && !self.committing.get()
204 }
205
206 pub(crate) fn is_finished(&self) -> bool {
207 self.finished.get()
208 }
209
210 pub(crate) fn set_cleanup_event_loop(&self) {
211 self.cleanup_event_loop.set(ScriptEventLoopId::installed());
214 }
215
216 pub(crate) fn clear_cleanup_event_loop(&self) {
217 self.cleanup_event_loop.set(None);
220 }
221
222 pub(crate) fn cleanup_event_loop_matches_current(&self) -> bool {
223 match ScriptEventLoopId::installed() {
224 Some(current) => self.cleanup_event_loop.get() == Some(current),
225 None => false,
226 }
227 }
228
229 pub(crate) fn set_registered_in_global(&self) {
230 self.registered_in_global.set(true);
231 }
232
233 pub(crate) fn clear_registered_in_global(&self) {
234 self.registered_in_global.set(false);
235 }
236
237 pub(crate) fn set_versionchange_old_version(&self, version: u64) {
238 self.version_change_old_version.set(Some(version));
239 }
240
241 fn attempt_commit(&self) -> bool {
242 let this = Trusted::new(self);
243 let global = self.global();
244 let task_source = global
245 .task_manager()
246 .dom_manipulation_task_source()
247 .to_sendable();
248
249 let callback = GenericCallback::new(
252 global.time_profiler_chan().clone(),
253 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
254 let this = this.clone();
255 let task_source = task_source.clone();
256 task_source.queue(task!(handle_commit_result: move || {
257 let this = this.root();
258 let message = message.expect("Could not unwrap message");
259 match message.result {
260 Ok(()) => {
261 this.finalize_commit();
262 }
263 Err(_err) => {
264 this.initiate_abort(Error::Operation(None), CanGc::note());
266
267 this.finalize_abort();
268 }
269 }
270 }));
273 },
274 )
275 .expect("Could not create callback");
276
277 let commit_operation = SyncOperation::Commit(
278 callback,
279 global.origin().immutable().clone(),
280 self.db.get_name().to_string(),
281 self.serial_number,
282 );
283
284 let send_result = self
287 .get_idb_thread()
288 .send(IndexedDBThreadMsg::Sync(commit_operation));
289 if send_result.is_err() {
290 return false;
291 }
292
293 self.committing.set(true);
294 true
295 }
296
297 pub(crate) fn maybe_commit(&self) {
298 let finished = self.finished.get();
304 let abort_initiated = self.abort_initiated.get();
305 let committing = self.committing.get();
306 let active = self.active.get();
307 let pending_request_count = self.pending_request_count.get();
308 let next_unhandled_request_id = self.next_unhandled_request_id.get();
309 let issued_count = self.issued_count();
310 if finished || abort_initiated || committing {
311 return;
312 }
313 if active || pending_request_count != 0 {
314 return;
315 }
316 if next_unhandled_request_id != issued_count {
317 return;
318 }
319 if !self.attempt_commit() {
320 self.initiate_abort(Error::InvalidState(None), CanGc::note());
324 self.finalize_abort();
325 }
326 }
327
328 fn force_commit(&self) {
329 if self.finished.get() || self.abort_initiated.get() || self.committing.get() {
338 return;
339 }
340 if self.active.get() || self.pending_request_count.get() != 0 {
341 return;
342 }
343 self.attempt_commit();
344 }
345
346 pub fn get_mode(&self) -> IDBTransactionMode {
347 self.mode
348 }
349
350 pub fn get_db_name(&self) -> DOMString {
351 self.db.get_name()
352 }
353
354 pub fn get_serial_number(&self) -> u64 {
355 self.serial_number
356 }
357
358 pub(crate) fn issued_count(&self) -> u64 {
359 self.next_request_id.get()
360 }
361
362 pub(crate) fn allocate_request_id(&self) -> u64 {
365 let id = self.next_request_id.get();
366 self.next_request_id.set(id + 1);
367 id
368 }
369
370 pub(crate) fn mark_request_handled(&self, request_id: u64) {
371 let current = self.next_unhandled_request_id.get();
372 if request_id == current {
373 let mut next = current + 1;
374 {
375 let mut pending = self.handled_pending.borrow_mut();
376 while pending.remove(&next) {
377 next += 1;
378 }
379 }
380 self.next_unhandled_request_id.set(next);
381 } else if request_id > current {
382 self.handled_pending.borrow_mut().insert(request_id);
383 }
384 }
385
386 pub fn add_request(&self, request: &IDBRequest) {
387 self.requests.borrow_mut().push(Dom::from_ref(request));
388 self.pending_request_count
391 .set(self.pending_request_count.get() + 1);
392 }
393
394 pub fn request_finished(&self) {
395 if self.pending_request_count.get() == 0 {
400 return;
401 }
402 let remaining = self.pending_request_count.get() - 1;
403 self.pending_request_count.set(remaining);
404 }
405
406 pub(crate) fn initiate_abort(&self, error: Error, can_gc: CanGc) {
407 if self.finished.get() || self.abort_initiated.get() {
412 return;
413 }
414 if self.mode == IDBTransactionMode::Versionchange {
415 if let Some(names) = self
419 .version_change_old_object_store_names
420 .borrow()
421 .as_ref()
422 .cloned()
423 {
424 self.db.restore_object_store_names(names);
425 }
426 }
427 self.abort_initiated.set(true);
428 if self.error.get().is_none() {
432 if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
433 self.error.set(Some(&exception));
434 }
435 }
436 }
437
438 pub(crate) fn request_backend_abort(&self) {
439 if self.abort_requested.get() {
440 return;
441 }
442 self.abort_requested.set(true);
443 let this = Trusted::new(self);
444 let global = self.global();
445 let task_source = global
446 .task_manager()
447 .dom_manipulation_task_source()
448 .to_sendable();
449 let callback = GenericCallback::new(
450 global.time_profiler_chan().clone(),
451 move |message: Result<TxnCompleteMsg, ipc_channel::IpcError>| {
452 let this = this.clone();
453 let task_source = task_source.clone();
454 task_source.queue(task!(handle_abort_result: move || {
455 let this = this.root();
456 let _ = message.expect("Could not unwrap message");
457 this.finalize_abort();
458 }));
459 },
460 )
461 .expect("Could not create callback");
462 let operation = SyncOperation::Abort(
463 callback,
464 global.origin().immutable().clone(),
465 self.db.get_name().to_string(),
466 self.serial_number,
467 );
468 let _ = self
469 .get_idb_thread()
470 .send(IndexedDBThreadMsg::Sync(operation));
471 }
472
473 fn notify_backend_transaction_finished(&self) {
474 let global = self.global();
475 let _ = self.get_idb_thread().send(IndexedDBThreadMsg::Sync(
476 SyncOperation::TransactionFinished {
477 origin: global.origin().immutable().clone(),
478 db_name: self.db.get_name().to_string(),
479 txn: self.serial_number,
480 },
481 ));
482 }
483
484 pub(crate) fn finalize_abort(&self) {
485 if self.finished.get() {
486 return;
487 }
488 self.committing.set(false);
489 let this = Trusted::new(self);
490 self.global()
491 .task_manager()
492 .dom_manipulation_task_source()
493 .queue(task!(send_abort_notification: move || {
494 let this = this.root();
495 this.active.set(false);
496 if this.mode == IDBTransactionMode::Versionchange {
497 if let Some(old_version) = this.version_change_old_version.get() {
498 this.db.set_version(old_version);
503 }
504 this.db.clear_upgrade_transaction(&this);
505 }
506 let global = this.global();
507 let event = Event::new(
508 &global,
509 Atom::from("abort"),
510 EventBubbles::DoesNotBubble,
511 EventCancelable::NotCancelable,
512 CanGc::note(),
513 );
514 event.fire(this.upcast(), CanGc::note());
515 if this.mode == IDBTransactionMode::Versionchange {
516 this.global()
517 .get_indexeddb()
518 .clear_open_request_transaction_for_txn(&this);
519 let origin = this.global().origin().immutable().clone();
520 let db_name = this.db.get_name().to_string();
521 let txn = this.serial_number;
522 let _ = this.get_idb_thread().send(IndexedDBThreadMsg::Sync(
523 SyncOperation::UpgradeTransactionFinished {
524 origin,
525 db_name,
526 txn,
527 committed: false,
528 },
529 ));
530 }
531 this.finished.set(true);
534 this.version_change_old_version.set(None);
535 this.version_change_old_object_store_names.borrow_mut().take();
536 this.notify_backend_transaction_finished();
537 if this.registered_in_global.get() {
538 this.global().get_indexeddb().unregister_indexeddb_transaction(&this);
539 }
540 }));
541 }
542
543 pub(crate) fn finalize_commit(&self) {
544 if self.finished.get() {
545 return;
546 }
547 self.committing.set(false);
548 self.version_change_old_version.set(None);
549 self.version_change_old_object_store_names
550 .borrow_mut()
551 .take();
552 self.finished.set(true);
555 if self.mode == IDBTransactionMode::Versionchange {
556 self.db.clear_upgrade_transaction(self);
557 }
558 self.dispatch_complete();
561 self.notify_backend_transaction_finished();
562 if self.registered_in_global.get() {
563 self.global()
564 .get_indexeddb()
565 .unregister_indexeddb_transaction(self);
566 }
567 }
568
569 fn dispatch_complete(&self) {
570 let global = self.global();
571 let this = Trusted::new(self);
572 global.task_manager().database_access_task_source().queue(
573 task!(send_complete_notification: move || {
574 let this = this.root();
575 let global = this.global();
576 let event = Event::new(
577 &global,
578 Atom::from("complete"),
579 EventBubbles::DoesNotBubble,
580 EventCancelable::NotCancelable,
581 CanGc::note()
582 );
583 event.fire(this.upcast(), CanGc::note());
584 if this.mode == IDBTransactionMode::Versionchange {
585 this.global()
586 .get_indexeddb()
587 .clear_open_request_transaction_for_txn(&this);
588 let origin = this.global().origin().immutable().clone();
589 let db_name = this.db.get_name().to_string();
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: true,
597 },
598 ));
599 }
600 }),
601 );
602 }
603
604 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
605 self.global().storage_threads().sender()
606 }
607
608 fn object_store_parameters(
609 &self,
610 object_store_name: &DOMString,
611 ) -> Option<(IDBObjectStoreParameters, Vec<IndexedDBIndex>, Option<i32>)> {
612 let global = self.global();
613 let idb_sender = global.storage_threads().sender();
614 let (sender, receiver) =
615 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
616
617 let origin = global.origin().immutable().clone();
618 let db_name = self.db.get_name().to_string();
619 let object_store_name = object_store_name.to_string();
620
621 let operation = SyncOperation::GetObjectStore(sender, origin, db_name, object_store_name);
622
623 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
624
625 let object_store = receiver.recv().ok()?.ok()?;
628
629 let key_path = object_store.key_path.map(|key_path| match key_path {
632 KeyPath::String(string) => StringOrStringSequence::String(string.into()),
633 KeyPath::Sequence(seq) => {
634 StringOrStringSequence::StringSequence(seq.into_iter().map(Into::into).collect())
635 },
636 });
637 Some((
638 IDBObjectStoreParameters {
639 autoIncrement: object_store.has_key_generator,
640 keyPath: key_path,
641 },
642 object_store.indexes,
643 object_store.key_generator_current_number,
644 ))
645 }
646}
647
648impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
649 fn Db(&self) -> DomRoot<IDBDatabase> {
651 DomRoot::from_ref(&*self.db)
652 }
653
654 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
656 if self.finished.get() || self.abort_initiated.get() {
658 return Err(Error::InvalidState(None));
659 }
660
661 let in_scope = if self.mode == IDBTransactionMode::Versionchange {
665 self.db.object_store_exists(&name)
666 } else {
667 self.object_store_names.Contains(name.clone())
668 };
669 if !in_scope {
670 return Err(Error::NotFound(None));
671 }
672
673 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
677 return Ok(DomRoot::from_ref(store));
678 }
679
680 let parameters = self.object_store_parameters(&name);
681 let store = IDBObjectStore::new(
682 &self.global(),
683 self.db.get_name(),
684 name.clone(),
685 parameters.as_ref().map(|(params, _, _)| params),
686 parameters
687 .as_ref()
688 .and_then(|(_, _, key_generator_current_number)| *key_generator_current_number),
689 can_gc,
690 self,
691 );
692 if let Some(indexes) = parameters.map(|(_, indexes, _)| indexes) {
693 for index in indexes {
694 store.add_index(
695 index.name.into(),
696 &IDBIndexParameters {
697 multiEntry: index.multi_entry,
698 unique: index.unique,
699 },
700 index.key_path.into(),
701 can_gc,
702 );
703 }
704 }
705 self.store_handles
706 .borrow_mut()
707 .insert(name.to_string(), Dom::from_ref(&*store));
708 Ok(store)
709 }
710
711 fn Commit(&self) -> Fallible<()> {
713 if self.finished.get() {
715 return Err(Error::InvalidState(None));
716 }
717
718 self.set_active_flag(false);
726 self.force_commit();
727
728 Ok(())
729 }
730
731 fn Abort(&self) -> Fallible<()> {
733 if self.finished.get() || self.committing.get() {
734 return Err(Error::InvalidState(None));
735 }
736 self.active.set(false);
737 self.initiate_abort(Error::Abort(None), CanGc::note());
738 self.request_backend_abort();
739
740 Ok(())
741 }
742
743 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
745 if self.mode == IDBTransactionMode::Versionchange {
746 self.db.object_stores()
747 } else {
748 self.object_store_names.as_rooted()
749 }
750 }
751
752 fn Mode(&self) -> IDBTransactionMode {
754 self.mode
755 }
756
757 fn GetError(&self) -> Option<DomRoot<DOMException>> {
765 self.error.get()
766 }
767
768 event_handler!(abort, GetOnabort, SetOnabort);
770
771 event_handler!(complete, GetOncomplete, SetOncomplete);
773
774 event_handler!(error, GetOnerror, SetOnerror);
776}