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;
6
7use dom_struct::dom_struct;
8use ipc_channel::router::ROUTER;
9use js::jsapi::Heap;
10use js::jsval::{DoubleValue, JSVal, UndefinedValue};
11use js::rust::HandleValue;
12use net_traits::IpcSend;
13use net_traits::indexeddb_thread::{
14    AsyncOperation, BackendError, BackendResult, IndexedDBKeyType, IndexedDBThreadMsg,
15    IndexedDBTxnMode, PutItemResult,
16};
17use profile_traits::ipc::IpcReceiver;
18use serde::{Deserialize, Serialize};
19use stylo_atoms::Atom;
20
21use crate::dom::bindings::codegen::Bindings::IDBRequestBinding::{
22    IDBRequestMethods, IDBRequestReadyState,
23};
24use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
25use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
29use crate::dom::bindings::root::{DomRoot, MutNullableDom};
30use crate::dom::bindings::structuredclone;
31use crate::dom::domexception::DOMException;
32use crate::dom::event::{Event, EventBubbles, EventCancelable};
33use crate::dom::eventtarget::EventTarget;
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::idbobjectstore::IDBObjectStore;
36use crate::dom::idbtransaction::IDBTransaction;
37use crate::indexed_db::key_type_to_jsval;
38use crate::realms::enter_realm;
39use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
40
41#[derive(Clone)]
42struct RequestListener {
43    request: Trusted<IDBRequest>,
44}
45
46pub enum IdbResult {
47    Key(IndexedDBKeyType),
48    Data(Vec<u8>),
49    Count(u64),
50    Error(Error),
51    None,
52}
53
54impl From<IndexedDBKeyType> for IdbResult {
55    fn from(value: IndexedDBKeyType) -> Self {
56        IdbResult::Key(value)
57    }
58}
59
60impl From<Vec<u8>> for IdbResult {
61    fn from(value: Vec<u8>) -> Self {
62        IdbResult::Data(value)
63    }
64}
65
66impl From<PutItemResult> for IdbResult {
67    fn from(value: PutItemResult) -> Self {
68        match value {
69            PutItemResult::Success => Self::None,
70            PutItemResult::CannotOverwrite => Self::Error(Error::Constraint),
71        }
72    }
73}
74
75impl From<()> for IdbResult {
76    fn from(_value: ()) -> Self {
77        Self::None
78    }
79}
80
81impl<T> From<Option<T>> for IdbResult
82where
83    T: Into<IdbResult>,
84{
85    fn from(value: Option<T>) -> Self {
86        match value {
87            Some(value) => value.into(),
88            None => IdbResult::None,
89        }
90    }
91}
92
93impl From<u64> for IdbResult {
94    fn from(value: u64) -> Self {
95        IdbResult::Count(value)
96    }
97}
98
99impl RequestListener {
100    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
101    // Implements Step 5.4
102    fn handle_async_request_finished(&self, result: BackendResult<IdbResult>) {
103        let request = self.request.root();
104        let global = request.global();
105        let cx = GlobalScope::get_cx();
106
107        // Substep 1: Set the result of request to result.
108        request.set_ready_state_done();
109
110        let _ac = enter_realm(&*request);
111        rooted!(in(*cx) let mut answer = UndefinedValue());
112
113        if let Ok(data) = result {
114            match data {
115                IdbResult::Key(key) => {
116                    key_type_to_jsval(GlobalScope::get_cx(), &key, answer.handle_mut())
117                },
118                IdbResult::Data(serialized_data) => {
119                    let result = bincode::deserialize(&serialized_data)
120                        .map_err(|_| Error::Data)
121                        .and_then(|data| structuredclone::read(&global, data, answer.handle_mut()));
122                    if let Err(e) = result {
123                        warn!("Error reading structuredclone data");
124                        Self::handle_async_request_error(&global, cx, request, e);
125                        return;
126                    };
127                },
128                IdbResult::Count(count) => {
129                    answer.handle_mut().set(DoubleValue(count as f64));
130                },
131                IdbResult::None => {
132                    // no-op
133                },
134                IdbResult::Error(error) => {
135                    // Substep 2
136                    Self::handle_async_request_error(&global, cx, request, error);
137                    return;
138                },
139            }
140
141            // Substep 3.1: Set the result of request to answer.
142            request.set_result(answer.handle());
143
144            // Substep 3.2: Set the error of request to undefined
145            request.set_error(None, CanGc::note());
146
147            // Substep 3.3: Fire a success event at request.
148            // TODO: follow spec here
149            let transaction = request
150                .transaction
151                .get()
152                .expect("Request unexpectedly has no transaction");
153
154            let event = Event::new(
155                &global,
156                Atom::from("success"),
157                EventBubbles::DoesNotBubble,
158                EventCancelable::NotCancelable,
159                CanGc::note(),
160            );
161
162            transaction.set_active_flag(true);
163            event
164                .upcast::<Event>()
165                .fire(request.upcast(), CanGc::note());
166            transaction.set_active_flag(false);
167        } else {
168            // FIXME:(arihant2math) dispatch correct error
169            // Substep 2
170            Self::handle_async_request_error(&global, cx, request, Error::Data);
171        }
172    }
173
174    // https://www.w3.org/TR/IndexedDB-2/#async-execute-request
175    // Implements Step 5.4.2
176    fn handle_async_request_error(
177        global: &GlobalScope,
178        cx: SafeJSContext,
179        request: DomRoot<IDBRequest>,
180        error: Error,
181    ) {
182        // Substep 1: Set the result of request to undefined.
183        rooted!(in(*cx) let undefined = UndefinedValue());
184        request.set_result(undefined.handle());
185
186        // Substep 2: Set the error of request to result.
187        request.set_error(Some(error), CanGc::note());
188
189        // Substep 3: Fire an error event at request.
190        // TODO: follow the spec here
191        let transaction = request
192            .transaction
193            .get()
194            .expect("Request has no transaction");
195
196        let event = Event::new(
197            global,
198            Atom::from("error"),
199            EventBubbles::Bubbles,
200            EventCancelable::Cancelable,
201            CanGc::note(),
202        );
203
204        // TODO: why does the transaction need to be active?
205        transaction.set_active_flag(true);
206        event
207            .upcast::<Event>()
208            .fire(request.upcast(), CanGc::note());
209        transaction.set_active_flag(false);
210    }
211}
212
213#[dom_struct]
214pub struct IDBRequest {
215    eventtarget: EventTarget,
216    #[ignore_malloc_size_of = "mozjs"]
217    result: Heap<JSVal>,
218    error: MutNullableDom<DOMException>,
219    source: MutNullableDom<IDBObjectStore>,
220    transaction: MutNullableDom<IDBTransaction>,
221    ready_state: Cell<IDBRequestReadyState>,
222}
223
224impl IDBRequest {
225    pub fn new_inherited() -> IDBRequest {
226        IDBRequest {
227            eventtarget: EventTarget::new_inherited(),
228
229            result: Heap::default(),
230            error: Default::default(),
231            source: Default::default(),
232            transaction: Default::default(),
233            ready_state: Cell::new(IDBRequestReadyState::Pending),
234        }
235    }
236
237    pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBRequest> {
238        reflect_dom_object(Box::new(IDBRequest::new_inherited()), global, can_gc)
239    }
240
241    pub fn set_source(&self, source: Option<&IDBObjectStore>) {
242        self.source.set(source);
243    }
244
245    pub fn set_ready_state_done(&self) {
246        self.ready_state.set(IDBRequestReadyState::Done);
247    }
248
249    pub fn set_result(&self, result: HandleValue) {
250        self.result.set(result.get());
251    }
252
253    pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
254        if let Some(error) = error {
255            if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) {
256                self.error.set(Some(&exception));
257            }
258        } else {
259            self.error.set(None);
260        }
261    }
262
263    pub fn set_transaction(&self, transaction: &IDBTransaction) {
264        self.transaction.set(Some(transaction));
265    }
266
267    // https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request
268    pub fn execute_async<T>(
269        source: &IDBObjectStore,
270        operation: AsyncOperation,
271        receiver: IpcReceiver<BackendResult<T>>,
272        request: Option<DomRoot<IDBRequest>>,
273        can_gc: CanGc,
274    ) -> Fallible<DomRoot<IDBRequest>>
275    where
276        T: Into<IdbResult> + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static,
277    {
278        // Step 1: Let transaction be the transaction associated with source.
279        let transaction = source.transaction();
280        let global = transaction.global();
281
282        // Step 2: Assert: transaction is active.
283        if !transaction.is_active() {
284            return Err(Error::TransactionInactive);
285        }
286
287        // Step 3: If request was not given, let request be a new request with source as source.
288        let request = request.unwrap_or_else(|| {
289            let new_request = IDBRequest::new(&global, can_gc);
290            new_request.set_source(Some(source));
291            new_request.set_transaction(&transaction);
292            new_request
293        });
294
295        // Step 4: Add request to the end of transaction’s request list.
296        transaction.add_request(&request);
297
298        // Step 5: Run the operation, and queue a returning task in parallel
299        // the result will be put into `receiver`
300        let transaction_mode = match transaction.get_mode() {
301            IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
302            IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
303            IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
304        };
305
306        let response_listener = RequestListener {
307            request: Trusted::new(&request),
308        };
309
310        let task_source = global
311            .task_manager()
312            .database_access_task_source()
313            .to_sendable();
314
315        ROUTER.add_typed_route(
316            receiver.to_ipc_receiver(),
317            Box::new(move |message| {
318                let response_listener = response_listener.clone();
319                task_source.queue(task!(request_callback: move || {
320                    response_listener.handle_async_request_finished(
321                        message.expect("Could not unwrap message").inspect_err(|e| {
322                            if let BackendError::DbErr(e) = e {
323                                error!("Error in IndexedDB operation: {}", e);
324                            }
325                        }).map(|t| t.into()));
326                }));
327            }),
328        );
329
330        transaction
331            .global()
332            .resource_threads()
333            .send(IndexedDBThreadMsg::Async(
334                global.origin().immutable().clone(),
335                transaction.get_db_name().to_string(),
336                source.get_name().to_string(),
337                transaction.get_serial_number(),
338                transaction_mode,
339                operation,
340            ))
341            .unwrap();
342
343        // Step 6
344        Ok(request)
345    }
346}
347
348impl IDBRequestMethods<crate::DomTypeHolder> for IDBRequest {
349    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result
350    fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) {
351        val.set(self.result.get());
352    }
353
354    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error
355    fn GetError(&self) -> Option<DomRoot<DOMException>> {
356        self.error.get()
357    }
358
359    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source
360    fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
361        self.source.get()
362    }
363
364    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction
365    fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
366        self.transaction.get()
367    }
368
369    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate
370    fn ReadyState(&self) -> IDBRequestReadyState {
371        self.ready_state.get()
372    }
373
374    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess
375    event_handler!(success, GetOnsuccess, SetOnsuccess);
376
377    // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror
378    event_handler!(error, GetOnerror, SetOnerror);
379}