1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use profile_traits::generic_channel::channel;
10use servo_base::generic_channel::{GenericSend, GenericSender};
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 close_pending: 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 close_pending: 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::deprecated_note(),
96 )
97 }
98
99 pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
100 self.object_store_names.borrow().clone()
104 }
105
106 pub(crate) fn set_object_store_names_from_backend(&self, names: Vec<String>) {
107 *self.object_store_names.borrow_mut() = names.into_iter().map(Into::into).collect();
110 }
111
112 pub(crate) fn restore_object_store_names(&self, names: Vec<DOMString>) {
113 *self.object_store_names.borrow_mut() = names;
116 }
117
118 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
119 self.object_store_names
120 .borrow()
121 .iter()
122 .any(|store_name| store_name == name)
123 }
124
125 pub(crate) fn version(&self) -> u64 {
127 self.version.get()
129 }
130
131 pub(crate) fn set_version(&self, version: u64) {
132 self.version.set(version);
133 }
134
135 pub fn set_transaction(&self, transaction: &IDBTransaction) {
136 self.upgrade_transaction.set(Some(transaction));
137 }
138
139 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
140 let current = self
141 .upgrade_transaction
142 .get()
143 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
144
145 debug_assert!(
146 &*current == transaction,
147 "clear_upgrade_transaction called with non-current transaction"
148 );
149
150 self.upgrade_transaction.set(None);
151 }
152
153 pub fn dispatch_versionchange(
155 &self,
156 old_version: u64,
157 new_version: Option<u64>,
158 can_gc: CanGc,
159 ) {
160 let global = self.global();
161 let _ = IDBVersionChangeEvent::fire_version_change_event(
162 &global,
163 self.upcast(),
164 Atom::from("versionchange"),
165 old_version,
166 new_version,
167 can_gc,
168 );
169 }
170}
171
172impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
173 fn Transaction(
175 &self,
176 store_names: StringOrStringSequence,
177 mode: IDBTransactionMode,
178 options: &IDBTransactionOptions,
179 ) -> Fallible<DomRoot<IDBTransaction>> {
180 if self.upgrade_transaction.get().is_some() {
183 return Err(Error::InvalidState(None));
184 }
185
186 if self.close_pending.get() {
189 return Err(Error::InvalidState(None));
190 }
191
192 let mut scope = match store_names {
195 StringOrStringSequence::String(name) => vec![name],
196 StringOrStringSequence::StringSequence(sequence) => sequence,
197 };
198 scope.sort_unstable_by(|left, right| {
199 left.str().encode_utf16().cmp(right.str().encode_utf16())
200 });
201 scope.dedup();
202
203 if scope.iter().any(|name| !self.object_store_exists(name)) {
206 return Err(Error::NotFound(None));
207 }
208
209 if scope.is_empty() {
211 return Err(Error::InvalidAccess(None));
212 }
213
214 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
216 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
217 }
218
219 let durability = options.durability;
223 let scope = DOMStringList::new(&self.global(), scope, CanGc::deprecated_note());
224 let transaction = IDBTransaction::new(
225 &self.global(),
226 self,
227 mode,
228 durability,
229 &scope,
230 CanGc::deprecated_note(),
231 );
232
233 transaction.set_cleanup_event_loop();
235 self.global()
242 .get_indexeddb()
243 .register_indexeddb_transaction(&transaction);
244
245 Ok(transaction)
247 }
248
249 fn CreateObjectStore(
251 &self,
252 cx: &mut JSContext,
253 name: DOMString,
254 options: &IDBObjectStoreParameters,
255 ) -> Fallible<DomRoot<IDBObjectStore>> {
256 let transaction = match self.upgrade_transaction.get() {
261 Some(txn) => txn,
262 None => return Err(Error::InvalidState(None)),
263 };
264
265 if !transaction.is_active() {
268 return Err(Error::TransactionInactive(None));
269 }
270
271 let key_path = options.keyPath.as_ref();
274
275 if let Some(path) = key_path {
278 if !is_valid_key_path(cx, path)? {
279 return Err(Error::Syntax(None));
280 }
281 }
282
283 if self.object_store_names.borrow().contains(&name) {
286 return Err(Error::Constraint(None));
287 }
288
289 let auto_increment = options.autoIncrement;
291
292 if auto_increment {
295 match key_path {
296 Some(StringOrStringSequence::String(path)) => {
297 if path.is_empty() {
298 return Err(Error::InvalidAccess(None));
299 }
300 },
301 Some(StringOrStringSequence::StringSequence(_)) => {
302 return Err(Error::InvalidAccess(None));
303 },
304 None => {},
305 }
306 }
307
308 let object_store = IDBObjectStore::new(
313 &self.global(),
314 self.name.clone(),
315 name.clone(),
316 Some(options),
317 if auto_increment { Some(1) } else { None },
318 CanGc::from_cx(cx),
319 &transaction,
320 );
321
322 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
323
324 let key_paths = key_path.map(|p| match p {
325 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
326 StringOrStringSequence::StringSequence(s) => {
327 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
328 },
329 });
330 let operation = SyncOperation::CreateObjectStore(
331 sender,
332 self.global().origin().immutable().clone(),
333 self.name.to_string(),
334 name.to_string(),
335 key_paths,
336 auto_increment,
337 );
338
339 self.get_idb_thread()
340 .send(IndexedDBThreadMsg::Sync(operation))
341 .unwrap();
342
343 if receiver
344 .recv()
345 .expect("Could not receive object store creation status")
346 .is_err()
347 {
348 warn!("Object store creation failed in idb thread");
349 return Err(Error::InvalidState(None));
350 };
351
352 self.object_store_names.borrow_mut().push(name);
353
354 Ok(object_store)
356 }
357
358 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
360 let transaction = self.upgrade_transaction.get();
362 let transaction = match transaction {
363 Some(transaction) => transaction,
364 None => return Err(Error::InvalidState(None)),
365 };
366
367 if !transaction.is_active() {
369 return Err(Error::TransactionInactive(None));
370 }
371
372 if !self.object_store_names.borrow().contains(&name) {
374 return Err(Error::NotFound(None));
375 }
376
377 self.object_store_names
379 .borrow_mut()
380 .retain(|store_name| *store_name != name);
381
382 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
387
388 let operation = SyncOperation::DeleteObjectStore(
389 sender,
390 self.global().origin().immutable().clone(),
391 self.name.to_string(),
392 name.to_string(),
393 );
394
395 self.get_idb_thread()
396 .send(IndexedDBThreadMsg::Sync(operation))
397 .unwrap();
398
399 if receiver
400 .recv()
401 .expect("Could not receive object store deletion status")
402 .is_err()
403 {
404 warn!("Object store deletion failed in idb thread");
405 return Err(Error::InvalidState(None));
406 };
407 Ok(())
408 }
409
410 fn Name(&self) -> DOMString {
412 self.name.clone()
413 }
414
415 fn Version(&self) -> u64 {
417 self.version()
418 }
419
420 fn ObjectStoreNames(&self, can_gc: CanGc) -> DomRoot<DOMStringList> {
422 DOMStringList::new_sorted(&self.global(), &*self.object_store_names.borrow(), can_gc)
423 }
424
425 fn Close(&self) {
427 self.close_pending.set(true);
432
433 let operation = SyncOperation::CloseDatabase(
435 self.global().origin().immutable().clone(),
436 self.id,
437 self.name.to_string(),
438 );
439 let _ = self
440 .get_idb_thread()
441 .send(IndexedDBThreadMsg::Sync(operation));
442 }
443
444 event_handler!(abort, GetOnabort, SetOnabort);
446
447 event_handler!(close, GetOnclose, SetOnclose);
449
450 event_handler!(error, GetOnerror, SetOnerror);
452
453 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
455}