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