1use std::collections::HashSet;
5use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::jsval::UndefinedValue;
10use js::rust::HandleValue;
11use profile_traits::generic_callback::GenericCallback;
12use script_bindings::inheritance::Castable;
13use servo_base::generic_channel::GenericSend;
14use servo_config::pref;
15use servo_url::origin::ImmutableOrigin;
16use storage_traits::indexeddb::{
17 BackendResult, ConnectionMsg, DatabaseInfo, IndexedDBThreadMsg, SyncOperation,
18};
19use stylo_atoms::Atom;
20use uuid::Uuid;
21
22use crate::dom::bindings::cell::DomRefCell;
23use crate::dom::bindings::codegen::Bindings::IDBFactoryBinding::{
24 IDBDatabaseInfo, IDBFactoryMethods,
25};
26use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
27use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
28use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
29use crate::dom::bindings::root::{Dom, DomRoot};
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::trace::HashMapTracedValues;
32use crate::dom::event::{Event, EventBubbles, EventCancelable};
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::indexeddb::idbopendbrequest::IDBOpenDBRequest;
35use crate::dom::promise::Promise;
36use crate::dom::types::IDBTransaction;
37use crate::indexeddb::{convert_value_to_key, map_backend_error_to_dom_error};
38use crate::script_runtime::CanGc;
39
40#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
42pub(crate) struct DBName(pub(crate) String);
43
44#[dom_struct]
45pub struct IDBFactory {
46 reflector_: Reflector,
47 connections:
52 DomRefCell<HashMapTracedValues<DBName, HashMapTracedValues<Uuid, Dom<IDBOpenDBRequest>>>>,
53
54 indexeddb_transactions: DomRefCell<HashMapTracedValues<DBName, Vec<Dom<IDBTransaction>>>>,
57
58 #[no_trace]
59 callback: DomRefCell<Option<GenericCallback<ConnectionMsg>>>,
60}
61
62impl IDBFactory {
63 fn obtain_storage_key_for_non_storage_purposes(environment: &GlobalScope) -> ImmutableOrigin {
65 environment.origin().immutable().clone()
68 }
69
70 fn obtain_storage_key(environment: &GlobalScope) -> Option<ImmutableOrigin> {
72 let key = Self::obtain_storage_key_for_non_storage_purposes(environment);
75
76 if let ImmutableOrigin::Opaque(_) = key {
78 return None;
79 }
80
81 if !pref!(dom_indexeddb_enabled) {
83 return None;
84 }
85
86 Some(key)
88 }
89
90 pub fn new_inherited() -> IDBFactory {
91 IDBFactory {
92 reflector_: Reflector::new(),
93 connections: Default::default(),
94 indexeddb_transactions: Default::default(),
95 callback: Default::default(),
96 }
97 }
98
99 pub(crate) fn register_indexeddb_transaction(&self, txn: &IDBTransaction) {
100 let db_name = DBName(txn.get_db_name().to_string());
101 let mut map = self.indexeddb_transactions.borrow_mut();
102 let bucket = map.entry(db_name).or_default();
103 if !bucket.iter().any(|entry| &**entry == txn) {
104 bucket.push(Dom::from_ref(txn));
105 }
106 txn.set_registered_in_global();
107 }
108
109 pub(crate) fn unregister_indexeddb_transaction(&self, txn: &IDBTransaction) {
110 let db_name = DBName(txn.get_db_name().to_string());
111 let mut map = self.indexeddb_transactions.borrow_mut();
112 if let Some(bucket) = map.get_mut(&db_name) {
113 bucket.retain(|entry| &**entry != txn);
114 if bucket.is_empty() {
115 map.remove(&db_name);
116 }
117 }
118 txn.clear_registered_in_global();
119 }
120
121 pub(crate) fn cleanup_indexeddb_transactions(&self) -> bool {
122 let snapshot: Vec<DomRoot<IDBTransaction>> = {
125 let mut map = self.indexeddb_transactions.borrow_mut();
126
127 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
131 for key in keys {
132 if let Some(bucket) = map.get_mut(&key) {
133 bucket.retain(|txn| !txn.is_finished());
134 if bucket.is_empty() {
135 map.remove(&key);
136 }
137 }
138 }
139
140 map.iter()
141 .flat_map(|(_db, bucket)| bucket.iter())
142 .map(|txn| DomRoot::from_ref(&**txn))
143 .collect()
144 };
145 let any_matching = snapshot
155 .iter()
156 .any(|txn| txn.cleanup_event_loop_matches_current());
157
158 if !any_matching {
159 return false;
160 }
161
162 for txn in snapshot {
163 if txn.cleanup_event_loop_matches_current() {
164 txn.set_active_flag(false);
165 txn.clear_cleanup_event_loop();
166 if txn.is_usable() {
167 txn.maybe_commit();
168 }
169 }
170 }
171
172 let mut map = self.indexeddb_transactions.borrow_mut();
174 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
175 for key in keys {
176 if let Some(bucket) = map.get_mut(&key) {
177 bucket.retain(|txn| !txn.is_finished());
178 if bucket.is_empty() {
179 map.remove(&key);
180 }
181 }
182 }
183
184 true
185 }
186
187 pub(crate) fn maybe_commit_txn(&self, db_name: &str, txn_serial: u64) {
188 let key = DBName(db_name.to_string());
189 let snapshot: Vec<DomRoot<IDBTransaction>> = {
190 let map = self.indexeddb_transactions.borrow();
191 let Some(bucket) = map.get(&key) else {
192 return;
193 };
194 bucket.iter().map(|t| DomRoot::from_ref(&**t)).collect()
195 };
196
197 for txn in snapshot {
198 if txn.get_serial_number() == txn_serial {
199 txn.maybe_commit();
200 break;
201 }
202 }
203 }
204
205 pub(crate) fn clear_open_request_transaction_for_txn(&self, transaction: &IDBTransaction) {
208 let requests: Vec<DomRoot<IDBOpenDBRequest>> = {
209 let pending = self.connections.borrow();
210 pending
211 .iter()
212 .flat_map(|(_db_name, entry)| entry.iter())
213 .map(|(_id, request)| request.as_rooted())
214 .collect()
215 };
216 let mut cleared = 0usize;
217 for request in requests {
218 cleared += request.clear_transaction_if_matches(transaction) as usize;
219 }
220
221 debug_assert_eq!(
222 cleared, 1,
223 "A versionchange transaction should belong to exactly one IDBOpenDBRequest."
224 );
225 }
226
227 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBFactory> {
228 reflect_dom_object(Box::new(IDBFactory::new_inherited()), global, can_gc)
229 }
230
231 fn get_or_setup_callback(&self) -> GenericCallback<ConnectionMsg> {
233 if let Some(cb) = self.callback.borrow().as_ref() {
234 return cb.clone();
235 }
236
237 let global = self.global();
238 let response_listener = Trusted::new(self);
239
240 let task_source = global
241 .task_manager()
242 .database_access_task_source()
243 .to_sendable();
244 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
245 let response_listener = response_listener.clone();
246 let response = match message {
247 Ok(inner) => inner,
248 Err(err) => return error!("Error in IndexedDB factory callback {:?}.", err),
249 };
250 task_source.queue(task!(set_request_result_to_database: move |cx| {
252 let factory = response_listener.root();
253 factory.handle_connection_message(cx, response)
254 }));
255 })
256 .expect("Could not create open database callback");
257
258 *self.callback.borrow_mut() = Some(callback.clone());
259
260 callback
261 }
262
263 fn get_request(&self, name: String, request_id: &Uuid) -> Option<DomRoot<IDBOpenDBRequest>> {
264 let name = DBName(name);
265 let mut pending = self.connections.borrow_mut();
266 let Some(entry) = pending.get_mut(&name) else {
267 debug_assert!(false, "There should be a pending connection for {:?}", name);
268 return None;
269 };
270 let Some(request) = entry.get_mut(request_id) else {
271 debug_assert!(
272 false,
273 "There should be a pending connection for {:?}",
274 request_id
275 );
276 return None;
277 };
278 Some(request.as_rooted())
279 }
280
281 fn handle_connection_message(&self, cx: &mut JSContext, response: ConnectionMsg) {
286 match response {
287 ConnectionMsg::Connection {
288 name,
289 id,
290 version,
291 upgraded,
292 object_store_names,
293 } => {
294 let Some(request) = self.get_request(name.clone(), &id) else {
295 return debug_assert!(
296 false,
297 "There should be a request to handle ConnectionMsg::Connection."
298 );
299 };
300
301 let connection = request.get_or_init_connection(
304 &self.global(),
305 name.clone(),
306 version,
307 upgraded,
308 CanGc::from_cx(cx),
309 );
310 connection.set_object_store_names_from_backend(object_store_names);
311
312 request.dispatch_success(name, version, upgraded, CanGc::from_cx(cx));
317 },
318 ConnectionMsg::Upgrade {
319 name,
320 id,
321 version,
322 old_version,
323 transaction,
324 object_store_names,
325 } => {
326 let global = self.global();
327
328 let Some(request) = self.get_request(name.clone(), &id) else {
329 return debug_assert!(
330 false,
331 "There should be a request to handle ConnectionMsg::Upgrade."
332 );
333 };
334
335 let connection = request.get_or_init_connection(
336 &global,
337 name,
338 version,
339 false,
340 CanGc::from_cx(cx),
341 );
342 connection.set_object_store_names_from_backend(object_store_names);
345 request.upgrade_db_version(
346 &connection,
347 old_version,
348 version,
349 transaction,
350 CanGc::from_cx(cx),
351 );
352 },
353 ConnectionMsg::VersionError { name, id } => {
354 self.dispatch_error(cx, name, id, Error::Version(None));
356 },
357 ConnectionMsg::AbortError { name, id } => {
358 self.dispatch_error(cx, name, id, Error::Abort(None));
360 },
361 ConnectionMsg::DatabaseError { name, id, error } => {
362 self.dispatch_error(cx, name, id, map_backend_error_to_dom_error(error));
364 },
365 ConnectionMsg::VersionChange {
366 name,
367 id,
368 version,
369 old_version,
370 } => {
371 let global = self.global();
372 let Some(request) = self.get_request(name.clone(), &id) else {
373 return debug_assert!(
374 false,
375 "There should be a request to handle ConnectionMsg::VersionChange."
376 );
377 };
378 let connection = request.get_or_init_connection(
379 &global,
380 name.clone(),
381 version,
382 false,
383 CanGc::from_cx(cx),
384 );
385
386 connection.dispatch_versionchange(old_version, Some(version), CanGc::from_cx(cx));
388
389 let operation = SyncOperation::NotifyEndOfVersionChange {
392 id,
393 name,
394 old_version,
395 origin: global.origin().immutable().clone(),
396 };
397 if global
398 .storage_threads()
399 .send(IndexedDBThreadMsg::Sync(operation))
400 .is_err()
401 {
402 error!("Failed to send SyncOperation::NotifyEndOfVersionChange.");
403 }
404 },
405 ConnectionMsg::Blocked {
406 name,
407 id,
408 version,
409 old_version,
410 } => {
411 let Some(request) = self.get_request(name, &id) else {
412 return debug_assert!(
413 false,
414 "There should be a request to handle ConnectionMsg::VersionChange."
415 );
416 };
417
418 request.dispatch_blocked(old_version, Some(version), CanGc::from_cx(cx));
420 },
421 ConnectionMsg::TxnMaybeCommit { db_name, txn } => {
422 let factory = Trusted::new(self);
423 self.global()
424 .task_manager()
425 .dom_manipulation_task_source()
426 .queue(task!(indexeddb_maybe_commit_txn: move || {
427 let factory = factory.root();
428 factory.maybe_commit_txn(&db_name, txn);
429 }));
430 },
431 }
432 }
433
434 fn dispatch_error(
437 &self,
438 cx: &mut JSContext,
439 name: String,
440 request_id: Uuid,
441 dom_exception: Error,
442 ) {
443 let name = DBName(name);
444
445 let request = {
447 let mut pending = self.connections.borrow_mut();
448 let Some(entry) = pending.get_mut(&name) else {
449 return debug_assert!(false, "There should be a pending connection for {:?}", name);
450 };
451 let Some(request) = entry.get_mut(&request_id) else {
452 return debug_assert!(
453 false,
454 "There should be a pending connection for {:?}",
455 request_id
456 );
457 };
458 request.as_rooted()
459 };
460 let global = request.global();
461
462 request.set_result(HandleValue::undefined());
464
465 request.set_error(Some(dom_exception), CanGc::from_cx(cx));
467 request.clear_transaction();
473
474 request.set_ready_state_done();
476
477 let event = Event::new(
481 &global,
482 Atom::from("error"),
483 EventBubbles::Bubbles,
484 EventCancelable::Cancelable,
485 CanGc::from_cx(cx),
486 );
487 event.fire(request.upcast(), CanGc::from_cx(cx));
488 }
489
490 fn open_database(
492 &self,
493 storage_key: ImmutableOrigin,
494 name: DOMString,
495 version: Option<u64>,
496 request: &IDBOpenDBRequest,
497 ) -> Result<(), ()> {
498 let global = self.global();
499 let request_id = request.get_id();
500
501 {
502 let mut pending = self.connections.borrow_mut();
503 let outer = pending.entry(DBName(name.to_string())).or_default();
504 outer.insert(request_id, Dom::from_ref(request));
505 }
506
507 let callback = self.get_or_setup_callback();
508
509 let open_operation = SyncOperation::OpenDatabase(
513 callback,
514 storage_key,
515 name.to_string(),
516 version,
517 request.get_id(),
518 );
519
520 if global
522 .storage_threads()
523 .send(IndexedDBThreadMsg::Sync(open_operation))
524 .is_err()
525 {
526 return Err(());
527 }
528 Ok(())
529 }
530
531 pub(crate) fn abort_pending_upgrades(&self) {
532 let global = self.global();
533 let pending = self.connections.borrow();
534 let pending_upgrades = pending
535 .iter()
536 .map(|(key, val)| {
537 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
538 (key.0.clone(), ids)
539 })
540 .collect();
541 let origin = global.origin().immutable().clone();
542 if global
543 .storage_threads()
544 .send(IndexedDBThreadMsg::Sync(
545 SyncOperation::AbortPendingUpgrades {
546 pending_upgrades,
547 origin,
548 },
549 ))
550 .is_err()
551 {
552 error!("Failed to send SyncOperation::AbortPendingUpgrade");
553 }
554 }
555}
556
557impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
558 fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
560 if version == Some(0) {
562 return Err(Error::Type(
563 c"The version must be an integer >= 1".to_owned(),
564 ));
565 };
566
567 let global = self.global();
569
570 let Some(storage_key) = Self::obtain_storage_key(&global) else {
573 return Err(Error::Security(None));
574 };
575
576 let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
578
579 if self
581 .open_database(storage_key, name, version, &request)
582 .is_err()
583 {
584 return Err(Error::Operation(None));
585 }
586
587 Ok(request)
589 }
590
591 fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
593 let global = self.global();
595
596 let Some(storage_key) = Self::obtain_storage_key(&global) else {
599 return Err(Error::Security(None));
600 };
601
602 let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
604
605 if request
607 .delete_database(storage_key, name.to_string())
608 .is_err()
609 {
610 return Err(Error::Operation(None));
611 }
612
613 Ok(request)
615 }
616
617 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
619 let global = self.global();
621
622 let storage_key = match Self::obtain_storage_key(&global) {
625 Some(storage_key) => storage_key,
626 None => {
627 let p = Promise::new(&global, CanGc::from_cx(cx));
628 p.reject_error(Error::Security(None), CanGc::from_cx(cx));
629 return p;
630 },
631 };
632
633 let p = Promise::new(&global, CanGc::from_cx(cx));
635
636 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
639
640 let task_source = global
643 .task_manager()
644 .database_access_task_source()
645 .to_sendable();
646 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
647 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
648 let Some(trusted_promise) = trusted_promise.take() else {
649 return error!("Callback for `DataBases` called twice.");
650 };
651
652 task_source.queue(task!(set_request_result_to_database: move |cx| {
654 let promise = trusted_promise.root();
655 match result {
656 Err(err) => {
657 let error = map_backend_error_to_dom_error(err);
658 rooted!(&in(cx) let mut rval = UndefinedValue());
659 error
660 .to_jsval(cx.into(), &promise.global(), rval.handle_mut(), CanGc::from_cx(cx));
661 promise.reject_native(&rval.handle(), CanGc::from_cx(cx));
662 },
663 Ok(info_list) => {
664 let info_list: Vec<IDBDatabaseInfo> = info_list
665 .into_iter()
666 .map(|info| IDBDatabaseInfo {
667 name: Some(DOMString::from(info.name)),
668 version: Some(info.version),
669 })
670 .collect();
671 promise.resolve_native(&info_list, CanGc::from_cx(cx));
672 },
673 }
674 }));
675 })
676 .expect("Could not create databases callback");
677
678 let get_operation = SyncOperation::GetDatabases(callback, storage_key);
679 if global
680 .storage_threads()
681 .send(IndexedDBThreadMsg::Sync(get_operation))
682 .is_err()
683 {
684 error!("Failed to send SyncOperation::GetDatabases");
685 }
686
687 p
689 }
690
691 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
693 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
694 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
695 let cmp = first_key.partial_cmp(&second_key);
696 if let Some(cmp) = cmp {
697 match cmp {
698 std::cmp::Ordering::Less => Ok(-1),
699 std::cmp::Ordering::Equal => Ok(0),
700 std::cmp::Ordering::Greater => Ok(1),
701 }
702 } else {
703 Ok(i16::MAX)
704 }
705 }
706}