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