1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use script_bindings::cell::DomRefCell;
10use script_bindings::domstring::TracedDOMString;
11use script_bindings::reflector::reflect_dom_object;
12use servo_base::generic_channel::{GenericSend, GenericSender};
13use storage_traits::indexeddb::{AsyncSchemaOperation, 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::dom::indexeddb::key::is_valid_key_path;
34
35#[dom_struct]
36pub struct IDBDatabase {
37 eventtarget: EventTarget,
38 name: TracedDOMString,
40 version: Cell<u64>,
42 object_store_names: DomRefCell<Vec<TracedDOMString>>,
44 upgrade_transaction: MutNullableDom<IDBTransaction>,
46
47 #[no_trace]
48 id: Uuid,
49
50 close_pending: Cell<bool>,
53}
54
55impl IDBDatabase {
56 pub fn new_inherited(
57 name: DOMString,
58 id: Uuid,
59 version: u64,
60 object_store_names: Vec<String>,
61 ) -> IDBDatabase {
62 IDBDatabase {
63 eventtarget: EventTarget::new_inherited(),
64 name: name.traced(),
65 id,
66 version: Cell::new(version),
67 object_store_names: DomRefCell::new(
68 object_store_names.into_iter().map(Into::into).collect(),
69 ),
70 upgrade_transaction: Default::default(),
71 close_pending: Cell::new(false),
72 }
73 }
74
75 pub fn new(
76 cx: &mut JSContext,
77 global: &GlobalScope,
78 name: DOMString,
79 id: Uuid,
80 version: u64,
81 object_store_names: Vec<String>,
82 ) -> DomRoot<IDBDatabase> {
83 reflect_dom_object(
84 cx,
85 Box::new(IDBDatabase::new_inherited(
86 name,
87 id,
88 version,
89 object_store_names,
90 )),
91 global,
92 )
93 }
94
95 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
96 self.global().storage_threads().sender()
97 }
98
99 pub fn get_name(&self) -> DOMString {
100 self.name.clone().root()
101 }
102
103 pub fn object_stores(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
104 DOMStringList::new(
105 cx,
106 &self.global(),
107 self.object_store_names
108 .borrow()
109 .clone()
110 .into_iter()
111 .map(|domstring| domstring.root())
112 .collect(),
113 )
114 }
115
116 pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
117 self.object_store_names
121 .borrow()
122 .clone()
123 .into_iter()
124 .map(|domstring| domstring.root())
125 .collect()
126 }
127
128 pub(crate) fn restore_object_store_names(&self, names: Vec<DOMString>) {
129 *self.object_store_names.borrow_mut() = names
132 .into_iter()
133 .map(|domstring| domstring.traced())
134 .collect();
135 }
136
137 pub(crate) fn rename_object_store_name(&self, old_name: &TracedDOMString, new_name: DOMString) {
138 let mut object_store_names = self.object_store_names.borrow_mut();
139 if let Some(position) = object_store_names
140 .iter()
141 .position(|name| *name == *old_name)
142 {
143 object_store_names[position] = new_name.traced();
144 }
145 }
146
147 pub(crate) fn object_store_exists(&self, name: &TracedDOMString) -> bool {
148 self.object_store_names.borrow().contains(name)
149 }
150
151 pub(crate) fn version(&self) -> u64 {
153 self.version.get()
155 }
156
157 pub(crate) fn set_version(&self, version: u64) {
158 self.version.set(version);
159 }
160
161 pub fn set_transaction(&self, transaction: &IDBTransaction) {
162 self.upgrade_transaction.set(Some(transaction));
163 }
164
165 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
166 let current = self
167 .upgrade_transaction
168 .get()
169 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
170
171 debug_assert!(
172 &*current == transaction,
173 "clear_upgrade_transaction called with non-current transaction"
174 );
175
176 self.upgrade_transaction.set(None);
177 }
178
179 pub fn dispatch_versionchange(
181 &self,
182 cx: &mut JSContext,
183 old_version: u64,
184 new_version: Option<u64>,
185 ) {
186 let global = self.global();
187 let _ = IDBVersionChangeEvent::fire_version_change_event(
188 cx,
189 &global,
190 self.upcast(),
191 Atom::from("versionchange"),
192 old_version,
193 new_version,
194 );
195 }
196
197 pub(crate) fn close_a_database_connection(&self, _forced: bool) {
199 self.close_pending.set(true);
201
202 let operation = SyncOperation::CloseDatabase(
205 self.global().origin().immutable().clone(),
206 self.id,
207 self.name.to_string(),
208 );
209 let _ = self
210 .get_idb_thread()
211 .send(IndexedDBThreadMsg::Sync(operation));
212 }
213}
214
215impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
216 fn Transaction(
218 &self,
219 cx: &mut JSContext,
220 store_names: StringOrStringSequence,
221 mode: IDBTransactionMode,
222 options: &IDBTransactionOptions,
223 ) -> Fallible<DomRoot<IDBTransaction>> {
224 if self.upgrade_transaction.get().is_some() {
227 return Err(Error::InvalidState(None));
228 }
229
230 if self.close_pending.get() {
233 return Err(Error::InvalidState(None));
234 }
235
236 let mut scope = match store_names {
239 StringOrStringSequence::String(name) => vec![name],
240 StringOrStringSequence::StringSequence(sequence) => sequence,
241 };
242 scope.sort_unstable_by(|left, right| {
243 left.str().encode_utf16().cmp(right.str().encode_utf16())
244 });
245 scope.dedup();
246
247 if scope.iter().any(|name| !self.object_store_exists(name)) {
250 return Err(Error::NotFound(None));
251 }
252
253 if scope.is_empty() {
255 return Err(Error::InvalidAccess(None));
256 }
257
258 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
260 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
261 }
262
263 let durability = options.durability;
267 let scope = DOMStringList::new(cx, &self.global(), scope);
268 let transaction = IDBTransaction::new(cx, &self.global(), self, mode, durability, &scope);
269
270 transaction.set_cleanup_event_loop();
272 self.global()
279 .ensure_indexeddb_factory(cx)
280 .register_indexeddb_transaction(&transaction);
281
282 Ok(transaction)
284 }
285
286 fn CreateObjectStore(
288 &self,
289 cx: &mut JSContext,
290 name: DOMString,
291 options: &IDBObjectStoreParameters,
292 ) -> Fallible<DomRoot<IDBObjectStore>> {
293 let transaction = match self.upgrade_transaction.get() {
298 Some(txn) => txn,
299 None => return Err(Error::InvalidState(None)),
300 };
301
302 if !transaction.is_active() {
305 return Err(Error::TransactionInactive(None));
306 }
307
308 let key_path = options.keyPath.as_ref();
311
312 if let Some(path) = key_path &&
315 !is_valid_key_path(cx, path)?
316 {
317 return Err(Error::Syntax(None));
318 }
319
320 if self.object_store_names.borrow().contains(&name) {
323 return Err(Error::Constraint(None));
324 }
325
326 let auto_increment = options.autoIncrement;
328
329 if auto_increment {
332 match key_path {
333 Some(StringOrStringSequence::String(path)) if path.is_empty() => {
334 return Err(Error::InvalidAccess(None));
335 },
336 Some(StringOrStringSequence::StringSequence(_)) => {
337 return Err(Error::InvalidAccess(None));
338 },
339 _ => {},
340 }
341 }
342
343 let object_store = IDBObjectStore::new(
348 cx,
349 &self.global(),
350 self.name.clone().root(),
351 name.clone(),
352 Some(options),
353 IDBObjectStoreAbortState {
354 newly_created_during_transaction: true,
355 rollback_indexes_on_abort: vec![],
356 key_generator_current_number: if auto_increment { Some(1_i64) } else { None },
357 },
358 &transaction,
359 );
360
361 let key_paths = key_path.map(|p| match p {
362 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
363 StringOrStringSequence::StringSequence(s) => {
364 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
365 },
366 });
367
368 let operation = AsyncSchemaOperation::CreateObjectStore {
369 callback: transaction.create_abort_callback(),
370 key_path: key_paths,
371 auto_increment,
372 };
373
374 self.get_idb_thread()
375 .send(IndexedDBThreadMsg::AsyncSchemaOperation {
376 origin: self.global().origin().immutable().clone(),
377 database_name: self.name.to_string(),
378 store_name: name.to_string(),
379 operation,
380 transaction_serial_number: transaction.get_serial_number(),
381 })
382 .unwrap();
383
384 self.object_store_names
385 .safe_borrow_mut(cx.no_gc())
386 .push(name.traced());
387 transaction.register_object_store_handle(&object_store.get_name(), &object_store);
388
389 Ok(object_store)
391 }
392
393 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
395 let transaction = self.upgrade_transaction.get();
397 let transaction = match transaction {
398 Some(transaction) => transaction,
399 None => return Err(Error::InvalidState(None)),
400 };
401
402 if !transaction.is_active() {
404 return Err(Error::TransactionInactive(None));
405 }
406
407 if !self.object_store_names.borrow().contains(&name) {
409 return Err(Error::NotFound(None));
410 }
411
412 self.object_store_names
414 .borrow_mut()
415 .retain(|store_name| *store_name != *name);
416
417 let operation = AsyncSchemaOperation::DeleteObjectStore {
422 callback: transaction.create_abort_callback(),
423 };
424 self.get_idb_thread()
425 .send(IndexedDBThreadMsg::AsyncSchemaOperation {
426 origin: self.global().origin().immutable().clone(),
427 database_name: self.name.to_string(),
428 store_name: name.to_string(),
429 operation,
430 transaction_serial_number: transaction.get_serial_number(),
431 })
432 .unwrap();
433
434 Ok(())
435 }
436
437 fn Name(&self) -> DOMString {
439 self.name.clone().root()
440 }
441
442 fn Version(&self) -> u64 {
444 self.version()
445 }
446
447 fn ObjectStoreNames(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
449 DOMStringList::new_sorted(cx, &self.global(), &*self.object_store_names.borrow())
450 }
451
452 fn Close(&self) {
454 self.close_a_database_connection(false);
456 }
457
458 event_handler!(abort, GetOnabort, SetOnabort);
460
461 event_handler!(close, GetOnclose, SetOnclose);
463
464 event_handler!(error, GetOnerror, SetOnerror);
466
467 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
469}