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};
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};
39use crate::script_runtime::CanGc;
40
41#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
43pub(crate) struct DBName(pub(crate) String);
44
45#[dom_struct]
46pub struct IDBFactory {
47 reflector_: Reflector,
48 connections:
53 DomRefCell<HashMapTracedValues<DBName, HashMapTracedValues<Uuid, Dom<IDBOpenDBRequest>>>>,
54
55 indexeddb_transactions: DomRefCell<HashMapTracedValues<DBName, Vec<Dom<IDBTransaction>>>>,
58
59 #[no_trace]
60 callback: DomRefCell<Option<GenericCallback<ConnectionMsg>>>,
61}
62
63impl IDBFactory {
64 pub fn new_inherited() -> IDBFactory {
65 IDBFactory {
66 reflector_: Reflector::new(),
67 connections: Default::default(),
68 indexeddb_transactions: Default::default(),
69 callback: Default::default(),
70 }
71 }
72
73 pub(crate) fn register_indexeddb_transaction(&self, txn: &IDBTransaction) {
74 let db_name = DBName(txn.get_db_name().to_string());
75 let mut map = self.indexeddb_transactions.borrow_mut();
76 let bucket = map.entry(db_name).or_default();
77 if !bucket.iter().any(|entry| &**entry == txn) {
78 bucket.push(Dom::from_ref(txn));
79 }
80 txn.set_registered_in_global();
81 }
82
83 pub(crate) fn unregister_indexeddb_transaction(&self, txn: &IDBTransaction) {
84 let db_name = DBName(txn.get_db_name().to_string());
85 let mut map = self.indexeddb_transactions.borrow_mut();
86 if let Some(bucket) = map.get_mut(&db_name) {
87 bucket.retain(|entry| &**entry != txn);
88 if bucket.is_empty() {
89 map.remove(&db_name);
90 }
91 }
92 txn.clear_registered_in_global();
93 }
94
95 pub(crate) fn cleanup_indexeddb_transactions(&self) -> bool {
96 let snapshot: Vec<DomRoot<IDBTransaction>> = {
99 let mut map = self.indexeddb_transactions.borrow_mut();
100
101 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
105 for key in keys {
106 if let Some(bucket) = map.get_mut(&key) {
107 bucket.retain(|txn| !txn.is_finished());
108 if bucket.is_empty() {
109 map.remove(&key);
110 }
111 }
112 }
113
114 map.iter()
115 .flat_map(|(_db, bucket)| bucket.iter())
116 .map(|txn| DomRoot::from_ref(&**txn))
117 .collect()
118 };
119 let any_matching = snapshot
129 .iter()
130 .any(|txn| txn.cleanup_event_loop_matches_current());
131
132 if !any_matching {
133 return false;
134 }
135
136 for txn in snapshot {
137 if txn.cleanup_event_loop_matches_current() {
138 txn.set_active_flag(false);
139 txn.clear_cleanup_event_loop();
140 if txn.is_usable() {
141 txn.maybe_commit();
142 }
143 }
144 }
145
146 let mut map = self.indexeddb_transactions.borrow_mut();
148 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
149 for key in keys {
150 if let Some(bucket) = map.get_mut(&key) {
151 bucket.retain(|txn| !txn.is_finished());
152 if bucket.is_empty() {
153 map.remove(&key);
154 }
155 }
156 }
157
158 true
159 }
160
161 pub(crate) fn maybe_commit_txn(&self, db_name: &str, txn_serial: u64) {
162 let key = DBName(db_name.to_string());
163 let snapshot: Vec<DomRoot<IDBTransaction>> = {
164 let map = self.indexeddb_transactions.borrow();
165 let Some(bucket) = map.get(&key) else {
166 return;
167 };
168 bucket.iter().map(|t| DomRoot::from_ref(&**t)).collect()
169 };
170
171 for txn in snapshot {
172 if txn.get_serial_number() == txn_serial {
173 txn.maybe_commit();
174 break;
175 }
176 }
177 }
178
179 pub(crate) fn clear_open_request_transaction_for_txn(&self, transaction: &IDBTransaction) {
182 let requests: Vec<DomRoot<IDBOpenDBRequest>> = {
183 let pending = self.connections.borrow();
184 pending
185 .iter()
186 .flat_map(|(_db_name, entry)| entry.iter())
187 .map(|(_id, request)| request.as_rooted())
188 .collect()
189 };
190 let mut cleared = 0usize;
191 for request in requests {
192 cleared += request.clear_transaction_if_matches(transaction) as usize;
193 }
194
195 debug_assert_eq!(
196 cleared, 1,
197 "A versionchange transaction should belong to exactly one IDBOpenDBRequest."
198 );
199 }
200
201 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBFactory> {
202 reflect_dom_object(Box::new(IDBFactory::new_inherited()), global, can_gc)
203 }
204
205 fn get_or_setup_callback(&self) -> GenericCallback<ConnectionMsg> {
207 if let Some(cb) = self.callback.borrow().as_ref() {
208 return cb.clone();
209 }
210
211 let global = self.global();
212 let response_listener = Trusted::new(self);
213
214 let task_source = global
215 .task_manager()
216 .database_access_task_source()
217 .to_sendable();
218 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
219 let response_listener = response_listener.clone();
220 let response = match message {
221 Ok(inner) => inner,
222 Err(err) => return error!("Error in IndexedDB factory callback {:?}.", err),
223 };
224 task_source.queue(task!(set_request_result_to_database: move |cx| {
226 let factory = response_listener.root();
227 factory.handle_connection_message(cx, response)
228 }));
229 })
230 .expect("Could not create open database callback");
231
232 *self.callback.borrow_mut() = Some(callback.clone());
233
234 callback
235 }
236
237 fn get_request(&self, name: String, request_id: &Uuid) -> Option<DomRoot<IDBOpenDBRequest>> {
238 let name = DBName(name);
239 let mut pending = self.connections.borrow_mut();
240 let Some(entry) = pending.get_mut(&name) else {
241 debug_assert!(false, "There should be a pending connection for {:?}", name);
242 return None;
243 };
244 let Some(request) = entry.get_mut(request_id) else {
245 debug_assert!(
246 false,
247 "There should be a pending connection for {:?}",
248 request_id
249 );
250 return None;
251 };
252 Some(request.as_rooted())
253 }
254
255 fn handle_connection_message(&self, cx: &mut JSContext, response: ConnectionMsg) {
260 match response {
261 ConnectionMsg::Connection {
262 name,
263 id,
264 version,
265 upgraded,
266 object_store_names,
267 } => {
268 let Some(request) = self.get_request(name.clone(), &id) else {
269 return debug_assert!(
270 false,
271 "There should be a request to handle ConnectionMsg::Connection."
272 );
273 };
274
275 let connection = request.get_or_init_connection(
278 cx,
279 &self.global(),
280 name.clone(),
281 version,
282 upgraded,
283 );
284 connection.set_object_store_names_from_backend(object_store_names);
285
286 request.dispatch_success(cx, name, version, upgraded);
291 },
292 ConnectionMsg::Upgrade {
293 name,
294 id,
295 version,
296 old_version,
297 transaction,
298 object_store_names,
299 } => {
300 let global = self.global();
301
302 let Some(request) = self.get_request(name.clone(), &id) else {
303 return debug_assert!(
304 false,
305 "There should be a request to handle ConnectionMsg::Upgrade."
306 );
307 };
308
309 let connection = request.get_or_init_connection(cx, &global, name, version, false);
310 connection.set_object_store_names_from_backend(object_store_names);
313 request.upgrade_db_version(cx, &connection, old_version, version, transaction);
314 },
315 ConnectionMsg::VersionError { name, id } => {
316 self.dispatch_error(cx, name, id, Error::Version(None));
318 },
319 ConnectionMsg::AbortError { name, id } => {
320 self.dispatch_error(cx, name, id, Error::Abort(None));
322 },
323 ConnectionMsg::DatabaseError { name, id, error } => {
324 self.dispatch_error(cx, name, id, map_backend_error_to_dom_error(error));
326 },
327 ConnectionMsg::VersionChange {
328 name,
329 id,
330 version,
331 old_version,
332 } => {
333 let global = self.global();
334 let Some(request) = self.get_request(name.clone(), &id) else {
335 return debug_assert!(
336 false,
337 "There should be a request to handle ConnectionMsg::VersionChange."
338 );
339 };
340 let connection =
341 request.get_or_init_connection(cx, &global, name.clone(), version, false);
342
343 connection.dispatch_versionchange(cx, old_version, Some(version));
345
346 let operation = SyncOperation::NotifyEndOfVersionChange {
349 id,
350 name,
351 old_version,
352 origin: global.origin().immutable().clone(),
353 };
354 if global
355 .storage_threads()
356 .send(IndexedDBThreadMsg::Sync(operation))
357 .is_err()
358 {
359 error!("Failed to send SyncOperation::NotifyEndOfVersionChange.");
360 }
361 },
362 ConnectionMsg::Blocked {
363 name,
364 id,
365 version,
366 old_version,
367 } => {
368 let Some(request) = self.get_request(name, &id) else {
369 return debug_assert!(
370 false,
371 "There should be a request to handle ConnectionMsg::VersionChange."
372 );
373 };
374
375 request.dispatch_blocked(cx, old_version, Some(version));
377 },
378 ConnectionMsg::TxnMaybeCommit { db_name, txn } => {
379 let factory = Trusted::new(self);
380 self.global()
381 .task_manager()
382 .dom_manipulation_task_source()
383 .queue(task!(indexeddb_maybe_commit_txn: move || {
384 let factory = factory.root();
385 factory.maybe_commit_txn(&db_name, txn);
386 }));
387 },
388 }
389 }
390
391 fn dispatch_error(
394 &self,
395 cx: &mut JSContext,
396 name: String,
397 request_id: Uuid,
398 dom_exception: Error,
399 ) {
400 let name = DBName(name);
401
402 let request = {
404 let mut pending = self.connections.borrow_mut();
405 let Some(entry) = pending.get_mut(&name) else {
406 return debug_assert!(false, "There should be a pending connection for {:?}", name);
407 };
408 let Some(request) = entry.get_mut(&request_id) else {
409 return debug_assert!(
410 false,
411 "There should be a pending connection for {:?}",
412 request_id
413 );
414 };
415 request.as_rooted()
416 };
417 let global = request.global();
418
419 request.set_result(HandleValue::undefined());
421
422 request.set_error(Some(dom_exception), CanGc::from_cx(cx));
424 request.clear_transaction();
430
431 request.set_ready_state_done();
433
434 let event = Event::new(
438 &global,
439 Atom::from("error"),
440 EventBubbles::Bubbles,
441 EventCancelable::Cancelable,
442 CanGc::from_cx(cx),
443 );
444 event.fire(request.upcast(), CanGc::from_cx(cx));
445 }
446
447 fn open_database(
449 &self,
450 storage_key: ImmutableOrigin,
451 name: DOMString,
452 version: Option<u64>,
453 request: &IDBOpenDBRequest,
454 proxy_map: StorageProxyMap,
455 ) -> Result<(), ()> {
456 let global = self.global();
457 let request_id = request.get_id();
458
459 {
460 let mut pending = self.connections.borrow_mut();
461 let outer = pending.entry(DBName(name.to_string())).or_default();
462 outer.insert(request_id, Dom::from_ref(request));
463 }
464
465 let callback = self.get_or_setup_callback();
466
467 let open_operation = SyncOperation::OpenDatabase(
471 callback,
472 storage_key,
473 name.to_string(),
474 version,
475 request.get_id(),
476 proxy_map,
477 );
478
479 if global
481 .storage_threads()
482 .send(IndexedDBThreadMsg::Sync(open_operation))
483 .is_err()
484 {
485 return Err(());
486 }
487 Ok(())
488 }
489
490 pub(crate) fn abort_pending_upgrades(&self) {
491 let global = self.global();
492 let pending = self.connections.borrow();
493 let pending_upgrades = pending
494 .iter()
495 .map(|(key, val)| {
496 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
497 (key.0.clone(), ids)
498 })
499 .collect();
500 let origin = global.origin().immutable().clone();
501 let Ok(proxy_map) = self.obtain_a_local_storage_bottle_map(&global, origin.clone()) else {
502 debug_assert!(false, "Failed to obtain a proxy map.");
503 return;
504 };
505 if global
506 .storage_threads()
507 .send(IndexedDBThreadMsg::Sync(
508 SyncOperation::AbortPendingUpgrades {
509 pending_upgrades,
510 origin,
511 proxy_map,
512 },
513 ))
514 .is_err()
515 {
516 error!("Failed to send SyncOperation::AbortPendingUpgrade");
517 }
518 }
519
520 fn obtain_a_local_storage_bottle_map(
523 &self,
524 global: &GlobalScope,
525 origin: ImmutableOrigin,
526 ) -> Result<StorageProxyMap, Error> {
527 let handle = global.storage_threads().client_storage_handle();
528 let message = handle
529 .obtain_a_storage_bottle_map(
530 StorageType::Local,
531 global.webview_id(),
532 StorageIdentifier::IndexedDB,
533 origin,
534 )
535 .recv();
536 let Ok(response) = message else {
537 return Err(Error::Operation(None));
538 };
539 let Ok(proxy_map) = response else {
540 return Err(Error::Operation(None));
541 };
542 Ok(proxy_map)
543 }
544}
545
546impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
547 fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
549 if version == Some(0) {
551 return Err(Error::Type(
552 c"The version must be an integer >= 1".to_owned(),
553 ));
554 };
555
556 let global = self.global();
558
559 let Some(storage_key) = global.obtain_storage_key() else {
562 return Err(Error::Security(None));
563 };
564
565 let proxy_map =
568 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
569
570 let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
572
573 if self
575 .open_database(storage_key, name, version, &request, proxy_map)
576 .is_err()
577 {
578 return Err(Error::Operation(None));
579 }
580
581 Ok(request)
583 }
584
585 fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
587 let global = self.global();
589
590 let Some(storage_key) = global.obtain_storage_key() else {
593 return Err(Error::Security(None));
594 };
595
596 let proxy_map =
599 self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
600
601 let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
603
604 if request
606 .delete_database(storage_key, name.to_string(), proxy_map)
607 .is_err()
608 {
609 return Err(Error::Operation(None));
610 }
611
612 Ok(request)
614 }
615
616 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
618 let global = self.global();
620
621 let storage_key = match global.obtain_storage_key() {
624 Some(storage_key) => storage_key,
625 None => {
626 let p = Promise::new(&global, CanGc::from_cx(cx));
627 p.reject_error(Error::Security(None), CanGc::from_cx(cx));
628 return p;
629 },
630 };
631
632 let p = Promise::new(&global, CanGc::from_cx(cx));
634
635 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
638
639 let task_source = global
642 .task_manager()
643 .database_access_task_source()
644 .to_sendable();
645 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
646 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
647 let Some(trusted_promise) = trusted_promise.take() else {
648 return error!("Callback for `DataBases` called twice.");
649 };
650
651 task_source.queue(task!(set_request_result_to_database: move |cx| {
653 let promise = trusted_promise.root();
654 match result {
655 Err(err) => {
656 let error = map_backend_error_to_dom_error(err);
657 rooted!(&in(cx) let mut rval = UndefinedValue());
658 error
659 .to_jsval(cx.into(), &promise.global(), rval.handle_mut(), CanGc::from_cx(cx));
660 promise.reject_native(&rval.handle(), CanGc::from_cx(cx));
661 },
662 Ok(info_list) => {
663 let info_list: Vec<IDBDatabaseInfo> = info_list
664 .into_iter()
665 .map(|info| IDBDatabaseInfo {
666 name: Some(DOMString::from(info.name)),
667 version: Some(info.version),
668 })
669 .collect();
670 promise.resolve_native(&info_list, CanGc::from_cx(cx));
671 },
672 }
673 }));
674 })
675 .expect("Could not create databases callback");
676
677 let get_operation = SyncOperation::GetDatabases(callback, storage_key);
678 if global
679 .storage_threads()
680 .send(IndexedDBThreadMsg::Sync(get_operation))
681 .is_err()
682 {
683 error!("Failed to send SyncOperation::GetDatabases");
684 }
685
686 p
688 }
689
690 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
692 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
693 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
694 let cmp = first_key.partial_cmp(&second_key);
695 if let Some(cmp) = cmp {
696 match cmp {
697 std::cmp::Ordering::Less => Ok(-1),
698 std::cmp::Ordering::Equal => Ok(0),
699 std::cmp::Ordering::Greater => Ok(1),
700 }
701 } else {
702 Ok(i16::MAX)
703 }
704 }
705}