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_and_close_databases(&self) {
495 let global = self.global();
496 let connections = self.connections.borrow();
497 let pending_upgrades = connections
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 for (_, requests) in connections.iter() {
524 for (_, request) in requests.iter() {
525 if let Some(database) = request.pending_connection() {
526 database.close_a_database_connection(true );
527 }
528 }
529 }
530 }
531
532 fn obtain_a_local_storage_bottle_map(
535 &self,
536 global: &GlobalScope,
537 origin: ImmutableOrigin,
538 ) -> Result<StorageProxyMap, Error> {
539 let handle = global.storage_threads().client_storage_handle();
540 let message = handle
541 .obtain_a_storage_bottle_map(
542 StorageType::Local,
543 global.webview_id(),
544 StorageIdentifier::IndexedDB,
545 origin,
546 )
547 .recv();
548 let Ok(response) = message else {
549 return Err(Error::Operation(None));
550 };
551 let Ok(proxy_map) = response else {
552 return Err(Error::Operation(None));
553 };
554 Ok(proxy_map)
555 }
556}
557
558impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
559 fn Open(
561 &self,
562 cx: &mut JSContext,
563 name: DOMString,
564 version: Option<u64>,
565 ) -> Fallible<DomRoot<IDBOpenDBRequest>> {
566 if version == Some(0) {
568 return Err(Error::Type(
569 c"The version must be an integer >= 1".to_owned(),
570 ));
571 };
572
573 let global = self.global();
575
576 let Some(storage_key) = global.obtain_storage_key() else {
579 return Err(Error::Security(None));
580 };
581
582 let proxy_map =
585 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
586
587 let request = IDBOpenDBRequest::new(cx, &self.global());
589
590 if self
592 .open_database(storage_key, name, version, &request, proxy_map)
593 .is_err()
594 {
595 return Err(Error::Operation(None));
596 }
597
598 Ok(request)
600 }
601
602 fn DeleteDatabase(
604 &self,
605 cx: &mut JSContext,
606 name: DOMString,
607 ) -> Fallible<DomRoot<IDBOpenDBRequest>> {
608 let global = self.global();
610
611 let Some(storage_key) = global.obtain_storage_key() else {
614 return Err(Error::Security(None));
615 };
616
617 let proxy_map =
620 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
621
622 let request = IDBOpenDBRequest::new(cx, &self.global());
624
625 if request
627 .delete_database(storage_key, String::from(name), proxy_map)
628 .is_err()
629 {
630 return Err(Error::Operation(None));
631 }
632
633 Ok(request)
635 }
636
637 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
639 let global = self.global();
641
642 let storage_key = match global.obtain_storage_key() {
645 Some(storage_key) => storage_key,
646 None => {
647 let p = Promise::new(cx, &global);
648 p.reject_error(cx, Error::Security(None));
649 return p;
650 },
651 };
652
653 let p = Promise::new(cx, &global);
655
656 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
659
660 let task_source = global
663 .task_manager()
664 .database_access_task_source()
665 .to_sendable();
666 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
667 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
668 let Some(trusted_promise) = trusted_promise.take() else {
669 return error!("Callback for `DataBases` called twice.");
670 };
671
672 task_source.queue(task!(set_request_result_to_database: move |cx| {
674 let promise = trusted_promise.root();
675 match result {
676 Err(err) => {
677 let error = map_backend_error_to_dom_error(err);
678 rooted!(&in(cx) let mut rval = UndefinedValue());
679 error
680 .to_jsval(cx, &promise.global(), rval.handle_mut());
681 promise.reject_native(cx, &rval.handle());
682 },
683 Ok(info_list) => {
684 let info_list: Vec<IDBDatabaseInfo> = info_list
685 .into_iter()
686 .map(|info| IDBDatabaseInfo {
687 name: Some(DOMString::from(info.name)),
688 version: Some(info.version),
689 })
690 .collect();
691 promise.resolve_native(cx, &info_list);
692 },
693 }
694 }));
695 })
696 .expect("Could not create databases callback");
697
698 let get_operation = SyncOperation::GetDatabases(callback, storage_key);
699 if global
700 .storage_threads()
701 .send(IndexedDBThreadMsg::Sync(get_operation))
702 .is_err()
703 {
704 error!("Failed to send SyncOperation::GetDatabases");
705 }
706
707 p
709 }
710
711 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
713 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
714 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
715 let cmp = first_key.partial_cmp(&second_key);
716 if let Some(cmp) = cmp {
717 match cmp {
718 std::cmp::Ordering::Less => Ok(-1),
719 std::cmp::Ordering::Equal => Ok(0),
720 std::cmp::Ordering::Greater => Ok(1),
721 }
722 } else {
723 Ok(i16::MAX)
724 }
725 }
726}