Skip to main content

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