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 contionuation 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        // TODO: implemen the flag.
47        // Note: the flag may be need to be set on the backend(as well as here?).
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 event = IDBVersionChangeEvent::new(
71                    &global,
72                    Atom::from("success"),
73                    EventBubbles::DoesNotBubble,
74                    EventCancelable::NotCancelable,
75                    version,
76                    None,
77                    CanGc::note(),
78                );
79                event.upcast::<Event>().fire(open_request.upcast(), can_gc);
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
89                // TODO: transform backend error into jsval.
90                let error = map_backend_error_to_dom_error(err);
91                let cx = GlobalScope::get_cx();
92                rooted!(in(*cx) let mut rval = UndefinedValue());
93                error
94                    .clone()
95                    .to_jsval(cx, &global, rval.handle_mut(), can_gc);
96                open_request.set_result(rval.handle());
97                let event = Event::new(
98                    &global,
99                    Atom::from("error"),
100                    EventBubbles::Bubbles,
101                    EventCancelable::Cancelable,
102                    can_gc,
103                );
104                event.fire(open_request.upcast(), can_gc);
105            },
106        }
107    }
108}
109
110#[dom_struct]
111pub struct IDBOpenDBRequest {
112    idbrequest: IDBRequest,
113    pending_connection: MutNullableDom<IDBDatabase>,
114
115    #[no_trace]
116    id: Uuid,
117}
118
119impl IDBOpenDBRequest {
120    pub fn new_inherited() -> IDBOpenDBRequest {
121        IDBOpenDBRequest {
122            idbrequest: IDBRequest::new_inherited(),
123            pending_connection: Default::default(),
124            id: Uuid::new_v4(),
125        }
126    }
127
128    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
129        reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
130    }
131
132    pub(crate) fn get_id(&self) -> Uuid {
133        self.id
134    }
135
136    pub(crate) fn set_connection(&self, connection: &IDBDatabase) {
137        self.pending_connection.set(Some(connection));
138    }
139
140    /// <https://w3c.github.io/IndexedDB/#upgrade-a-database>
141    /// Step 10: Queue a database task to run these steps:
142    /// The below are the steps in the task.
143    pub(crate) fn upgrade_db_version(
144        &self,
145        connection: &IDBDatabase,
146        old_version: u64,
147        version: u64,
148        transaction: u64,
149        can_gc: CanGc,
150    ) {
151        let global = self.global();
152        let cx = GlobalScope::get_cx();
153
154        // Note: the transaction should link wiht one created on the backend.
155        // Steps here are meant to create the corresponding webidl object.
156        let transaction = IDBTransaction::new_with_id(
157            &global,
158            connection,
159            IDBTransactionMode::Versionchange,
160            &connection.object_stores(),
161            transaction,
162            Some(self.get_id()),
163            can_gc,
164        );
165        connection.set_transaction(&transaction);
166
167        rooted!(in(*cx) let mut connection_val = UndefinedValue());
168        connection.safe_to_jsval(cx, connection_val.handle_mut(), can_gc);
169
170        // Step 10.1: Set request’s result to connection.
171        self.idbrequest.set_result(connection_val.handle());
172
173        // Step 10.2: Set request’s transaction to transaction.
174        self.idbrequest.set_transaction(&transaction);
175
176        // Step 10.3: Set request’s done flag to true.
177        self.idbrequest.set_ready_state_done();
178
179        // Step 10.4: Set transaction’s state to active.
180        // TODO: message to set state of backend transaction.
181        transaction.set_active_flag(true);
182
183        // Step 10.5: Let didThrow be the result of
184        // firing a version change event named upgradeneeded
185        // at request with old version and version.
186        let event = IDBVersionChangeEvent::new(
187            &global,
188            Atom::from("upgradeneeded"),
189            EventBubbles::DoesNotBubble,
190            EventCancelable::NotCancelable,
191            old_version,
192            Some(version),
193            CanGc::note(),
194        );
195
196        // TODO: use as part of step 10.6.2
197        let _did_throw = event.upcast::<Event>().fire(self.upcast(), can_gc);
198
199        // Step 10.6: If transaction’s state is active, then:
200        if transaction.is_active() {
201            // Step 10.6.1: Set transaction’s state to inactive.
202            transaction.set_active_flag(false);
203
204            // Step 10.6.2: If didThrow is true,
205            // run abort a transaction with transaction
206            // and a newly created "AbortError" DOMException.
207            // TODO: implement.
208
209            // Note: only sending this if active,
210            // because if the transaction was aborted
211            // we don't want to create a connection.
212            if global
213                .storage_threads()
214                .send(IndexedDBThreadMsg::OpenTransactionInactive {
215                    name: connection.get_name().to_string(),
216                    origin: global.origin().immutable().clone(),
217                })
218                .is_err()
219            {
220                error!("Failed to send OpenTransactionInactive.");
221            }
222        }
223    }
224
225    pub fn set_result(&self, result: HandleValue) {
226        self.idbrequest.set_result(result);
227    }
228
229    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
230        self.idbrequest.set_error(error, can_gc);
231    }
232
233    pub fn delete_database(&self, name: String) -> Result<(), ()> {
234        let global = self.global();
235
236        let task_source = global
237            .task_manager()
238            .database_access_task_source()
239            .to_sendable();
240        let response_listener = OpenRequestListener {
241            open_request: Trusted::new(self),
242        };
243        let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
244            let response_listener = response_listener.clone();
245            task_source.queue(task!(request_callback: move || {
246                response_listener.handle_delete_db(message.unwrap(), CanGc::note());
247            }))
248        })
249        .expect("Could not create delete database callback");
250
251        let delete_operation = SyncOperation::DeleteDatabase(
252            callback,
253            global.origin().immutable().clone(),
254            name,
255            self.get_id(),
256        );
257
258        if global
259            .storage_threads()
260            .send(IndexedDBThreadMsg::Sync(delete_operation))
261            .is_err()
262        {
263            return Err(());
264        }
265        Ok(())
266    }
267
268    pub fn dispatch_success(&self, name: String, version: u64, upgraded: bool, can_gc: CanGc) {
269        let global = self.global();
270        let result = self.pending_connection.or_init(|| {
271            debug_assert!(!upgraded, "A connection should exist for the upgraded db.");
272            IDBDatabase::new(
273                &global,
274                DOMString::from_string(name.clone()),
275                version,
276                can_gc,
277            )
278        });
279        self.idbrequest.set_ready_state_done();
280        let cx = GlobalScope::get_cx();
281
282        let _ac = enter_realm(&*result);
283        rooted!(in(*cx) let mut result_val = UndefinedValue());
284        result.safe_to_jsval(cx, result_val.handle_mut(), CanGc::note());
285        self.set_result(result_val.handle());
286
287        let event = Event::new(
288            &global,
289            Atom::from("success"),
290            EventBubbles::DoesNotBubble,
291            EventCancelable::NotCancelable,
292            CanGc::note(),
293        );
294        event.fire(self.upcast(), CanGc::note());
295    }
296}
297
298impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
299    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onblocked
300    event_handler!(blocked, GetOnblocked, SetOnblocked);
301
302    // https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onupgradeneeded
303    event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
304}