1use std::collections::HashSet;
5use std::rc::Rc;
6
7use base::generic_channel::GenericSend;
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::jsval::UndefinedValue;
11use js::rust::HandleValue;
12use profile_traits::generic_callback::GenericCallback;
13use script_bindings::inheritance::Castable;
14use servo_url::origin::ImmutableOrigin;
15use storage_traits::indexeddb::{
16 BackendResult, ConnectionMsg, DatabaseInfo, IndexedDBThreadMsg, SyncOperation,
17};
18use stylo_atoms::Atom;
19use uuid::Uuid;
20
21use crate::dom::bindings::cell::DomRefCell;
22use crate::dom::bindings::codegen::Bindings::IDBFactoryBinding::{
23 IDBDatabaseInfo, IDBFactoryMethods,
24};
25use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
26use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
27use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::bindings::str::DOMString;
30use crate::dom::bindings::trace::HashMapTracedValues;
31use crate::dom::event::{Event, EventBubbles, EventCancelable};
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::indexeddb::idbopendbrequest::IDBOpenDBRequest;
34use crate::dom::promise::Promise;
35use crate::dom::types::IDBTransaction;
36use crate::indexeddb::{convert_value_to_key, map_backend_error_to_dom_error};
37use crate::script_runtime::CanGc;
38
39#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
41pub(crate) struct DBName(pub(crate) String);
42
43#[dom_struct]
44pub struct IDBFactory {
45 reflector_: Reflector,
46 connections:
51 DomRefCell<HashMapTracedValues<DBName, HashMapTracedValues<Uuid, Dom<IDBOpenDBRequest>>>>,
52
53 indexeddb_transactions: DomRefCell<HashMapTracedValues<DBName, Vec<Dom<IDBTransaction>>>>,
56
57 #[no_trace]
58 callback: DomRefCell<Option<GenericCallback<ConnectionMsg>>>,
59}
60
61impl IDBFactory {
62 pub fn new_inherited() -> IDBFactory {
63 IDBFactory {
64 reflector_: Reflector::new(),
65 connections: Default::default(),
66 indexeddb_transactions: Default::default(),
67 callback: Default::default(),
68 }
69 }
70
71 pub(crate) fn register_indexeddb_transaction(&self, txn: &IDBTransaction) {
72 let db_name = DBName(txn.get_db_name().to_string());
73 let mut map = self.indexeddb_transactions.borrow_mut();
74 let bucket = map.entry(db_name).or_default();
75 if !bucket.iter().any(|entry| &**entry == txn) {
76 bucket.push(Dom::from_ref(txn));
77 }
78 txn.set_registered_in_global();
79 }
80
81 pub(crate) fn unregister_indexeddb_transaction(&self, txn: &IDBTransaction) {
82 let db_name = DBName(txn.get_db_name().to_string());
83 let mut map = self.indexeddb_transactions.borrow_mut();
84 if let Some(bucket) = map.get_mut(&db_name) {
85 bucket.retain(|entry| &**entry != txn);
86 if bucket.is_empty() {
87 map.remove(&db_name);
88 }
89 }
90 txn.clear_registered_in_global();
91 }
92
93 pub(crate) fn cleanup_indexeddb_transactions(&self) -> bool {
94 let snapshot: Vec<DomRoot<IDBTransaction>> = {
97 let mut map = self.indexeddb_transactions.borrow_mut();
98
99 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
103 for key in keys {
104 if let Some(bucket) = map.get_mut(&key) {
105 bucket.retain(|txn| !txn.is_finished());
106 if bucket.is_empty() {
107 map.remove(&key);
108 }
109 }
110 }
111
112 map.iter()
113 .flat_map(|(_db, bucket)| bucket.iter())
114 .map(|txn| DomRoot::from_ref(&**txn))
115 .collect()
116 };
117 let any_matching = snapshot
127 .iter()
128 .any(|txn| txn.cleanup_event_loop_matches_current());
129
130 if !any_matching {
131 return false;
132 }
133
134 for txn in snapshot {
135 if txn.cleanup_event_loop_matches_current() {
136 txn.set_active_flag(false);
137 txn.clear_cleanup_event_loop();
138 if txn.is_usable() {
139 txn.maybe_commit();
140 }
141 }
142 }
143
144 let mut map = self.indexeddb_transactions.borrow_mut();
146 let keys: Vec<DBName> = map.iter().map(|(k, _)| k.clone()).collect();
147 for key in keys {
148 if let Some(bucket) = map.get_mut(&key) {
149 bucket.retain(|txn| !txn.is_finished());
150 if bucket.is_empty() {
151 map.remove(&key);
152 }
153 }
154 }
155
156 true
157 }
158
159 pub(crate) fn maybe_commit_txn(&self, db_name: &str, txn_serial: u64) {
160 let key = DBName(db_name.to_string());
161 let snapshot: Vec<DomRoot<IDBTransaction>> = {
162 let map = self.indexeddb_transactions.borrow();
163 let Some(bucket) = map.get(&key) else {
164 return;
165 };
166 bucket.iter().map(|t| DomRoot::from_ref(&**t)).collect()
167 };
168
169 for txn in snapshot {
170 if txn.get_serial_number() == txn_serial {
171 txn.maybe_commit();
172 break;
173 }
174 }
175 }
176
177 pub(crate) fn clear_open_request_transaction_for_txn(&self, transaction: &IDBTransaction) {
180 let requests: Vec<DomRoot<IDBOpenDBRequest>> = {
181 let pending = self.connections.borrow();
182 pending
183 .iter()
184 .flat_map(|(_db_name, entry)| entry.iter())
185 .map(|(_id, request)| request.as_rooted())
186 .collect()
187 };
188 let mut cleared = 0usize;
189 for request in requests {
190 cleared += request.clear_transaction_if_matches(transaction) as usize;
191 }
192
193 debug_assert_eq!(
194 cleared, 1,
195 "A versionchange transaction should belong to exactly one IDBOpenDBRequest."
196 );
197 }
198
199 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBFactory> {
200 reflect_dom_object(Box::new(IDBFactory::new_inherited()), global, can_gc)
201 }
202
203 fn get_or_setup_callback(&self) -> GenericCallback<ConnectionMsg> {
205 if let Some(cb) = self.callback.borrow().as_ref() {
206 return cb.clone();
207 }
208
209 let global = self.global();
210 let response_listener = Trusted::new(self);
211
212 let task_source = global
213 .task_manager()
214 .database_access_task_source()
215 .to_sendable();
216 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
217 let response_listener = response_listener.clone();
218 let response = match message {
219 Ok(inner) => inner,
220 Err(err) => return error!("Error in IndexedDB factory callback {:?}.", err),
221 };
222 task_source.queue(task!(set_request_result_to_database: move || {
223 let factory = response_listener.root();
224 factory.handle_connection_message(response, CanGc::note())
225 }));
226 })
227 .expect("Could not create open database callback");
228
229 *self.callback.borrow_mut() = Some(callback.clone());
230
231 callback
232 }
233
234 fn get_request(&self, name: String, request_id: &Uuid) -> Option<DomRoot<IDBOpenDBRequest>> {
235 let name = DBName(name);
236 let mut pending = self.connections.borrow_mut();
237 let Some(entry) = pending.get_mut(&name) else {
238 debug_assert!(false, "There should be a pending connection for {:?}", name);
239 return None;
240 };
241 let Some(request) = entry.get_mut(request_id) else {
242 debug_assert!(
243 false,
244 "There should be a pending connection for {:?}",
245 request_id
246 );
247 return None;
248 };
249 Some(request.as_rooted())
250 }
251
252 fn handle_connection_message(&self, response: ConnectionMsg, can_gc: CanGc) {
257 match response {
258 ConnectionMsg::Connection {
259 name,
260 id,
261 version,
262 upgraded,
263 object_store_names,
264 } => {
265 let Some(request) = self.get_request(name.clone(), &id) else {
266 return debug_assert!(
267 false,
268 "There should be a request to handle ConnectionMsg::Connection."
269 );
270 };
271
272 let connection = request.get_or_init_connection(
275 &self.global(),
276 name.clone(),
277 version,
278 upgraded,
279 can_gc,
280 );
281 connection.set_object_store_names_from_backend(object_store_names);
282
283 request.dispatch_success(name, version, upgraded, can_gc);
288 },
289 ConnectionMsg::Upgrade {
290 name,
291 id,
292 version,
293 old_version,
294 transaction,
295 object_store_names,
296 } => {
297 let global = self.global();
298
299 let Some(request) = self.get_request(name.clone(), &id) else {
300 return debug_assert!(
301 false,
302 "There should be a request to handle ConnectionMsg::Upgrade."
303 );
304 };
305
306 let connection =
307 request.get_or_init_connection(&global, name, version, false, can_gc);
308 connection.set_object_store_names_from_backend(object_store_names);
311 request.upgrade_db_version(&connection, old_version, version, transaction, can_gc);
312 },
313 ConnectionMsg::VersionError { name, id } => {
314 self.dispatch_error(name, id, Error::Version(None), can_gc);
316 },
317 ConnectionMsg::AbortError { name, id } => {
318 self.dispatch_error(name, id, Error::Abort(None), can_gc);
320 },
321 ConnectionMsg::DatabaseError { name, id, error } => {
322 self.dispatch_error(name, id, map_backend_error_to_dom_error(error), can_gc);
324 },
325 ConnectionMsg::VersionChange {
326 name,
327 id,
328 version,
329 old_version,
330 } => {
331 let global = self.global();
332 let Some(request) = self.get_request(name.clone(), &id) else {
333 return debug_assert!(
334 false,
335 "There should be a request to handle ConnectionMsg::VersionChange."
336 );
337 };
338 let connection =
339 request.get_or_init_connection(&global, name.clone(), version, false, can_gc);
340
341 connection.dispatch_versionchange(old_version, Some(version), can_gc);
343
344 let operation = SyncOperation::NotifyEndOfVersionChange {
347 id,
348 name,
349 old_version,
350 origin: global.origin().immutable().clone(),
351 };
352 if global
353 .storage_threads()
354 .send(IndexedDBThreadMsg::Sync(operation))
355 .is_err()
356 {
357 error!("Failed to send SyncOperation::NotifyEndOfVersionChange.");
358 }
359 },
360 ConnectionMsg::Blocked {
361 name,
362 id,
363 version,
364 old_version,
365 } => {
366 let Some(request) = self.get_request(name, &id) else {
367 return debug_assert!(
368 false,
369 "There should be a request to handle ConnectionMsg::VersionChange."
370 );
371 };
372
373 request.dispatch_blocked(old_version, Some(version), can_gc);
375 },
376 ConnectionMsg::TxnMaybeCommit { db_name, txn } => {
377 let factory = Trusted::new(self);
378 self.global()
379 .task_manager()
380 .dom_manipulation_task_source()
381 .queue(task!(indexeddb_maybe_commit_txn: move || {
382 let factory = factory.root();
383 factory.maybe_commit_txn(&db_name, txn);
384 }));
385 },
386 }
387 }
388
389 fn dispatch_error(&self, name: String, request_id: Uuid, dom_exception: Error, can_gc: CanGc) {
392 let name = DBName(name);
393
394 let request = {
396 let mut pending = self.connections.borrow_mut();
397 let Some(entry) = pending.get_mut(&name) else {
398 return debug_assert!(false, "There should be a pending connection for {:?}", name);
399 };
400 let Some(request) = entry.get_mut(&request_id) else {
401 return debug_assert!(
402 false,
403 "There should be a pending connection for {:?}",
404 request_id
405 );
406 };
407 request.as_rooted()
408 };
409 let global = request.global();
410
411 request.set_result(HandleValue::undefined());
413
414 request.set_error(Some(dom_exception), can_gc);
416 request.clear_transaction();
422
423 request.set_ready_state_done();
425
426 let event = Event::new(
430 &global,
431 Atom::from("error"),
432 EventBubbles::Bubbles,
433 EventCancelable::Cancelable,
434 can_gc,
435 );
436 event.fire(request.upcast(), can_gc);
437 }
438
439 fn open_database(
441 &self,
442 name: DOMString,
443 version: Option<u64>,
444 request: &IDBOpenDBRequest,
445 ) -> Result<(), ()> {
446 let global = self.global();
447 let request_id = request.get_id();
448
449 {
450 let mut pending = self.connections.borrow_mut();
451 let outer = pending.entry(DBName(name.to_string())).or_default();
452 outer.insert(request_id, Dom::from_ref(request));
453 }
454
455 let callback = self.get_or_setup_callback();
456
457 let open_operation = SyncOperation::OpenDatabase(
458 callback,
459 global.origin().immutable().clone(),
460 name.to_string(),
461 version,
462 request.get_id(),
463 );
464
465 if global
467 .storage_threads()
468 .send(IndexedDBThreadMsg::Sync(open_operation))
469 .is_err()
470 {
471 return Err(());
472 }
473 Ok(())
474 }
475
476 pub(crate) fn abort_pending_upgrades(&self) {
477 let global = self.global();
478 let pending = self.connections.borrow();
479 let pending_upgrades = pending
480 .iter()
481 .map(|(key, val)| {
482 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
483 (key.0.clone(), ids)
484 })
485 .collect();
486 let origin = global.origin().immutable().clone();
487 if global
488 .storage_threads()
489 .send(IndexedDBThreadMsg::Sync(
490 SyncOperation::AbortPendingUpgrades {
491 pending_upgrades,
492 origin,
493 },
494 ))
495 .is_err()
496 {
497 error!("Failed to send SyncOperation::AbortPendingUpgrade");
498 }
499 }
500}
501
502impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
503 fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
505 if version == Some(0) {
507 return Err(Error::Type(
508 c"The version must be an integer >= 1".to_owned(),
509 ));
510 };
511
512 let global = self.global();
517 let origin = global.origin();
518
519 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
524 return Err(Error::Security(None));
525 }
526
527 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
529
530 if self.open_database(name, version, &request).is_err() {
532 return Err(Error::Operation(None));
533 }
534
535 Ok(request)
537 }
538
539 fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
541 let global = self.global();
543
544 let origin = global.origin();
548
549 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
553 return Err(Error::Security(None));
554 }
555
556 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
558
559 if request.delete_database(name.to_string()).is_err() {
561 return Err(Error::Operation(None));
562 }
563
564 Ok(request)
566 }
567
568 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
570 let global = self.global();
572
573 let p = Promise::new(&global, CanGc::from_cx(cx));
579
580 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
583
584 let task_source = global
587 .task_manager()
588 .database_access_task_source()
589 .to_sendable();
590 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
591 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
592 let Some(trusted_promise) = trusted_promise.take() else {
593 return error!("Callback for `DataBases` called twice.");
594 };
595
596 task_source.queue(task!(set_request_result_to_database: move |cx| {
598 let promise = trusted_promise.root();
599 match result {
600 Err(err) => {
601 let error = map_backend_error_to_dom_error(err);
602 rooted!(&in(cx) let mut rval = UndefinedValue());
603 error
604 .to_jsval(cx.into(), &promise.global(), rval.handle_mut(), CanGc::from_cx(cx));
605 promise.reject_native(&rval.handle(), CanGc::from_cx(cx));
606 },
607 Ok(info_list) => {
608 let info_list: Vec<IDBDatabaseInfo> = info_list
609 .into_iter()
610 .map(|info| IDBDatabaseInfo {
611 name: Some(DOMString::from(info.name)),
612 version: Some(info.version),
613 })
614 .collect();
615 promise.resolve_native(&info_list, CanGc::from_cx(cx));
616 },
617 }
618 }));
619 })
620 .expect("Could not create delete database callback");
621
622 let get_operation =
623 SyncOperation::GetDatabases(callback, global.origin().immutable().clone());
624 if global
625 .storage_threads()
626 .send(IndexedDBThreadMsg::Sync(get_operation))
627 .is_err()
628 {
629 error!("Failed to send SyncOperation::GetDatabases");
630 }
631
632 p
634 }
635
636 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
638 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
639 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
640 let cmp = first_key.partial_cmp(&second_key);
641 if let Some(cmp) = cmp {
642 match cmp {
643 std::cmp::Ordering::Less => Ok(-1),
644 std::cmp::Ordering::Equal => Ok(0),
645 std::cmp::Ordering::Greater => Ok(1),
646 }
647 } else {
648 Ok(i16::MAX)
649 }
650 }
651}