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;
13use uuid::Uuid;
14
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
17 IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
18};
19use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
20use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::inheritance::Castable;
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::eventtarget::EventTarget;
28use crate::dom::globalscope::GlobalScope;
29use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
30use crate::dom::indexeddb::idbtransaction::IDBTransaction;
31use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
32use crate::indexeddb::is_valid_key_path;
33use crate::script_runtime::CanGc;
34
35#[dom_struct]
36pub struct IDBDatabase {
37 eventtarget: EventTarget,
38 name: DOMString,
40 version: Cell<u64>,
42 object_store_names: DomRefCell<Vec<DOMString>>,
44 upgrade_transaction: MutNullableDom<IDBTransaction>,
46
47 #[no_trace]
48 #[ignore_malloc_size_of = "Uuid"]
49 id: Uuid,
50
51 closing: Cell<bool>,
54}
55
56impl IDBDatabase {
57 pub fn new_inherited(name: DOMString, id: Uuid, version: u64) -> IDBDatabase {
58 IDBDatabase {
59 eventtarget: EventTarget::new_inherited(),
60 name,
61 id,
62 version: Cell::new(version),
63 object_store_names: Default::default(),
64 upgrade_transaction: Default::default(),
65 closing: Cell::new(false),
66 }
67 }
68
69 pub fn new(
70 global: &GlobalScope,
71 name: DOMString,
72 id: Uuid,
73 version: u64,
74 can_gc: CanGc,
75 ) -> DomRoot<IDBDatabase> {
76 reflect_dom_object(
77 Box::new(IDBDatabase::new_inherited(name, id, version)),
78 global,
79 can_gc,
80 )
81 }
82
83 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
84 self.global().storage_threads().sender()
85 }
86
87 pub fn get_name(&self) -> DOMString {
88 self.name.clone()
89 }
90
91 pub fn object_stores(&self) -> DomRoot<DOMStringList> {
92 DOMStringList::new(
93 &self.global(),
94 self.object_store_names.borrow().clone(),
95 CanGc::note(),
96 )
97 }
98
99 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
100 self.object_store_names
101 .borrow()
102 .iter()
103 .any(|store_name| store_name == name)
104 }
105
106 pub(crate) fn version(&self) -> u64 {
108 self.version.get()
110 }
111
112 pub(crate) fn set_version(&self, version: u64) {
113 self.version.set(version);
114 }
115
116 pub fn set_transaction(&self, transaction: &IDBTransaction) {
117 self.upgrade_transaction.set(Some(transaction));
118 }
119
120 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
121 let current = self
122 .upgrade_transaction
123 .get()
124 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
125
126 debug_assert!(
127 &*current == transaction,
128 "clear_upgrade_transaction called with non-current transaction"
129 );
130
131 self.upgrade_transaction.set(None);
132 }
133
134 pub fn dispatch_versionchange(
136 &self,
137 old_version: u64,
138 new_version: Option<u64>,
139 can_gc: CanGc,
140 ) {
141 let global = self.global();
142 let _ = IDBVersionChangeEvent::fire_version_change_event(
143 &global,
144 self.upcast(),
145 Atom::from("versionchange"),
146 old_version,
147 new_version,
148 can_gc,
149 );
150 }
151}
152
153impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
154 fn Transaction(
156 &self,
157 store_names: StringOrStringSequence,
158 mode: IDBTransactionMode,
159 _options: &IDBTransactionOptions,
160 ) -> Fallible<DomRoot<IDBTransaction>> {
161 if self.closing.get() {
167 return Err(Error::InvalidState(None));
168 }
169
170 let transaction = match store_names {
172 StringOrStringSequence::String(name) => IDBTransaction::new(
173 &self.global(),
174 self,
175 mode,
176 &DOMStringList::new(&self.global(), vec![name], CanGc::note()),
177 CanGc::note(),
178 ),
179 StringOrStringSequence::StringSequence(sequence) => {
180 IDBTransaction::new(
183 &self.global(),
184 self,
185 mode,
186 &DOMStringList::new(&self.global(), sequence, CanGc::note()),
187 CanGc::note(),
188 )
189 },
190 };
191
192 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
195 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
196 }
197
198 transaction.set_cleanup_event_loop();
201 self.global()
208 .get_indexeddb()
209 .register_indexeddb_transaction(&transaction);
210
211 Ok(transaction)
212 }
213
214 fn CreateObjectStore(
216 &self,
217 cx: &mut JSContext,
218 name: DOMString,
219 options: &IDBObjectStoreParameters,
220 ) -> Fallible<DomRoot<IDBObjectStore>> {
221 let upgrade_transaction = match self.upgrade_transaction.get() {
223 Some(txn) => txn,
224 None => return Err(Error::InvalidState(None)),
225 };
226
227 if !upgrade_transaction.is_active() {
229 return Err(Error::TransactionInactive(None));
230 }
231
232 let key_path = options.keyPath.as_ref();
234
235 if let Some(path) = key_path {
237 if !is_valid_key_path(cx, path)? {
238 return Err(Error::Syntax(None));
239 }
240 }
241
242 if self.object_store_names.borrow().contains(&name) {
244 return Err(Error::Constraint(None));
245 }
246
247 let auto_increment = options.autoIncrement;
249
250 if auto_increment {
252 match key_path {
253 Some(StringOrStringSequence::String(path)) => {
254 if path.is_empty() {
255 return Err(Error::InvalidAccess(None));
256 }
257 },
258 Some(StringOrStringSequence::StringSequence(_)) => {
259 return Err(Error::InvalidAccess(None));
260 },
261 None => {},
262 }
263 }
264
265 let object_store = IDBObjectStore::new(
267 &self.global(),
268 self.name.clone(),
269 name.clone(),
270 Some(options),
271 CanGc::from_cx(cx),
272 &upgrade_transaction,
273 );
274
275 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
276
277 let key_paths = key_path.map(|p| match p {
278 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
279 StringOrStringSequence::StringSequence(s) => {
280 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
281 },
282 });
283 let operation = SyncOperation::CreateObjectStore(
284 sender,
285 self.global().origin().immutable().clone(),
286 self.name.to_string(),
287 name.to_string(),
288 key_paths,
289 auto_increment,
290 );
291
292 self.get_idb_thread()
293 .send(IndexedDBThreadMsg::Sync(operation))
294 .unwrap();
295
296 if receiver
297 .recv()
298 .expect("Could not receive object store creation status")
299 .is_err()
300 {
301 warn!("Object store creation failed in idb thread");
302 return Err(Error::InvalidState(None));
303 };
304
305 self.object_store_names.borrow_mut().push(name);
306 Ok(object_store)
307 }
308
309 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
311 let transaction = self.upgrade_transaction.get();
313 let transaction = match transaction {
314 Some(transaction) => transaction,
315 None => return Err(Error::InvalidState(None)),
316 };
317
318 if !transaction.is_active() {
320 return Err(Error::TransactionInactive(None));
321 }
322
323 if !self.object_store_names.borrow().contains(&name) {
325 return Err(Error::NotFound(None));
326 }
327
328 self.object_store_names
330 .borrow_mut()
331 .retain(|store_name| *store_name != name);
332
333 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
338
339 let operation = SyncOperation::DeleteObjectStore(
340 sender,
341 self.global().origin().immutable().clone(),
342 self.name.to_string(),
343 name.to_string(),
344 );
345
346 self.get_idb_thread()
347 .send(IndexedDBThreadMsg::Sync(operation))
348 .unwrap();
349
350 if receiver
351 .recv()
352 .expect("Could not receive object store deletion status")
353 .is_err()
354 {
355 warn!("Object store deletion failed in idb thread");
356 return Err(Error::InvalidState(None));
357 };
358 Ok(())
359 }
360
361 fn Name(&self) -> DOMString {
363 self.name.clone()
364 }
365
366 fn Version(&self) -> u64 {
368 self.version()
369 }
370
371 fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
373 DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
374 }
375
376 fn Close(&self) {
378 self.closing.set(true);
383
384 let operation = SyncOperation::CloseDatabase(
386 self.global().origin().immutable().clone(),
387 self.id,
388 self.name.to_string(),
389 );
390 let _ = self
391 .get_idb_thread()
392 .send(IndexedDBThreadMsg::Sync(operation));
393 }
394
395 event_handler!(abort, GetOnabort, SetOnabort);
397
398 event_handler!(close, GetOnclose, SetOnclose);
400
401 event_handler!(error, GetOnerror, SetOnerror);
403
404 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
406}