Skip to main content

script/dom/indexeddb/
idbfactory.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4use 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/// A non-jstraceable string wrapper for use in `HashMapTracedValues`.
42#[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    /// <https://www.w3.org/TR/IndexedDB-3/#connection>
49    /// The connections opened through this factory.
50    /// We store the open request, which contains the connection.
51    /// TODO: remove when we are sure they are not needed anymore.
52    connections:
53        DomRefCell<HashMapTracedValues<DBName, HashMapTracedValues<Uuid, Dom<IDBOpenDBRequest>>>>,
54
55    /// <https://www.w3.org/TR/IndexedDB-3/#transaction>
56    /// Active transactions associated with this factory's global.
57    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        // We implement the HTML-triggered deactivation effect by tracking script-created
97        // transactions on the global and deactivating them at the microtask checkpoint.
98        let snapshot: Vec<DomRoot<IDBTransaction>> = {
99            let mut map = self.indexeddb_transactions.borrow_mut();
100
101            // Transactions are normally unregistered when they finish (commit/abort),
102            // but unregister can occur in a queued task (e.g. finalize_abort), so we can
103            // briefly observe finished transactions here. Prune them defensively.
104            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        // https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint
120        // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions
121        // To cleanup Indexed Database transactions, run the following steps.
122        // They will return true if any transactions were cleaned up, or false otherwise.
123        // Step 1: If there are no transactions with cleanup event loop matching the current event loop, return false.
124        // Step 2: For each transaction transaction with cleanup event loop matching the current event loop:
125        // Step 2.1: Set transaction’s state to inactive.
126        // Step 2.2: Clear transaction’s cleanup event loop.
127        // Step 3: Return true.
128        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        // Prune finished transactions again after maybe_commit() progress.
147        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    /// <https://w3c.github.io/IndexedDB/#dom-idbrequest-transaction>
180    /// Clear IDBOpenDBRequest.transaction once the upgrade transaction is finished.
181    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    /// Setup the callback to the backend service, if this hasn't been done already.
206    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            // Step 5.3: Queue a database task to run these steps:
225            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    /// <https://w3c.github.io/IndexedDB/#open-a-database-connection>
256    /// The steps that continue on the script-thread.
257    /// This covers interacting with the current open request,
258    /// as well as with other open connections preventing the request from making progress.
259    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                // https://w3c.github.io/IndexedDB/#upgrade-transaction-steps
276                // Step 3. Set transaction’s scope to connection’s object store set.
277                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                // Step 2.2: Otherwise,
287                // set request’s result to result,
288                // set request’s done flag,
289                // and fire an event named success at request.
290                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                // https://w3c.github.io/IndexedDB/#upgrade-transaction-steps
311                // Step 3. Set transaction’s scope to connection’s object store set.
312                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                // Step 2.1 If result is an error, see dispatch_error().
317                self.dispatch_error(cx, name, id, Error::Version(None));
318            },
319            ConnectionMsg::AbortError { name, id } => {
320                // Step 2.1 If result is an error, see dispatch_error().
321                self.dispatch_error(cx, name, id, Error::Abort(None));
322            },
323            ConnectionMsg::DatabaseError { name, id, error } => {
324                // Step 2.1 If result is an error, see dispatch_error().
325                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                // Step 10.2: fire a version change event named versionchange at entry with db’s version and version.
344                connection.dispatch_versionchange(cx, old_version, Some(version));
345
346                // Step 10.3: Wait for all of the events to be fired.
347                // Note: backend is at this step; sending a message to continue algo there.
348                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                // Step 10.4: fire a version change event named blocked at request with db’s version and version.
376                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    /// <https://w3c.github.io/IndexedDB/#dom-idbfactory-open>
392    /// The error dispatching part from within a task part.
393    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        // Step 5.3.1: If result is an error, then:
403        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        // Step 5.3.1.1: Set request’s result to undefined.
420        request.set_result(HandleValue::undefined());
421
422        // Step 5.3.1.2: Set request’s error to result.
423        request.set_error(Some(dom_exception), CanGc::from_cx(cx));
424        // Open requests expose a transaction only while `upgradeneeded` is being dispatched;
425        // otherwise `IDBOpenDBRequest.transaction` must be null.
426        // https://w3c.github.io/IndexedDB/#dom-idbrequest-transaction
427        // https://w3c.github.io/IndexedDB/#open-a-database-connection
428        // Open requests that have completed with an error must not retain an upgrade transaction.
429        request.clear_transaction();
430
431        // Step 5.3.1.3: Set request’s done flag to true.
432        request.set_ready_state_done();
433
434        // Step 5.3.1.4: Fire an event named error at request
435        // with its bubbles
436        // and cancelable attributes initialized to true.
437        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    /// <https://w3c.github.io/IndexedDB/#open-a-database-connection>
448    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        // Step 5: Run these steps in parallel:
468        // Step 5.1: Let result be the result of opening a database connection,
469        // with storageKey, name, version if given and undefined otherwise, and request.
470        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        // Note: algo continues in parallel.
480        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    /// The indexeddb call into
521    /// <https://storage.spec.whatwg.org/#obtain-a-local-storage-bottle-map>
522    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    /// <https://w3c.github.io/IndexedDB/#dom-idbfactory-open>
548    fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
549        // Step 1: If version is 0 (zero), throw a TypeError.
550        if version == Some(0) {
551            return Err(Error::Type(
552                c"The version must be an integer >= 1".to_owned(),
553            ));
554        };
555
556        // Step 2: Let environment be this’s relevant settings object.
557        let global = self.global();
558
559        // Step 3: Let storageKey be the result of running obtain a storage key given environment.
560        // If failure is returned, then throw a "SecurityError" DOMException and abort these steps.
561        let Some(storage_key) = global.obtain_storage_key() else {
562            return Err(Error::Security(None));
563        };
564
565        // Note: switching to obtaining a storage bottle map,
566        // as per https://github.com/w3c/IndexedDB/pull/334/
567        let proxy_map =
568            self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
569
570        // Step 4: Let request be a new open request.
571        let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
572
573        // Step 5: Runs in parallel
574        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        // Step 6: Return a new IDBOpenDBRequest object for request.
582        Ok(request)
583    }
584
585    /// <https://www.w3.org/TR/IndexedDB/#dom-idbfactory-deletedatabase>
586    fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
587        // Step 1: Let environment be this’s relevant settings object.
588        let global = self.global();
589
590        // Step 2: Let storageKey be the result of running obtain a storage key given environment.
591        // If failure is returned, then throw a "SecurityError" DOMException and abort these steps.
592        let Some(storage_key) = global.obtain_storage_key() else {
593            return Err(Error::Security(None));
594        };
595
596        // Note: switching to obtaining a storage bottle map,
597        // as per https://github.com/w3c/IndexedDB/pull/334/
598        let proxy_map =
599            self.obtain_a_local_storage_bottle_map(&global, global.origin().immutable().clone())?;
600
601        // Step 3: Let request be a new open request
602        let request = IDBOpenDBRequest::new(&self.global(), CanGc::deprecated_note());
603
604        // Step 4: Runs in parallel
605        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        // Step 5: Return request
613        Ok(request)
614    }
615
616    /// <https://www.w3.org/TR/IndexedDB/#dom-idbfactory-databases>
617    fn Databases(&self, cx: &mut JSContext) -> Rc<Promise> {
618        // Step 1: Let environment be this’s relevant settings object.
619        let global = self.global();
620
621        // Step 2: Let storageKey be the result of running obtain a storage key given environment.
622        // If failure is returned, then return a promise rejected with a "SecurityError" DOMException.
623        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        // Step 3: Let p be a new promise.
633        let p = Promise::new(&global, CanGc::from_cx(cx));
634
635        // Note: the option is required to pass the promise to a task from within the generic callback,
636        // see #41356
637        let mut trusted_promise: Option<TrustedPromise> = Some(TrustedPromise::new(p.clone()));
638
639        // Step 4: Run these steps in parallel:
640        // Note implementing by communicating with the backend.
641        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            // Step 4.4: Queue a database task to resolve p with result.
652            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        // Step 5: Return p.
687        p
688    }
689
690    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbfactory-cmp>
691    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}