1use base::generic_channel::GenericSend;
6use dom_struct::dom_struct;
7use js::jsval::UndefinedValue;
8use js::rust::HandleValue;
9use profile_traits::generic_callback::GenericCallback;
10use script_bindings::conversions::SafeToJSValConvertible;
11use storage_traits::indexeddb::{BackendResult, IndexedDBThreadMsg, SyncOperation};
12use stylo_atoms::Atom;
13use uuid::Uuid;
14
15use crate::dom::bindings::codegen::Bindings::IDBOpenDBRequestBinding::IDBOpenDBRequestMethods;
16use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
17use crate::dom::bindings::error::{Error, ErrorToJsval};
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
21use crate::dom::bindings::root::{DomRoot, MutNullableDom};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::event::{Event, EventBubbles, EventCancelable};
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::indexeddb::idbdatabase::IDBDatabase;
26use crate::dom::indexeddb::idbrequest::IDBRequest;
27use crate::dom::indexeddb::idbtransaction::IDBTransaction;
28use crate::dom::indexeddb::idbversionchangeevent::IDBVersionChangeEvent;
29use crate::indexeddb::map_backend_error_to_dom_error;
30use crate::realms::enter_realm;
31use crate::script_runtime::CanGc;
32
33#[derive(Clone)]
34struct OpenRequestListener {
35 open_request: Trusted<IDBOpenDBRequest>,
36}
37
38impl OpenRequestListener {
39 fn handle_delete_db(&self, result: BackendResult<u64>, can_gc: CanGc) {
42 let open_request = self.open_request.root();
52 let global = open_request.global();
53
54 open_request.idbrequest.set_ready_state_done();
56
57 let cx = GlobalScope::get_cx();
58 rooted!(in(*cx) let mut rval = UndefinedValue());
59
60 let _ac = enter_realm(&*open_request);
61
62 match result {
63 Ok(version) => {
64 open_request.set_result(rval.handle());
69 let _ = IDBVersionChangeEvent::fire_version_change_event(
70 &global,
71 open_request.upcast(),
72 Atom::from("success"),
73 version,
74 None,
75 can_gc,
76 );
77 },
78 Err(err) => {
79 let error = map_backend_error_to_dom_error(err);
88 let cx = GlobalScope::get_cx();
89 rooted!(in(*cx) let mut rval = UndefinedValue());
90 error
91 .clone()
92 .to_jsval(cx, &global, rval.handle_mut(), can_gc);
93 open_request.set_result(rval.handle());
94 let event = Event::new(
95 &global,
96 Atom::from("error"),
97 EventBubbles::Bubbles,
98 EventCancelable::Cancelable,
99 can_gc,
100 );
101 event.fire(open_request.upcast(), can_gc);
102 },
103 }
104 }
105}
106
107#[dom_struct]
108pub struct IDBOpenDBRequest {
109 idbrequest: IDBRequest,
110 pending_connection: MutNullableDom<IDBDatabase>,
111
112 #[no_trace]
114 id: Uuid,
115}
116
117impl IDBOpenDBRequest {
118 pub fn new_inherited() -> IDBOpenDBRequest {
119 IDBOpenDBRequest {
120 idbrequest: IDBRequest::new_inherited(),
121 pending_connection: Default::default(),
122 id: Uuid::new_v4(),
123 }
124 }
125
126 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
127 reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
128 }
129
130 pub(crate) fn get_id(&self) -> Uuid {
131 self.id
132 }
133
134 pub(crate) fn get_or_init_connection(
135 &self,
136 global: &GlobalScope,
137 name: String,
138 version: u64,
139 upgraded: bool,
140 can_gc: CanGc,
141 ) -> DomRoot<IDBDatabase> {
142 self.pending_connection.or_init(|| {
143 debug_assert!(!upgraded, "A connection should exist for the upgraded db.");
144 IDBDatabase::new(
145 global,
146 DOMString::from_string(name.clone()),
147 self.get_id(),
148 version,
149 can_gc,
150 )
151 })
152 }
153
154 pub(crate) fn upgrade_db_version(
158 &self,
159 connection: &IDBDatabase,
160 old_version: u64,
161 version: u64,
162 transaction: u64,
163 can_gc: CanGc,
164 ) {
165 let global = self.global();
166 let cx = GlobalScope::get_cx();
167
168 let transaction = IDBTransaction::new_with_serial(
169 &global,
170 connection,
171 IDBTransactionMode::Versionchange,
172 &connection.object_stores(),
173 transaction,
174 can_gc,
175 );
176 transaction.set_versionchange_old_version(old_version);
177 connection.set_transaction(&transaction);
178 transaction.set_active_flag(false);
180
181 rooted!(in(*cx) let mut connection_val = UndefinedValue());
182 connection.safe_to_jsval(cx, connection_val.handle_mut(), can_gc);
183
184 self.idbrequest.set_result(connection_val.handle());
186
187 self.idbrequest.set_transaction(&transaction);
189
190 self.idbrequest.set_ready_state_done();
192
193 transaction.set_active_flag(true);
195
196 let did_throw = IDBVersionChangeEvent::fire_version_change_event(
199 &global,
200 self.upcast(),
201 Atom::from("upgradeneeded"),
202 old_version,
203 Some(version),
204 can_gc,
205 );
206
207 if transaction.is_active() {
209 transaction.set_active_flag(false);
211
212 if did_throw {
215 transaction.initiate_abort(Error::Abort(None), can_gc);
216 transaction.request_backend_abort();
217 } else {
218 transaction.maybe_commit();
220 }
221 }
222 }
223
224 pub(crate) fn delete_database(&self, name: String) -> Result<(), ()> {
225 let global = self.global();
226
227 let task_source = global
228 .task_manager()
229 .database_access_task_source()
230 .to_sendable();
231 let response_listener = OpenRequestListener {
232 open_request: Trusted::new(self),
233 };
234 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
235 let response_listener = response_listener.clone();
236 task_source.queue(task!(request_callback: move || {
237 response_listener.handle_delete_db(message.unwrap(), CanGc::note());
238 }))
239 })
240 .expect("Could not create delete database callback");
241
242 let delete_operation = SyncOperation::DeleteDatabase(
243 callback,
244 global.origin().immutable().clone(),
245 name,
246 self.get_id(),
247 );
248
249 if global
250 .storage_threads()
251 .send(IndexedDBThreadMsg::Sync(delete_operation))
252 .is_err()
253 {
254 return Err(());
255 }
256 Ok(())
257 }
258
259 pub fn set_result(&self, result: HandleValue) {
260 self.idbrequest.set_result(result);
261 }
262
263 pub fn set_ready_state_done(&self) {
264 self.idbrequest.set_ready_state_done();
265 }
266
267 pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
268 self.idbrequest.set_error(error, can_gc);
269 }
270
271 pub fn clear_transaction(&self) {
272 self.idbrequest.clear_transaction();
273 }
274
275 pub(crate) fn clear_transaction_if_matches(&self, transaction: &IDBTransaction) -> bool {
276 let matches = self
277 .idbrequest
278 .transaction()
279 .is_some_and(|current| &*current == transaction);
280 if matches {
281 self.idbrequest.clear_transaction();
282 }
283 matches
284 }
285
286 pub fn dispatch_success(&self, name: String, version: u64, upgraded: bool, can_gc: CanGc) {
287 let global = self.global();
288 let result = self.get_or_init_connection(&global, name, version, upgraded, can_gc);
289 self.idbrequest.set_ready_state_done();
290 let cx = GlobalScope::get_cx();
291
292 let _ac = enter_realm(&*result);
293 rooted!(in(*cx) let mut result_val = UndefinedValue());
294 result.safe_to_jsval(cx, result_val.handle_mut(), CanGc::note());
295 self.set_result(result_val.handle());
296
297 let event = Event::new(
298 &global,
299 Atom::from("success"),
300 EventBubbles::DoesNotBubble,
301 EventCancelable::NotCancelable,
302 CanGc::note(),
303 );
304 event.fire(self.upcast(), CanGc::note());
305 }
306
307 pub fn dispatch_blocked(&self, old_version: u64, new_version: Option<u64>, can_gc: CanGc) {
309 let global = self.global();
310 let _ = IDBVersionChangeEvent::fire_version_change_event(
311 &global,
312 self.upcast(),
313 Atom::from("blocked"),
314 old_version,
315 new_version,
316 can_gc,
317 );
318 }
319}
320
321impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
322 event_handler!(blocked, GetOnblocked, SetOnblocked);
324
325 event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
327}