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::IpcSend;
6use dom_struct::dom_struct;
7use ipc_channel::router::ROUTER;
8use js::jsval::UndefinedValue;
9use js::rust::HandleValue;
10use profile_traits::ipc;
11use script_bindings::conversions::SafeToJSValConvertible;
12use storage_traits::indexeddb::{BackendResult, IndexedDBThreadMsg, SyncOperation};
13use stylo_atoms::Atom;
14
15use crate::dom::bindings::codegen::Bindings::IDBOpenDBRequestBinding::IDBOpenDBRequestMethods;
16use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
17use crate::dom::bindings::error::{Error, Fallible};
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;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::event::{Event, EventBubbles, EventCancelable};
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::indexeddb::idbdatabase::IDBDatabase;
26use crate::dom::indexeddb::idbrequest::IDBRequest;
27use crate::dom::indexeddb::idbtransaction::IDBTransaction;
28use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
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    /// <https://www.w3.org/TR/IndexedDB-2/#open-a-database>
39    fn handle_open_db(
40        &self,
41        name: String,
42        request_version: Option<u64>,
43        db_version: u64,
44        can_gc: CanGc,
45    ) -> (Fallible<DomRoot<IDBDatabase>>, bool) {
46        // Step 5-6
47        let request_version = match request_version {
48            Some(v) => v,
49            None => {
50                if db_version == 0 {
51                    1
52                } else {
53                    db_version
54                }
55            },
56        };
57
58        // Step 7
59        if request_version < db_version {
60            return (Err(Error::Version(None)), false);
61        }
62
63        // Step 8-9
64        let open_request = self.open_request.root();
65        let global = open_request.global();
66        let connection = IDBDatabase::new(
67            &global,
68            DOMString::from_string(name.clone()),
69            request_version,
70            can_gc,
71        );
72
73        // Step 10
74        if request_version > db_version {
75            // FIXME:(rasviitanen) Do step 10.1-10.5
76            // connection.dispatch_versionchange(db_version, Some(request_version));
77            // Step 10.6
78            open_request.upgrade_db_version(&connection, request_version, CanGc::note());
79            // Step 11
80            (Ok(connection), true)
81        } else {
82            // Step 11
83            (Ok(connection), false)
84        }
85    }
86
87    fn handle_delete_db(&self, result: BackendResult<()>, can_gc: CanGc) {
88        let open_request = self.open_request.root();
89        let global = open_request.global();
90        open_request.idbrequest.set_ready_state_done();
91
92        match result {
93            Ok(_) => {
94                let _ac = enter_realm(&*open_request);
95                #[expect(unsafe_code)]
96                unsafe {
97                    open_request
98                        .set_result(js::gc::Handle::from_raw(js::jsapi::UndefinedHandleValue));
99                }
100
101                let event = Event::new(
102                    &global,
103                    Atom::from("success"),
104                    EventBubbles::DoesNotBubble,
105                    EventCancelable::NotCancelable,
106                    can_gc,
107                );
108                event.fire(open_request.upcast(), can_gc);
109            },
110            Err(_e) => {
111                // FIXME(arihant2math) Set the error of request to the
112                // appropriate error
113
114                let event = Event::new(
115                    &global,
116                    Atom::from("error"),
117                    EventBubbles::Bubbles,
118                    EventCancelable::Cancelable,
119                    can_gc,
120                );
121                event.fire(open_request.upcast(), can_gc);
122            },
123        }
124    }
125}
126
127#[dom_struct]
128pub struct IDBOpenDBRequest {
129    idbrequest: IDBRequest,
130}
131
132impl IDBOpenDBRequest {
133    pub fn new_inherited() -> IDBOpenDBRequest {
134        IDBOpenDBRequest {
135            idbrequest: IDBRequest::new_inherited(),
136        }
137    }
138
139    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
140        reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
141    }
142
143    /// <https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction>
144    fn upgrade_db_version(&self, connection: &IDBDatabase, version: u64, can_gc: CanGc) {
145        let global = self.global();
146        // Step 2
147        let transaction = IDBTransaction::new(
148            &global,
149            connection,
150            IDBTransactionMode::Versionchange,
151            &connection.object_stores(),
152            can_gc,
153        );
154
155        // Step 3
156        connection.set_transaction(&transaction);
157
158        // Associate the open request with the upgrade transaction so that
159        // its "success" event is dispatched only once the transaction finishes.
160        transaction.set_open_request(self);
161
162        // Step 4
163        transaction.set_active_flag(false);
164
165        // Step 5-7
166        let old_version = connection.version();
167        transaction.upgrade_db_version(version);
168
169        // Step 8
170        let this = Trusted::new(self);
171        let connection = Trusted::new(connection);
172        let trusted_transaction = Trusted::new(&*transaction);
173        global.task_manager().database_access_task_source().queue(
174            task!(send_upgradeneeded_notification: move || {
175                let this = this.root();
176                let txn = trusted_transaction.root();
177                let conn = connection.root();
178                let global = this.global();
179                let cx = GlobalScope::get_cx();
180
181                // Step 8.1
182                let _ac = enter_realm(&*conn);
183                rooted!(in(*cx) let mut connection_val = UndefinedValue());
184                conn.safe_to_jsval(cx, connection_val.handle_mut(), CanGc::note());
185                this.idbrequest.set_result(connection_val.handle());
186
187                // Step 8.2
188                this.idbrequest.set_transaction(&txn);
189
190                // Step 8.3
191                this.idbrequest.set_ready_state_done();
192
193                let event = IDBVersionChangeEvent::new(
194                    &global,
195                    Atom::from("upgradeneeded"),
196                    EventBubbles::DoesNotBubble,
197                    EventCancelable::NotCancelable,
198                    old_version,
199                    Some(version),
200                    CanGc::note(),
201                );
202
203                // Step 8.4
204                txn.set_active_flag(true);
205                // Step 8.5
206                let _did_throw = event.upcast::<Event>().fire(this.upcast(), CanGc::note());
207                // FIXME:(rasviitanen) Handle throw (Step 8.5)
208                // https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction
209                // Step 8.6
210                txn.set_active_flag(false);
211
212                // Wait for the upgrade transaction to finish; its completion
213                // handler will now dispatch the open request's "success" event
214                // after all other queued request success events.
215                txn.wait();
216            }),
217        );
218
219        // Step 9: Starts and waits for the transaction to finish
220        transaction.wait();
221    }
222
223    pub fn set_result(&self, result: HandleValue) {
224        self.idbrequest.set_result(result);
225    }
226
227    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
228        self.idbrequest.set_error(error, can_gc);
229    }
230
231    pub fn open_database(&self, name: DOMString, version: Option<u64>) -> Result<(), ()> {
232        let global = self.global();
233
234        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
235        let response_listener = OpenRequestListener {
236            open_request: Trusted::new(self),
237        };
238
239        let open_operation = SyncOperation::OpenDatabase(
240            sender,
241            global.origin().immutable().clone(),
242            name.to_string(),
243            version,
244        );
245
246        let task_source = global
247            .task_manager()
248            .database_access_task_source()
249            .to_sendable();
250
251        let trusted_request = Trusted::new(self);
252        let name = name.to_string();
253        ROUTER.add_typed_route(
254            receiver.to_ipc_receiver(),
255            Box::new(move |message| {
256                let trusted_request = trusted_request.clone();
257                let response_listener = response_listener.clone();
258                let name = name.clone();
259
260                task_source.queue(
261                    task!(set_request_result_to_database: move || {
262                        let (result, did_upgrade) =
263                            response_listener.handle_open_db(name, version, message.unwrap(), CanGc::note());
264                        // If an upgrade event was created, it will be responsible for
265                        // dispatching the success event
266                        if did_upgrade {
267                            return;
268                        }
269                        let request = trusted_request.root();
270                        let global = request.global();
271                        match result {
272                            Ok(db) => {
273                                request.dispatch_success(&db);
274                            },
275                            Err(dom_exception) => {
276                                request.set_result(HandleValue::undefined());
277                                request.set_error(Some(dom_exception), CanGc::note());
278                                let event = Event::new(
279                                    &global,
280                                    Atom::from("error"),
281                                    EventBubbles::Bubbles,
282                                    EventCancelable::Cancelable,
283                                    CanGc::note()
284                                );
285                                event.fire(request.upcast(), CanGc::note());
286                            }
287                        }
288                    }),
289                );
290            }),
291        );
292
293        if global
294            .storage_threads()
295            .send(IndexedDBThreadMsg::Sync(open_operation))
296            .is_err()
297        {
298            return Err(());
299        }
300        Ok(())
301    }
302
303    pub fn delete_database(&self, name: String) -> Result<(), ()> {
304        let global = self.global();
305
306        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
307        let task_source = global
308            .task_manager()
309            .database_access_task_source()
310            .to_sendable();
311        let response_listener = OpenRequestListener {
312            open_request: Trusted::new(self),
313        };
314
315        let delete_operation =
316            SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
317
318        ROUTER.add_typed_route(
319            receiver.to_ipc_receiver(),
320            Box::new(move |message| {
321                let response_listener = response_listener.clone();
322                task_source.queue(task!(request_callback: move || {
323                    response_listener.handle_delete_db(message.unwrap(), CanGc::note());
324                }));
325            }),
326        );
327
328        if global
329            .storage_threads()
330            .send(IndexedDBThreadMsg::Sync(delete_operation))
331            .is_err()
332        {
333            return Err(());
334        }
335        Ok(())
336    }
337
338    pub fn dispatch_success(&self, result: &IDBDatabase) {
339        let global = self.global();
340        let this = Trusted::new(self);
341        let result = Trusted::new(result);
342
343        global.task_manager().database_access_task_source().queue(
344            task!(send_success_notification: move || {
345                let this = this.root();
346                let result = result.root();
347                this.idbrequest.set_ready_state_done();
348                let global = this.global();
349                let cx = GlobalScope::get_cx();
350
351                let _ac = enter_realm(&*result);
352                rooted!(in(*cx) let mut result_val = UndefinedValue());
353                result.safe_to_jsval(cx, result_val.handle_mut(), CanGc::note());
354                this.set_result(result_val.handle());
355
356                let event = Event::new(
357                    &global,
358                    Atom::from("success"),
359                    EventBubbles::DoesNotBubble,
360                    EventCancelable::NotCancelable,
361                    CanGc::note()
362                );
363                event.fire(this.upcast(), CanGc::note());
364            }),
365        );
366    }
367}
368
369impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
370    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onblocked
371    event_handler!(blocked, GetOnblocked, SetOnblocked);
372
373    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onupgradeneeded
374    event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
375}