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