script/dom/
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 dom_struct::dom_struct;
6use ipc_channel::router::ROUTER;
7use js::jsval::UndefinedValue;
8use js::rust::HandleValue;
9use net_traits::IpcSend;
10use net_traits::indexeddb_thread::{BackendResult, IndexedDBThreadMsg, SyncOperation};
11use profile_traits::ipc;
12use script_bindings::conversions::SafeToJSValConvertible;
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::idbdatabase::IDBDatabase;
26use crate::dom::idbrequest::IDBRequest;
27use crate::dom::idbtransaction::IDBTransaction;
28use crate::dom::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), 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                #[allow(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        // Step 4
159        transaction.set_active_flag(false);
160
161        // Step 5-7
162        let old_version = connection.version();
163        transaction.upgrade_db_version(version);
164
165        // Step 8
166        let this = Trusted::new(self);
167        let connection = Trusted::new(connection);
168        let trusted_transaction = Trusted::new(&*transaction);
169        global.task_manager().database_access_task_source().queue(
170            task!(send_upgradeneeded_notification: move || {
171                let this = this.root();
172                let txn = trusted_transaction.root();
173                let conn = connection.root();
174                let global = this.global();
175                let cx = GlobalScope::get_cx();
176
177                // Step 8.1
178                let _ac = enter_realm(&*conn);
179                rooted!(in(*cx) let mut connection_val = UndefinedValue());
180                conn.safe_to_jsval(cx, connection_val.handle_mut());
181                this.idbrequest.set_result(connection_val.handle());
182
183                // Step 8.2
184                this.idbrequest.set_transaction(&txn);
185
186                // Step 8.3
187                this.idbrequest.set_ready_state_done();
188
189                let event = IDBVersionChangeEvent::new(
190                    &global,
191                    Atom::from("upgradeneeded"),
192                    EventBubbles::DoesNotBubble,
193                    EventCancelable::NotCancelable,
194                    old_version,
195                    Some(version),
196                    CanGc::note(),
197                );
198
199                // Step 8.4
200                txn.set_active_flag(true);
201                // Step 8.5
202                let _did_throw = event.upcast::<Event>().fire(this.upcast(), CanGc::note());
203                // FIXME:(rasviitanen) Handle throw (Step 8.5)
204                // https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction
205                // Step 8.6
206                txn.set_active_flag(false);
207
208                // Implementation specific: we fire the success on db here
209                // to make sure the success event occurs after the upgrade event.
210                txn.wait();
211                this.dispatch_success(&conn);
212            }),
213        );
214
215        // Step 9: Starts and waits for the transaction to finish
216        transaction.wait();
217    }
218
219    pub fn set_result(&self, result: HandleValue) {
220        self.idbrequest.set_result(result);
221    }
222
223    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
224        self.idbrequest.set_error(error, can_gc);
225    }
226
227    pub fn open_database(&self, name: DOMString, version: Option<u64>) {
228        let global = self.global();
229
230        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
231        let response_listener = OpenRequestListener {
232            open_request: Trusted::new(self),
233        };
234
235        let open_operation = SyncOperation::OpenDatabase(
236            sender,
237            global.origin().immutable().clone(),
238            name.to_string(),
239            version,
240        );
241
242        let task_source = global
243            .task_manager()
244            .database_access_task_source()
245            .to_sendable();
246
247        let trusted_request = Trusted::new(self);
248        let name = name.to_string();
249        ROUTER.add_typed_route(
250            receiver.to_ipc_receiver(),
251            Box::new(move |message| {
252                let trusted_request = trusted_request.clone();
253                let response_listener = response_listener.clone();
254                let name = name.clone();
255
256                task_source.queue(
257                    task!(set_request_result_to_database: move || {
258                        let (result, did_upgrade) =
259                            response_listener.handle_open_db(name, version, message.unwrap(), CanGc::note());
260                        // If an upgrade event was created, it will be responsible for
261                        // dispatching the success event
262                        if did_upgrade {
263                            return;
264                        }
265                        let request = trusted_request.root();
266                        let global = request.global();
267                        match result {
268                            Ok(db) => {
269                                request.dispatch_success(&db);
270                            },
271                            Err(dom_exception) => {
272                                request.set_result(HandleValue::undefined());
273                                request.set_error(Some(dom_exception), CanGc::note());
274                                let event = Event::new(
275                                    &global,
276                                    Atom::from("error"),
277                                    EventBubbles::Bubbles,
278                                    EventCancelable::Cancelable,
279                                    CanGc::note()
280                                );
281                                event.fire(request.upcast(), CanGc::note());
282                            }
283                        }
284                    }),
285                );
286            }),
287        );
288
289        global
290            .resource_threads()
291            .send(IndexedDBThreadMsg::Sync(open_operation))
292            .unwrap();
293    }
294
295    pub fn delete_database(&self, name: String) {
296        let global = self.global();
297
298        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
299        let task_source = global
300            .task_manager()
301            .database_access_task_source()
302            .to_sendable();
303        let response_listener = OpenRequestListener {
304            open_request: Trusted::new(self),
305        };
306
307        let delete_operation =
308            SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
309
310        ROUTER.add_typed_route(
311            receiver.to_ipc_receiver(),
312            Box::new(move |message| {
313                let response_listener = response_listener.clone();
314                task_source.queue(task!(request_callback: move || {
315                    response_listener.handle_delete_db(message.unwrap(), CanGc::note());
316                }));
317            }),
318        );
319
320        global
321            .resource_threads()
322            .send(IndexedDBThreadMsg::Sync(delete_operation))
323            .unwrap();
324    }
325
326    pub fn dispatch_success(&self, result: &IDBDatabase) {
327        let global = self.global();
328        let this = Trusted::new(self);
329        let result = Trusted::new(result);
330
331        global.task_manager().database_access_task_source().queue(
332            task!(send_success_notification: move || {
333                let this = this.root();
334                let result = result.root();
335                this.idbrequest.set_ready_state_done();
336                let global = this.global();
337                let cx = GlobalScope::get_cx();
338
339                let _ac = enter_realm(&*result);
340                rooted!(in(*cx) let mut result_val = UndefinedValue());
341                result.safe_to_jsval(cx, result_val.handle_mut());
342                this.set_result(result_val.handle());
343
344                let event = Event::new(
345                    &global,
346                    Atom::from("success"),
347                    EventBubbles::DoesNotBubble,
348                    EventCancelable::NotCancelable,
349                    CanGc::note()
350                );
351                event.fire(this.upcast(), CanGc::note());
352            }),
353        );
354    }
355}
356
357impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
358    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onblocked
359    event_handler!(blocked, GetOnblocked, SetOnblocked);
360
361    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onupgradeneeded
362    event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
363}