blob: 9c051cbd0ba53645954c145bff3118e8e5693ea1 [file] [log] [blame] [edit]
/* 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 dom_struct::dom_struct;
use ipc_channel::router::ROUTER;
use js::jsapi::Heap;
use js::jsval::{DoubleValue, JSVal, UndefinedValue};
use js::rust::HandleValue;
use net_traits::IpcSend;
use net_traits::indexeddb_thread::{
AsyncOperation, BackendError, BackendResult, IndexedDBKeyType, 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, 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::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<IDBRequest>,
}
pub enum IdbResult {
Key(IndexedDBKeyType),
Keys(Vec<IndexedDBKeyType>),
Value(Vec<u8>),
Values(Vec<Vec<u8>>),
Count(u64),
Error(Error),
None,
}
impl From<IndexedDBKeyType> for IdbResult {
fn from(value: IndexedDBKeyType) -> Self {
IdbResult::Key(value)
}
}
impl From<Vec<IndexedDBKeyType>> for IdbResult {
fn from(value: Vec<IndexedDBKeyType>) -> Self {
IdbResult::Keys(value)
}
}
impl From<Vec<u8>> for IdbResult {
fn from(value: Vec<u8>) -> Self {
IdbResult::Value(value)
}
}
impl From<Vec<Vec<u8>>> for IdbResult {
fn from(value: Vec<Vec<u8>>) -> Self {
IdbResult::Values(value)
}
}
impl From<PutItemResult> 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: ()) -> Self {
Self::None
}
}
impl<T> From<Option<T>> for IdbResult
where
T: Into<IdbResult>,
{
fn from(value: Option<T>) -> Self {
match value {
Some(value) => value.into(),
None => IdbResult::None,
}
}
}
impl From<u64> 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<IdbResult>) {
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::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::<Event>()
.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<IDBRequest>,
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::<Event>()
.fire(request.upcast(), CanGc::note());
transaction.set_active_flag(false);
}
}
#[dom_struct]
pub struct IDBRequest {
eventtarget: EventTarget,
#[ignore_malloc_size_of = "mozjs"]
result: Heap<JSVal>,
error: MutNullableDom<DOMException>,
source: MutNullableDom<IDBObjectStore>,
transaction: MutNullableDom<IDBTransaction>,
ready_state: Cell<IDBRequestReadyState>,
}
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<IDBRequest> {
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<Error>, 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<T>(
source: &IDBObjectStore,
operation: AsyncOperation,
receiver: IpcReceiver<BackendResult<T>>,
request: Option<DomRoot<IDBRequest>>,
can_gc: CanGc,
) -> Fallible<DomRoot<IDBRequest>>
where
T: Into<IdbResult> + 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,
};
let response_listener = RequestListener {
request: Trusted::new(&request),
};
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<crate::DomTypeHolder> 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<DomRoot<DOMException>> {
self.error.get()
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source
fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
self.source.get()
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction
fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
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);
}