1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use ipc_channel::ipc::IpcSender;
9use net_traits::IpcSend;
10use net_traits::indexeddb_thread::{IndexedDBThreadMsg, KeyPath, SyncOperation};
11use profile_traits::ipc;
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::idbobjectstore::IDBObjectStore;
31use crate::dom::idbtransaction::IDBTransaction;
32use crate::dom::idbversionchangeevent::IDBVersionChangeEvent;
33use crate::indexed_db::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) -> IpcSender<IndexedDBThreadMsg> {
80 self.global().resource_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) = ipc::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 #[allow(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);
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 name: DOMString,
196 options: &IDBObjectStoreParameters,
197 ) -> Fallible<DomRoot<IDBObjectStore>> {
198 let upgrade_transaction = match self.upgrade_transaction.get() {
200 Some(txn) => txn,
201 None => return Err(Error::InvalidState),
202 };
203
204 if !upgrade_transaction.is_active() {
206 return Err(Error::TransactionInactive);
207 }
208
209 let key_path = options.keyPath.as_ref();
211
212 if let Some(path) = key_path {
214 if !is_valid_key_path(path) {
215 return Err(Error::Syntax(None));
216 }
217 }
218
219 if self
221 .object_store_names
222 .borrow()
223 .iter()
224 .any(|store_name| store_name.to_string() == name.to_string())
225 {
226 return Err(Error::Constraint);
227 }
228
229 let auto_increment = options.autoIncrement;
231
232 if auto_increment {
234 match key_path {
235 Some(StringOrStringSequence::String(path)) => {
236 if path.is_empty() {
237 return Err(Error::InvalidAccess);
238 }
239 },
240 Some(StringOrStringSequence::StringSequence(_)) => {
241 return Err(Error::InvalidAccess);
242 },
243 None => {},
244 }
245 }
246
247 let object_store = IDBObjectStore::new(
249 &self.global(),
250 self.name.clone(),
251 name.clone(),
252 Some(options),
253 CanGc::note(),
254 &upgrade_transaction,
255 );
256
257 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
258
259 let key_paths = key_path.map(|p| match p {
260 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
261 StringOrStringSequence::StringSequence(s) => {
262 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
263 },
264 });
265 let operation = SyncOperation::CreateObjectStore(
266 sender,
267 self.global().origin().immutable().clone(),
268 self.name.to_string(),
269 name.to_string(),
270 key_paths,
271 auto_increment,
272 );
273
274 self.get_idb_thread()
275 .send(IndexedDBThreadMsg::Sync(operation))
276 .unwrap();
277
278 if receiver
279 .recv()
280 .expect("Could not receive object store creation status")
281 .is_err()
282 {
283 warn!("Object store creation failed in idb thread");
284 return Err(Error::InvalidState);
285 };
286
287 self.object_store_names.borrow_mut().push(name);
288 Ok(object_store)
289 }
290
291 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
293 let transaction = self.upgrade_transaction.get();
295 let transaction = match transaction {
296 Some(transaction) => transaction,
297 None => return Err(Error::InvalidState),
298 };
299
300 if !transaction.is_active() {
302 return Err(Error::TransactionInactive);
303 }
304
305 if !self
307 .object_store_names
308 .borrow()
309 .iter()
310 .any(|store_name| store_name.to_string() == name.to_string())
311 {
312 return Err(Error::NotFound);
313 }
314
315 self.object_store_names
317 .borrow_mut()
318 .retain(|store_name| store_name.to_string() != name.to_string());
319
320 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
325
326 let operation = SyncOperation::DeleteObjectStore(
327 sender,
328 self.global().origin().immutable().clone(),
329 self.name.to_string(),
330 name.to_string(),
331 );
332
333 self.get_idb_thread()
334 .send(IndexedDBThreadMsg::Sync(operation))
335 .unwrap();
336
337 if receiver
338 .recv()
339 .expect("Could not receive object store deletion status")
340 .is_err()
341 {
342 warn!("Object store deletion failed in idb thread");
343 return Err(Error::InvalidState);
344 };
345 Ok(())
346 }
347
348 fn Name(&self) -> DOMString {
350 self.name.clone()
351 }
352
353 fn Version(&self) -> u64 {
355 self.version()
356 }
357
358 fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
360 DOMStringList::new(
362 &self.global(),
363 self.object_store_names.borrow().clone(),
364 CanGc::note(),
365 )
366 }
367
368 fn Close(&self) {
370 self.closing.set(true);
372
373 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
379 let operation = SyncOperation::CloseDatabase(
380 sender,
381 self.global().origin().immutable().clone(),
382 self.name.to_string(),
383 );
384 let _ = self
385 .get_idb_thread()
386 .send(IndexedDBThreadMsg::Sync(operation));
387
388 if receiver.recv().is_err() {
389 warn!("Database close failed in idb thread");
390 };
391 }
392
393 event_handler!(abort, GetOnabort, SetOnabort);
395
396 event_handler!(close, GetOnclose, SetOnclose);
398
399 event_handler!(error, GetOnerror, SetOnerror);
401
402 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
404}