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::IpcSend;
8use dom_struct::dom_struct;
9use ipc_channel::router::ROUTER;
10use js::jsapi::Heap;
11use js::jsval::{DoubleValue, JSVal, ObjectValue, UndefinedValue};
12use js::rust::HandleValue;
13use profile_traits::ipc::IpcReceiver;
14use script_bindings::conversions::SafeToJSValConvertible;
15use serde::{Deserialize, Serialize};
16use storage_traits::indexeddb_thread::{
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::indexed_db::key_type_to_jsval;
41use crate::realms::enter_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),
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, result: BackendResult<IdbResult>, can_gc: CanGc) {
128        let request = self.request.root();
129        let global = request.global();
130        let cx = GlobalScope::get_cx();
131
132        // Substep 1: Set the result of request to result.
133        request.set_ready_state_done();
134
135        let _ac = enter_realm(&*request);
136        rooted!(in(*cx) let mut answer = UndefinedValue());
137
138        if let Ok(data) = result {
139            match data {
140                IdbResult::Key(key) => {
141                    key_type_to_jsval(GlobalScope::get_cx(), &key, answer.handle_mut(), can_gc)
142                },
143                IdbResult::Keys(keys) => {
144                    rooted_vec!(let mut array);
145                    for key in keys.into_iter() {
146                        rooted!(in(*cx) let mut val = UndefinedValue());
147                        key_type_to_jsval(GlobalScope::get_cx(), &key, val.handle_mut(), can_gc);
148                        array.push(Heap::boxed(val.get()));
149                    }
150                    array.safe_to_jsval(cx, answer.handle_mut(), can_gc);
151                },
152                IdbResult::Value(serialized_data) => {
153                    let result = bincode::deserialize(&serialized_data)
154                        .map_err(|_| Error::Data)
155                        .and_then(|data| {
156                            structuredclone::read(&global, data, answer.handle_mut(), can_gc)
157                        });
158                    if let Err(e) = result {
159                        warn!("Error reading structuredclone data");
160                        Self::handle_async_request_error(&global, cx, request, e);
161                        return;
162                    };
163                },
164                IdbResult::Values(serialized_values) => {
165                    rooted_vec!(let mut values);
166                    for serialized_data in serialized_values.into_iter() {
167                        rooted!(in(*cx) let mut val = UndefinedValue());
168                        let result = bincode::deserialize(&serialized_data)
169                            .map_err(|_| Error::Data)
170                            .and_then(|data| {
171                                structuredclone::read(&global, data, val.handle_mut(), can_gc)
172                            });
173                        if let Err(e) = result {
174                            warn!("Error reading structuredclone data");
175                            Self::handle_async_request_error(&global, cx, request, e);
176                            return;
177                        };
178                        values.push(Heap::boxed(val.get()));
179                    }
180                    values.safe_to_jsval(cx, answer.handle_mut(), can_gc);
181                },
182                IdbResult::Count(count) => {
183                    answer.handle_mut().set(DoubleValue(count as f64));
184                },
185                IdbResult::Iterate(records) => {
186                    let param = self.iteration_param.as_ref().expect(
187                        "iteration_param must be provided by IDBRequest::execute_async for Iterate",
188                    );
189                    let cursor = match iterate_cursor(&global, cx, param, records, can_gc) {
190                        Ok(cursor) => cursor,
191                        Err(e) => {
192                            warn!("Error reading structuredclone data");
193                            Self::handle_async_request_error(&global, cx, request, e);
194                            return;
195                        },
196                    };
197                    if let Some(cursor) = cursor {
198                        match cursor.downcast::<IDBCursorWithValue>() {
199                            Some(cursor_with_value) => {
200                                answer.handle_mut().set(ObjectValue(
201                                    *cursor_with_value.reflector().get_jsobject(),
202                                ));
203                            },
204                            None => {
205                                answer
206                                    .handle_mut()
207                                    .set(ObjectValue(*cursor.reflector().get_jsobject()));
208                            },
209                        }
210                    }
211                },
212                IdbResult::None => {
213                    // no-op
214                },
215                IdbResult::Error(error) => {
216                    // Substep 2
217                    Self::handle_async_request_error(&global, cx, request, error);
218                    return;
219                },
220            }
221
222            // Substep 3.1: Set the result of request to answer.
223            request.set_result(answer.handle());
224
225            // Substep 3.2: Set the error of request to undefined
226            request.set_error(None, CanGc::note());
227
228            // Substep 3.3: Fire a success event at request.
229            // TODO: follow spec here
230            let transaction = request
231                .transaction
232                .get()
233                .expect("Request unexpectedly has no transaction");
234
235            let event = Event::new(
236                &global,
237                Atom::from("success"),
238                EventBubbles::DoesNotBubble,
239                EventCancelable::NotCancelable,
240                CanGc::note(),
241            );
242
243            transaction.set_active_flag(true);
244            event
245                .upcast::<Event>()
246                .fire(request.upcast(), CanGc::note());
247            transaction.set_active_flag(false);
248        } else {
249            // FIXME:(arihant2math) dispatch correct error
250            // Substep 2
251            Self::handle_async_request_error(&global, cx, request, Error::Data);
252        }
253    }
254
255    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
256    // Implements Step 5.4.2
257    fn handle_async_request_error(
258        global: &GlobalScope,
259        cx: SafeJSContext,
260        request: DomRoot<IDBRequest>,
261        error: Error,
262    ) {
263        // Substep 1: Set the result of request to undefined.
264        rooted!(in(*cx) let undefined = UndefinedValue());
265        request.set_result(undefined.handle());
266
267        // Substep 2: Set the error of request to result.
268        request.set_error(Some(error), CanGc::note());
269
270        // Substep 3: Fire an error event at request.
271        // TODO: follow the spec here
272        let transaction = request
273            .transaction
274            .get()
275            .expect("Request has no transaction");
276
277        let event = Event::new(
278            global,
279            Atom::from("error"),
280            EventBubbles::Bubbles,
281            EventCancelable::Cancelable,
282            CanGc::note(),
283        );
284
285        // TODO: why does the transaction need to be active?
286        transaction.set_active_flag(true);
287        event
288            .upcast::<Event>()
289            .fire(request.upcast(), CanGc::note());
290        transaction.set_active_flag(false);
291    }
292}
293
294#[dom_struct]
295pub struct IDBRequest {
296    eventtarget: EventTarget,
297    #[ignore_malloc_size_of = "mozjs"]
298    result: Heap<JSVal>,
299    error: MutNullableDom<DOMException>,
300    source: MutNullableDom<IDBObjectStore>,
301    transaction: MutNullableDom<IDBTransaction>,
302    ready_state: Cell<IDBRequestReadyState>,
303}
304
305impl IDBRequest {
306    pub fn new_inherited() -> IDBRequest {
307        IDBRequest {
308            eventtarget: EventTarget::new_inherited(),
309
310            result: Heap::default(),
311            error: Default::default(),
312            source: Default::default(),
313            transaction: Default::default(),
314            ready_state: Cell::new(IDBRequestReadyState::Pending),
315        }
316    }
317
318    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBRequest> {
319        reflect_dom_object(Box::new(IDBRequest::new_inherited()), global, can_gc)
320    }
321
322    pub fn set_source(&self, source: Option<&IDBObjectStore>) {
323        self.source.set(source);
324    }
325
326    pub fn set_ready_state_done(&self) {
327        self.ready_state.set(IDBRequestReadyState::Done);
328    }
329
330    pub fn set_result(&self, result: HandleValue) {
331        self.result.set(result.get());
332    }
333
334    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
335        if let Some(error) = error {
336            if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
337                self.error.set(Some(&exception));
338            }
339        } else {
340            self.error.set(None);
341        }
342    }
343
344    pub fn set_transaction(&self, transaction: &IDBTransaction) {
345        self.transaction.set(Some(transaction));
346    }
347
348    // https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request
349    pub fn execute_async<T>(
350        source: &IDBObjectStore,
351        operation: AsyncOperation,
352        receiver: IpcReceiver<BackendResult<T>>,
353        request: Option<DomRoot<IDBRequest>>,
354        iteration_param: Option<IterationParam>,
355        can_gc: CanGc,
356    ) -> Fallible<DomRoot<IDBRequest>>
357    where
358        T: Into<IdbResult> + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static,
359    {
360        // Step 1: Let transaction be the transaction associated with source.
361        let transaction = source.transaction();
362        let global = transaction.global();
363
364        // Step 2: Assert: transaction is active.
365        if !transaction.is_active() {
366            return Err(Error::TransactionInactive);
367        }
368
369        // Step 3: If request was not given, let request be a new request with source as source.
370        let request = request.unwrap_or_else(|| {
371            let new_request = IDBRequest::new(&global, can_gc);
372            new_request.set_source(Some(source));
373            new_request.set_transaction(&transaction);
374            new_request
375        });
376
377        // Step 4: Add request to the end of transaction’s request list.
378        transaction.add_request(&request);
379
380        // Step 5: Run the operation, and queue a returning task in parallel
381        // the result will be put into `receiver`
382        let transaction_mode = match transaction.get_mode() {
383            IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
384            IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
385            IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
386        };
387
388        if matches!(
389            operation,
390            AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate { .. })
391        ) {
392            assert!(
393                iteration_param.is_some(),
394                "iteration_param must be provided for Iterate"
395            );
396        } else {
397            assert!(
398                iteration_param.is_none(),
399                "iteration_param should not be provided for operation other than Iterate"
400            );
401        }
402
403        let response_listener = RequestListener {
404            request: Trusted::new(&request),
405            iteration_param,
406        };
407
408        let task_source = global
409            .task_manager()
410            .database_access_task_source()
411            .to_sendable();
412
413        ROUTER.add_typed_route(
414            receiver.to_ipc_receiver(),
415            Box::new(move |message| {
416                let response_listener = response_listener.clone();
417                task_source.queue(task!(request_callback: move || {
418                    response_listener.handle_async_request_finished(
419                        message.expect("Could not unwrap message").inspect_err(|e| {
420                            if let BackendError::DbErr(e) = e {
421                                error!("Error in IndexedDB operation: {}", e);
422                            }
423                        }).map(|t| t.into()), CanGc::note());
424                }));
425            }),
426        );
427
428        transaction
429            .global()
430            .storage_threads()
431            .send(IndexedDBThreadMsg::Async(
432                global.origin().immutable().clone(),
433                transaction.get_db_name().to_string(),
434                source.get_name().to_string(),
435                transaction.get_serial_number(),
436                transaction_mode,
437                operation,
438            ))
439            .unwrap();
440
441        // Step 6
442        Ok(request)
443    }
444}
445
446impl IDBRequestMethods<crate::DomTypeHolder> for IDBRequest {
447    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result
448    fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) {
449        val.set(self.result.get());
450    }
451
452    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error
453    fn GetError(&self) -> Option<DomRoot<DOMException>> {
454        self.error.get()
455    }
456
457    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source
458    fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
459        self.source.get()
460    }
461
462    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction
463    fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
464        self.transaction.get()
465    }
466
467    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate
468    fn ReadyState(&self) -> IDBRequestReadyState {
469        self.ready_state.get()
470    }
471
472    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess
473    event_handler!(success, GetOnsuccess, SetOnsuccess);
474
475    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror
476    event_handler!(error, GetOnerror, SetOnerror);
477}