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