script/dom/indexeddb/
idbrequest.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 std::cell::Cell;
6
7use base::generic_channel::GenericSend;
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::conversions::ToJSValConvertible;
11use js::jsapi::Heap;
12use js::jsval::{DoubleValue, JSVal, ObjectValue, UndefinedValue};
13use js::rust::HandleValue;
14use profile_traits::generic_callback::GenericCallback;
15use serde::{Deserialize, Serialize};
16use storage_traits::indexeddb::{
17    AsyncOperation, AsyncReadOnlyOperation, BackendError, BackendResult, IndexedDBKeyType,
18    IndexedDBRecord, IndexedDBThreadMsg, IndexedDBTxnMode, PutItemResult,
19};
20use stylo_atoms::Atom;
21
22use crate::dom::bindings::codegen::Bindings::IDBRequestBinding::{
23    IDBRequestMethods, IDBRequestReadyState,
24};
25use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
26use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object};
30use crate::dom::bindings::root::{DomRoot, MutNullableDom};
31use crate::dom::bindings::structuredclone;
32use crate::dom::domexception::DOMException;
33use crate::dom::event::{Event, EventBubbles, EventCancelable};
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::globalscope::GlobalScope;
36use crate::dom::indexeddb::idbcursor::{IterationParam, iterate_cursor};
37use crate::dom::indexeddb::idbcursorwithvalue::IDBCursorWithValue;
38use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
39use crate::dom::indexeddb::idbtransaction::IDBTransaction;
40use crate::indexeddb::key_type_to_jsval;
41use crate::realms::enter_auto_realm;
42use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
43
44#[derive(Clone)]
45struct RequestListener {
46    request: Trusted<IDBRequest>,
47    iteration_param: Option<IterationParam>,
48}
49
50pub enum IdbResult {
51    Key(IndexedDBKeyType),
52    Keys(Vec<IndexedDBKeyType>),
53    Value(Vec<u8>),
54    Values(Vec<Vec<u8>>),
55    Count(u64),
56    Iterate(Vec<IndexedDBRecord>),
57    Error(Error),
58    None,
59}
60
61impl From<IndexedDBKeyType> for IdbResult {
62    fn from(value: IndexedDBKeyType) -> Self {
63        IdbResult::Key(value)
64    }
65}
66
67impl From<Vec<IndexedDBKeyType>> for IdbResult {
68    fn from(value: Vec<IndexedDBKeyType>) -> Self {
69        IdbResult::Keys(value)
70    }
71}
72
73impl From<Vec<u8>> for IdbResult {
74    fn from(value: Vec<u8>) -> Self {
75        IdbResult::Value(value)
76    }
77}
78
79impl From<Vec<Vec<u8>>> for IdbResult {
80    fn from(value: Vec<Vec<u8>>) -> Self {
81        IdbResult::Values(value)
82    }
83}
84
85impl From<PutItemResult> for IdbResult {
86    fn from(value: PutItemResult) -> Self {
87        match value {
88            PutItemResult::Success => Self::None,
89            PutItemResult::CannotOverwrite => Self::Error(Error::Constraint(None)),
90        }
91    }
92}
93
94impl From<Vec<IndexedDBRecord>> for IdbResult {
95    fn from(value: Vec<IndexedDBRecord>) -> Self {
96        Self::Iterate(value)
97    }
98}
99
100impl From<()> for IdbResult {
101    fn from(_value: ()) -> Self {
102        Self::None
103    }
104}
105
106impl<T> From<Option<T>> for IdbResult
107where
108    T: Into<IdbResult>,
109{
110    fn from(value: Option<T>) -> Self {
111        match value {
112            Some(value) => value.into(),
113            None => IdbResult::None,
114        }
115    }
116}
117
118impl From<u64> for IdbResult {
119    fn from(value: u64) -> Self {
120        IdbResult::Count(value)
121    }
122}
123
124impl RequestListener {
125    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
126    // Implements Step 5.4
127    fn handle_async_request_finished(&self, cx: &mut JSContext, result: BackendResult<IdbResult>) {
128        let request = self.request.root();
129        let global = request.global();
130
131        // Substep 1: Set the result of request to result.
132        request.set_ready_state_done();
133
134        let mut realm = enter_auto_realm(cx, &*request);
135        let cx: &mut JSContext = &mut realm;
136        rooted!(&in(cx) let mut answer = UndefinedValue());
137
138        if let Ok(data) = result {
139            match data {
140                IdbResult::Key(key) => key_type_to_jsval(cx, &key, answer.handle_mut()),
141                IdbResult::Keys(keys) => {
142                    rooted!(&in(cx) let mut array = vec![JSVal::default(); keys.len()]);
143                    for (i, key) in keys.into_iter().enumerate() {
144                        key_type_to_jsval(cx, &key, array.handle_mut_at(i));
145                    }
146                    array.safe_to_jsval(cx, answer.handle_mut());
147                },
148                IdbResult::Value(serialized_data) => {
149                    let result = postcard::from_bytes(&serialized_data)
150                        .map_err(|_| Error::Data(None))
151                        .and_then(|data| {
152                            structuredclone::read(
153                                &global,
154                                data,
155                                answer.handle_mut(),
156                                CanGc::from_cx(cx),
157                            )
158                        });
159                    if let Err(e) = result {
160                        warn!("Error reading structuredclone data");
161                        Self::handle_async_request_error(&global, cx, request, e);
162                        return;
163                    };
164                },
165                IdbResult::Values(serialized_values) => {
166                    rooted!(&in(cx) let mut values = vec![JSVal::default(); serialized_values.len()]);
167                    for (i, serialized_data) in serialized_values.into_iter().enumerate() {
168                        let result = postcard::from_bytes(&serialized_data)
169                            .map_err(|_| Error::Data(None))
170                            .and_then(|data| {
171                                structuredclone::read(
172                                    &global,
173                                    data,
174                                    values.handle_mut_at(i),
175                                    CanGc::from_cx(cx),
176                                )
177                            });
178                        if let Err(e) = result {
179                            warn!("Error reading structuredclone data");
180                            Self::handle_async_request_error(&global, cx, request, e);
181                            return;
182                        };
183                    }
184                    values.safe_to_jsval(cx, answer.handle_mut());
185                },
186                IdbResult::Count(count) => {
187                    answer.handle_mut().set(DoubleValue(count as f64));
188                },
189                IdbResult::Iterate(records) => {
190                    let param = self.iteration_param.as_ref().expect(
191                        "iteration_param must be provided by IDBRequest::execute_async for Iterate",
192                    );
193                    let cursor = match iterate_cursor(&global, cx, param, records) {
194                        Ok(cursor) => cursor,
195                        Err(e) => {
196                            warn!("Error reading structuredclone data");
197                            Self::handle_async_request_error(&global, cx, request, e);
198                            return;
199                        },
200                    };
201                    if let Some(cursor) = cursor {
202                        match cursor.downcast::<IDBCursorWithValue>() {
203                            Some(cursor_with_value) => {
204                                answer.handle_mut().set(ObjectValue(
205                                    *cursor_with_value.reflector().get_jsobject(),
206                                ));
207                            },
208                            None => {
209                                answer
210                                    .handle_mut()
211                                    .set(ObjectValue(*cursor.reflector().get_jsobject()));
212                            },
213                        }
214                    }
215                },
216                IdbResult::None => {
217                    // no-op
218                },
219                IdbResult::Error(error) => {
220                    // Substep 2
221                    Self::handle_async_request_error(&global, cx, request, error);
222                    return;
223                },
224            }
225
226            // Substep 3.1: Set the result of request to answer.
227            request.set_result(answer.handle());
228
229            // Substep 3.2: Set the error of request to undefined
230            request.set_error(None, CanGc::from_cx(cx));
231
232            // Substep 3.3: Fire a success event at request.
233            // TODO: follow spec here
234            let transaction = request
235                .transaction
236                .get()
237                .expect("Request unexpectedly has no transaction");
238
239            let event = Event::new(
240                &global,
241                Atom::from("success"),
242                EventBubbles::DoesNotBubble,
243                EventCancelable::NotCancelable,
244                CanGc::from_cx(cx),
245            );
246
247            transaction.set_active_flag(true);
248            event
249                .upcast::<Event>()
250                .fire(request.upcast(), CanGc::from_cx(cx));
251            transaction.set_active_flag(false);
252            // Notify the transaction that this request has finished.
253            transaction.request_finished();
254        } else {
255            // FIXME:(arihant2math) dispatch correct error
256            // Substep 2
257            Self::handle_async_request_error(&global, cx, request, Error::Data(None));
258        }
259    }
260
261    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
262    // Implements Step 5.4.2
263    fn handle_async_request_error(
264        global: &GlobalScope,
265        cx: &mut JSContext,
266        request: DomRoot<IDBRequest>,
267        error: Error,
268    ) {
269        // Substep 1: Set the result of request to undefined.
270        rooted!(&in(cx) let undefined = UndefinedValue());
271        request.set_result(undefined.handle());
272
273        // Substep 2: Set the error of request to result.
274        request.set_error(Some(error), CanGc::from_cx(cx));
275
276        // Substep 3: Fire an error event at request.
277        // TODO: follow the spec here
278        let transaction = request
279            .transaction
280            .get()
281            .expect("Request has no transaction");
282
283        let event = Event::new(
284            global,
285            Atom::from("error"),
286            EventBubbles::Bubbles,
287            EventCancelable::Cancelable,
288            CanGc::from_cx(cx),
289        );
290
291        // TODO: why does the transaction need to be active?
292        transaction.set_active_flag(true);
293        event
294            .upcast::<Event>()
295            .fire(request.upcast(), CanGc::from_cx(cx));
296        transaction.set_active_flag(false);
297        // Notify the transaction that this request has finished.
298        transaction.request_finished();
299    }
300}
301
302#[dom_struct]
303pub struct IDBRequest {
304    eventtarget: EventTarget,
305    #[ignore_malloc_size_of = "mozjs"]
306    result: Heap<JSVal>,
307    error: MutNullableDom<DOMException>,
308    source: MutNullableDom<IDBObjectStore>,
309    transaction: MutNullableDom<IDBTransaction>,
310    ready_state: Cell<IDBRequestReadyState>,
311}
312
313impl IDBRequest {
314    pub fn new_inherited() -> IDBRequest {
315        IDBRequest {
316            eventtarget: EventTarget::new_inherited(),
317
318            result: Heap::default(),
319            error: Default::default(),
320            source: Default::default(),
321            transaction: Default::default(),
322            ready_state: Cell::new(IDBRequestReadyState::Pending),
323        }
324    }
325
326    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBRequest> {
327        reflect_dom_object(Box::new(IDBRequest::new_inherited()), global, can_gc)
328    }
329
330    pub fn set_source(&self, source: Option<&IDBObjectStore>) {
331        self.source.set(source);
332    }
333
334    pub fn set_ready_state_done(&self) {
335        self.ready_state.set(IDBRequestReadyState::Done);
336    }
337
338    pub fn set_result(&self, result: HandleValue) {
339        self.result.set(result.get());
340    }
341
342    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
343        if let Some(error) = error {
344            if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
345                self.error.set(Some(&exception));
346            }
347        } else {
348            self.error.set(None);
349        }
350    }
351
352    pub fn set_transaction(&self, transaction: &IDBTransaction) {
353        self.transaction.set(Some(transaction));
354    }
355
356    // https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request
357    pub fn execute_async<T, F>(
358        source: &IDBObjectStore,
359        operation_fn: F,
360        request: Option<DomRoot<IDBRequest>>,
361        iteration_param: Option<IterationParam>,
362        can_gc: CanGc,
363    ) -> Fallible<DomRoot<IDBRequest>>
364    where
365        T: Into<IdbResult> + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static,
366        F: FnOnce(GenericCallback<BackendResult<T>>) -> AsyncOperation,
367    {
368        // Step 1: Let transaction be the transaction associated with source.
369        let transaction = source.transaction();
370        let global = transaction.global();
371
372        // Step 2: Assert: transaction is active.
373        if !transaction.is_active() {
374            return Err(Error::TransactionInactive(None));
375        }
376
377        // Step 3: If request was not given, let request be a new request with source as source.
378        let request = request.unwrap_or_else(|| {
379            let new_request = IDBRequest::new(&global, can_gc);
380            new_request.set_source(Some(source));
381            new_request.set_transaction(&transaction);
382            new_request
383        });
384
385        // Step 4: Add request to the end of transaction’s request list.
386        transaction.add_request(&request);
387
388        // Step 5: Run the operation, and queue a returning task in parallel
389        // the result will be put into `receiver`
390        let transaction_mode = match transaction.get_mode() {
391            IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
392            IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
393            IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
394        };
395
396        let response_listener = RequestListener {
397            request: Trusted::new(&request),
398            iteration_param: iteration_param.clone(),
399        };
400
401        let task_source = global
402            .task_manager()
403            .database_access_task_source()
404            .to_sendable();
405
406        let closure = move |message: Result<BackendResult<T>, ipc_channel::Error>| {
407            let response_listener = response_listener.clone();
408            task_source.queue(task!(request_callback: move |cx| {
409                response_listener.handle_async_request_finished(
410                    cx,
411                    message.expect("Could not unwrap message").inspect_err(|e| {
412                        if let BackendError::DbErr(e) = e {
413                            error!("Error in IndexedDB operation: {}", e);
414                        }
415                    }).map(|t| t.into()));
416            }));
417        };
418        let callback = GenericCallback::new(global.time_profiler_chan().clone(), closure)
419            .expect("Could not create callback");
420        let operation = operation_fn(callback);
421
422        if matches!(
423            operation,
424            AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate { .. })
425        ) {
426            assert!(
427                iteration_param.is_some(),
428                "iteration_param must be provided for Iterate"
429            );
430        } else {
431            assert!(
432                iteration_param.is_none(),
433                "iteration_param should not be provided for operation other than Iterate"
434            );
435        }
436
437        transaction
438            .global()
439            .storage_threads()
440            .send(IndexedDBThreadMsg::Async(
441                global.origin().immutable().clone(),
442                transaction.get_db_name().to_string(),
443                source.get_name().to_string(),
444                transaction.get_serial_number(),
445                transaction_mode,
446                operation,
447            ))
448            .unwrap();
449
450        // Step 6
451        Ok(request)
452    }
453}
454
455impl IDBRequestMethods<crate::DomTypeHolder> for IDBRequest {
456    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result>
457    fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) {
458        val.set(self.result.get());
459    }
460
461    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error>
462    fn GetError(&self) -> Option<DomRoot<DOMException>> {
463        self.error.get()
464    }
465
466    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source>
467    fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
468        self.source.get()
469    }
470
471    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction>
472    fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
473        self.transaction.get()
474    }
475
476    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate>
477    fn ReadyState(&self) -> IDBRequestReadyState {
478        self.ready_state.get()
479    }
480
481    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess
482    event_handler!(success, GetOnsuccess, SetOnsuccess);
483
484    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror
485    event_handler!(error, GetOnerror, SetOnerror);
486}