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 } => {
264 let Some(request) = self.get_request(name.clone(), &id) else {
265 return debug_assert!(
266 false,
267 "There should be a request to handle ConnectionMsg::Connection."
268 );
269 };
270
271 request.dispatch_success(name, version, upgraded, can_gc);
276 },
277 ConnectionMsg::Upgrade {
278 name,
279 id,
280 version,
281 old_version,
282 transaction,
283 } => {
284 let global = self.global();
285
286 let Some(request) = self.get_request(name.clone(), &id) else {
287 return debug_assert!(
288 false,
289 "There should be a request to handle ConnectionMsg::Upgrade."
290 );
291 };
292
293 let connection =
294 request.get_or_init_connection(&global, name, version, false, can_gc);
295 request.upgrade_db_version(&connection, old_version, version, transaction, can_gc);
296 },
297 ConnectionMsg::VersionError { name, id } => {
298 self.dispatch_error(name, id, Error::Version(None), can_gc);
300 },
301 ConnectionMsg::AbortError { name, id } => {
302 self.dispatch_error(name, id, Error::Abort(None), can_gc);
304 },
305 ConnectionMsg::DatabaseError { name, id, error } => {
306 self.dispatch_error(name, id, map_backend_error_to_dom_error(error), can_gc);
308 },
309 ConnectionMsg::VersionChange {
310 name,
311 id,
312 version,
313 old_version,
314 } => {
315 let global = self.global();
316 let Some(request) = self.get_request(name.clone(), &id) else {
317 return debug_assert!(
318 false,
319 "There should be a request to handle ConnectionMsg::VersionChange."
320 );
321 };
322 let connection =
323 request.get_or_init_connection(&global, name.clone(), version, false, can_gc);
324
325 connection.dispatch_versionchange(old_version, Some(version), can_gc);
327
328 let operation = SyncOperation::NotifyEndOfVersionChange {
331 id,
332 name,
333 old_version,
334 origin: global.origin().immutable().clone(),
335 };
336 if global
337 .storage_threads()
338 .send(IndexedDBThreadMsg::Sync(operation))
339 .is_err()
340 {
341 error!("Failed to send SyncOperation::NotifyEndOfVersionChange.");
342 }
343 },
344 ConnectionMsg::Blocked {
345 name,
346 id,
347 version,
348 old_version,
349 } => {
350 let Some(request) = self.get_request(name, &id) else {
351 return debug_assert!(
352 false,
353 "There should be a request to handle ConnectionMsg::VersionChange."
354 );
355 };
356
357 request.dispatch_blocked(old_version, Some(version), can_gc);
359 },
360 ConnectionMsg::TxnMaybeCommit { db_name, txn } => {
361 let factory = Trusted::new(self);
362 self.global()
363 .task_manager()
364 .dom_manipulation_task_source()
365 .queue(task!(indexeddb_maybe_commit_txn: move || {
366 let factory = factory.root();
367 factory.maybe_commit_txn(&db_name, txn);
368 }));
369 },
370 }
371 }
372
373 fn dispatch_error(&self, name: String, request_id: Uuid, dom_exception: Error, can_gc: CanGc) {
376 let name = DBName(name);
377
378 let request = {
380 let mut pending = self.connections.borrow_mut();
381 let Some(entry) = pending.get_mut(&name) else {
382 return debug_assert!(false, "There should be a pending connection for {:?}", name);
383 };
384 let Some(request) = entry.get_mut(&request_id) else {
385 return debug_assert!(
386 false,
387 "There should be a pending connection for {:?}",
388 request_id
389 );
390 };
391 request.as_rooted()
392 };
393 let global = request.global();
394
395 request.set_result(HandleValue::undefined());
397
398 request.set_error(Some(dom_exception), can_gc);
400 request.clear_transaction();
406
407 request.set_ready_state_done();
409
410 let event = Event::new(
414 &global,
415 Atom::from("error"),
416 EventBubbles::Bubbles,
417 EventCancelable::Cancelable,
418 can_gc,
419 );
420 event.fire(request.upcast(), can_gc);
421 }
422
423 fn open_database(
425 &self,
426 name: DOMString,
427 version: Option<u64>,
428 request: &IDBOpenDBRequest,
429 ) -> Result<(), ()> {
430 let global = self.global();
431 let request_id = request.get_id();
432
433 {
434 let mut pending = self.connections.borrow_mut();
435 let outer = pending.entry(DBName(name.to_string())).or_default();
436 outer.insert(request_id, Dom::from_ref(request));
437 }
438
439 let callback = self.get_or_setup_callback();
440
441 let open_operation = SyncOperation::OpenDatabase(
442 callback,
443 global.origin().immutable().clone(),
444 name.to_string(),
445 version,
446 request.get_id(),
447 );
448
449 if global
451 .storage_threads()
452 .send(IndexedDBThreadMsg::Sync(open_operation))
453 .is_err()
454 {
455 return Err(());
456 }
457 Ok(())
458 }
459
460 pub(crate) fn abort_pending_upgrades(&self) {
461 let global = self.global();
462 let pending = self.connections.borrow();
463 let pending_upgrades = pending
464 .iter()
465 .map(|(key, val)| {
466 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
467 (key.0.clone(), ids)
468 })
469 .collect();
470 let origin = global.origin().immutable().clone();
471 if global
472 .storage_threads()
473 .send(IndexedDBThreadMsg::Sync(
474 SyncOperation::AbortPendingUpgrades {
475 pending_upgrades,
476 origin,
477 },
478 ))
479 .is_err()
480 {
481 error!("Failed to send SyncOperation::AbortPendingUpgrade");
482 }
483 }
484}
485
486impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
487 fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
489 if version == Some(0) {
491 return Err(Error::Type(
492 c"The version must be an integer >= 1".to_owned(),
493 ));
494 };
495
496 let global = self.global();
501 let origin = global.origin();
502
503 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
508 return Err(Error::Security(None));
509 }
510
511 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
513
514 if self.open_database(name, version, &request).is_err() {
516 return Err(Error::Operation(None));
517 }
518
519 Ok(request)
521 }
522
523 fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
525 let global = self.global();
527
528 let origin = global.origin();
532
533 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
537 return Err(Error::Security(None));
538 }
539
540 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
542
543 if request.delete_database(name.to_string()).is_err() {
545 return Err(Error::Operation(None));
546 }
547
548 Ok(request)
550 }
551
552 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
554 let global = self.global();
556
557 let p = Promise::new(&global, CanGc::from_cx(cx));
563
564 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
567
568 let task_source = global
571 .task_manager()
572 .database_access_task_source()
573 .to_sendable();
574 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
575 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
576 let Some(trusted_promise) = trusted_promise.take() else {
577 return error!("Callback for `DataBases` called twice.");
578 };
579
580 task_source.queue(task!(set_request_result_to_database: move |cx| {
582 let promise = trusted_promise.root();
583 match result {
584 Err(err) => {
585 let error = map_backend_error_to_dom_error(err);
586 rooted!(&in(cx) let mut rval = UndefinedValue());
587 error
588 .clone()
589 .to_jsval(cx.into(), &promise.global(), rval.handle_mut(), CanGc::from_cx(cx));
590 promise.reject_native(&rval.handle(), CanGc::from_cx(cx));
591 },
592 Ok(info_list) => {
593 let info_list: Vec<IDBDatabaseInfo> = info_list
594 .into_iter()
595 .map(|info| IDBDatabaseInfo {
596 name: Some(DOMString::from(info.name)),
597 version: Some(info.version),
598 })
599 .collect();
600 promise.resolve_native(&info_list, CanGc::from_cx(cx));
601 },
602 }
603 }));
604 })
605 .expect("Could not create delete database callback");
606
607 let get_operation =
608 SyncOperation::GetDatabases(callback, global.origin().immutable().clone());
609 if global
610 .storage_threads()
611 .send(IndexedDBThreadMsg::Sync(get_operation))
612 .is_err()
613 {
614 error!("Failed to send SyncOperation::GetDatabases");
615 }
616
617 p
619 }
620
621 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
623 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
624 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
625 let cmp = first_key.partial_cmp(&second_key);
626 if let Some(cmp) = cmp {
627 match cmp {
628 std::cmp::Ordering::Less => Ok(-1),
629 std::cmp::Ordering::Equal => Ok(0),
630 std::cmp::Ordering::Greater => Ok(1),
631 }
632 } else {
633 Ok(i16::MAX)
634 }
635 }
636}