1use std::cell::Cell;
6use std::collections::HashMap;
7
8use base::generic_channel::{GenericSend, GenericSender};
9use dom_struct::dom_struct;
10use profile_traits::generic_channel::channel;
11use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
12use storage_traits::indexeddb::{IndexedDBThreadMsg, KeyPath, SyncOperation};
13use stylo_atoms::Atom;
14use uuid::Uuid;
15
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods;
18use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
19use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
20 IDBTransactionMethods, IDBTransactionMode,
21};
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
26use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
27use crate::dom::bindings::str::DOMString;
28use crate::dom::domexception::DOMException;
29use crate::dom::domstringlist::DOMStringList;
30use crate::dom::event::{Event, EventBubbles, EventCancelable};
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::indexeddb::idbdatabase::IDBDatabase;
34use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
35use crate::dom::indexeddb::idbrequest::IDBRequest;
36use crate::script_runtime::CanGc;
37
38#[dom_struct]
39pub struct IDBTransaction {
40 eventtarget: EventTarget,
41 object_store_names: Dom<DOMStringList>,
42 mode: IDBTransactionMode,
43 db: Dom<IDBDatabase>,
44 error: MutNullableDom<DOMException>,
45
46 store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
47 requests: DomRefCell<Vec<Dom<IDBRequest>>>,
49 active: Cell<bool>,
51 finished: Cell<bool>,
53 pending_request_count: Cell<usize>,
58
59 serial_number: u64,
62
63 #[no_trace]
65 open_request_id: Option<Uuid>,
66}
67
68impl IDBTransaction {
69 fn new_inherited(
70 connection: &IDBDatabase,
71 mode: IDBTransactionMode,
72 scope: &DOMStringList,
73 serial_number: u64,
74 open_request_id: Option<Uuid>,
75 ) -> IDBTransaction {
76 IDBTransaction {
77 eventtarget: EventTarget::new_inherited(),
78 object_store_names: Dom::from_ref(scope),
79 mode,
80 db: Dom::from_ref(connection),
81 error: Default::default(),
82
83 store_handles: Default::default(),
84 requests: Default::default(),
85 active: Cell::new(true),
86 finished: Cell::new(false),
87 pending_request_count: Cell::new(0),
88 serial_number,
89 open_request_id,
90 }
91 }
92
93 pub fn new(
96 global: &GlobalScope,
97 connection: &IDBDatabase,
98 mode: IDBTransactionMode,
99 scope: &DOMStringList,
100 can_gc: CanGc,
101 ) -> DomRoot<IDBTransaction> {
102 let serial_number = IDBTransaction::register_new(global, connection.get_name());
103 reflect_dom_object(
104 Box::new(IDBTransaction::new_inherited(
105 connection,
106 mode,
107 scope,
108 serial_number,
109 None,
110 )),
111 global,
112 can_gc,
113 )
114 }
115
116 pub(crate) fn new_with_id(
120 global: &GlobalScope,
121 connection: &IDBDatabase,
122 mode: IDBTransactionMode,
123 scope: &DOMStringList,
124 transaction_id: u64,
125 open_request_id: Option<Uuid>,
126 can_gc: CanGc,
127 ) -> DomRoot<IDBTransaction> {
128 reflect_dom_object(
129 Box::new(IDBTransaction::new_inherited(
130 connection,
131 mode,
132 scope,
133 transaction_id,
134 open_request_id,
135 )),
136 global,
137 can_gc,
138 )
139 }
140
141 fn register_new(global: &GlobalScope, db_name: DOMString) -> u64 {
147 let (sender, receiver) = channel(global.time_profiler_chan().clone()).unwrap();
148
149 global
150 .storage_threads()
151 .send(IndexedDBThreadMsg::Sync(SyncOperation::RegisterNewTxn(
152 sender,
153 global.origin().immutable().clone(),
154 db_name.to_string(),
155 )))
156 .unwrap();
157
158 receiver.recv().unwrap()
159 }
160
161 pub fn set_active_flag(&self, status: bool) {
162 self.active.set(status);
163 if !status && self.pending_request_count.get() == 0 && !self.finished.get() {
166 self.finished.set(true);
167 self.dispatch_complete();
168 }
169 }
170
171 pub fn is_active(&self) -> bool {
172 self.active.get()
173 }
174
175 pub fn get_mode(&self) -> IDBTransactionMode {
176 self.mode
177 }
178
179 pub fn get_db_name(&self) -> DOMString {
180 self.db.get_name()
181 }
182
183 pub fn get_serial_number(&self) -> u64 {
184 self.serial_number
185 }
186
187 pub fn add_request(&self, request: &IDBRequest) {
188 self.requests.borrow_mut().push(Dom::from_ref(request));
189 self.pending_request_count
192 .set(self.pending_request_count.get() + 1);
193 }
194
195 pub fn request_finished(&self) {
200 if self.pending_request_count.get() == 0 {
201 return;
202 }
203 let remaining = self.pending_request_count.get() - 1;
204 self.pending_request_count.set(remaining);
205
206 if remaining == 0 && !self.active.get() && !self.finished.get() {
207 self.finished.set(true);
208 self.dispatch_complete();
209 }
210 }
211
212 fn dispatch_complete(&self) {
213 let global = self.global();
214 let this = Trusted::new(self);
215 global.task_manager().database_access_task_source().queue(
216 task!(send_complete_notification: move || {
217 let this = this.root();
218 let global = this.global();
219 let event = Event::new(
220 &global,
221 Atom::from("complete"),
222 EventBubbles::DoesNotBubble,
223 EventCancelable::NotCancelable,
224 CanGc::note()
225 );
226 event.fire(this.upcast(), CanGc::note());
227 }),
228 );
229 }
230
231 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
232 self.global().storage_threads().sender()
233 }
234
235 fn object_store_parameters(
236 &self,
237 object_store_name: &DOMString,
238 ) -> Option<IDBObjectStoreParameters> {
239 let global = self.global();
240 let idb_sender = global.storage_threads().sender();
241 let (sender, receiver) =
242 channel(global.time_profiler_chan().clone()).expect("failed to create channel");
243
244 let origin = global.origin().immutable().clone();
245 let db_name = self.db.get_name().to_string();
246 let object_store_name = object_store_name.to_string();
247
248 let operation = SyncOperation::HasKeyGenerator(
249 sender,
250 origin.clone(),
251 db_name.clone(),
252 object_store_name.clone(),
253 );
254
255 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
256
257 let auto_increment = receiver.recv().ok()?.ok()?;
260
261 let (sender, receiver) = channel(self.global().time_profiler_chan().clone())?;
262 let operation = SyncOperation::KeyPath(sender, origin, db_name, object_store_name);
263
264 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
265
266 let key_path = receiver.recv().unwrap().ok()?;
269 let key_path = key_path.map(|key_path| match key_path {
270 KeyPath::String(s) => StringOrStringSequence::String(DOMString::from_string(s)),
271 KeyPath::Sequence(seq) => StringOrStringSequence::StringSequence(
272 seq.into_iter().map(DOMString::from_string).collect(),
273 ),
274 });
275 Some(IDBObjectStoreParameters {
276 autoIncrement: auto_increment,
277 keyPath: key_path,
278 })
279 }
280}
281
282impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
283 fn Db(&self) -> DomRoot<IDBDatabase> {
285 DomRoot::from_ref(&*self.db)
286 }
287
288 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
290 if self.finished.get() {
292 return Err(Error::InvalidState(None));
293 }
294
295 if !self.object_store_names.Contains(name.clone()) {
297 return Err(Error::NotFound(None));
298 }
299
300 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
304 return Ok(DomRoot::from_ref(store));
305 }
306
307 let parameters = self.object_store_parameters(&name);
308 let store = IDBObjectStore::new(
309 &self.global(),
310 self.db.get_name(),
311 name.clone(),
312 parameters.as_ref(),
313 can_gc,
314 self,
315 );
316 self.store_handles
317 .borrow_mut()
318 .insert(name.to_string(), Dom::from_ref(&*store));
319 Ok(store)
320 }
321
322 fn Commit(&self) -> Fallible<()> {
324 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
326 let start_operation = SyncOperation::Commit(
327 sender,
328 self.global().origin().immutable().clone(),
329 self.db.get_name().to_string(),
330 self.serial_number,
331 );
332
333 self.get_idb_thread()
334 .send(IndexedDBThreadMsg::Sync(start_operation))
335 .unwrap();
336
337 let result = receiver.recv().unwrap();
338
339 if let Err(_result) = result {
341 return Err(Error::QuotaExceeded {
343 quota: None,
344 requested: None,
345 });
346 }
347
348 self.dispatch_complete();
353
354 Ok(())
355 }
356
357 fn Abort(&self) -> Fallible<()> {
359 if self.finished.get() {
363 return Err(Error::InvalidState(None));
364 }
365
366 self.active.set(false);
367
368 if self.mode == IDBTransactionMode::Versionchange {
369 let name = self.db.get_name().to_string();
370 let global = self.global();
371 let origin = global.origin().immutable().clone();
372 let Some(id) = self.open_request_id else {
373 debug_assert!(
374 false,
375 "A Versionchange transaction should have an open request id."
376 );
377 return Err(Error::InvalidState(None));
378 };
379 if global
380 .storage_threads()
381 .send(IndexedDBThreadMsg::Sync(
382 SyncOperation::AbortPendingUpgrade { name, id, origin },
383 ))
384 .is_err()
385 {
386 error!("Failed to send SyncOperation::AbortPendingUpgrade");
387 }
388 }
389
390 Ok(())
391 }
392
393 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
395 self.object_store_names.as_rooted()
396 }
397
398 fn Mode(&self) -> IDBTransactionMode {
400 self.mode
401 }
402
403 fn GetError(&self) -> Option<DomRoot<DOMException>> {
411 self.error.get()
412 }
413
414 event_handler!(abort, GetOnabort, SetOnabort);
416
417 event_handler!(complete, GetOncomplete, SetOncomplete);
419
420 event_handler!(error, GetOnerror, SetOnerror);
422}