1use 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 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 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 },
134 IdbResult::Error(error) => {
135 Self::handle_async_request_error(&global, cx, request, error);
137 return;
138 },
139 }
140
141 request.set_result(answer.handle());
143
144 request.set_error(None, CanGc::note());
146
147 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 Self::handle_async_request_error(&global, cx, request, Error::Data);
171 }
172 }
173
174 fn handle_async_request_error(
177 global: &GlobalScope,
178 cx: SafeJSContext,
179 request: DomRoot<IDBRequest>,
180 error: Error,
181 ) {
182 rooted!(in(*cx) let undefined = UndefinedValue());
184 request.set_result(undefined.handle());
185
186 request.set_error(Some(error), CanGc::note());
188
189 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 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 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 let transaction = source.transaction();
280 let global = transaction.global();
281
282 if !transaction.is_active() {
284 return Err(Error::TransactionInactive);
285 }
286
287 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 transaction.add_request(&request);
297
298 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 Ok(request)
345 }
346}
347
348impl IDBRequestMethods<crate::DomTypeHolder> for IDBRequest {
349 fn Result(&self, _cx: SafeJSContext, mut val: js::rust::MutableHandle<'_, js::jsapi::Value>) {
351 val.set(self.result.get());
352 }
353
354 fn GetError(&self) -> Option<DomRoot<DOMException>> {
356 self.error.get()
357 }
358
359 fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
361 self.source.get()
362 }
363
364 fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
366 self.transaction.get()
367 }
368
369 fn ReadyState(&self) -> IDBRequestReadyState {
371 self.ready_state.get()
372 }
373
374 event_handler!(success, GetOnsuccess, SetOnsuccess);
376
377 event_handler!(error, GetOnerror, SetOnerror);
379}