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