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 BackendError, BackendResult, DatabaseInfo, IndexedDBThreadMsg, OpenDatabaseResult,
17 SyncOperation,
18};
19use stylo_atoms::Atom;
20use uuid::Uuid;
21
22use crate::dom::bindings::cell::DomRefCell;
23use crate::dom::bindings::codegen::Bindings::IDBFactoryBinding::{
24 IDBDatabaseInfo, IDBFactoryMethods,
25};
26use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
27use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
28use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
29use crate::dom::bindings::root::{Dom, DomRoot};
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::trace::HashMapTracedValues;
32use crate::dom::event::{Event, EventBubbles, EventCancelable};
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::indexeddb::idbdatabase::IDBDatabase;
35use crate::dom::indexeddb::idbopendbrequest::IDBOpenDBRequest;
36use crate::dom::promise::Promise;
37use crate::indexeddb::{convert_value_to_key, map_backend_error_to_dom_error};
38use crate::script_runtime::CanGc;
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 pending_connections:
50 DomRefCell<HashMapTracedValues<DBName, HashMapTracedValues<Uuid, Dom<IDBOpenDBRequest>>>>,
51}
52
53impl IDBFactory {
54 pub fn new_inherited() -> IDBFactory {
55 IDBFactory {
56 reflector_: Reflector::new(),
57 pending_connections: Default::default(),
58 }
59 }
60
61 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBFactory> {
62 reflect_dom_object(Box::new(IDBFactory::new_inherited()), global, can_gc)
63 }
64
65 fn handle_open_db(
68 &self,
69 name: String,
70 response: OpenDatabaseResult,
71 request_id: Uuid,
72 can_gc: CanGc,
73 ) {
74 let name = DBName(name);
75 let request = {
76 let mut pending = self.pending_connections.borrow_mut();
77 let Some(entry) = pending.get_mut(&name) else {
78 return debug_assert!(false, "There should be a pending connection for {:?}", name);
79 };
80 let Some(request) = entry.get_mut(&request_id) else {
81 return debug_assert!(
82 false,
83 "There should be a pending connection for {:?}",
84 request_id
85 );
86 };
87 request.as_rooted()
88 };
89 let global = request.global();
90 let finished = match response {
91 OpenDatabaseResult::Connection { version, upgraded } => {
92 request.dispatch_success(name.0.clone(), version, upgraded, can_gc);
97 true
98 },
99 OpenDatabaseResult::Upgrade {
100 version,
101 old_version,
102 transaction,
103 } => {
104 let connection = IDBDatabase::new(
106 &global,
107 DOMString::from_string(name.0.clone()),
108 version,
109 can_gc,
110 );
111 request.set_connection(&connection);
112 request.upgrade_db_version(&connection, old_version, version, transaction, can_gc);
113 false
114 },
115 OpenDatabaseResult::VersionError => {
116 self.dispatch_error(name.clone(), request_id, Error::Version(None), can_gc);
118 true
119 },
120 OpenDatabaseResult::AbortError => {
121 self.dispatch_error(name.clone(), request_id, Error::Abort(None), can_gc);
123 true
124 },
125 };
126 if finished {
127 self.note_end_of_open(&name, &request.get_id());
128 }
129 }
130
131 fn handle_backend_error(
132 &self,
133 name: String,
134 request_id: Uuid,
135 backend_error: BackendError,
136 can_gc: CanGc,
137 ) {
138 self.dispatch_error(
139 DBName(name),
140 request_id,
141 map_backend_error_to_dom_error(backend_error),
142 can_gc,
143 );
144 }
145
146 fn dispatch_error(&self, name: DBName, request_id: Uuid, dom_exception: Error, can_gc: CanGc) {
149 let request = {
151 let mut pending = self.pending_connections.borrow_mut();
152 let Some(entry) = pending.get_mut(&name) else {
153 return debug_assert!(false, "There should be a pending connection for {:?}", name);
154 };
155 let Some(request) = entry.get_mut(&request_id) else {
156 return debug_assert!(
157 false,
158 "There should be a pending connection for {:?}",
159 request_id
160 );
161 };
162 request.as_rooted()
163 };
164 let global = request.global();
165
166 request.set_result(HandleValue::undefined());
168
169 request.set_error(Some(dom_exception), can_gc);
171
172 let event = Event::new(
179 &global,
180 Atom::from("error"),
181 EventBubbles::Bubbles,
182 EventCancelable::Cancelable,
183 can_gc,
184 );
185 event.fire(request.upcast(), can_gc);
186 }
187
188 pub fn open_database(
190 &self,
191 name: DOMString,
192 version: Option<u64>,
193 request: &IDBOpenDBRequest,
194 ) -> Result<(), ()> {
195 let global = self.global();
196 let request_id = request.get_id();
197
198 {
199 let mut pending = self.pending_connections.borrow_mut();
200 let outer = pending.entry(DBName(name.to_string())).or_default();
201 outer.insert(request_id, Dom::from_ref(request));
202 }
203
204 let response_listener = Trusted::new(self);
205
206 let task_source = global
207 .task_manager()
208 .database_access_task_source()
209 .to_sendable();
210 let name = name.to_string();
211 let name_copy = name.clone();
212 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
213 let response_listener = response_listener.clone();
214 let name = name_copy.clone();
215 let request_id = request_id;
216 let backend_result = match message {
217 Ok(inner) => inner,
218 Err(err) => Err(BackendError::DbErr(format!("{err:?}"))),
219 };
220 task_source.queue(task!(set_request_result_to_database: move || {
221 let factory = response_listener.root();
222 match backend_result {
223 Ok(response) => {
224 factory.handle_open_db(name, response, request_id, CanGc::note())
225 }
226 Err(error) => factory.handle_backend_error(name, request_id, error, CanGc::note()),
227 }
228 }));
229 })
230 .expect("Could not create open database callback");
231
232 let open_operation = SyncOperation::OpenDatabase(
233 callback,
234 global.origin().immutable().clone(),
235 name.to_string(),
236 version,
237 request.get_id(),
238 );
239
240 if global
242 .storage_threads()
243 .send(IndexedDBThreadMsg::Sync(open_operation))
244 .is_err()
245 {
246 return Err(());
247 }
248 Ok(())
249 }
250
251 pub(crate) fn note_end_of_open(&self, name: &DBName, id: &Uuid) {
252 let mut pending = self.pending_connections.borrow_mut();
253 let empty = {
254 let Some(entry) = pending.get_mut(name) else {
255 return debug_assert!(false, "There should be a pending connection for {:?}", name);
256 };
257 entry.remove(id);
258 entry.is_empty()
259 };
260 if empty {
261 pending.remove(name);
262 }
263 }
264
265 pub(crate) fn abort_pending_upgrades(&self) {
266 let global = self.global();
267
268 let pending = self.pending_connections.borrow();
270 let pending_upgrades = pending
271 .iter()
272 .map(|(key, val)| {
273 let ids: HashSet<Uuid> = val.iter().map(|(k, _v)| *k).collect();
274 (key.0.clone(), ids)
275 })
276 .collect();
277 let origin = global.origin().immutable().clone();
278 if global
279 .storage_threads()
280 .send(IndexedDBThreadMsg::Sync(
281 SyncOperation::AbortPendingUpgrades {
282 pending_upgrades,
283 origin,
284 },
285 ))
286 .is_err()
287 {
288 error!("Failed to send SyncOperation::AbortPendingUpgrade");
289 }
290 }
291}
292
293impl IDBFactoryMethods<crate::DomTypeHolder> for IDBFactory {
294 fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
296 if version == Some(0) {
298 return Err(Error::Type(
299 "The version must be an integer >= 1".to_owned(),
300 ));
301 };
302
303 let global = self.global();
308 let origin = global.origin();
309
310 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
315 return Err(Error::Security(None));
316 }
317
318 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
320
321 if self.open_database(name, version, &request).is_err() {
323 return Err(Error::Operation(None));
324 }
325
326 Ok(request)
328 }
329
330 fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
332 let global = self.global();
334
335 let origin = global.origin();
339
340 if let ImmutableOrigin::Opaque(_) = origin.immutable() {
344 return Err(Error::Security(None));
345 }
346
347 let request = IDBOpenDBRequest::new(&self.global(), CanGc::note());
349
350 if request.delete_database(name.to_string()).is_err() {
352 return Err(Error::Operation(None));
353 }
354
355 Ok(request)
357 }
358
359 fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
361 let global = self.global();
363
364 let p = Promise::new(&global, CanGc::from_cx(cx));
370
371 let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
374
375 let task_source = global
378 .task_manager()
379 .database_access_task_source()
380 .to_sendable();
381 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
382 let result: BackendResult<Vec<DatabaseInfo>> = message.unwrap();
383 let Some(trusted_promise) = trusted_promise.take() else {
384 return error!("Callback for `DataBases` called twice.");
385 };
386
387 task_source.queue(task!(set_request_result_to_database: move |cx| {
389 let promise = trusted_promise.root();
390 match result {
391 Err(err) => {
392 let error = map_backend_error_to_dom_error(err);
393 rooted!(&in(cx) let mut rval = UndefinedValue());
394 error
395 .clone()
396 .to_jsval(cx.into(), &promise.global(), rval.handle_mut(), CanGc::from_cx(cx));
397 promise.reject_native(&rval.handle(), CanGc::from_cx(cx));
398 },
399 Ok(info_list) => {
400 let info_list: Vec<IDBDatabaseInfo> = info_list
401 .into_iter()
402 .map(|info| IDBDatabaseInfo {
403 name: Some(DOMString::from(info.name)),
404 version: Some(info.version),
405 })
406 .collect();
407 promise.resolve_native(&info_list, CanGc::from_cx(cx));
408 },
409 }
410 }));
411 })
412 .expect("Could not create delete database callback");
413
414 let get_operation =
415 SyncOperation::GetDatabases(callback, global.origin().immutable().clone());
416 if global
417 .storage_threads()
418 .send(IndexedDBThreadMsg::Sync(get_operation))
419 .is_err()
420 {
421 error!("Failed to send SyncOperation::GetDatabases");
422 }
423
424 p
426 }
427
428 fn Cmp(&self, cx: &mut JSContext, first: HandleValue, second: HandleValue) -> Fallible<i16> {
430 let first_key = convert_value_to_key(cx, first, None)?.into_result()?;
431 let second_key = convert_value_to_key(cx, second, None)?.into_result()?;
432 let cmp = first_key.partial_cmp(&second_key);
433 if let Some(cmp) = cmp {
434 match cmp {
435 std::cmp::Ordering::Less => Ok(-1),
436 std::cmp::Ordering::Equal => Ok(0),
437 std::cmp::Ordering::Greater => Ok(1),
438 }
439 } else {
440 Ok(i16::MAX)
441 }
442 }
443}