/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use std::iter::repeat_n; use base::IpcSend; use dom_struct::dom_struct; use ipc_channel::router::ROUTER; use js::jsapi::Heap; use js::jsval::{DoubleValue, JSVal, ObjectValue, UndefinedValue}; use js::rust::HandleValue; use net_traits::indexeddb_thread::{ AsyncOperation, AsyncReadOnlyOperation, BackendError, BackendResult, IndexedDBKeyType, IndexedDBRecord, IndexedDBThreadMsg, IndexedDBTxnMode, PutItemResult, }; use profile_traits::ipc::IpcReceiver; use script_bindings::conversions::SafeToJSValConvertible; use serde::{Deserialize, Serialize}; use stylo_atoms::Atom; use crate::dom::bindings::codegen::Bindings::IDBRequestBinding::{ IDBRequestMethods, IDBRequestReadyState, }; use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode; use crate::dom::bindings::error::{Error, Fallible, create_dom_exception}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object}; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::structuredclone; use crate::dom::domexception::DOMException; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::idbcursor::{IterationParam, iterate_cursor}; use crate::dom::idbcursorwithvalue::IDBCursorWithValue; use crate::dom::idbobjectstore::IDBObjectStore; use crate::dom::idbtransaction::IDBTransaction; use crate::indexed_db::key_type_to_jsval; use crate::realms::enter_realm; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; #[derive(Clone)] struct RequestListener { request: Trusted, iteration_param: Option, } pub enum IdbResult { Key(IndexedDBKeyType), Keys(Vec), Value(Vec), Values(Vec>), Count(u64), Iterate(Vec), Error(Error), None, } impl From for IdbResult { fn from(value: IndexedDBKeyType) -> Self { IdbResult::Key(value) } } impl From> for IdbResult { fn from(value: Vec) -> Self { IdbResult::Keys(value) } } impl From> for IdbResult { fn from(value: Vec) -> Self { IdbResult::Value(value) } } impl From>> for IdbResult { fn from(value: Vec>) -> Self { IdbResult::Values(value) } } impl From for IdbResult { fn from(value: PutItemResult) -> Self { match value { PutItemResult::Success => Self::None, PutItemResult::CannotOverwrite => Self::Error(Error::Constraint), } } } impl From> for IdbResult { fn from(value: Vec) -> Self { Self::Iterate(value) } } impl From<()> for IdbResult { fn from(_value: ()) -> Self { Self::None } } impl From> for IdbResult where T: Into, { fn from(value: Option) -> Self { match value { Some(value) => value.into(), None => IdbResult::None, } } } impl From for IdbResult { fn from(value: u64) -> Self { IdbResult::Count(value) } } impl RequestListener { // https://www.w3.org/TR/IndexedDB-2/#async-execute-request // Implements Step 5.4 fn handle_async_request_finished(&self, result: BackendResult) { let request = self.request.root(); let global = request.global(); let cx = GlobalScope::get_cx(); // Substep 1: Set the result of request to result. request.set_ready_state_done(); let _ac = enter_realm(&*request); rooted!(in(*cx) let mut answer = UndefinedValue()); if let Ok(data) = result { match data { IdbResult::Key(key) => { key_type_to_jsval(GlobalScope::get_cx(), &key, answer.handle_mut()) }, IdbResult::Keys(keys) => { rooted_vec!(let mut array <- repeat_n(UndefinedValue(), keys.len())); for (count, key) in keys.into_iter().enumerate() { rooted!(in(*cx) let mut val = UndefinedValue()); key_type_to_jsval(GlobalScope::get_cx(), &key, val.handle_mut()); array[count] = val.get(); } array.safe_to_jsval(cx, answer.handle_mut()); }, IdbResult::Value(serialized_data) => { let result = bincode::deserialize(&serialized_data) .map_err(|_| Error::Data) .and_then(|data| structuredclone::read(&global, data, answer.handle_mut())); if let Err(e) = result { warn!("Error reading structuredclone data"); Self::handle_async_request_error(&global, cx, request, e); return; }; }, IdbResult::Values(serialized_values) => { rooted_vec!(let mut values <- repeat_n(UndefinedValue(), serialized_values.len())); for (count, serialized_data) in serialized_values.into_iter().enumerate() { rooted!(in(*cx) let mut val = UndefinedValue()); let result = bincode::deserialize(&serialized_data) .map_err(|_| Error::Data) .and_then(|data| { structuredclone::read(&global, data, val.handle_mut()) }); if let Err(e) = result { warn!("Error reading structuredclone data"); Self::handle_async_request_error(&global, cx, request, e); return; }; values[count] = val.get(); } values.safe_to_jsval(cx, answer.handle_mut()); }, IdbResult::Count(count) => { answer.handle_mut().set(DoubleValue(count as f64)); }, IdbResult::Iterate(records) => { let param = self.iteration_param.as_ref().expect( "iteration_param must be provided by IDBRequest::execute_async for Iterate", ); let cursor = match iterate_cursor(&global, cx, param, records) { Ok(cursor) => cursor, Err(e) => { warn!("Error reading structuredclone data"); Self::handle_async_request_error(&global, cx, request, e); return; }, }; if let Some(cursor) = cursor { match cursor.downcast::() { Some(cursor_with_value) => { answer.handle_mut().set(ObjectValue( *cursor_with_value.reflector().get_jsobject(), )); }, None => { answer .handle_mut() .set(ObjectValue(*cursor.reflector().get_jsobject())); }, } } }, IdbResult::None => { // no-op }, IdbResult::Error(error) => { // Substep 2 Self::handle_async_request_error(&global, cx, request, error); return; }, } // Substep 3.1: Set the result of request to answer. request.set_result(answer.handle()); // Substep 3.2: Set the error of request to undefined request.set_error(None, CanGc::note()); // Substep 3.3: Fire a success event at request. // TODO: follow spec here let transaction = request .transaction .get() .expect("Request unexpectedly has no transaction"); let event = Event::new( &global, Atom::from("success"), EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, CanGc::note(), ); transaction.set_active_flag(true); event .upcast::() .fire(request.upcast(), CanGc::note()); transaction.set_active_flag(false); } else { // FIXME:(arihant2math) dispatch correct error // Substep 2 Self::handle_async_request_error(&global, cx, request, Error::Data); } } // https://www.w3.org/TR/IndexedDB-2/#async-execute-request // Implements Step 5.4.2 fn handle_async_request_error( global: &GlobalScope, cx: SafeJSContext, request: DomRoot, error: Error, ) { // Substep 1: Set the result of request to undefined. rooted!(in(*cx) let undefined = UndefinedValue()); request.set_result(undefined.handle()); // Substep 2: Set the error of request to result. request.set_error(Some(error), CanGc::note()); // Substep 3: Fire an error event at request. // TODO: follow the spec here let transaction = request .transaction .get() .expect("Request has no transaction"); let event = Event::new( global, Atom::from("error"), EventBubbles::Bubbles, EventCancelable::Cancelable, CanGc::note(), ); // TODO: why does the transaction need to be active? transaction.set_active_flag(true); event .upcast::() .fire(request.upcast(), CanGc::note()); transaction.set_active_flag(false); } } #[dom_struct] pub struct IDBRequest { eventtarget: EventTarget, #[ignore_malloc_size_of = "mozjs"] result: Heap, error: MutNullableDom, source: MutNullableDom, transaction: MutNullableDom, ready_state: Cell, } impl IDBRequest { pub fn new_inherited() -> IDBRequest { IDBRequest { eventtarget: EventTarget::new_inherited(), result: Heap::default(), error: Default::default(), source: Default::default(), transaction: Default::default(), ready_state: Cell::new(IDBRequestReadyState::Pending), } } pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot { reflect_dom_object(Box::new(IDBRequest::new_inherited()), global, can_gc) } pub fn set_source(&self, source: Option<&IDBObjectStore>) { self.source.set(source); } pub fn set_ready_state_done(&self) { self.ready_state.set(IDBRequestReadyState::Done); } pub fn set_result(&self, result: HandleValue) { self.result.set(result.get()); } pub fn set_error(&self, error: Option, can_gc: CanGc) { if let Some(error) = error { if let Ok(exception) = create_dom_exception(&self.global(), error, can_gc) { self.error.set(Some(&exception)); } } else { self.error.set(None); } } pub fn set_transaction(&self, transaction: &IDBTransaction) { self.transaction.set(Some(transaction)); } // https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request pub fn execute_async( source: &IDBObjectStore, operation: AsyncOperation, receiver: IpcReceiver>, request: Option>, iteration_param: Option, can_gc: CanGc, ) -> Fallible> where T: Into + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static, { // Step 1: Let transaction be the transaction associated with source. let transaction = source.transaction(); let global = transaction.global(); // Step 2: Assert: transaction is active. if !transaction.is_active() { return Err(Error::TransactionInactive); } // Step 3: If request was not given, let request be a new request with source as source. let request = request.unwrap_or_else(|| { let new_request = IDBRequest::new(&global, can_gc); new_request.set_source(Some(source)); new_request.set_transaction(&transaction); new_request }); // Step 4: Add request to the end of transaction’s request list. transaction.add_request(&request); // Step 5: Run the operation, and queue a returning task in parallel // the result will be put into `receiver` let transaction_mode = match transaction.get_mode() { IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly, IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite, IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange, }; if matches!( operation, AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate { .. }) ) { assert!( iteration_param.is_some(), "iteration_param must be provided for Iterate" ); } else { assert!( iteration_param.is_none(), "iteration_param should not be provided for operation other than Iterate" ); } let response_listener = RequestListener { request: Trusted::new(&request), iteration_param, }; let task_source = global .task_manager() .database_access_task_source() .to_sendable(); ROUTER.add_typed_route( receiver.to_ipc_receiver(), Box::new(move |message| { let response_listener = response_listener.clone(); task_source.queue(task!(request_callback: move || { response_listener.handle_async_request_finished( message.expect("Could not unwrap message").inspect_err(|e| { if let BackendError::DbErr(e) = e { error!("Error in IndexedDB operation: {}", e); } }).map(|t| t.into())); })); }), ); transaction .global() .resource_threads() .send(IndexedDBThreadMsg::Async( global.origin().immutable().clone(), transaction.get_db_name().to_string(), source.get_name().to_string(), transaction.get_serial_number(), transaction_mode, operation, )) .unwrap(); // Step 6 Ok(request) } } impl IDBRequestMethods for IDBRequest { // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) { val.set(self.result.get()); } // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error fn GetError(&self) -> Option> { self.error.get() } // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source fn GetSource(&self) -> Option> { self.source.get() } // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction fn GetTransaction(&self) -> Option> { self.transaction.get() } // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate fn ReadyState(&self) -> IDBRequestReadyState { self.ready_state.get() } // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess event_handler!(success, GetOnsuccess, SetOnsuccess); // https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror event_handler!(error, GetOnerror, SetOnerror); }