1use std::cell::Cell;
6
7use base::generic_channel::{GenericSend, GenericSender};
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use profile_traits::generic_channel::channel;
11use storage_traits::indexeddb::{IndexedDBThreadMsg, KeyPath, SyncOperation};
12use stylo_atoms::Atom;
13
14use crate::dom::bindings::cell::DomRefCell;
15use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
16 IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
17};
18use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
19use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::refcounted::Trusted;
23use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
24use crate::dom::bindings::root::{DomRoot, MutNullableDom};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::domstringlist::DOMStringList;
27use crate::dom::event::{Event, EventBubbles, EventCancelable};
28use crate::dom::eventtarget::EventTarget;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
31use crate::dom::indexeddb::idbtransaction::IDBTransaction;
32use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
33use crate::indexeddb::is_valid_key_path;
34use crate::script_runtime::CanGc;
35
36#[dom_struct]
37pub struct IDBDatabase {
38 eventtarget: EventTarget,
39 name: DOMString,
41 version: Cell<u64>,
43 object_store_names: DomRefCell<Vec<DOMString>>,
45 upgrade_transaction: MutNullableDom<IDBTransaction>,
47
48 closing: Cell<bool>,
51}
52
53impl IDBDatabase {
54 pub fn new_inherited(name: DOMString, version: u64) -> IDBDatabase {
55 IDBDatabase {
56 eventtarget: EventTarget::new_inherited(),
57 name,
58 version: Cell::new(version),
59 object_store_names: Default::default(),
60
61 upgrade_transaction: Default::default(),
62 closing: Cell::new(false),
63 }
64 }
65
66 pub fn new(
67 global: &GlobalScope,
68 name: DOMString,
69 version: u64,
70 can_gc: CanGc,
71 ) -> DomRoot<IDBDatabase> {
72 reflect_dom_object(
73 Box::new(IDBDatabase::new_inherited(name, version)),
74 global,
75 can_gc,
76 )
77 }
78
79 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
80 self.global().storage_threads().sender()
81 }
82
83 pub fn get_name(&self) -> DOMString {
84 self.name.clone()
85 }
86
87 pub fn object_stores(&self) -> DomRoot<DOMStringList> {
88 DOMStringList::new(
89 &self.global(),
90 self.object_store_names.borrow().clone(),
91 CanGc::note(),
92 )
93 }
94
95 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
96 self.object_store_names
97 .borrow()
98 .iter()
99 .any(|store_name| store_name == name)
100 }
101
102 pub fn version(&self) -> u64 {
103 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
104 let operation = SyncOperation::Version(
105 sender,
106 self.global().origin().immutable().clone(),
107 self.name.to_string(),
108 );
109
110 let _ = self
111 .get_idb_thread()
112 .send(IndexedDBThreadMsg::Sync(operation));
113
114 receiver.recv().unwrap().unwrap_or_else(|e| {
115 error!("{e:?}");
116 u64::MAX
117 })
118 }
119
120 pub fn set_transaction(&self, transaction: &IDBTransaction) {
121 self.upgrade_transaction.set(Some(transaction));
122 }
123
124 #[expect(dead_code)] pub fn dispatch_versionchange(
126 &self,
127 old_version: u64,
128 new_version: Option<u64>,
129 _can_gc: CanGc,
130 ) {
131 let global = self.global();
132 let this = Trusted::new(self);
133 global.task_manager().database_access_task_source().queue(
134 task!(send_versionchange_notification: move || {
135 let this = this.root();
136 let global = this.global();
137 let event = IDBVersionChangeEvent::new(
138 &global,
139 Atom::from("versionchange"),
140 EventBubbles::DoesNotBubble,
141 EventCancelable::NotCancelable,
142 old_version,
143 new_version,
144 CanGc::note()
145 );
146 event.upcast::<Event>().fire(this.upcast(), CanGc::note());
147 }),
148 );
149 }
150}
151
152impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
153 fn Transaction(
155 &self,
156 store_names: StringOrStringSequence,
157 mode: IDBTransactionMode,
158 _options: &IDBTransactionOptions,
159 ) -> Fallible<DomRoot<IDBTransaction>> {
160 if self.closing.get() {
166 return Err(Error::InvalidState(None));
167 }
168
169 Ok(match store_names {
171 StringOrStringSequence::String(name) => IDBTransaction::new(
172 &self.global(),
173 self,
174 mode,
175 &DOMStringList::new(&self.global(), vec![name], CanGc::note()),
176 CanGc::note(),
177 ),
178 StringOrStringSequence::StringSequence(sequence) => {
179 IDBTransaction::new(
182 &self.global(),
183 self,
184 mode,
185 &DOMStringList::new(&self.global(), sequence, CanGc::note()),
186 CanGc::note(),
187 )
188 },
189 })
190 }
191
192 fn CreateObjectStore(
194 &self,
195 cx: &mut JSContext,
196 name: DOMString,
197 options: &IDBObjectStoreParameters,
198 ) -> Fallible<DomRoot<IDBObjectStore>> {
199 let upgrade_transaction = match self.upgrade_transaction.get() {
201 Some(txn) => txn,
202 None => return Err(Error::InvalidState(None)),
203 };
204
205 if !upgrade_transaction.is_active() {
207 return Err(Error::TransactionInactive(None));
208 }
209
210 let key_path = options.keyPath.as_ref();
212
213 if let Some(path) = key_path {
215 if !is_valid_key_path(cx, path)? {
216 return Err(Error::Syntax(None));
217 }
218 }
219
220 if self.object_store_names.borrow().contains(&name) {
222 return Err(Error::Constraint(None));
223 }
224
225 let auto_increment = options.autoIncrement;
227
228 if auto_increment {
230 match key_path {
231 Some(StringOrStringSequence::String(path)) => {
232 if path.is_empty() {
233 return Err(Error::InvalidAccess(None));
234 }
235 },
236 Some(StringOrStringSequence::StringSequence(_)) => {
237 return Err(Error::InvalidAccess(None));
238 },
239 None => {},
240 }
241 }
242
243 let object_store = IDBObjectStore::new(
245 &self.global(),
246 self.name.clone(),
247 name.clone(),
248 Some(options),
249 CanGc::from_cx(cx),
250 &upgrade_transaction,
251 );
252
253 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
254
255 let key_paths = key_path.map(|p| match p {
256 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
257 StringOrStringSequence::StringSequence(s) => {
258 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
259 },
260 });
261 let operation = SyncOperation::CreateObjectStore(
262 sender,
263 self.global().origin().immutable().clone(),
264 self.name.to_string(),
265 name.to_string(),
266 key_paths,
267 auto_increment,
268 );
269
270 self.get_idb_thread()
271 .send(IndexedDBThreadMsg::Sync(operation))
272 .unwrap();
273
274 if receiver
275 .recv()
276 .expect("Could not receive object store creation status")
277 .is_err()
278 {
279 warn!("Object store creation failed in idb thread");
280 return Err(Error::InvalidState(None));
281 };
282
283 self.object_store_names.borrow_mut().push(name);
284 Ok(object_store)
285 }
286
287 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
289 let transaction = self.upgrade_transaction.get();
291 let transaction = match transaction {
292 Some(transaction) => transaction,
293 None => return Err(Error::InvalidState(None)),
294 };
295
296 if !transaction.is_active() {
298 return Err(Error::TransactionInactive(None));
299 }
300
301 if !self.object_store_names.borrow().contains(&name) {
303 return Err(Error::NotFound(None));
304 }
305
306 self.object_store_names
308 .borrow_mut()
309 .retain(|store_name| *store_name != name);
310
311 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
316
317 let operation = SyncOperation::DeleteObjectStore(
318 sender,
319 self.global().origin().immutable().clone(),
320 self.name.to_string(),
321 name.to_string(),
322 );
323
324 self.get_idb_thread()
325 .send(IndexedDBThreadMsg::Sync(operation))
326 .unwrap();
327
328 if receiver
329 .recv()
330 .expect("Could not receive object store deletion status")
331 .is_err()
332 {
333 warn!("Object store deletion failed in idb thread");
334 return Err(Error::InvalidState(None));
335 };
336 Ok(())
337 }
338
339 fn Name(&self) -> DOMString {
341 self.name.clone()
342 }
343
344 fn Version(&self) -> u64 {
346 self.version()
347 }
348
349 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
351 DOMStringList::new(
353 &self.global(),
354 self.object_store_names.borrow().clone(),
355 CanGc::note(),
356 )
357 }
358
359 fn Close(&self) {
361 self.closing.set(true);
363
364 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
370 let operation = SyncOperation::CloseDatabase(
371 sender,
372 self.global().origin().immutable().clone(),
373 self.name.to_string(),
374 );
375 let _ = self
376 .get_idb_thread()
377 .send(IndexedDBThreadMsg::Sync(operation));
378
379 if receiver.recv().is_err() {
380 warn!("Database close failed in idb thread");
381 };
382 }
383
384 event_handler!(abort, GetOnabort, SetOnabort);
386
387 event_handler!(close, GetOnclose, SetOnclose);
389
390 event_handler!(error, GetOnerror, SetOnerror);
392
393 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
395}