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