1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use script_bindings::cell::DomRefCell;
10use script_bindings::reflector::reflect_dom_object;
11use servo_base::generic_channel::{GenericSend, GenericSender};
12use storage_traits::indexeddb::{AsyncSchemaOperation, IndexedDBThreadMsg, KeyPath, SyncOperation};
13use stylo_atoms::Atom;
14use uuid::Uuid;
15
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;
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, IDBObjectStoreAbortState};
30use crate::dom::indexeddb::idbtransaction::IDBTransaction;
31use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
32use crate::dom::indexeddb::key::is_valid_key_path;
33
34#[dom_struct]
35pub struct IDBDatabase {
36 eventtarget: EventTarget,
37 name: DOMString,
39 version: Cell<u64>,
41 object_store_names: DomRefCell<Vec<DOMString>>,
43 upgrade_transaction: MutNullableDom<IDBTransaction>,
45
46 #[no_trace]
47 id: Uuid,
48
49 close_pending: Cell<bool>,
52}
53
54impl IDBDatabase {
55 pub fn new_inherited(
56 name: DOMString,
57 id: Uuid,
58 version: u64,
59 object_store_names: Vec<String>,
60 ) -> IDBDatabase {
61 IDBDatabase {
62 eventtarget: EventTarget::new_inherited(),
63 name,
64 id,
65 version: Cell::new(version),
66 object_store_names: DomRefCell::new(
67 object_store_names.into_iter().map(Into::into).collect(),
68 ),
69 upgrade_transaction: Default::default(),
70 close_pending: Cell::new(false),
71 }
72 }
73
74 pub fn new(
75 cx: &mut JSContext,
76 global: &GlobalScope,
77 name: DOMString,
78 id: Uuid,
79 version: u64,
80 object_store_names: Vec<String>,
81 ) -> DomRoot<IDBDatabase> {
82 reflect_dom_object(
83 cx,
84 Box::new(IDBDatabase::new_inherited(
85 name,
86 id,
87 version,
88 object_store_names,
89 )),
90 global,
91 )
92 }
93
94 fn get_idb_thread(&self) -> GenericSender<IndexedDBThreadMsg> {
95 self.global().storage_threads().sender()
96 }
97
98 pub fn get_name(&self) -> DOMString {
99 self.name.clone()
100 }
101
102 pub fn object_stores(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
103 DOMStringList::new(cx, &self.global(), self.object_store_names.borrow().clone())
104 }
105
106 pub(crate) fn object_store_names_snapshot(&self) -> Vec<DOMString> {
107 self.object_store_names.borrow().clone()
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 rename_object_store_name(&self, old_name: &DOMString, new_name: DOMString) {
120 let mut object_store_names = self.object_store_names.borrow_mut();
121 if let Some(position) = object_store_names.iter().position(|name| name == old_name) {
122 object_store_names[position] = new_name;
123 }
124 }
125
126 pub(crate) fn object_store_exists(&self, name: &DOMString) -> bool {
127 self.object_store_names
128 .borrow()
129 .iter()
130 .any(|store_name| store_name == name)
131 }
132
133 pub(crate) fn version(&self) -> u64 {
135 self.version.get()
137 }
138
139 pub(crate) fn set_version(&self, version: u64) {
140 self.version.set(version);
141 }
142
143 pub fn set_transaction(&self, transaction: &IDBTransaction) {
144 self.upgrade_transaction.set(Some(transaction));
145 }
146
147 pub(crate) fn clear_upgrade_transaction(&self, transaction: &IDBTransaction) {
148 let current = self
149 .upgrade_transaction
150 .get()
151 .expect("clear_upgrade_transaction called but no upgrade transaction is set");
152
153 debug_assert!(
154 &*current == transaction,
155 "clear_upgrade_transaction called with non-current transaction"
156 );
157
158 self.upgrade_transaction.set(None);
159 }
160
161 pub fn dispatch_versionchange(
163 &self,
164 cx: &mut JSContext,
165 old_version: u64,
166 new_version: Option<u64>,
167 ) {
168 let global = self.global();
169 let _ = IDBVersionChangeEvent::fire_version_change_event(
170 cx,
171 &global,
172 self.upcast(),
173 Atom::from("versionchange"),
174 old_version,
175 new_version,
176 );
177 }
178
179 pub(crate) fn close_a_database_connection(&self, _forced: bool) {
181 self.close_pending.set(true);
183
184 let operation = SyncOperation::CloseDatabase(
187 self.global().origin().immutable().clone(),
188 self.id,
189 self.name.to_string(),
190 );
191 let _ = self
192 .get_idb_thread()
193 .send(IndexedDBThreadMsg::Sync(operation));
194 }
195}
196
197impl IDBDatabaseMethods<crate::DomTypeHolder> for IDBDatabase {
198 fn Transaction(
200 &self,
201 cx: &mut JSContext,
202 store_names: StringOrStringSequence,
203 mode: IDBTransactionMode,
204 options: &IDBTransactionOptions,
205 ) -> Fallible<DomRoot<IDBTransaction>> {
206 if self.upgrade_transaction.get().is_some() {
209 return Err(Error::InvalidState(None));
210 }
211
212 if self.close_pending.get() {
215 return Err(Error::InvalidState(None));
216 }
217
218 let mut scope = match store_names {
221 StringOrStringSequence::String(name) => vec![name],
222 StringOrStringSequence::StringSequence(sequence) => sequence,
223 };
224 scope.sort_unstable_by(|left, right| {
225 left.str().encode_utf16().cmp(right.str().encode_utf16())
226 });
227 scope.dedup();
228
229 if scope.iter().any(|name| !self.object_store_exists(name)) {
232 return Err(Error::NotFound(None));
233 }
234
235 if scope.is_empty() {
237 return Err(Error::InvalidAccess(None));
238 }
239
240 if mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite {
242 return Err(Error::Type(c"Invalid transaction mode".to_owned()));
243 }
244
245 let durability = options.durability;
249 let scope = DOMStringList::new(cx, &self.global(), scope);
250 let transaction = IDBTransaction::new(cx, &self.global(), self, mode, durability, &scope);
251
252 transaction.set_cleanup_event_loop();
254 self.global()
261 .ensure_indexeddb_factory(cx)
262 .register_indexeddb_transaction(&transaction);
263
264 Ok(transaction)
266 }
267
268 fn CreateObjectStore(
270 &self,
271 cx: &mut JSContext,
272 name: DOMString,
273 options: &IDBObjectStoreParameters,
274 ) -> Fallible<DomRoot<IDBObjectStore>> {
275 let transaction = match self.upgrade_transaction.get() {
280 Some(txn) => txn,
281 None => return Err(Error::InvalidState(None)),
282 };
283
284 if !transaction.is_active() {
287 return Err(Error::TransactionInactive(None));
288 }
289
290 let key_path = options.keyPath.as_ref();
293
294 if let Some(path) = key_path &&
297 !is_valid_key_path(cx, path)?
298 {
299 return Err(Error::Syntax(None));
300 }
301
302 if self.object_store_names.borrow().contains(&name) {
305 return Err(Error::Constraint(None));
306 }
307
308 let auto_increment = options.autoIncrement;
310
311 if auto_increment {
314 match key_path {
315 Some(StringOrStringSequence::String(path)) if path.is_empty() => {
316 return Err(Error::InvalidAccess(None));
317 },
318 Some(StringOrStringSequence::StringSequence(_)) => {
319 return Err(Error::InvalidAccess(None));
320 },
321 _ => {},
322 }
323 }
324
325 let object_store = IDBObjectStore::new(
330 cx,
331 &self.global(),
332 self.name.clone(),
333 name.clone(),
334 Some(options),
335 IDBObjectStoreAbortState {
336 newly_created_during_transaction: true,
337 rollback_indexes_on_abort: vec![],
338 key_generator_current_number: if auto_increment { Some(1_i64) } else { None },
339 },
340 &transaction,
341 );
342
343 let key_paths = key_path.map(|p| match p {
344 StringOrStringSequence::String(s) => KeyPath::String(s.to_string()),
345 StringOrStringSequence::StringSequence(s) => {
346 KeyPath::Sequence(s.iter().map(|s| s.to_string()).collect())
347 },
348 });
349
350 let operation = AsyncSchemaOperation::CreateObjectStore {
351 callback: transaction.create_abort_callback(),
352 key_path: key_paths,
353 auto_increment,
354 };
355
356 self.get_idb_thread()
357 .send(IndexedDBThreadMsg::AsyncSchemaOperation {
358 origin: self.global().origin().immutable().clone(),
359 database_name: self.name.to_string(),
360 store_name: name.to_string(),
361 operation,
362 transaction_serial_number: transaction.get_serial_number(),
363 })
364 .unwrap();
365
366 self.object_store_names.borrow_mut().push(name);
367 transaction.register_object_store_handle(&object_store.get_name(), &object_store);
368
369 Ok(object_store)
371 }
372
373 fn DeleteObjectStore(&self, name: DOMString) -> Fallible<()> {
375 let transaction = self.upgrade_transaction.get();
377 let transaction = match transaction {
378 Some(transaction) => transaction,
379 None => return Err(Error::InvalidState(None)),
380 };
381
382 if !transaction.is_active() {
384 return Err(Error::TransactionInactive(None));
385 }
386
387 if !self.object_store_names.borrow().contains(&name) {
389 return Err(Error::NotFound(None));
390 }
391
392 self.object_store_names
394 .borrow_mut()
395 .retain(|store_name| *store_name != name);
396
397 let operation = AsyncSchemaOperation::DeleteObjectStore {
402 callback: transaction.create_abort_callback(),
403 };
404 self.get_idb_thread()
405 .send(IndexedDBThreadMsg::AsyncSchemaOperation {
406 origin: self.global().origin().immutable().clone(),
407 database_name: self.name.to_string(),
408 store_name: name.to_string(),
409 operation,
410 transaction_serial_number: transaction.get_serial_number(),
411 })
412 .unwrap();
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_a_database_connection(false);
436 }
437
438 event_handler!(abort, GetOnabort, SetOnabort);
440
441 event_handler!(close, GetOnclose, SetOnclose);
443
444 event_handler!(error, GetOnerror, SetOnerror);
446
447 event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
449}