1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use profile_traits::generic_channel::channel;
10use script_bindings::cell::DomRefCell;
11use script_bindings::reflector::reflect_dom_object;
12use servo_base::generic_channel::{GenericSend, GenericSender};
13use storage_traits::indexeddb::{IndexedDBThreadMsg, KeyPath, SyncOperation};
14use stylo_atoms::Atom;
15use uuid::Uuid;
16
17use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
18 IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
19};
20use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
21use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::reflector::DomGlobal;
25use crate::dom::bindings::root::{DomRoot, MutNullableDom};
26use crate::dom::bindings::str::DOMString;
27use crate::dom::domstringlist::DOMStringList;
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 #[no_trace]
49 #[ignore_malloc_size_of = "Uuid"]
50 id: Uuid,
51
52 close_pending: Cell<bool>,
55}
56
57impl IDBDatabase {
58 pub fn new_inherited(name: DOMString, id: Uuid, version: u64) -> IDBDatabase {
59 IDBDatabase {
60 eventtarget: EventTarget::new_inherited(),
61 name,
62 id,
63 version: Cell::new(version),
64 object_store_names: Default::default(),
65 upgrade_transaction: Default::default(),
66 close_pending: Cell::new(false),
67 }
68 }
69
70 pub fn new(
71 global: &GlobalScope,
72 name: DOMString,
73 id: Uuid,
74 version: u64,
75 can_gc: CanGc,
76 ) -> DomRoot<IDBDatabase> {
77 reflect_dom_object(
78 Box::new(IDBDatabase::new_inherited(name, id, version)),
79 global,
80 can_gc,
81 )
82 }
83
84 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
85 self.global().storage_threads().sender()
86 }
87
88 pub fn get_name(&self) -> DOMString {
89 self.name.clone()
90 }
91
92 pub fn object_stores(&self) -> DomRoot<DOMStringList> {
93 DOMStringList::new(
94 &self.global(),
95 self.object_store_names.borrow().clone(),
96 CanGc::deprecated_note(),
97 )
98 }
99
100 pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
101 self.object_store_names.borrow().clone()
105 }
106
107 pub(crate) fn set_object_store_names_from_backend(&self, names: Vec<String>) {
108 *self.object_store_names.borrow_mut() = names.into_iter().map(Into::into).collect();
111 }
112
113 pub(crate) fn restore_object_store_names(&self, names: Vec<DOMString>) {
114 *self.object_store_names.borrow_mut() = names;
117 }
118
119 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
120 self.object_store_names
121 .borrow()
122 .iter()
123 .any(|store_name| store_name == name)
124 }
125
126 pub(crate) fn version(&self) -> u64 {
128 self.version.get()
130 }
131
132 pub(crate) fn set_version(&self, version: u64) {
133 self.version.set(version);
134 }
135
136 pub fn set_transaction(&self, transaction: &IDBTransaction) {
137 self.upgrade_transaction.set(Some(transaction));
138 }
139
140 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
141 let current = self
142 .upgrade_transaction
143 .get()
144 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
145
146 debug_assert!(
147 &*current == transaction,
148 "clear_upgrade_transaction called with non-current transaction"
149 );
150
151 self.upgrade_transaction.set(None);
152 }
153
154 pub fn dispatch_versionchange(
156 &self,
157 cx: &mut JSContext,
158 old_version: u64,
159 new_version: Option<u64>,
160 ) {
161 let global = self.global();
162 let _ = IDBVersionChangeEvent::fire_version_change_event(
163 cx,
164 &global,
165 self.upcast(),
166 Atom::from("versionchange"),
167 old_version,
168 new_version,
169 );
170 }
171}
172
173impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
174 fn Transaction(
176 &self,
177 store_names: StringOrStringSequence,
178 mode: IDBTransactionMode,
179 options: &IDBTransactionOptions,
180 ) -> Fallible<DomRoot<IDBTransaction>> {
181 if self.upgrade_transaction.get().is_some() {
184 return Err(Error::InvalidState(None));
185 }
186
187 if self.close_pending.get() {
190 return Err(Error::InvalidState(None));
191 }
192
193 let mut scope = match store_names {
196 StringOrStringSequence::String(name) => vec![name],
197 StringOrStringSequence::StringSequence(sequence) => sequence,
198 };
199 scope.sort_unstable_by(|left, right| {
200 left.str().encode_utf16().cmp(right.str().encode_utf16())
201 });
202 scope.dedup();
203
204 if scope.iter().any(|name| !self.object_store_exists(name)) {
207 return Err(Error::NotFound(None));
208 }
209
210 if scope.is_empty() {
212 return Err(Error::InvalidAccess(None));
213 }
214
215 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
217 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
218 }
219
220 let durability = options.durability;
224 let scope = DOMStringList::new(&self.global(), scope, CanGc::deprecated_note());
225 let transaction = IDBTransaction::new(
226 &self.global(),
227 self,
228 mode,
229 durability,
230 &scope,
231 CanGc::deprecated_note(),
232 );
233
234 transaction.set_cleanup_event_loop();
236 self.global()
243 .get_indexeddb()
244 .register_indexeddb_transaction(&transaction);
245
246 Ok(transaction)
248 }
249
250 fn CreateObjectStore(
252 &self,
253 cx: &mut JSContext,
254 name: DOMString,
255 options: &IDBObjectStoreParameters,
256 ) -> Fallible<DomRoot<IDBObjectStore>> {
257 let transaction = match self.upgrade_transaction.get() {
262 Some(txn) => txn,
263 None => return Err(Error::InvalidState(None)),
264 };
265
266 if !transaction.is_active() {
269 return Err(Error::TransactionInactive(None));
270 }
271
272 let key_path = options.keyPath.as_ref();
275
276 if let Some(path) = key_path &&
279 !is_valid_key_path(cx, path)?
280 {
281 return Err(Error::Syntax(None));
282 }
283
284 if self.object_store_names.borrow().contains(&name) {
287 return Err(Error::Constraint(None));
288 }
289
290 let auto_increment = options.autoIncrement;
292
293 if auto_increment {
296 match key_path {
297 Some(StringOrStringSequence::String(path)) if path.is_empty() => {
298 return Err(Error::InvalidAccess(None));
299 },
300 Some(StringOrStringSequence::StringSequence(_)) => {
301 return Err(Error::InvalidAccess(None));
302 },
303 _ => {},
304 }
305 }
306
307 let object_store = IDBObjectStore::new(
312 &self.global(),
313 self.name.clone(),
314 name.clone(),
315 Some(options),
316 if auto_increment { Some(1) } else { None },
317 CanGc::from_cx(cx),
318 &transaction,
319 );
320
321 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
322
323 let key_paths = key_path.map(|p| match p {
324 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
325 StringOrStringSequence::StringSequence(s) => {
326 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
327 },
328 });
329 let operation = SyncOperation::CreateObjectStore(
330 sender,
331 self.global().origin().immutable().clone(),
332 self.name.to_string(),
333 name.to_string(),
334 key_paths,
335 auto_increment,
336 );
337
338 self.get_idb_thread()
339 .send(IndexedDBThreadMsg::Sync(operation))
340 .unwrap();
341
342 if receiver
343 .recv()
344 .expect("Could not receive object store creation status")
345 .is_err()
346 {
347 warn!("Object store creation failed in idb thread");
348 return Err(Error::InvalidState(None));
349 };
350
351 self.object_store_names.borrow_mut().push(name);
352
353 Ok(object_store)
355 }
356
357 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
359 let transaction = self.upgrade_transaction.get();
361 let transaction = match transaction {
362 Some(transaction) => transaction,
363 None => return Err(Error::InvalidState(None)),
364 };
365
366 if !transaction.is_active() {
368 return Err(Error::TransactionInactive(None));
369 }
370
371 if !self.object_store_names.borrow().contains(&name) {
373 return Err(Error::NotFound(None));
374 }
375
376 self.object_store_names
378 .borrow_mut()
379 .retain(|store_name| *store_name != name);
380
381 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
386
387 let operation = SyncOperation::DeleteObjectStore(
388 sender,
389 self.global().origin().immutable().clone(),
390 self.name.to_string(),
391 name.to_string(),
392 );
393
394 self.get_idb_thread()
395 .send(IndexedDBThreadMsg::Sync(operation))
396 .unwrap();
397
398 if receiver
399 .recv()
400 .expect("Could not receive object store deletion status")
401 .is_err()
402 {
403 warn!("Object store deletion failed in idb thread");
404 return Err(Error::InvalidState(None));
405 };
406 Ok(())
407 }
408
409 fn Name(&self) -> DOMString {
411 self.name.clone()
412 }
413
414 fn Version(&self) -> u64 {
416 self.version()
417 }
418
419 fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
421 DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
422 }
423
424 fn Close(&self) {
426 self.close_pending.set(true);
431
432 let operation = SyncOperation::CloseDatabase(
434 self.global().origin().immutable().clone(),
435 self.id,
436 self.name.to_string(),
437 );
438 let _ = self
439 .get_idb_thread()
440 .send(IndexedDBThreadMsg::Sync(operation));
441 }
442
443 event_handler!(abort, GetOnabort, SetOnabort);
445
446 event_handler!(close, GetOnclose, SetOnclose);
448
449 event_handler!(error, GetOnerror, SetOnerror);
451
452 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
454}