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::cell::DomRefCell;
13use script_bindings::inheritance::Castable;
14use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
15use servo_base::generic_channel::GenericSend;
16use servo_url::origin::ImmutableOrigin;
17use storage_traits::client_storage::{StorageIdentifier, StorageProxyMap, StorageType};
18use storage_traits::indexeddb::{
19 BackendResult, ConnectionMsg, DatabaseInfo, IndexedDBThreadMsg, SyncOperation,
20};
21use stylo_atoms::Atom;
22use uuid::Uuid;
23
24use crate::dom::bindings::codegen::Bindings::IDBFactoryBinding::{
25 IDBDatabaseInfo, IDBFactoryMethods,
26};
27use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
28use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::{Dom, DomRoot};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::bindings::trace::HashMapTracedValues;
33use crate::dom::event::{Event, EventBubbles, EventCancelable};
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::indexeddb::idbopendbrequest::IDBOpenDBRequest;
36use crate::dom::promise::Promise;
37use crate::dom::types::IDBTransaction;
38use crate::indexeddb::{convert_value_to_key, map_backend_error_to_dom_error};
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 pub fn new_inherited() -> IDBFactory {
64 IDBFactory {
65 reflector_: Reflector::new(),
66 connections: Default::default(),
67 indexeddb_transactions: Default::default(),
68 callback: Default::default(),
69 }
70 }
71
72 pub(crate) fn register_indexeddb_transaction(&self, txn: &IDBTransaction) {
73 let db_name = DBName(String::from(txn.get_db_name()));
74 let mut map = self.indexeddb_transactions.borrow_mut();
75 let bucket = map.entry(db_name).or_default();
76 if !bucket.iter().any(|entry| &**entry == txn) {
77 bucket.push(Dom::from_ref(txn));
78 }
79 txn.set_registered_in_global();
80 }
81
82 pub(crate) fn unregister_indexeddb_transaction(&self, txn: &IDBTransaction) {
83 let db_name = DBName(String::from(txn.get_db_name()));
84 let mut map = self.indexeddb_transactions.borrow_mut();
85 if let Some(bucket) = map.get_mut(&db_name) {
86 bucket.retain(|entry| &**entry != txn);
87 if bucket.is_empty() {
88 map.remove(&db_name);
89 }
90 }
91 txn.clear_registered_in_global();
92 }
93
94 pub(crate) fn cleanup_indexeddb_transactions(&self, cx: &mut JSContext) -> bool {
95 let snapshot: Vec<DomRoot<IDBTransaction>> = {
98 let mut map = self.indexeddb_transactions.borrow_mut();
99
100 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
104 for key in keys {
105 if let Some(bucket) = map.get_mut(&key) {
106 bucket.retain(|txn| !txn.is_finished());
107 if bucket.is_empty() {
108 map.remove(&key);
109 }
110 }
111 }
112
113 map.iter()
114 .flat_map(|(_db, bucket)| bucket.iter())
115 .map(|txn| DomRoot::from_ref(&**txn))
116 .collect()
117 };
118 let any_matching = snapshot
128 .iter()
129 .any(|txn| txn.cleanup_event_loop_matches_current());
130
131 if !any_matching {
132 return false;
133 }
134
135 for txn in snapshot {
136 if txn.cleanup_event_loop_matches_current() {
137 txn.set_active_flag(false);
138 txn.clear_cleanup_event_loop();
139 if txn.is_usable() {
140 txn.maybe_commit(cx);
141 }
142 }
143 }
144
145 let mut map = self.indexeddb_transactions.borrow_mut();
147 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
148 for key in keys {
149 if let Some(bucket) = map.get_mut(&key) {
150 bucket.retain(|txn| !txn.is_finished());
151 if bucket.is_empty() {
152 map.remove(&key);
153 }
154 }
155 }
156
157 true
158 }
159
160 pub(crate) fn maybe_commit_txn(&self, cx: &mut JSContext, db_name: &str, txn_serial: u64) {
161 let key = DBName(db_name.to_string());
162 let snapshot: Vec<DomRoot<IDBTransaction>> = {
163 let map = self.indexeddb_transactions.borrow();
164 let Some(bucket) = map.get(&key) else {
165 return;
166 };
167 bucket.iter().map(|t| DomRoot::from_ref(&**t)).collect()
168 };
169
170 for txn in snapshot {
171 if txn.get_serial_number() == txn_serial {
172 txn.maybe_commit(cx);
173 break;
174 }
175 }
176 }
177
178 pub(crate) fn clear_open_request_transaction_for_txn(&self, transaction: &IDBTransaction) {
181 let requests: Vec<DomRoot<IDBOpenDBRequest>> = {
182 let pending = self.connections.borrow();
183 pending
184 .iter()
185 .flat_map(|(_db_name, entry)| entry.iter())
186 .map(|(_id, request)| request.as_rooted())
187 .collect()
188 };
189 let mut cleared = 0usize;
190 for request in requests {
191 cleared += request.clear_transaction_if_matches(transaction) as usize;
192 }
193
194 debug_assert_eq!(
195 cleared, 1,
196 "A versionchange transaction should belong to exactly one IDBOpenDBRequest."
197 );
198 }
199
200 pub fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<IDBFactory> {
201 reflect_dom_object_with_cx(Box::new(IDBFactory::new_inherited()), global, cx)
202 }
203
204 fn get_or_setup_callback(&self) -> GenericCallback<ConnectionMsg> {
206 if let Some(cb) = self.callback.borrow().as_ref() {
207 return cb.clone();
208 }
209
210 let global = self.global();
211 let response_listener = Trusted::new(self);
212
213 let task_source = global
214 .task_manager()
215 .database_access_task_source()
216 .to_sendable();
217 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
218 let response_listener = response_listener.clone();
219 let response = match message {
220 Ok(inner) => inner,
221 Err(err) => return error!("Error in IndexedDB factory callback {:?}.", err),
222 };
223 task_source.queue(task!(set_request_result_to_database: move |cx| {
225 let factory = response_listener.root();
226 factory.handle_connection_message(cx, response)
227 }));
228 })
229 .expect("Could not create open database callback");
230
231 *self.callback.borrow_mut() = Some(callback.clone());
232
233 callback
234 }
235
236 fn get_request(&self, name: String, request_id: &Uuid) -> Option<DomRoot<IDBOpenDBRequest>> {
237 let name = DBName(name);
238 let mut pending = self.connections.borrow_mut();
239 let Some(entry) = pending.get_mut(&name) else {
240 debug_assert!(false, "There should be a pending connection for {:?}", name);
241 return None;
242 };
243 let Some(request) = entry.get_mut(request_id) else {
244 debug_assert!(
245 false,
246 "There should be a pending connection for {:?}",
247 request_id
248 );
249 return None;
250 };
251 Some(request.as_rooted())
252 }
253
254 fn handle_connection_message(&self, cx: &mut JSContext, response: ConnectionMsg) {
259 match response {
260 ConnectionMsg::Connection {
261 name,
262 id,
263 version,
264 upgraded,
265 object_store_names,
266 } => {
267 let Some(request) = self.get_request(name.clone(), &id) else {
268 return debug_assert!(
269 false,
270 "There should be a request to handle ConnectionMsg::Connection."
271 );
272 };
273
274 let connection = request.get_or_init_connection(
277 cx,
278 &self.global(),
279 name,
280 version,
281 object_store_names,
282 upgraded,
283 );
284
285 request.dispatch_success(cx, connection);
290 },
291 ConnectionMsg::Upgrade {
292 name,
293 id,
294 version,
295 old_version,
296 transaction,
297 object_store_names,
298 } => {
299 let global = self.global();
300
301 let Some(request) = self.get_request(name.clone(), &id) else {
302 return debug_assert!(
303 false,
304 "There should be a request to handle ConnectionMsg::Upgrade."
305 );
306 };
307
308 let connection = request.get_or_init_connection(
309 cx,
310 &global,
311 name,
312 version,
313 object_store_names,
314 false,
315 );
316 request.upgrade_db_version(cx, &connection, old_version, version, transaction);
319 },
320 ConnectionMsg::VersionError { name, id } => {
321 self.dispatch_error(cx, name, id, Error::Version(None));
323 },
324 ConnectionMsg::AbortError { name, id } => {
325 self.dispatch_error(cx, name, id, Error::Abort(None));
327 },
328 ConnectionMsg::DatabaseError { name, id, error } => {
329 self.dispatch_error(cx, name, id, map_backend_error_to_dom_error(error));
331 },
332 ConnectionMsg::VersionChange {
333 name,
334 id,
335 version,
336 old_version,
337 } => {
338 let global = self.global();
339 let Some(request) = self.get_request(name.clone(), &id) else {
340 return debug_assert!(
341 false,
342 "There should be a request to handle ConnectionMsg::VersionChange."
343 );
344 };
345 let connection = request.connection();
346
347 connection.dispatch_versionchange(cx, old_version, Some(version));
349
350 let operation = SyncOperation::NotifyEndOfVersionChange {
353 id,
354 name,
355 old_version,
356 origin: global.origin().immutable().clone(),
357 };
358 if global
359 .storage_threads()
360 .send(IndexedDBThreadMsg::Sync(operation))
361 .is_err()
362 {
363 error!("Failed to send SyncOperation::NotifyEndOfVersionChange.");
364 }
365 },
366 ConnectionMsg::Blocked {
367 name,
368 id,
369 version,
370 old_version,
371 } => {
372 let Some(request) = self.get_request(name, &id) else {
373 return debug_assert!(
374 false,
375 "There should be a request to handle ConnectionMsg::VersionChange."
376 );
377 };
378
379 request.dispatch_blocked(cx, old_version, Some(version));
381 },
382 ConnectionMsg::TxnMaybeCommit { db_name, txn } => {
383 let factory = Trusted::new(self);
384 self.global()
385 .task_manager()
386 .dom_manipulation_task_source()
387 .queue(task!(indexeddb_maybe_commit_txn: move |cx| {
388 let factory = factory.root();
389 factory.maybe_commit_txn(cx, &db_name, txn);
390 }));
391 },
392 }
393 }
394
395 fn dispatch_error(
398 &self,
399 cx: &mut JSContext,
400 name: String,
401 request_id: Uuid,
402 dom_exception: Error,
403 ) {
404 let name = DBName(name);
405
406 let request = {
408 let mut pending = self.connections.borrow_mut();
409 let Some(entry) = pending.get_mut(&name) else {
410 return debug_assert!(false, "There should be a pending connection for {:?}", name);
411 };
412 let Some(request) = entry.get_mut(&request_id) else {
413 return debug_assert!(
414 false,
415 "There should be a pending connection for {:?}",
416 request_id
417 );
418 };
419 request.as_rooted()
420 };
421 let global = request.global();
422
423 request.set_result(HandleValue::undefined());
425
426 request.set_error(cx, Some(dom_exception));
428 request.clear_transaction();
434
435 request.set_ready_state_done();
437
438 let event = Event::new(
442 cx,
443 &global,
444 Atom::from("error"),
445 EventBubbles::Bubbles,
446 EventCancelable::Cancelable,
447 );
448 event.fire(cx, request.upcast());
449 }
450
451 fn open_database(
453 &self,
454 storage_key: ImmutableOrigin,
455 name: DOMString,
456 version: Option<u64>,
457 request: &IDBOpenDBRequest,
458 proxy_map: StorageProxyMap,
459 ) -> Result<(), ()> {
460 let global = self.global();
461 let request_id = request.get_id();
462
463 {
464 let mut pending = self.connections.borrow_mut();
465 let outer = pending.entry(DBName(name.to_string())).or_default();
466 outer.insert(request_id, Dom::from_ref(request));
467 }
468
469 let callback = self.get_or_setup_callback();
470
471 let open_operation = SyncOperation::OpenDatabase(
475 callback,
476 storage_key,
477 String::from(name),
478 version,
479 request.get_id(),
480 proxy_map,
481 );
482
483 if global
485 .storage_threads()
486 .send(IndexedDBThreadMsg::Sync(open_operation))
487 .is_err()
488 {
489 return Err(());
490 }
491 Ok(())
492 }
493
494 pub(crate) fn abort_pending_upgrades(&self) {
495 let global = self.global();
496 let pending = self.connections.borrow();
497 let pending_upgrades = pending
498 .iter()
499 .map(|(key, val)| {
500 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
501 (key.0.clone(), ids)
502 })
503 .collect();
504 let origin = global.origin().immutable().clone();
505 let Ok(proxy_map) = self.obtain_a_local_storage_bottle_map(&global, origin.clone()) else {
506 debug_assert!(false, "Failed to obtain a proxy map.");
507 return;
508 };
509 if global
510 .storage_threads()
511 .send(IndexedDBThreadMsg::Sync(
512 SyncOperation::AbortPendingUpgrades {
513 pending_upgrades,
514 origin,
515 proxy_map,
516 },
517 ))
518 .is_err()
519 {
520 error!("Failed to send SyncOperation::AbortPendingUpgrade");
521 }
522 }
523
524 fn obtain_a_local_storage_bottle_map(
527 &self,
528 global: &GlobalScope,
529 origin: ImmutableOrigin,
530 ) -> Result<StorageProxyMap, Error> {
531 let handle = global.storage_threads().client_storage_handle();
532 let message = handle
533 .obtain_a_storage_bottle_map(
534 StorageType::Local,
535 global.webview_id(),
536 StorageIdentifier::IndexedDB,
537 origin,
538 )
539 .recv();
540 let Ok(response) = message else {
541 return Err(Error::Operation(None));
542 };
543 let Ok(proxy_map) = response else {
544 return Err(Error::Operation(None));
545 };
546 Ok(proxy_map)
547 }
548}
549
550impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
551 fn Open(
553 &self,
554 cx: &mut JSContext,
555 name: DOMString,
556 version: Option<u64>,
557 ) -> Fallible<DomRoot<IDBOpenDBRequest>> {
558 if version == Some(0) {
560 return Err(Error::Type(
561 c"The version must be an integer >= 1".to_owned(),
562 ));
563 };
564
565 let global = self.global();
567
568 let Some(storage_key) = global.obtain_storage_key() else {
571 return Err(Error::Security(None));
572 };
573
574 let proxy_map =
577 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
578
579 let request = IDBOpenDBRequest::new(cx, &self.global());
581
582 if self
584 .open_database(storage_key, name, version, &request, proxy_map)
585 .is_err()
586 {
587 return Err(Error::Operation(None));
588 }
589
590 Ok(request)
592 }
593
594 fn DeleteDatabase(
596 &self,
597 cx: &mut JSContext,
598 name: DOMString,
599 ) -> Fallible<DomRoot<IDBOpenDBRequest>> {
600 let global = self.global();
602
603 let Some(storage_key) = global.obtain_storage_key() else {
606 return Err(Error::Security(None));
607 };
608
609 let proxy_map =
612 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
613
614 let request = IDBOpenDBRequest::new(cx, &self.global());
616
617 if request
619 .delete_database(storage_key, String::from(name), proxy_map)
620 .is_err()
621 {
622 return Err(Error::Operation(None));
623 }
624
625 Ok(request)
627 }
628
629 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
631 let global = self.global();
633
634 let storage_key = match global.obtain_storage_key() {
637 Some(storage_key) => storage_key,
638 None => {
639 let p = Promise::new(cx, &global);
640 p.reject_error(cx, Error::Security(None));
641 return p;
642 },
643 };
644
645 let p = Promise::new(cx, &global);
647
648 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
651
652 let task_source = global
655 .task_manager()
656 .database_access_task_source()
657 .to_sendable();
658 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
659 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
660 let Some(trusted_promise) = trusted_promise.take() else {
661 return error!("Callback for `DataBases` called twice.");
662 };
663
664 task_source.queue(task!(set_request_result_to_database: move |cx| {
666 let promise = trusted_promise.root();
667 match result {
668 Err(err) => {
669 let error = map_backend_error_to_dom_error(err);
670 rooted!(&in(cx) let mut rval = UndefinedValue());
671 error
672 .to_jsval(cx, &promise.global(), rval.handle_mut());
673 promise.reject_native(cx, &rval.handle());
674 },
675 Ok(info_list) => {
676 let info_list: Vec<IDBDatabaseInfo> = info_list
677 .into_iter()
678 .map(|info| IDBDatabaseInfo {
679 name: Some(DOMString::from(info.name)),
680 version: Some(info.version),
681 })
682 .collect();
683 promise.resolve_native(cx, &info_list);
684 },
685 }
686 }));
687 })
688 .expect("Could not create databases callback");
689
690 let get_operation = SyncOperation::GetDatabases(callback, storage_key);
691 if global
692 .storage_threads()
693 .send(IndexedDBThreadMsg::Sync(get_operation))
694 .is_err()
695 {
696 error!("Failed to send SyncOperation::GetDatabases");
697 }
698
699 p
701 }
702
703 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
705 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
706 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
707 let cmp = first_key.partial_cmp(&second_key);
708 if let Some(cmp) = cmp {
709 match cmp {
710 std::cmp::Ordering::Less => Ok(-1),
711 std::cmp::Ordering::Equal => Ok(0),
712 std::cmp::Ordering::Greater => Ok(1),
713 }
714 } else {
715 Ok(i16::MAX)
716 }
717 }
718}