1use std::cell::Cell;
6use std::collections::HashMap;
7
8use base::IpcSend;
9use dom_struct::dom_struct;
10use ipc_channel::ipc::IpcSender;
11use profile_traits::ipc;
12use script_bindings::codegen::GenericUnionTypes::StringOrStringSequence;
13use storage_traits::indexeddb::{IndexedDBThreadMsg, KeyPath, SyncOperation};
14use stylo_atoms::Atom;
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::idbopendbrequest::IDBOpenDBRequest;
36use crate::dom::indexeddb::idbrequest::IDBRequest;
37use crate::script_runtime::CanGc;
38
39#[dom_struct]
40pub struct IDBTransaction {
41 eventtarget: EventTarget,
42 object_store_names: Dom<DOMStringList>,
43 mode: IDBTransactionMode,
44 db: Dom<IDBDatabase>,
45 error: MutNullableDom<DOMException>,
46
47 store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
48 requests: DomRefCell<Vec<Dom<IDBRequest>>>,
50 active: Cell<bool>,
52 finished: Cell<bool>,
54 pending_request_count: Cell<usize>,
59 open_request: MutNullableDom<IDBOpenDBRequest>,
63
64 serial_number: u64,
67}
68
69impl IDBTransaction {
70 fn new_inherited(
71 connection: &IDBDatabase,
72 mode: IDBTransactionMode,
73 scope: &DOMStringList,
74 serial_number: u64,
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 open_request: Default::default(),
89 serial_number,
90 }
91 }
92
93 pub fn new(
94 global: &GlobalScope,
95 connection: &IDBDatabase,
96 mode: IDBTransactionMode,
97 scope: &DOMStringList,
98 can_gc: CanGc,
99 ) -> DomRoot<IDBTransaction> {
100 let serial_number = IDBTransaction::register_new(global, connection.get_name());
101 reflect_dom_object(
102 Box::new(IDBTransaction::new_inherited(
103 connection,
104 mode,
105 scope,
106 serial_number,
107 )),
108 global,
109 can_gc,
110 )
111 }
112
113 fn register_new(global: &GlobalScope, db_name: DOMString) -> u64 {
119 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
120
121 global
122 .storage_threads()
123 .send(IndexedDBThreadMsg::Sync(SyncOperation::RegisterNewTxn(
124 sender,
125 global.origin().immutable().clone(),
126 db_name.to_string(),
127 )))
128 .unwrap();
129
130 receiver.recv().unwrap()
131 }
132
133 pub fn wait(&self) {
135 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
137
138 let start_operation = SyncOperation::StartTransaction(
139 sender,
140 self.global().origin().immutable().clone(),
141 self.db.get_name().to_string(),
142 self.serial_number,
143 );
144
145 self.get_idb_thread()
146 .send(IndexedDBThreadMsg::Sync(start_operation))
147 .unwrap();
148
149 let _ = receiver.recv();
151 self.finished.set(true);
155 self.dispatch_complete();
156 }
157
158 pub fn set_active_flag(&self, status: bool) {
159 self.active.set(status);
160 if !status && self.pending_request_count.get() == 0 && !self.finished.get() {
163 self.finished.set(true);
164 self.dispatch_complete();
165 }
166 }
167
168 pub fn is_active(&self) -> bool {
169 self.active.get()
170 }
171
172 pub fn get_mode(&self) -> IDBTransactionMode {
173 self.mode
174 }
175
176 pub fn get_db_name(&self) -> DOMString {
177 self.db.get_name()
178 }
179
180 pub fn get_serial_number(&self) -> u64 {
181 self.serial_number
182 }
183
184 pub fn set_open_request(&self, request: &IDBOpenDBRequest) {
188 self.open_request.set(Some(request));
189 }
190
191 pub fn add_request(&self, request: &IDBRequest) {
192 self.requests.borrow_mut().push(Dom::from_ref(request));
193 self.pending_request_count
196 .set(self.pending_request_count.get() + 1);
197 }
198
199 pub fn request_finished(&self) {
204 if self.pending_request_count.get() == 0 {
205 return;
206 }
207 let remaining = self.pending_request_count.get() - 1;
208 self.pending_request_count.set(remaining);
209
210 if remaining == 0 && !self.active.get() && !self.finished.get() {
211 self.finished.set(true);
212 self.dispatch_complete();
213 }
214 }
215
216 pub fn upgrade_db_version(&self, version: u64) {
217 self.wait();
219 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
221 let upgrade_version_operation = SyncOperation::UpgradeVersion(
222 sender,
223 self.global().origin().immutable().clone(),
224 self.db.get_name().to_string(),
225 self.serial_number,
226 version,
227 );
228 self.get_idb_thread()
229 .send(IndexedDBThreadMsg::Sync(upgrade_version_operation))
230 .unwrap();
231 let _ = receiver.recv().unwrap();
234 }
235
236 fn dispatch_complete(&self) {
237 let global = self.global();
238 let this = Trusted::new(self);
239 global.task_manager().database_access_task_source().queue(
240 task!(send_complete_notification: move || {
241 let this = this.root();
242 let global = this.global();
243 let event = Event::new(
244 &global,
245 Atom::from("complete"),
246 EventBubbles::DoesNotBubble,
247 EventCancelable::NotCancelable,
248 CanGc::note()
249 );
250 event.fire(this.upcast(), CanGc::note());
251
252 if let Some(open_req) = this.open_request.get() {
256 open_req.dispatch_success(&this.db);
257 this.open_request.set(None);
258 }
259 }),
260 );
261 }
262
263 fn get_idb_thread(&self) -> IpcSender<IndexedDBThreadMsg> {
264 self.global().storage_threads().sender()
265 }
266
267 fn object_store_parameters(
268 &self,
269 object_store_name: &DOMString,
270 ) -> Option<IDBObjectStoreParameters> {
271 let global = self.global();
272 let idb_sender = global.storage_threads().sender();
273 let (sender, receiver) =
274 ipc::channel(global.time_profiler_chan().clone()).expect("failed to create channel");
275
276 let origin = global.origin().immutable().clone();
277 let db_name = self.db.get_name().to_string();
278 let object_store_name = object_store_name.to_string();
279
280 let operation = SyncOperation::HasKeyGenerator(
281 sender,
282 origin.clone(),
283 db_name.clone(),
284 object_store_name.clone(),
285 );
286
287 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
288
289 let auto_increment = receiver.recv().ok()?.ok()?;
292
293 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).ok()?;
294 let operation = SyncOperation::KeyPath(sender, origin, db_name, object_store_name);
295
296 let _ = idb_sender.send(IndexedDBThreadMsg::Sync(operation));
297
298 let key_path = receiver.recv().unwrap().ok()?;
301 let key_path = key_path.map(|key_path| match key_path {
302 KeyPath::String(s) => StringOrStringSequence::String(DOMString::from_string(s)),
303 KeyPath::Sequence(seq) => StringOrStringSequence::StringSequence(
304 seq.into_iter().map(DOMString::from_string).collect(),
305 ),
306 });
307 Some(IDBObjectStoreParameters {
308 autoIncrement: auto_increment,
309 keyPath: key_path,
310 })
311 }
312}
313
314impl IDBTransactionMethods<crate::DomTypeHolder> for IDBTransaction {
315 fn Db(&self) -> DomRoot<IDBDatabase> {
317 DomRoot::from_ref(&*self.db)
318 }
319
320 fn ObjectStore(&self, name: DOMString, can_gc: CanGc) -> Fallible<DomRoot<IDBObjectStore>> {
322 if self.finished.get() {
324 return Err(Error::InvalidState(None));
325 }
326
327 if !self.object_store_names.Contains(name.clone()) {
329 return Err(Error::NotFound(None));
330 }
331
332 if let Some(store) = self.store_handles.borrow().get(&*name.str()) {
336 return Ok(DomRoot::from_ref(store));
337 }
338
339 let parameters = self.object_store_parameters(&name);
340 let store = IDBObjectStore::new(
341 &self.global(),
342 self.db.get_name(),
343 name.clone(),
344 parameters.as_ref(),
345 can_gc,
346 self,
347 );
348 self.store_handles
349 .borrow_mut()
350 .insert(name.to_string(), Dom::from_ref(&*store));
351 Ok(store)
352 }
353
354 fn Commit(&self) -> Fallible<()> {
356 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
358 let start_operation = SyncOperation::Commit(
359 sender,
360 self.global().origin().immutable().clone(),
361 self.db.get_name().to_string(),
362 self.serial_number,
363 );
364
365 self.get_idb_thread()
366 .send(IndexedDBThreadMsg::Sync(start_operation))
367 .unwrap();
368
369 let result = receiver.recv().unwrap();
370
371 if let Err(_result) = result {
373 return Err(Error::QuotaExceeded {
375 quota: None,
376 requested: None,
377 });
378 }
379
380 self.dispatch_complete();
385
386 Ok(())
387 }
388
389 fn Abort(&self) -> Fallible<()> {
391 if self.finished.get() {
395 return Err(Error::InvalidState(None));
396 }
397
398 self.active.set(false);
399
400 Ok(())
401 }
402
403 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
405 self.object_store_names.as_rooted()
406 }
407
408 fn Mode(&self) -> IDBTransactionMode {
410 self.mode
411 }
412
413 fn GetError(&self) -> Option<DomRoot<DOMException>> {
421 self.error.get()
422 }
423
424 event_handler!(abort, GetOnabort, SetOnabort);
426
427 event_handler!(complete, GetOncomplete, SetOncomplete);
429
430 event_handler!(error, GetOnerror, SetOnerror);
432}