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;
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;
35use crate::script_runtime::CanGc;
36
37#[derive(Clone)]
38struct OpenRequestListener {
39 open_request: Trusted<IDBOpenDBRequest>,
40}
41
42impl OpenRequestListener {
43 fn handle_delete_db(&self, cx: &mut JSContext, result: BackendResult<u64>) {
46 let open_request = self.open_request.root();
56 let global = open_request.global();
57
58 open_request.idbrequest.set_ready_state_done();
60
61 rooted!(&in(cx) let mut rval = UndefinedValue());
62
63 let mut realm = enter_auto_realm(cx, &*open_request);
64 let cx = &mut realm.current_realm();
65
66 match result {
67 Ok(version) => {
68 open_request.set_result(rval.handle());
73 let _ = IDBVersionChangeEvent::fire_version_change_event(
74 cx,
75 &global,
76 open_request.upcast(),
77 Atom::from("success"),
78 version,
79 None,
80 );
81 },
82 Err(err) => {
83 let error = map_backend_error_to_dom_error(err);
90 open_request.set_error(Some(error), CanGc::from_cx(cx));
91 let event = Event::new(
92 &global,
93 Atom::from("error"),
94 EventBubbles::Bubbles,
95 EventCancelable::Cancelable,
96 CanGc::from_cx(cx),
97 );
98 event.fire(open_request.upcast(), CanGc::from_cx(cx));
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 cx: &mut JSContext,
134 global: &GlobalScope,
135 name: String,
136 version: u64,
137 upgraded: bool,
138 ) -> DomRoot<IDBDatabase> {
139 self.pending_connection.or_init(|| {
140 debug_assert!(!upgraded, "A connection should exist for the upgraded db.");
141 IDBDatabase::new(
142 global,
143 name.into(),
144 self.get_id(),
145 version,
146 CanGc::from_cx(cx),
147 )
148 })
149 }
150
151 pub(crate) fn upgrade_db_version(
155 &self,
156 cx: &mut JSContext,
157 connection: &IDBDatabase,
158 old_version: u64,
159 version: u64,
160 transaction: u64,
161 ) {
162 let global = self.global();
163
164 let transaction = IDBTransaction::new_with_serial(
165 &global,
166 connection,
167 IDBTransactionMode::Versionchange,
168 IDBTransactionDurability::Default,
169 &connection.object_stores(),
170 transaction,
171 CanGc::from_cx(cx),
172 );
173 transaction.set_versionchange_old_version(old_version);
174 connection.set_transaction(&transaction);
175 transaction.set_active_flag(false);
177
178 rooted!(&in(cx) let mut connection_val = UndefinedValue());
179 connection.safe_to_jsval(cx.into(), connection_val.handle_mut(), CanGc::from_cx(cx));
180
181 self.idbrequest.set_result(connection_val.handle());
183
184 self.idbrequest.set_transaction(&transaction);
186
187 self.idbrequest.set_ready_state_done();
189
190 transaction.set_active_flag(true);
192
193 let did_throw = IDBVersionChangeEvent::fire_version_change_event(
196 cx,
197 &global,
198 self.upcast(),
199 Atom::from("upgradeneeded"),
200 old_version,
201 Some(version),
202 );
203
204 if transaction.is_active() {
206 transaction.set_active_flag(false);
208
209 if did_throw {
212 transaction.initiate_abort(Error::Abort(None), CanGc::from_cx(cx));
213 transaction.request_backend_abort();
214 } else {
215 transaction.maybe_commit();
217 }
218 }
219 }
220
221 pub(crate) fn delete_database(
222 &self,
223 storage_key: ImmutableOrigin,
224 name: String,
225 proxy_map: StorageProxyMap,
226 ) -> Result<(), ()> {
227 let global = self.global();
228
229 let task_source = global
230 .task_manager()
231 .database_access_task_source()
232 .to_sendable();
233 let response_listener = OpenRequestListener {
234 open_request: Trusted::new(self),
235 };
236 let callback = GenericCallback::new(global.time_profiler_chan().clone(), move |message| {
237 let response_listener = response_listener.clone();
238 task_source.queue(task!(request_callback: move |cx| {
239 response_listener.handle_delete_db(cx, message.unwrap());
240 }))
241 })
242 .expect("Could not create delete database callback");
243
244 let delete_operation =
245 SyncOperation::DeleteDatabase(callback, storage_key, name, proxy_map, self.get_id());
246
247 if global
248 .storage_threads()
249 .send(IndexedDBThreadMsg::Sync(delete_operation))
250 .is_err()
251 {
252 return Err(());
253 }
254 Ok(())
255 }
256
257 pub fn set_result(&self, result: HandleValue) {
258 self.idbrequest.set_result(result);
259 }
260
261 pub fn set_ready_state_done(&self) {
262 self.idbrequest.set_ready_state_done();
263 }
264
265 pub fn set_error(&self, error: Option<Error>, can_gc: CanGc) {
266 self.idbrequest.set_error(error, can_gc);
267 }
268
269 pub fn clear_transaction(&self) {
270 self.idbrequest.clear_transaction();
271 }
272
273 pub(crate) fn clear_transaction_if_matches(&self, transaction: &IDBTransaction) -> bool {
274 let matches = self
275 .idbrequest
276 .transaction()
277 .is_some_and(|current| &*current == transaction);
278 if matches {
279 self.idbrequest.clear_transaction();
280 }
281 matches
282 }
283
284 pub fn dispatch_success(&self, cx: &mut JSContext, name: String, version: u64, upgraded: bool) {
285 let global = self.global();
286 let result = self.get_or_init_connection(cx, &global, name, version, upgraded);
287 self.idbrequest.set_ready_state_done();
288
289 let mut realm = enter_auto_realm(cx, &*result);
290 let cx = &mut realm.current_realm();
291 rooted!(&in(cx) let mut result_val = UndefinedValue());
292 result.safe_to_jsval(cx.into(), result_val.handle_mut(), CanGc::from_cx(cx));
293 self.set_result(result_val.handle());
294
295 let event = Event::new(
296 &global,
297 Atom::from("success"),
298 EventBubbles::DoesNotBubble,
299 EventCancelable::NotCancelable,
300 CanGc::from_cx(cx),
301 );
302 event.fire_with_cx(cx, self.upcast());
303 }
304
305 pub fn dispatch_blocked(&self, cx: &mut JSContext, old_version: u64, new_version: Option<u64>) {
307 let global = self.global();
308 let _ = IDBVersionChangeEvent::fire_version_change_event(
309 cx,
310 &global,
311 self.upcast(),
312 Atom::from("blocked"),
313 old_version,
314 new_version,
315 );
316 }
317}
318
319impl IDBOpenDBRequestMethods<crate::DomTypeHolder> for IDBOpenDBRequest {
320 event_handler!(blocked, GetOnblocked, SetOnblocked);
322
323 event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
325}