1use std::ffi::CString;
6use std::ptr;
7
8use itertools::Itertools;
9use js::context::JSContext;
10use js::conversions::{ToJSValConvertible, jsstr_to_string};
11use js::jsapi::{
12 ClippedTime, ESClass, IsArrayBufferObject, JS_GetStringLength, JS_IsArrayBufferViewObject,
13 PropertyKey,
14};
15use js::jsval::{DoubleValue, JSVal, UndefinedValue};
16use js::rust::wrappers2::{
17 GetArrayLength, GetBuiltinClass, IsArrayObject, JS_GetProperty, JS_HasOwnProperty,
18 JS_HasOwnPropertyById, JS_IndexToId, JS_IsIdentifier, JS_NewObject, NewDateObject,
19};
20use js::rust::{HandleValue, MutableHandleValue};
21use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
22
23use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
24use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
25use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
26use crate::dom::bindings::conversions::{
27 get_property_jsval, root_from_handlevalue, root_from_object,
28};
29use crate::dom::bindings::error::Error;
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::structuredclone;
32use crate::dom::bindings::utils::set_dictionary_property;
33use crate::dom::blob::Blob;
34use crate::dom::file::File;
35use crate::dom::idbkeyrange::IDBKeyRange;
36use crate::dom::idbobjectstore::KeyPath;
37
38#[expect(unsafe_code)]
40pub fn key_type_to_jsval(
41 cx: &mut JSContext,
42 key: &IndexedDBKeyType,
43 mut result: MutableHandleValue,
44) {
45 match key {
46 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
47 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
48 IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result),
49 IndexedDBKeyType::Date(d) => {
50 let time = js::jsapi::ClippedTime { t: *d };
51 let date = unsafe { js::rust::wrappers2::NewDateObject(cx, time) };
52 date.safe_to_jsval(cx, result);
53 },
54 IndexedDBKeyType::Array(a) => {
55 rooted!(&in(cx) let mut values = vec![JSVal::default(); a.len()]);
56 for (i, key) in a.iter().enumerate() {
57 key_type_to_jsval(cx, key, values.handle_mut_at(i));
58 }
59 values.safe_to_jsval(cx, result);
60 },
61 }
62}
63
64pub(crate) fn is_valid_key_path(
66 cx: &mut JSContext,
67 key_path: &StrOrStringSequence,
68) -> Result<bool, Error> {
69 #[expect(unsafe_code)]
71 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
72 rooted!(&in(cx) let mut value = UndefinedValue());
73 name.safe_to_jsval(cx, value.handle_mut());
74 rooted!(&in(cx) let string = value.to_string());
75
76 unsafe {
77 let mut is_identifier = false;
78 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
79 return Err(Error::JSFailed);
80 }
81 Ok(is_identifier)
82 }
83 };
84
85 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
87 let is_empty_string = path.is_empty();
89
90 let is_identifier = is_identifier_name(cx, &path.str())?;
93
94 let is_identifier_list = path
96 .str()
97 .split('.')
98 .map(|s| is_identifier_name(cx, s))
99 .try_collect::<bool, Vec<bool>, Error>()?
100 .iter()
101 .all(|&value| value);
102
103 Ok(is_empty_string || is_identifier || is_identifier_list)
104 };
105
106 match key_path {
107 StrOrStringSequence::StringSequence(paths) => {
108 if paths.is_empty() {
110 Ok(false)
111 } else {
112 Ok(paths
113 .iter()
114 .map(|s| is_valid(cx, s))
115 .try_collect::<bool, Vec<bool>, Error>()?
116 .iter()
117 .all(|&value| value))
118 }
119 },
120 StrOrStringSequence::String(path) => is_valid(cx, path),
121 }
122}
123
124pub(crate) enum ConversionResult {
125 Valid(IndexedDBKeyType),
126 Invalid,
127}
128
129impl ConversionResult {
130 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
131 match self {
132 ConversionResult::Valid(key) => Ok(key),
133 ConversionResult::Invalid => Err(Error::Data(None)),
134 }
135 }
136}
137
138#[expect(unsafe_code)]
140pub fn convert_value_to_key(
141 cx: &mut JSContext,
142 input: HandleValue,
143 seen: Option<Vec<HandleValue>>,
144) -> Result<ConversionResult, Error> {
145 let mut seen = seen.unwrap_or_default();
147
148 if input.is_number() {
157 if input.to_number().is_nan() {
158 return Ok(ConversionResult::Invalid);
159 }
160 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
161 input.to_number(),
162 )));
163 }
164
165 if input.is_string() {
166 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
167 let key = unsafe { jsstr_to_string(cx.raw_cx(), string_ptr) };
168 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
169 }
170
171 if input.is_object() {
172 rooted!(&in(cx) let object = input.to_object());
173 unsafe {
174 let mut built_in_class = ESClass::Other;
175
176 if !GetBuiltinClass(cx, object.handle(), &mut built_in_class) {
177 return Err(Error::JSFailed);
178 }
179
180 if let ESClass::Date = built_in_class {
181 let mut f = f64::NAN;
182 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut f) {
183 return Err(Error::JSFailed);
184 }
185 if f.is_nan() {
186 return Err(Error::Data(None));
187 }
188 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(f)));
189 }
190
191 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
192 let key = structuredclone::write(cx.into(), input, None)?;
194 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(
195 key.serialized.clone(),
196 )));
197 }
198
199 if let ESClass::Array = built_in_class {
200 let mut len = 0;
201 if !GetArrayLength(cx, object.handle(), &mut len) {
202 return Err(Error::JSFailed);
203 }
204 seen.push(input);
205 let mut values = vec![];
206 for i in 0..len {
207 rooted!(&in(cx) let mut id: PropertyKey);
208 if !JS_IndexToId(cx, i, id.handle_mut()) {
209 return Err(Error::JSFailed);
210 }
211 let mut has_own = false;
212 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut has_own) {
213 return Err(Error::JSFailed);
214 }
215 if !has_own {
216 return Ok(ConversionResult::Invalid);
217 }
218 rooted!(&in(cx) let mut item = UndefinedValue());
219 if !js::rust::wrappers2::JS_GetPropertyById(
220 cx,
221 object.handle(),
222 id.handle(),
223 item.handle_mut(),
224 ) {
225 return Err(Error::JSFailed);
226 }
227 let key = match convert_value_to_key(cx, item.handle(), Some(seen.clone()))? {
228 ConversionResult::Valid(key) => key,
229 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
230 };
231 values.push(key);
232 }
233 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(values)));
234 }
235 }
236 }
237
238 Ok(ConversionResult::Invalid)
239}
240
241#[expect(unsafe_code)]
243pub fn convert_value_to_key_range(
244 cx: &mut JSContext,
245 input: HandleValue,
246 null_disallowed: Option<bool>,
247) -> Result<IndexedDBKeyRange, Error> {
248 if input.is_object() {
250 rooted!(&in(cx) let object = input.to_object());
251 unsafe {
252 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), cx.raw_cx()) {
253 let obj = obj.inner().clone();
254 return Ok(obj);
255 }
256 }
257 }
258
259 if input.get().is_undefined() || input.get().is_null() {
262 if null_disallowed.is_some_and(|flag| flag) {
263 return Err(Error::Data(None));
264 } else {
265 return Ok(IndexedDBKeyRange {
266 lower: None,
267 upper: None,
268 lower_open: Default::default(),
269 upper_open: Default::default(),
270 });
271 }
272 }
273
274 let key = convert_value_to_key(cx, input, None)?;
277
278 let key = key.into_result()?;
280
281 Ok(IndexedDBKeyRange::only(key))
283}
284
285pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
286 match error {
287 BackendError::QuotaExceeded => Error::QuotaExceeded {
288 quota: None,
289 requested: None,
290 },
291 BackendError::DbErr(details) => {
292 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
293 },
294 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
295 }
296}
297
298pub(crate) enum EvaluationResult {
301 Success,
302 Failure,
303}
304
305#[expect(unsafe_code)]
307pub(crate) fn evaluate_key_path_on_value(
308 cx: &mut JSContext,
309 value: HandleValue,
310 key_path: &KeyPath,
311 mut return_val: MutableHandleValue,
312) -> Result<EvaluationResult, Error> {
313 match key_path {
314 KeyPath::StringSequence(key_path) => {
316 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
318
319 for (i, item) in key_path.iter().enumerate() {
322 rooted!(&in(cx) let mut key = UndefinedValue());
327 if let EvaluationResult::Failure = evaluate_key_path_on_value(
328 cx,
329 value,
330 &KeyPath::String(item.clone()),
331 key.handle_mut(),
332 )? {
333 return Ok(EvaluationResult::Failure);
334 };
335
336 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
340 set_dictionary_property(
341 cx.into(),
342 result.handle(),
343 i_cstr.as_c_str(),
344 key.handle(),
345 )
346 .map_err(|_| Error::JSFailed)?;
347
348 }
351
352 result.safe_to_jsval(cx, return_val);
354 },
355 KeyPath::String(key_path) => {
356 if key_path.is_empty() {
358 return_val.set(*value);
359 return Ok(EvaluationResult::Success);
360 }
361
362 rooted!(&in(cx) let mut current_value = *value);
364
365 for identifier in key_path.str().split('.') {
369 if identifier == "length" && current_value.is_string() {
371 rooted!(&in(cx) let string_value = current_value.to_string());
373 unsafe {
374 let string_length = JS_GetStringLength(*string_value) as u64;
375 string_length.safe_to_jsval(cx, current_value.handle_mut());
376 }
377 continue;
378 }
379
380 if identifier == "length" {
382 unsafe {
383 let mut is_array = false;
384 if !IsArrayObject(cx, current_value.handle(), &mut is_array) {
385 return Err(Error::JSFailed);
386 }
387 if is_array {
388 rooted!(&in(cx) let object = current_value.to_object());
390 get_property_jsval(
391 cx.into(),
392 object.handle(),
393 c"length",
394 current_value.handle_mut(),
395 )?;
396
397 continue;
398 }
399 }
400 }
401
402 if identifier == "size" {
404 if let Ok(blob) =
405 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
406 {
407 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
409
410 continue;
411 }
412 }
413
414 if identifier == "type" {
416 if let Ok(blob) =
417 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
418 {
419 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
421
422 continue;
423 }
424 }
425
426 if identifier == "name" {
428 if let Ok(file) =
429 root_from_handlevalue::<File>(current_value.handle(), cx.into())
430 {
431 file.name().safe_to_jsval(cx, current_value.handle_mut());
433
434 continue;
435 }
436 }
437
438 if identifier == "lastModified" {
440 if let Ok(file) =
441 root_from_handlevalue::<File>(current_value.handle(), cx.into())
442 {
443 file.LastModified()
445 .safe_to_jsval(cx, current_value.handle_mut());
446
447 continue;
448 }
449 }
450
451 if identifier == "lastModifiedDate" {
453 if let Ok(file) =
454 root_from_handlevalue::<File>(current_value.handle(), cx.into())
455 {
456 let time = ClippedTime {
458 t: file.LastModified() as f64,
459 };
460 unsafe {
461 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
462 }
463
464 continue;
465 }
466 }
467
468 unsafe {
470 if !current_value.is_object() {
472 return Ok(EvaluationResult::Failure);
473 }
474
475 rooted!(&in(cx) let object = current_value.to_object());
476 let identifier_name =
477 CString::new(identifier).expect("Failed to convert str to CString");
478
479 let mut hop = false;
481 if !JS_HasOwnProperty(cx, object.handle(), identifier_name.as_ptr(), &mut hop) {
482 return Err(Error::JSFailed);
483 }
484
485 if !hop {
487 return Ok(EvaluationResult::Failure);
488 }
489
490 if !JS_GetProperty(
492 cx,
493 object.handle(),
494 identifier_name.as_ptr(),
495 current_value.handle_mut(),
496 ) {
497 return Err(Error::JSFailed);
498 }
499
500 if current_value.get().is_undefined() {
502 return Ok(EvaluationResult::Failure);
503 }
504 }
505 }
506
507 return_val.set(*current_value);
512 },
513 }
514 Ok(EvaluationResult::Success)
515}
516
517pub(crate) enum ExtractionResult {
520 Key(IndexedDBKeyType),
521 Invalid,
522 Failure,
523}
524
525pub(crate) fn extract_key(
527 cx: &mut JSContext,
528 value: HandleValue,
529 key_path: &KeyPath,
530 multi_entry: Option<bool>,
531) -> Result<ExtractionResult, Error> {
532 rooted!(&in(cx) let mut r = UndefinedValue());
536 if let EvaluationResult::Failure =
537 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
538 {
539 return Ok(ExtractionResult::Failure);
540 }
541
542 let key = match multi_entry {
546 Some(true) => {
547 unimplemented!("multiEntry keys are not yet supported");
549 },
550 _ => match convert_value_to_key(cx, r.handle(), None)? {
551 ConversionResult::Valid(key) => key,
552 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
554 },
555 };
556
557 Ok(ExtractionResult::Key(key))
559}