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_with_cx;
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, IDBObjectStoreAbortState};
31use crate::dom::indexeddb::idbtransaction::IDBTransaction;
32use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
33use crate::indexeddb::is_valid_key_path;
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(
58 name: DOMString,
59 id: Uuid,
60 version: u64,
61 object_store_names: Vec<String>,
62 ) -> IDBDatabase {
63 IDBDatabase {
64 eventtarget: EventTarget::new_inherited(),
65 name,
66 id,
67 version: Cell::new(version),
68 object_store_names: DomRefCell::new(
69 object_store_names.into_iter().map(Into::into).collect(),
70 ),
71 upgrade_transaction: Default::default(),
72 close_pending: Cell::new(false),
73 }
74 }
75
76 pub fn new(
77 cx: &mut JSContext,
78 global: &GlobalScope,
79 name: DOMString,
80 id: Uuid,
81 version: u64,
82 object_store_names: Vec<String>,
83 ) -> DomRoot<IDBDatabase> {
84 reflect_dom_object_with_cx(
85 Box::new(IDBDatabase::new_inherited(
86 name,
87 id,
88 version,
89 object_store_names,
90 )),
91 global,
92 cx,
93 )
94 }
95
96 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
97 self.global().storage_threads().sender()
98 }
99
100 pub fn get_name(&self) -> DOMString {
101 self.name.clone()
102 }
103
104 pub fn object_stores(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
105 DOMStringList::new(cx, &self.global(), self.object_store_names.borrow().clone())
106 }
107
108 pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
109 self.object_store_names.borrow().clone()
113 }
114
115 pub(crate) fn restore_object_store_names(&self, names: Vec<DOMString>) {
116 *self.object_store_names.borrow_mut() = names;
119 }
120
121 pub(crate) fn rename_object_store_name(&self, old_name: &DOMString, new_name: DOMString) {
122 let mut object_store_names = self.object_store_names.borrow_mut();
123 if let Some(position) = object_store_names.iter().position(|name| name == old_name) {
124 object_store_names[position] = new_name;
125 }
126 }
127
128 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
129 self.object_store_names
130 .borrow()
131 .iter()
132 .any(|store_name| store_name == name)
133 }
134
135 pub(crate) fn version(&self) -> u64 {
137 self.version.get()
139 }
140
141 pub(crate) fn set_version(&self, version: u64) {
142 self.version.set(version);
143 }
144
145 pub fn set_transaction(&self, transaction: &IDBTransaction) {
146 self.upgrade_transaction.set(Some(transaction));
147 }
148
149 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
150 let current = self
151 .upgrade_transaction
152 .get()
153 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
154
155 debug_assert!(
156 &*current == transaction,
157 "clear_upgrade_transaction called with non-current transaction"
158 );
159
160 self.upgrade_transaction.set(None);
161 }
162
163 pub fn dispatch_versionchange(
165 &self,
166 cx: &mut JSContext,
167 old_version: u64,
168 new_version: Option<u64>,
169 ) {
170 let global = self.global();
171 let _ = IDBVersionChangeEvent::fire_version_change_event(
172 cx,
173 &global,
174 self.upcast(),
175 Atom::from("versionchange"),
176 old_version,
177 new_version,
178 );
179 }
180}
181
182impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
183 fn Transaction(
185 &self,
186 cx: &mut JSContext,
187 store_names: StringOrStringSequence,
188 mode: IDBTransactionMode,
189 options: &IDBTransactionOptions,
190 ) -> Fallible<DomRoot<IDBTransaction>> {
191 if self.upgrade_transaction.get().is_some() {
194 return Err(Error::InvalidState(None));
195 }
196
197 if self.close_pending.get() {
200 return Err(Error::InvalidState(None));
201 }
202
203 let mut scope = match store_names {
206 StringOrStringSequence::String(name) => vec![name],
207 StringOrStringSequence::StringSequence(sequence) => sequence,
208 };
209 scope.sort_unstable_by(|left, right| {
210 left.str().encode_utf16().cmp(right.str().encode_utf16())
211 });
212 scope.dedup();
213
214 if scope.iter().any(|name| !self.object_store_exists(name)) {
217 return Err(Error::NotFound(None));
218 }
219
220 if scope.is_empty() {
222 return Err(Error::InvalidAccess(None));
223 }
224
225 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
227 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
228 }
229
230 let durability = options.durability;
234 let scope = DOMStringList::new(cx, &self.global(), scope);
235 let transaction = IDBTransaction::new(cx, &self.global(), self, mode, durability, &scope);
236
237 transaction.set_cleanup_event_loop();
239 self.global()
246 .get_indexeddb(cx)
247 .register_indexeddb_transaction(&transaction);
248
249 Ok(transaction)
251 }
252
253 fn CreateObjectStore(
255 &self,
256 cx: &mut JSContext,
257 name: DOMString,
258 options: &IDBObjectStoreParameters,
259 ) -> Fallible<DomRoot<IDBObjectStore>> {
260 let transaction = match self.upgrade_transaction.get() {
265 Some(txn) => txn,
266 None => return Err(Error::InvalidState(None)),
267 };
268
269 if !transaction.is_active() {
272 return Err(Error::TransactionInactive(None));
273 }
274
275 let key_path = options.keyPath.as_ref();
278
279 if let Some(path) = key_path &&
282 !is_valid_key_path(cx, path)?
283 {
284 return Err(Error::Syntax(None));
285 }
286
287 if self.object_store_names.borrow().contains(&name) {
290 return Err(Error::Constraint(None));
291 }
292
293 let auto_increment = options.autoIncrement;
295
296 if auto_increment {
299 match key_path {
300 Some(StringOrStringSequence::String(path)) if path.is_empty() => {
301 return Err(Error::InvalidAccess(None));
302 },
303 Some(StringOrStringSequence::StringSequence(_)) => {
304 return Err(Error::InvalidAccess(None));
305 },
306 _ => {},
307 }
308 }
309
310 let object_store = IDBObjectStore::new(
315 cx,
316 &self.global(),
317 self.name.clone(),
318 name.clone(),
319 Some(options),
320 IDBObjectStoreAbortState {
321 newly_created_during_transaction: true,
322 rollback_indexes_on_abort: vec![],
323 key_generator_current_number: if auto_increment { Some(1_i64) } else { None },
324 },
325 &transaction,
326 );
327
328 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
329
330 let key_paths = key_path.map(|p| match p {
331 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
332 StringOrStringSequence::StringSequence(s) => {
333 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
334 },
335 });
336 let operation = SyncOperation::CreateObjectStore(
337 sender,
338 self.global().origin().immutable().clone(),
339 self.name.to_string(),
340 name.to_string(),
341 key_paths,
342 auto_increment,
343 );
344
345 self.get_idb_thread()
346 .send(IndexedDBThreadMsg::Sync(operation))
347 .unwrap();
348
349 if receiver
350 .recv()
351 .expect("Could not receive object store creation status")
352 .is_err()
353 {
354 warn!("Object store creation failed in idb thread");
355 return Err(Error::InvalidState(None));
356 };
357
358 self.object_store_names.borrow_mut().push(name);
359 transaction.register_object_store_handle(&object_store.get_name(), &object_store);
360
361 Ok(object_store)
363 }
364
365 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
367 let transaction = self.upgrade_transaction.get();
369 let transaction = match transaction {
370 Some(transaction) => transaction,
371 None => return Err(Error::InvalidState(None)),
372 };
373
374 if !transaction.is_active() {
376 return Err(Error::TransactionInactive(None));
377 }
378
379 if !self.object_store_names.borrow().contains(&name) {
381 return Err(Error::NotFound(None));
382 }
383
384 self.object_store_names
386 .borrow_mut()
387 .retain(|store_name| *store_name != name);
388
389 let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
394
395 let operation = SyncOperation::DeleteObjectStore(
396 sender,
397 self.global().origin().immutable().clone(),
398 self.name.to_string(),
399 String::from(name),
400 );
401
402 self.get_idb_thread()
403 .send(IndexedDBThreadMsg::Sync(operation))
404 .unwrap();
405
406 if receiver
407 .recv()
408 .expect("Could not receive object store deletion status")
409 .is_err()
410 {
411 warn!("Object store deletion failed in idb thread");
412 return Err(Error::InvalidState(None));
413 };
414 Ok(())
415 }
416
417 fn Name(&self) -> DOMString {
419 self.name.clone()
420 }
421
422 fn Version(&self) -> u64 {
424 self.version()
425 }
426
427 fn ObjectStoreNames(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
429 DOMStringList::new_sorted(cx, &self.global(), &*self.object_store_names.borrow())
430 }
431
432 fn Close(&self) {
434 self.close_pending.set(true);
439
440 let operation = SyncOperation::CloseDatabase(
442 self.global().origin().immutable().clone(),
443 self.id,
444 self.name.to_string(),
445 );
446 let _ = self
447 .get_idb_thread()
448 .send(IndexedDBThreadMsg::Sync(operation));
449 }
450
451 event_handler!(abort, GetOnabort, SetOnabort);
453
454 event_handler!(close, GetOnclose, SetOnclose);
456
457 event_handler!(error, GetOnerror, SetOnerror);
459
460 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
462}