1use base::IpcSend;
6use dom_struct::dom_struct;
7use ipc_channel::router::ROUTER;
8use js::jsval::UndefinedValue;
9use js::rust::HandleValue;
10use profile_traits::ipc;
11use script_bindings::conversions::SafeToJSValConvertible;
12use storage_traits::indexeddb::{BackendResult, IndexedDBThreadMsg, SyncOperation};
13use stylo_atoms::Atom;
14
15use crate::dom::bindings::codegen::Bindings::IDBOpenDBRequestBinding::IDBOpenDBRequestMethods;
16use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
17use crate::dom::bindings::error::{Error, Fallible};
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;
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::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_open_db(
40 &self,
41 name: String,
42 request_version: Option<u64>,
43 db_version: u64,
44 can_gc: CanGc,
45 ) -> (Fallible<DomRoot<IDBDatabase>>, bool) {
46 let request_version = match request_version {
48 Some(v) => v,
49 None => {
50 if db_version == 0 {
51 1
52 } else {
53 db_version
54 }
55 },
56 };
57
58 if request_version < db_version {
60 return (Err(Error::Version(None)), false);
61 }
62
63 let open_request = self.open_request.root();
65 let global = open_request.global();
66 let connection = IDBDatabase::new(
67 &global,
68 DOMString::from_string(name.clone()),
69 request_version,
70 can_gc,
71 );
72
73 if request_version > db_version {
75 open_request.upgrade_db_version(&connection, request_version, CanGc::note());
79 (Ok(connection), true)
81 } else {
82 (Ok(connection), false)
84 }
85 }
86
87 fn handle_delete_db(&self, result: BackendResult<()>, can_gc: CanGc) {
88 let open_request = self.open_request.root();
89 let global = open_request.global();
90 open_request.idbrequest.set_ready_state_done();
91
92 match result {
93 Ok(_) => {
94 let _ac = enter_realm(&*open_request);
95 #[expect(unsafe_code)]
96 unsafe {
97 open_request
98 .set_result(js::gc::Handle::from_raw(js::jsapi::UndefinedHandleValue));
99 }
100
101 let event = Event::new(
102 &global,
103 Atom::from("success"),
104 EventBubbles::DoesNotBubble,
105 EventCancelable::NotCancelable,
106 can_gc,
107 );
108 event.fire(open_request.upcast(), can_gc);
109 },
110 Err(_e) => {
111 let event = Event::new(
115 &global,
116 Atom::from("error"),
117 EventBubbles::Bubbles,
118 EventCancelable::Cancelable,
119 can_gc,
120 );
121 event.fire(open_request.upcast(), can_gc);
122 },
123 }
124 }
125}
126
127#[dom_struct]
128pub struct IDBOpenDBRequest {
129 idbrequest: IDBRequest,
130}
131
132impl IDBOpenDBRequest {
133 pub fn new_inherited() -> IDBOpenDBRequest {
134 IDBOpenDBRequest {
135 idbrequest: IDBRequest::new_inherited(),
136 }
137 }
138
139 pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
140 reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
141 }
142
143 fn upgrade_db_version(&self, connection: &IDBDatabase, version: u64, can_gc: CanGc) {
145 let global = self.global();
146 let transaction = IDBTransaction::new(
148 &global,
149 connection,
150 IDBTransactionMode::Versionchange,
151 &connection.object_stores(),
152 can_gc,
153 );
154
155 connection.set_transaction(&transaction);
157
158 transaction.set_open_request(self);
161
162 transaction.set_active_flag(false);
164
165 let old_version = connection.version();
167 transaction.upgrade_db_version(version);
168
169 let this = Trusted::new(self);
171 let connection = Trusted::new(connection);
172 let trusted_transaction = Trusted::new(&*transaction);
173 global.task_manager().database_access_task_source().queue(
174 task!(send_upgradeneeded_notification: move || {
175 let this = this.root();
176 let txn = trusted_transaction.root();
177 let conn = connection.root();
178 let global = this.global();
179 let cx = GlobalScope::get_cx();
180
181 let _ac = enter_realm(&*conn);
183 rooted!(in(*cx) let mut connection_val = UndefinedValue());
184 conn.safe_to_jsval(cx, connection_val.handle_mut(), CanGc::note());
185 this.idbrequest.set_result(connection_val.handle());
186
187 this.idbrequest.set_transaction(&txn);
189
190 this.idbrequest.set_ready_state_done();
192
193 let event = IDBVersionChangeEvent::new(
194 &global,
195 Atom::from("upgradeneeded"),
196 EventBubbles::DoesNotBubble,
197 EventCancelable::NotCancelable,
198 old_version,
199 Some(version),
200 CanGc::note(),
201 );
202
203 txn.set_active_flag(true);
205 let _did_throw = event.upcast::<Event>().fire(this.upcast(), CanGc::note());
207 txn.set_active_flag(false);
211
212 txn.wait();
216 }),
217 );
218
219 transaction.wait();
221 }
222
223 pub fn set_result(&self, result: HandleValue) {
224 self.idbrequest.set_result(result);
225 }
226
227 pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
228 self.idbrequest.set_error(error, can_gc);
229 }
230
231 pub fn open_database(&self, name: DOMString, version: Option<u64>) -> Result<(), ()> {
232 let global = self.global();
233
234 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
235 let response_listener = OpenRequestListener {
236 open_request: Trusted::new(self),
237 };
238
239 let open_operation = SyncOperation::OpenDatabase(
240 sender,
241 global.origin().immutable().clone(),
242 name.to_string(),
243 version,
244 );
245
246 let task_source = global
247 .task_manager()
248 .database_access_task_source()
249 .to_sendable();
250
251 let trusted_request = Trusted::new(self);
252 let name = name.to_string();
253 ROUTER.add_typed_route(
254 receiver.to_ipc_receiver(),
255 Box::new(move |message| {
256 let trusted_request = trusted_request.clone();
257 let response_listener = response_listener.clone();
258 let name = name.clone();
259
260 task_source.queue(
261 task!(set_request_result_to_database: move || {
262 let (result, did_upgrade) =
263 response_listener.handle_open_db(name, version, message.unwrap(), CanGc::note());
264 if did_upgrade {
267 return;
268 }
269 let request = trusted_request.root();
270 let global = request.global();
271 match result {
272 Ok(db) => {
273 request.dispatch_success(&db);
274 },
275 Err(dom_exception) => {
276 request.set_result(HandleValue::undefined());
277 request.set_error(Some(dom_exception), CanGc::note());
278 let event = Event::new(
279 &global,
280 Atom::from("error"),
281 EventBubbles::Bubbles,
282 EventCancelable::Cancelable,
283 CanGc::note()
284 );
285 event.fire(request.upcast(), CanGc::note());
286 }
287 }
288 }),
289 );
290 }),
291 );
292
293 if global
294 .storage_threads()
295 .send(IndexedDBThreadMsg::Sync(open_operation))
296 .is_err()
297 {
298 return Err(());
299 }
300 Ok(())
301 }
302
303 pub fn delete_database(&self, name: String) -> Result<(), ()> {
304 let global = self.global();
305
306 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
307 let task_source = global
308 .task_manager()
309 .database_access_task_source()
310 .to_sendable();
311 let response_listener = OpenRequestListener {
312 open_request: Trusted::new(self),
313 };
314
315 let delete_operation =
316 SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
317
318 ROUTER.add_typed_route(
319 receiver.to_ipc_receiver(),
320 Box::new(move |message| {
321 let response_listener = response_listener.clone();
322 task_source.queue(task!(request_callback: move || {
323 response_listener.handle_delete_db(message.unwrap(), CanGc::note());
324 }));
325 }),
326 );
327
328 if global
329 .storage_threads()
330 .send(IndexedDBThreadMsg::Sync(delete_operation))
331 .is_err()
332 {
333 return Err(());
334 }
335 Ok(())
336 }
337
338 pub fn dispatch_success(&self, result: &IDBDatabase) {
339 let global = self.global();
340 let this = Trusted::new(self);
341 let result = Trusted::new(result);
342
343 global.task_manager().database_access_task_source().queue(
344 task!(send_success_notification: move || {
345 let this = this.root();
346 let result = result.root();
347 this.idbrequest.set_ready_state_done();
348 let global = this.global();
349 let cx = GlobalScope::get_cx();
350
351 let _ac = enter_realm(&*result);
352 rooted!(in(*cx) let mut result_val = UndefinedValue());
353 result.safe_to_jsval(cx, result_val.handle_mut(), CanGc::note());
354 this.set_result(result_val.handle());
355
356 let event = Event::new(
357 &global,
358 Atom::from("success"),
359 EventBubbles::DoesNotBubble,
360 EventCancelable::NotCancelable,
361 CanGc::note()
362 );
363 event.fire(this.upcast(), CanGc::note());
364 }),
365 );
366 }
367}
368
369impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
370 event_handler!(blocked, GetOnblocked, SetOnblocked);
372
373 event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
375}