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