1use dom_struct::dom_struct;
6use ipc_channel::router::ROUTER;
7use js::jsval::UndefinedValue;
8use js::rust::HandleValue;
9use net_traits::IpcSend;
10use net_traits::indexeddb_thread::{BackendResult, IndexedDBThreadMsg, SyncOperation};
11use profile_traits::ipc;
12use script_bindings::conversions::SafeToJSValConvertible;
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::idbdatabase::IDBDatabase;
26use crate::dom::idbrequest::IDBRequest;
27use crate::dom::idbtransaction::IDBTransaction;
28use crate::dom::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), 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 #[allow(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_active_flag(false);
160
161 let old_version = connection.version();
163 transaction.upgrade_db_version(version);
164
165 let this = Trusted::new(self);
167 let connection = Trusted::new(connection);
168 let trusted_transaction = Trusted::new(&*transaction);
169 global.task_manager().database_access_task_source().queue(
170 task!(send_upgradeneeded_notification: move || {
171 let this = this.root();
172 let txn = trusted_transaction.root();
173 let conn = connection.root();
174 let global = this.global();
175 let cx = GlobalScope::get_cx();
176
177 let _ac = enter_realm(&*conn);
179 rooted!(in(*cx) let mut connection_val = UndefinedValue());
180 conn.safe_to_jsval(cx, connection_val.handle_mut());
181 this.idbrequest.set_result(connection_val.handle());
182
183 this.idbrequest.set_transaction(&txn);
185
186 this.idbrequest.set_ready_state_done();
188
189 let event = IDBVersionChangeEvent::new(
190 &global,
191 Atom::from("upgradeneeded"),
192 EventBubbles::DoesNotBubble,
193 EventCancelable::NotCancelable,
194 old_version,
195 Some(version),
196 CanGc::note(),
197 );
198
199 txn.set_active_flag(true);
201 let _did_throw = event.upcast::<Event>().fire(this.upcast(), CanGc::note());
203 txn.set_active_flag(false);
207
208 txn.wait();
211 this.dispatch_success(&conn);
212 }),
213 );
214
215 transaction.wait();
217 }
218
219 pub fn set_result(&self, result: HandleValue) {
220 self.idbrequest.set_result(result);
221 }
222
223 pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
224 self.idbrequest.set_error(error, can_gc);
225 }
226
227 pub fn open_database(&self, name: DOMString, version: Option<u64>) {
228 let global = self.global();
229
230 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
231 let response_listener = OpenRequestListener {
232 open_request: Trusted::new(self),
233 };
234
235 let open_operation = SyncOperation::OpenDatabase(
236 sender,
237 global.origin().immutable().clone(),
238 name.to_string(),
239 version,
240 );
241
242 let task_source = global
243 .task_manager()
244 .database_access_task_source()
245 .to_sendable();
246
247 let trusted_request = Trusted::new(self);
248 let name = name.to_string();
249 ROUTER.add_typed_route(
250 receiver.to_ipc_receiver(),
251 Box::new(move |message| {
252 let trusted_request = trusted_request.clone();
253 let response_listener = response_listener.clone();
254 let name = name.clone();
255
256 task_source.queue(
257 task!(set_request_result_to_database: move || {
258 let (result, did_upgrade) =
259 response_listener.handle_open_db(name, version, message.unwrap(), CanGc::note());
260 if did_upgrade {
263 return;
264 }
265 let request = trusted_request.root();
266 let global = request.global();
267 match result {
268 Ok(db) => {
269 request.dispatch_success(&db);
270 },
271 Err(dom_exception) => {
272 request.set_result(HandleValue::undefined());
273 request.set_error(Some(dom_exception), CanGc::note());
274 let event = Event::new(
275 &global,
276 Atom::from("error"),
277 EventBubbles::Bubbles,
278 EventCancelable::Cancelable,
279 CanGc::note()
280 );
281 event.fire(request.upcast(), CanGc::note());
282 }
283 }
284 }),
285 );
286 }),
287 );
288
289 global
290 .resource_threads()
291 .send(IndexedDBThreadMsg::Sync(open_operation))
292 .unwrap();
293 }
294
295 pub fn delete_database(&self, name: String) {
296 let global = self.global();
297
298 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
299 let task_source = global
300 .task_manager()
301 .database_access_task_source()
302 .to_sendable();
303 let response_listener = OpenRequestListener {
304 open_request: Trusted::new(self),
305 };
306
307 let delete_operation =
308 SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
309
310 ROUTER.add_typed_route(
311 receiver.to_ipc_receiver(),
312 Box::new(move |message| {
313 let response_listener = response_listener.clone();
314 task_source.queue(task!(request_callback: move || {
315 response_listener.handle_delete_db(message.unwrap(), CanGc::note());
316 }));
317 }),
318 );
319
320 global
321 .resource_threads()
322 .send(IndexedDBThreadMsg::Sync(delete_operation))
323 .unwrap();
324 }
325
326 pub fn dispatch_success(&self, result: &IDBDatabase) {
327 let global = self.global();
328 let this = Trusted::new(self);
329 let result = Trusted::new(result);
330
331 global.task_manager().database_access_task_source().queue(
332 task!(send_success_notification: move || {
333 let this = this.root();
334 let result = result.root();
335 this.idbrequest.set_ready_state_done();
336 let global = this.global();
337 let cx = GlobalScope::get_cx();
338
339 let _ac = enter_realm(&*result);
340 rooted!(in(*cx) let mut result_val = UndefinedValue());
341 result.safe_to_jsval(cx, result_val.handle_mut());
342 this.set_result(result_val.handle());
343
344 let event = Event::new(
345 &global,
346 Atom::from("success"),
347 EventBubbles::DoesNotBubble,
348 EventCancelable::NotCancelable,
349 CanGc::note()
350 );
351 event.fire(this.upcast(), CanGc::note());
352 }),
353 );
354 }
355}
356
357impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
358 event_handler!(blocked, GetOnblocked, SetOnblocked);
360
361 event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
363}