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, SyncOperation,
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    request_id: u64,
49}
50
51pub enum IdbResult {
52    Key(IndexedDBKeyType),
53    Keys(Vec<IndexedDBKeyType>),
54    Value(Vec<u8>),
55    Values(Vec<Vec<u8>>),
56    Count(u64),
57    Iterate(Vec<IndexedDBRecord>),
58    Error(Error),
59    None,
60}
61
62impl From<IndexedDBKeyType> for IdbResult {
63    fn from(value: IndexedDBKeyType) -> Self {
64        IdbResult::Key(value)
65    }
66}
67
68impl From<Vec<IndexedDBKeyType>> for IdbResult {
69    fn from(value: Vec<IndexedDBKeyType>) -> Self {
70        IdbResult::Keys(value)
71    }
72}
73
74impl From<Vec<u8>> for IdbResult {
75    fn from(value: Vec<u8>) -> Self {
76        IdbResult::Value(value)
77    }
78}
79
80impl From<Vec<Vec<u8>>> for IdbResult {
81    fn from(value: Vec<Vec<u8>>) -> Self {
82        IdbResult::Values(value)
83    }
84}
85
86impl From<PutItemResult> for IdbResult {
87    fn from(value: PutItemResult) -> Self {
88        match value {
89            PutItemResult::Success => Self::None,
90            PutItemResult::CannotOverwrite => Self::Error(Error::Constraint(None)),
91        }
92    }
93}
94
95impl From<Vec<IndexedDBRecord>> for IdbResult {
96    fn from(value: Vec<IndexedDBRecord>) -> Self {
97        Self::Iterate(value)
98    }
99}
100
101impl From<()> for IdbResult {
102    fn from(_value: ()) -> Self {
103        Self::None
104    }
105}
106
107impl<T> From<Option<T>> for IdbResult
108where
109    T: Into<IdbResult>,
110{
111    fn from(value: Option<T>) -> Self {
112        match value {
113            Some(value) => value.into(),
114            None => IdbResult::None,
115        }
116    }
117}
118
119impl From<u64> for IdbResult {
120    fn from(value: u64) -> Self {
121        IdbResult::Count(value)
122    }
123}
124
125impl RequestListener {
126    fn send_request_handled(transaction: &IDBTransaction, request_id: u64) {
127        let global = transaction.global();
128        // https://w3c.github.io/IndexedDB/#transaction-lifecycle
129        // A transaction is inactive after control returns to the event loop and
130        // when events are not being dispatched. We call this after dispatching
131        // the request event, so the backend can reevaluate commit eligibility.
132        let send_result = global.storage_threads().send(IndexedDBThreadMsg::Sync(
133            SyncOperation::RequestHandled {
134                origin: global.origin().immutable().clone(),
135                db_name: transaction.get_db_name().to_string(),
136                txn: transaction.get_serial_number(),
137                request_id,
138            },
139        ));
140        if send_result.is_err() {
141            error!("Failed to send SyncOperation::RequestHandled");
142        }
143        transaction.mark_request_handled(request_id);
144
145        // This request's result has been handled by script, the
146        // transaction might finally be ready to auto-commit.
147        transaction.maybe_commit();
148    }
149
150    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
151    // Implements Step 5.4
152    fn handle_async_request_finished(&self, cx: &mut JSContext, result: BackendResult<IdbResult>) {
153        let request = self.request.root();
154        let global = request.global();
155
156        let transaction = request
157            .transaction
158            .get()
159            .expect("Request unexpectedly has no transaction");
160        // Substep 1: Set the result of request to result.
161        request.set_ready_state_done();
162
163        let mut realm = enter_auto_realm(cx, &*request);
164        let cx: &mut JSContext = &mut realm;
165        rooted!(&in(cx) let mut answer = UndefinedValue());
166
167        if let Ok(data) = result {
168            match data {
169                IdbResult::Key(key) => key_type_to_jsval(cx, &key, answer.handle_mut()),
170                IdbResult::Keys(keys) => {
171                    rooted!(&in(cx) let mut array = vec![JSVal::default(); keys.len()]);
172                    for (i, key) in keys.into_iter().enumerate() {
173                        key_type_to_jsval(cx, &key, array.handle_mut_at(i));
174                    }
175                    array.safe_to_jsval(cx, answer.handle_mut());
176                },
177                IdbResult::Value(serialized_data) => {
178                    let result = postcard::from_bytes(&serialized_data)
179                        .map_err(|_| Error::Data(None))
180                        .and_then(|data| {
181                            structuredclone::read(
182                                &global,
183                                data,
184                                answer.handle_mut(),
185                                CanGc::from_cx(cx),
186                            )
187                        });
188                    if let Err(e) = result {
189                        warn!("Error reading structuredclone data");
190                        Self::handle_async_request_error(&global, cx, request, e, self.request_id);
191                        return;
192                    };
193                },
194                IdbResult::Values(serialized_values) => {
195                    rooted!(&in(cx) let mut values = vec![JSVal::default(); serialized_values.len()]);
196                    for (i, serialized_data) in serialized_values.into_iter().enumerate() {
197                        let result = postcard::from_bytes(&serialized_data)
198                            .map_err(|_| Error::Data(None))
199                            .and_then(|data| {
200                                structuredclone::read(
201                                    &global,
202                                    data,
203                                    values.handle_mut_at(i),
204                                    CanGc::from_cx(cx),
205                                )
206                            });
207                        if let Err(e) = result {
208                            warn!("Error reading structuredclone data");
209                            Self::handle_async_request_error(
210                                &global,
211                                cx,
212                                request,
213                                e,
214                                self.request_id,
215                            );
216                            return;
217                        };
218                    }
219                    values.safe_to_jsval(cx, answer.handle_mut());
220                },
221                IdbResult::Count(count) => {
222                    answer.handle_mut().set(DoubleValue(count as f64));
223                },
224                IdbResult::Iterate(records) => {
225                    let param = self.iteration_param.as_ref().expect(
226                        "iteration_param must be provided by IDBRequest::execute_async for Iterate",
227                    );
228                    let cursor = match iterate_cursor(&global, cx, param, records) {
229                        Ok(cursor) => cursor,
230                        Err(e) => {
231                            warn!("Error reading structuredclone data");
232                            Self::handle_async_request_error(
233                                &global,
234                                cx,
235                                request,
236                                e,
237                                self.request_id,
238                            );
239                            return;
240                        },
241                    };
242                    if let Some(cursor) = cursor {
243                        match cursor.downcast::<IDBCursorWithValue>() {
244                            Some(cursor_with_value) => {
245                                answer.handle_mut().set(ObjectValue(
246                                    *cursor_with_value.reflector().get_jsobject(),
247                                ));
248                            },
249                            None => {
250                                answer
251                                    .handle_mut()
252                                    .set(ObjectValue(*cursor.reflector().get_jsobject()));
253                            },
254                        }
255                    }
256                },
257                IdbResult::None => {
258                    // no-op
259                },
260                IdbResult::Error(error) => {
261                    // Substep 2
262                    Self::handle_async_request_error(&global, cx, request, error, self.request_id);
263                    return;
264                },
265            }
266
267            // Substep 3.1: Set the result of request to answer.
268            request.set_result(answer.handle());
269
270            // Substep 3.2: Set the error of request to undefined
271            request.set_error(None, CanGc::from_cx(cx));
272
273            // Substep 3.3: Fire a success event at request.
274            // TODO: follow spec here
275            let event = Event::new(
276                &global,
277                Atom::from("success"),
278                EventBubbles::DoesNotBubble,
279                EventCancelable::NotCancelable,
280                CanGc::from_cx(cx),
281            );
282
283            transaction.set_active_flag(true);
284            event
285                .upcast::<Event>()
286                .fire(request.upcast(), CanGc::from_cx(cx));
287            // https://w3c.github.io/IndexedDB/#transaction-lifetime
288            // Step 3:
289            // When each request associated with a transaction is processed,
290            // a success or error event will be fired. While the event is being
291            // dispatched, the transaction state is set to active, allowing additional
292            // requests to be made against the transaction. Once the event dispatch
293            // is complete, the transaction’s state is set to inactive again.
294            transaction.set_active_flag(false);
295            // Notify the transaction that this request has finished.
296            transaction.request_finished();
297
298            Self::send_request_handled(&transaction, self.request_id);
299        } else {
300            // FIXME:(arihant2math) dispatch correct error
301            // Substep 2
302            Self::handle_async_request_error(
303                &global,
304                cx,
305                request,
306                Error::Data(None),
307                self.request_id,
308            );
309        }
310    }
311
312    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
313    // Implements Step 5.4.2
314    fn handle_async_request_error(
315        global: &GlobalScope,
316        cx: &mut JSContext,
317        request: DomRoot<IDBRequest>,
318        error: Error,
319        request_id: u64,
320    ) {
321        let transaction = request
322            .transaction
323            .get()
324            .expect("Request has no transaction");
325        // Substep 1: Set the result of request to undefined.
326        rooted!(&in(cx) let undefined = UndefinedValue());
327        request.set_result(undefined.handle());
328
329        // Substep 2: Set the error of request to result.
330        request.set_error(Some(error.clone()), CanGc::from_cx(cx));
331
332        // Substep 3: Fire an error event at request.
333        // TODO: follow the spec here
334        let event = Event::new(
335            global,
336            Atom::from("error"),
337            EventBubbles::Bubbles,
338            EventCancelable::Cancelable,
339            CanGc::from_cx(cx),
340        );
341
342        transaction.set_active_flag(true);
343        // https://w3c.github.io/IndexedDB/#events
344        // Step 3: Set event’s bubbles and cancelable attributes to false.
345        let default_not_prevented = event
346            .upcast::<Event>()
347            .fire(request.upcast(), CanGc::from_cx(cx));
348        // https://w3c.github.io/IndexedDB/#transaction-lifetime
349        // Step 3:
350        // When each request associated with a transaction is processed,
351        // a success or error event will be fired. While the event is being
352        // dispatched, the transaction state is set to active, allowing additional
353        // requests to be made against the transaction. Once the event dispatch
354        // is complete, the transaction’s state is set to inactive again.
355        transaction.set_active_flag(false);
356        // https://w3c.github.io/IndexedDB/#transaction-lifetime
357        // Step 4: A transaction can be aborted at any time before it is finished, even if the transaction isn’t currently active or hasn’t yet started.
358        // An explicit call to abort() will initiate an abort. An abort will also be initiated following a failed request that is not handled by script.
359        // When a transaction is aborted the implementation must undo (roll back) any changes that were made to the database during that transaction. This includes both changes to the contents of object stores as well as additions and removals of object stores and indexes.
360        if default_not_prevented {
361            transaction.initiate_abort(error.clone(), CanGc::from_cx(cx));
362            transaction.request_backend_abort();
363        }
364        // Notify the transaction that this request has finished.
365        transaction.request_finished();
366        Self::send_request_handled(&transaction, request_id);
367    }
368}
369
370#[dom_struct]
371pub struct IDBRequest {
372    eventtarget: EventTarget,
373    #[ignore_malloc_size_of = "mozjs"]
374    result: Heap<JSVal>,
375    error: MutNullableDom<DOMException>,
376    source: MutNullableDom<IDBObjectStore>,
377    transaction: MutNullableDom<IDBTransaction>,
378    ready_state: Cell<IDBRequestReadyState>,
379}
380
381impl IDBRequest {
382    pub fn new_inherited() -> IDBRequest {
383        IDBRequest {
384            eventtarget: EventTarget::new_inherited(),
385
386            result: Heap::default(),
387            error: Default::default(),
388            source: Default::default(),
389            transaction: Default::default(),
390            ready_state: Cell::new(IDBRequestReadyState::Pending),
391        }
392    }
393
394    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBRequest> {
395        reflect_dom_object(Box::new(IDBRequest::new_inherited()), global, can_gc)
396    }
397
398    pub fn set_source(&self, source: Option<&IDBObjectStore>) {
399        self.source.set(source);
400    }
401
402    pub fn set_ready_state_done(&self) {
403        self.ready_state.set(IDBRequestReadyState::Done);
404    }
405
406    pub fn set_result(&self, result: HandleValue) {
407        self.result.set(result.get());
408    }
409
410    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
411        if let Some(error) = error {
412            if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
413                self.error.set(Some(&exception));
414            }
415        } else {
416            self.error.set(None);
417        }
418    }
419
420    pub fn set_transaction(&self, transaction: &IDBTransaction) {
421        self.transaction.set(Some(transaction));
422    }
423
424    pub fn clear_transaction(&self) {
425        self.transaction.set(None);
426    }
427
428    pub(crate) fn transaction(&self) -> Option<DomRoot<IDBTransaction>> {
429        self.transaction.get()
430    }
431
432    // https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request
433    pub fn execute_async<T, F>(
434        source: &IDBObjectStore,
435        operation_fn: F,
436        request: Option<DomRoot<IDBRequest>>,
437        iteration_param: Option<IterationParam>,
438        can_gc: CanGc,
439    ) -> Fallible<DomRoot<IDBRequest>>
440    where
441        T: Into<IdbResult> + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static,
442        F: FnOnce(GenericCallback<BackendResult<T>>) -> AsyncOperation,
443    {
444        // Step 1: Let transaction be the transaction associated with source.
445        let transaction = source.transaction();
446        let global = transaction.global();
447        // Step 2: Assert: transaction is active.
448        if !transaction.is_active() || !transaction.is_usable() {
449            return Err(Error::TransactionInactive(None));
450        }
451
452        let request_id = transaction.allocate_request_id();
453
454        // Step 3: If request was not given, let request be a new request with source as source.
455        let request = request.unwrap_or_else(|| {
456            let new_request = IDBRequest::new(&global, can_gc);
457            new_request.set_source(Some(source));
458            new_request.set_transaction(&transaction);
459            new_request
460        });
461
462        // Step 4: Add request to the end of transaction’s request list.
463        transaction.add_request(&request);
464
465        // Step 5: Run the operation, and queue a returning task in parallel
466        // the result will be put into `receiver`
467        let transaction_mode = match transaction.get_mode() {
468            IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
469            IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
470            IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
471        };
472
473        let response_listener = RequestListener {
474            request: Trusted::new(&request),
475            iteration_param: iteration_param.clone(),
476            request_id,
477        };
478
479        let task_source = global
480            .task_manager()
481            .database_access_task_source()
482            .to_sendable();
483
484        let closure = move |message: Result<BackendResult<T>, ipc_channel::IpcError>| {
485            let response_listener = response_listener.clone();
486            task_source.queue(task!(request_callback: move |cx| {
487                response_listener.handle_async_request_finished(
488                    cx,
489                    message.expect("Could not unwrap message").inspect_err(|e| {
490                        if let BackendError::DbErr(e) = e {
491                            error!("Error in IndexedDB operation: {}", e);
492                        }
493                    }).map(|t| t.into()),
494                );
495            }));
496        };
497        let callback = GenericCallback::new(global.time_profiler_chan().clone(), closure)
498            .expect("Could not create callback");
499        let operation = operation_fn(callback);
500
501        if matches!(
502            operation,
503            AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate { .. })
504        ) {
505            assert!(
506                iteration_param.is_some(),
507                "iteration_param must be provided for Iterate"
508            );
509        } else {
510            assert!(
511                iteration_param.is_none(),
512                "iteration_param should not be provided for operation other than Iterate"
513            );
514        }
515
516        // Start is a backend database task (spec). Script does not model it with a
517        // separate queued task, backend scheduling decides when requests begin.
518        transaction
519            .global()
520            .storage_threads()
521            .send(IndexedDBThreadMsg::Async(
522                global.origin().immutable().clone(),
523                transaction.get_db_name().to_string(),
524                source.get_name().to_string(),
525                transaction.get_serial_number(),
526                request_id,
527                transaction_mode,
528                operation,
529            ))
530            .unwrap();
531
532        // Step 6
533        Ok(request)
534    }
535}
536
537impl IDBRequestMethods<crate::DomTypeHolder> for IDBRequest {
538    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result>
539    fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) {
540        val.set(self.result.get());
541    }
542
543    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error>
544    fn GetError(&self) -> Option<DomRoot<DOMException>> {
545        self.error.get()
546    }
547
548    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source>
549    fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
550        self.source.get()
551    }
552
553    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction>
554    fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
555        self.transaction.get()
556    }
557
558    /// <https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate>
559    fn ReadyState(&self) -> IDBRequestReadyState {
560        self.ready_state.get()
561    }
562
563    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess
564    event_handler!(success, GetOnsuccess, SetOnsuccess);
565
566    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror
567    event_handler!(error, GetOnerror, SetOnerror);
568}