script/dom/indexeddb/
idbopendbrequest.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/. */
4
5use base::generic_channel::GenericSend;
6use dom_struct::dom_struct;
7use js::jsval::UndefinedValue;
8use js::rust::HandleValue;
9use profile_traits::generic_callback::GenericCallback;
10use script_bindings::conversions::SafeToJSValConvertible;
11use storage_traits::indexeddb::{BackendResult, IndexedDBThreadMsg, SyncOperation};
12use stylo_atoms::Atom;
13use uuid::Uuid;
14
15use crate::dom::bindings::codegen::Bindings::IDBOpenDBRequestBinding::IDBOpenDBRequestMethods;
16use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
17use crate::dom::bindings::error::{Error, ErrorToJsval};
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
21use crate::dom::bindings::root::{DomRoot, MutNullableDom};
22use crate::dom::event::{Event, EventBubbles, EventCancelable};
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::indexeddb::idbdatabase::IDBDatabase;
25use crate::dom::indexeddb::idbrequest::IDBRequest;
26use crate::dom::indexeddb::idbtransaction::IDBTransaction;
27use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
28use crate::indexeddb::map_backend_error_to_dom_error;
29use crate::realms::enter_realm;
30use crate::script_runtime::CanGc;
31
32#[derive(Clone)]
33struct OpenRequestListener {
34    open_request: Trusted<IDBOpenDBRequest>,
35}
36
37impl OpenRequestListener {
38    /// The continuation of the parallel steps of
39    /// <https://www.w3.org/TR/IndexedDB/#dom-idbfactory-deletedatabase>
40    fn handle_delete_db(&self, result: BackendResult<u64>, can_gc: CanGc) {
41        // Step 4.1: Let result be the result of deleting a database, with storageKey, name, and request.
42        // Note: done with the `result` argument.
43
44        // Step 4.2: Set request’s processed flag to true.
45        // The backend tracks this flag for connection queue processing.
46
47        // Step 3: Queue a database task to run these steps:
48        // Note: we are in the queued task.
49
50        let open_request = self.open_request.root();
51        let global = open_request.global();
52
53        // Note: setting the done flag here as it is done in both branches below.
54        open_request.idbrequest.set_ready_state_done();
55
56        let cx = GlobalScope::get_cx();
57        rooted!(in(*cx) let mut rval = UndefinedValue());
58
59        let _ac = enter_realm(&*open_request);
60
61        match result {
62            Ok(version) => {
63                // Step 4.3.2: Otherwise,
64                // set request’s result to undefined,
65                // set request’s done flag to true,
66                // and fire a version change event named success at request with result and null.
67                open_request.set_result(rval.handle());
68                let _ = IDBVersionChangeEvent::fire_version_change_event(
69                    &global,
70                    open_request.upcast(),
71                    Atom::from("success"),
72                    version,
73                    None,
74                    can_gc,
75                );
76            },
77            Err(err) => {
78                // Step 4.3.1:
79                // If result is an error,
80                // set request’s error to result,
81                // set request’s done flag to true,
82                // and fire an event named error at request
83                // with its bubbles and cancelable attributes initialized to true.
84
85                // TODO: transform backend error into jsval.
86                let error = map_backend_error_to_dom_error(err);
87                let cx = GlobalScope::get_cx();
88                rooted!(in(*cx) let mut rval = UndefinedValue());
89                error.to_jsval(cx, &global, rval.handle_mut(), can_gc);
90                open_request.set_result(rval.handle());
91                let event = Event::new(
92                    &global,
93                    Atom::from("error"),
94                    EventBubbles::Bubbles,
95                    EventCancelable::Cancelable,
96                    can_gc,
97                );
98                event.fire(open_request.upcast(), can_gc);
99            },
100        }
101    }
102}
103
104#[dom_struct]
105pub struct IDBOpenDBRequest {
106    idbrequest: IDBRequest,
107    pending_connection: MutNullableDom<IDBDatabase>,
108
109    /// The id used both for the request and the related connection.
110    #[no_trace]
111    id: Uuid,
112}
113
114impl IDBOpenDBRequest {
115    pub fn new_inherited() -> IDBOpenDBRequest {
116        IDBOpenDBRequest {
117            idbrequest: IDBRequest::new_inherited(),
118            pending_connection: Default::default(),
119            id: Uuid::new_v4(),
120        }
121    }
122
123    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
124        reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
125    }
126
127    pub(crate) fn get_id(&self) -> Uuid {
128        self.id
129    }
130
131    pub(crate) fn get_or_init_connection(
132        &self,
133        global: &GlobalScope,
134        name: String,
135        version: u64,
136        upgraded: bool,
137        can_gc: CanGc,
138    ) -> DomRoot<IDBDatabase> {
139        self.pending_connection.or_init(|| {
140            debug_assert!(!upgraded, "A connection should exist for the upgraded db.");
141            IDBDatabase::new(global, name.into(), self.get_id(), version, can_gc)
142        })
143    }
144
145    /// <https://w3c.github.io/IndexedDB/#upgrade-a-database>
146    /// Step 10: Queue a database task to run these steps:
147    /// The below are the steps in the task.
148    pub(crate) fn upgrade_db_version(
149        &self,
150        connection: &IDBDatabase,
151        old_version: u64,
152        version: u64,
153        transaction: u64,
154        can_gc: CanGc,
155    ) {
156        let global = self.global();
157        let cx = GlobalScope::get_cx();
158
159        let transaction = IDBTransaction::new_with_serial(
160            &global,
161            connection,
162            IDBTransactionMode::Versionchange,
163            &connection.object_stores(),
164            transaction,
165            can_gc,
166        );
167        transaction.set_versionchange_old_version(old_version);
168        connection.set_transaction(&transaction);
169        // This task runs Step 10.4 later, so keep the transaction inactive until then.
170        transaction.set_active_flag(false);
171
172        rooted!(in(*cx) let mut connection_val = UndefinedValue());
173        connection.safe_to_jsval(cx, connection_val.handle_mut(), can_gc);
174
175        // Step 10.1: Set request’s result to connection.
176        self.idbrequest.set_result(connection_val.handle());
177
178        // Step 10.2: Set request’s transaction to transaction.
179        self.idbrequest.set_transaction(&transaction);
180
181        // Step 10.3: Set request’s done flag to true.
182        self.idbrequest.set_ready_state_done();
183
184        // Step 10.4: Set transaction’s state to active.
185        transaction.set_active_flag(true);
186
187        // Step 10.5: Let didThrow be the result of firing a version change event
188        // named upgradeneeded at request with old version and version.
189        let did_throw = IDBVersionChangeEvent::fire_version_change_event(
190            &global,
191            self.upcast(),
192            Atom::from("upgradeneeded"),
193            old_version,
194            Some(version),
195            can_gc,
196        );
197
198        // Step 10.6: If transaction’s state is active, then:
199        if transaction.is_active() {
200            // Step 10.6.1: Set transaction’s state to inactive.
201            transaction.set_active_flag(false);
202
203            // Step 10.6.2: If didThrow is true, run abort a transaction with
204            // transaction and a newly created "AbortError" DOMException.
205            if did_throw {
206                transaction.initiate_abort(Error::Abort(None), can_gc);
207                transaction.request_backend_abort();
208            } else {
209                // The upgrade transaction auto-commits once inactive and quiescent.
210                transaction.maybe_commit();
211            }
212        }
213    }
214
215    pub(crate) fn delete_database(&self, name: String) -> Result<(), ()> {
216        let global = self.global();
217
218        let task_source = global
219            .task_manager()
220            .database_access_task_source()
221            .to_sendable();
222        let response_listener = OpenRequestListener {
223            open_request: Trusted::new(self),
224        };
225        let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
226            let response_listener = response_listener.clone();
227            task_source.queue(task!(request_callback: move || {
228                response_listener.handle_delete_db(message.unwrap(), CanGc::note());
229            }))
230        })
231        .expect("Could not create delete database callback");
232
233        let delete_operation = SyncOperation::DeleteDatabase(
234            callback,
235            global.origin().immutable().clone(),
236            name,
237            self.get_id(),
238        );
239
240        if global
241            .storage_threads()
242            .send(IndexedDBThreadMsg::Sync(delete_operation))
243            .is_err()
244        {
245            return Err(());
246        }
247        Ok(())
248    }
249
250    pub fn set_result(&self, result: HandleValue) {
251        self.idbrequest.set_result(result);
252    }
253
254    pub fn set_ready_state_done(&self) {
255        self.idbrequest.set_ready_state_done();
256    }
257
258    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
259        self.idbrequest.set_error(error, can_gc);
260    }
261
262    pub fn clear_transaction(&self) {
263        self.idbrequest.clear_transaction();
264    }
265
266    pub(crate) fn clear_transaction_if_matches(&self, transaction: &IDBTransaction) -> bool {
267        let matches = self
268            .idbrequest
269            .transaction()
270            .is_some_and(|current| &*current == transaction);
271        if matches {
272            self.idbrequest.clear_transaction();
273        }
274        matches
275    }
276
277    pub fn dispatch_success(&self, name: String, version: u64, upgraded: bool, can_gc: CanGc) {
278        let global = self.global();
279        let result = self.get_or_init_connection(&global, name, version, upgraded, can_gc);
280        self.idbrequest.set_ready_state_done();
281        let cx = GlobalScope::get_cx();
282
283        let _ac = enter_realm(&*result);
284        rooted!(in(*cx) let mut result_val = UndefinedValue());
285        result.safe_to_jsval(cx, result_val.handle_mut(), CanGc::note());
286        self.set_result(result_val.handle());
287
288        let event = Event::new(
289            &global,
290            Atom::from("success"),
291            EventBubbles::DoesNotBubble,
292            EventCancelable::NotCancelable,
293            CanGc::note(),
294        );
295        event.fire(self.upcast(), CanGc::note());
296    }
297
298    /// <https://w3c.github.io/IndexedDB/#eventdef-idbopendbrequest-blocked>
299    pub fn dispatch_blocked(&self, old_version: u64, new_version: Option<u64>, can_gc: CanGc) {
300        let global = self.global();
301        let _ = IDBVersionChangeEvent::fire_version_change_event(
302            &global,
303            self.upcast(),
304            Atom::from("blocked"),
305            old_version,
306            new_version,
307            can_gc,
308        );
309    }
310}
311
312impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
313    // https://www.w3.org/TR/IndexedDB-3/#dom-idbopendbrequest-onblocked
314    event_handler!(blocked, GetOnblocked, SetOnblocked);
315
316    // https://www.w3.org/TR/IndexedDB-3/#dom-idbopendbrequest-onupgradeneeded
317    event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
318}