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