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, IsArrayBufferObject, IsDetachedArrayBufferObject, JS_GetArrayBufferViewBuffer,
13 JS_GetStringLength, JS_IsArrayBufferViewObject, NewArrayObject1, PropertyKey,
14};
15use js::jsval::{DoubleValue, ObjectValue, UndefinedValue};
16use js::rust::wrappers::SameValue;
17use js::rust::wrappers2::{
18 GetArrayLength, IsArrayObject, JS_HasOwnPropertyById, JS_IndexToId, JS_IsIdentifier,
19 JS_NewObject, NewDateObject, ObjectIsDate,
20};
21use js::rust::{HandleValue, MutableHandleValue};
22use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith};
23use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
24
25use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
26use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
27use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
28use crate::dom::bindings::conversions::{
29 get_property_jsval, root_from_handlevalue, root_from_object,
30};
31use crate::dom::bindings::error::Error;
32use crate::dom::bindings::str::DOMString;
33use crate::dom::bindings::utils::{define_dictionary_property, has_own_property};
34use crate::dom::blob::Blob;
35use crate::dom::file::File;
36use crate::dom::idbkeyrange::IDBKeyRange;
37use crate::dom::idbobjectstore::KeyPath;
38
39#[expect(unsafe_code)]
41pub fn key_type_to_jsval(
42 cx: &mut JSContext,
43 key: &IndexedDBKeyType,
44 mut result: MutableHandleValue,
45) {
46 match key {
50 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
52
53 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
55
56 IndexedDBKeyType::Date(d) => unsafe {
57 let date = NewDateObject(cx, ClippedTime { t: *d });
60
61 assert!(
63 !date.is_null(),
64 "Failed to convert IndexedDB date key into a Date"
65 );
66
67 date.safe_to_jsval(cx, result);
69 },
70
71 IndexedDBKeyType::Binary(b) => unsafe {
72 let len = b.len();
74
75 rooted!(&in(cx) let mut buffer = ptr::null_mut::<js::jsapi::JSObject>());
78 assert!(
79 ArrayBuffer::create(cx.raw_cx(), CreateWith::Length(len), buffer.handle_mut())
80 .is_ok(),
81 "Failed to convert IndexedDB binary key into an ArrayBuffer"
82 );
83
84 let mut array_buffer = ArrayBuffer::from(buffer.get())
89 .expect("ArrayBuffer::create should create an ArrayBuffer object");
90 array_buffer.as_mut_slice().copy_from_slice(b);
91
92 result.set(ObjectValue(buffer.get()));
94 },
95
96 IndexedDBKeyType::Array(a) => unsafe {
97 rooted!(&in(cx) let array = NewArrayObject1(cx.raw_cx(), 0));
100
101 assert!(
103 !array.get().is_null(),
104 "Failed to convert IndexedDB array key into an Array"
105 );
106
107 let len = a.len();
109
110 let mut index = 0;
112
113 while index < len {
115 rooted!(&in(cx) let mut entry = UndefinedValue());
118 key_type_to_jsval(cx, &a[index], entry.handle_mut());
119
120 let index_property = CString::new(index.to_string());
122 assert!(
123 index_property.is_ok(),
124 "Failed to convert IndexedDB array index to CString"
125 );
126 let index_property = index_property.unwrap();
127 let status = define_dictionary_property(
128 cx.into(),
129 array.handle(),
130 index_property.as_c_str(),
131 entry.handle(),
132 );
133
134 assert!(
136 status.is_ok(),
137 "CreateDataProperty on a fresh JS array should not fail"
138 );
139
140 index += 1;
142 }
143
144 result.set(ObjectValue(array.get()));
146 },
147 }
148}
149
150pub(crate) fn is_valid_key_path(
152 cx: &mut JSContext,
153 key_path: &StrOrStringSequence,
154) -> Result<bool, Error> {
155 #[expect(unsafe_code)]
157 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
158 rooted!(&in(cx) let mut value = UndefinedValue());
159 name.safe_to_jsval(cx, value.handle_mut());
160 rooted!(&in(cx) let string = value.to_string());
161
162 unsafe {
163 let mut is_identifier = false;
164 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
165 return Err(Error::JSFailed);
166 }
167 Ok(is_identifier)
168 }
169 };
170
171 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
173 let is_empty_string = path.is_empty();
175
176 let is_identifier = is_identifier_name(cx, &path.str())?;
179
180 let is_identifier_list = path
182 .str()
183 .split('.')
184 .map(|s| is_identifier_name(cx, s))
185 .try_collect::<bool, Vec<bool>, Error>()?
186 .iter()
187 .all(|&value| value);
188
189 Ok(is_empty_string || is_identifier || is_identifier_list)
190 };
191
192 match key_path {
193 StrOrStringSequence::StringSequence(paths) => {
194 if paths.is_empty() {
196 Ok(false)
197 } else {
198 Ok(paths
199 .iter()
200 .map(|s| is_valid(cx, s))
201 .try_collect::<bool, Vec<bool>, Error>()?
202 .iter()
203 .all(|&value| value))
204 }
205 },
206 StrOrStringSequence::String(path) => is_valid(cx, path),
207 }
208}
209
210pub(crate) enum ConversionResult {
211 Valid(IndexedDBKeyType),
212 Invalid,
213}
214
215impl ConversionResult {
216 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
217 match self {
218 ConversionResult::Valid(key) => Ok(key),
219 ConversionResult::Invalid => Err(Error::Data(None)),
220 }
221 }
222}
223
224#[expect(unsafe_code)]
226pub fn convert_value_to_key(
227 cx: &mut JSContext,
228 input: HandleValue,
229 seen: Option<Vec<HandleValue>>,
230) -> Result<ConversionResult, Error> {
231 let mut seen = seen.unwrap_or_default();
233
234 for seen_input in &seen {
236 let mut same = false;
237 if unsafe { !SameValue(cx.raw_cx(), *seen_input, input, &mut same) } {
238 return Err(Error::JSFailed);
239 }
240 if same {
241 return Ok(ConversionResult::Invalid);
242 }
243 }
244
245 if input.is_number() {
249 if input.to_number().is_nan() {
251 return Ok(ConversionResult::Invalid);
252 }
253 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
255 input.to_number(),
256 )));
257 }
258
259 if input.is_string() {
261 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
263 let key = unsafe { jsstr_to_string(cx, string_ptr) };
264 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
265 }
266
267 if input.is_object() {
268 rooted!(&in(cx) let object = input.to_object());
269 unsafe {
270 let mut is_date = false;
271 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
272 return Err(Error::JSFailed);
273 }
274
275 if is_date {
277 let mut ms = f64::NAN;
279 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
280 return Err(Error::JSFailed);
281 }
282 if ms.is_nan() {
284 return Ok(ConversionResult::Invalid);
285 }
286 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
288 }
289
290 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
292 let is_detached = if IsArrayBufferObject(*object) {
293 IsDetachedArrayBufferObject(*object)
294 } else {
295 let mut is_shared = false;
297 rooted!(
298 in (cx.raw_cx()) let view_buffer =
299 JS_GetArrayBufferViewBuffer(
300 cx.raw_cx(),
301 object.handle().into(),
302 &mut is_shared
303 )
304 );
305 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
306 };
307 if is_detached {
309 return Ok(ConversionResult::Invalid);
310 }
311 let bytes = if IsArrayBufferObject(*object) {
314 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
315 array_buffer.to_vec()
316 } else {
317 let array_buffer_view =
318 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
319 array_buffer_view.to_vec()
320 };
321 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(bytes)));
323 }
324
325 let mut is_array = false;
327 if !IsArrayObject(cx, input, &mut is_array) {
328 return Err(Error::JSFailed);
329 }
330 if is_array {
331 let mut len = 0;
333 if !GetArrayLength(cx, object.handle(), &mut len) {
334 return Err(Error::JSFailed);
335 }
336 seen.push(input);
338 let mut keys = vec![];
340 let mut index: u32 = 0;
342 while index < len {
344 rooted!(&in(cx) let mut id: PropertyKey);
345 if !JS_IndexToId(cx, index, id.handle_mut()) {
346 return Err(Error::JSFailed);
347 }
348 let mut hop = false;
350 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut hop) {
351 return Err(Error::JSFailed);
352 }
353 if !hop {
355 return Ok(ConversionResult::Invalid);
356 }
357 rooted!(&in(cx) let mut entry = UndefinedValue());
359 if !js::rust::wrappers2::JS_GetPropertyById(
360 cx,
361 object.handle(),
362 id.handle(),
363 entry.handle_mut(),
364 ) {
365 return Err(Error::JSFailed);
366 }
367
368 let key = match convert_value_to_key(cx, entry.handle(), Some(seen.clone()))? {
372 ConversionResult::Valid(key) => key,
373 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
376 };
377 keys.push(key);
379 index += 1;
381 }
382 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(keys)));
384 }
385 }
386 }
387
388 Ok(ConversionResult::Invalid)
390}
391
392#[expect(unsafe_code)]
394pub fn convert_value_to_key_range(
395 cx: &mut JSContext,
396 input: HandleValue,
397 null_disallowed: Option<bool>,
398) -> Result<IndexedDBKeyRange, Error> {
399 if input.is_object() {
401 rooted!(&in(cx) let object = input.to_object());
402 unsafe {
403 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), cx.raw_cx()) {
404 let obj = obj.inner().clone();
405 return Ok(obj);
406 }
407 }
408 }
409
410 if input.get().is_undefined() || input.get().is_null() {
413 if null_disallowed.is_some_and(|flag| flag) {
414 return Err(Error::Data(None));
415 } else {
416 return Ok(IndexedDBKeyRange {
417 lower: None,
418 upper: None,
419 lower_open: Default::default(),
420 upper_open: Default::default(),
421 });
422 }
423 }
424
425 let key = convert_value_to_key(cx, input, None)?;
428
429 let key = key.into_result()?;
431
432 Ok(IndexedDBKeyRange::only(key))
434}
435
436pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
437 match error {
438 BackendError::QuotaExceeded => Error::QuotaExceeded {
439 quota: None,
440 requested: None,
441 },
442 BackendError::DbErr(details) => {
443 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
444 },
445 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
446 }
447}
448
449pub(crate) enum EvaluationResult {
452 Success,
453 Failure,
454}
455
456#[expect(unsafe_code)]
458pub(crate) fn evaluate_key_path_on_value(
459 cx: &mut JSContext,
460 value: HandleValue,
461 key_path: &KeyPath,
462 mut return_val: MutableHandleValue,
463) -> Result<EvaluationResult, Error> {
464 match key_path {
465 KeyPath::StringSequence(key_path) => {
467 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
469
470 for (i, item) in key_path.iter().enumerate() {
473 rooted!(&in(cx) let mut key = UndefinedValue());
478 if let EvaluationResult::Failure = evaluate_key_path_on_value(
479 cx,
480 value,
481 &KeyPath::String(item.clone()),
482 key.handle_mut(),
483 )? {
484 return Ok(EvaluationResult::Failure);
485 };
486
487 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
491 define_dictionary_property(
492 cx.into(),
493 result.handle(),
494 i_cstr.as_c_str(),
495 key.handle(),
496 )
497 .map_err(|_| Error::JSFailed)?;
498
499 }
502
503 result.safe_to_jsval(cx, return_val);
505 },
506 KeyPath::String(key_path) => {
507 if key_path.is_empty() {
509 return_val.set(*value);
510 return Ok(EvaluationResult::Success);
511 }
512
513 rooted!(&in(cx) let mut current_value = *value);
515
516 for identifier in key_path.str().split('.') {
520 if identifier == "length" && current_value.is_string() {
522 rooted!(&in(cx) let string_value = current_value.to_string());
524 unsafe {
525 let string_length = JS_GetStringLength(*string_value) as u64;
526 string_length.safe_to_jsval(cx, current_value.handle_mut());
527 }
528 continue;
529 }
530
531 if identifier == "length" {
533 let mut is_array = false;
534 if unsafe { !IsArrayObject(cx, current_value.handle(), &mut is_array) } {
535 return Err(Error::JSFailed);
536 }
537 if is_array {
538 rooted!(&in(cx) let object = current_value.to_object());
540 get_property_jsval(
541 cx,
542 object.handle(),
543 c"length",
544 current_value.handle_mut(),
545 )?;
546
547 continue;
548 }
549 }
550
551 if identifier == "size" &&
553 let Ok(blob) =
554 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
555 {
556 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
558
559 continue;
560 }
561
562 if identifier == "type" &&
564 let Ok(blob) =
565 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
566 {
567 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
569
570 continue;
571 }
572
573 if identifier == "name" &&
575 let Ok(file) =
576 root_from_handlevalue::<File>(current_value.handle(), cx.into())
577 {
578 file.name().safe_to_jsval(cx, current_value.handle_mut());
580
581 continue;
582 }
583
584 if identifier == "lastModified" &&
586 let Ok(file) =
587 root_from_handlevalue::<File>(current_value.handle(), cx.into())
588 {
589 file.LastModified()
591 .safe_to_jsval(cx, current_value.handle_mut());
592
593 continue;
594 }
595
596 if identifier == "lastModifiedDate" &&
598 let Ok(file) =
599 root_from_handlevalue::<File>(current_value.handle(), cx.into())
600 {
601 let time = ClippedTime {
603 t: file.LastModified() as f64,
604 };
605 unsafe {
606 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
607 }
608
609 continue;
610 }
611
612 if !current_value.is_object() {
615 return Ok(EvaluationResult::Failure);
616 }
617
618 rooted!(&in(cx) let object = current_value.to_object());
619 let identifier_name =
620 CString::new(identifier).expect("Failed to convert str to CString");
621
622 let hop = has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
624 .map_err(|_| Error::JSFailed)?;
625
626 if !hop {
628 return Ok(EvaluationResult::Failure);
629 }
630
631 get_property_jsval(
633 cx,
634 object.handle(),
635 identifier_name.as_c_str(),
636 current_value.handle_mut(),
637 )?;
638
639 if current_value.get().is_undefined() {
641 return Ok(EvaluationResult::Failure);
642 }
643 }
644
645 return_val.set(*current_value);
650 },
651 }
652 Ok(EvaluationResult::Success)
653}
654
655pub(crate) enum ExtractionResult {
658 Key(IndexedDBKeyType),
659 Invalid,
660 Failure,
661}
662
663pub(crate) fn can_inject_key_into_value(
665 cx: &mut JSContext,
666 value: HandleValue,
667 key_path: &DOMString,
668) -> Result<bool, Error> {
669 let key_path_string = key_path.str();
672 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
673
674 let Some(_) = identifiers.pop() else {
676 return Ok(false);
677 };
678
679 rooted!(&in(cx) let mut current_value = *value);
680
681 for identifier in identifiers {
683 if !current_value.is_object() {
685 return Ok(false);
686 }
687
688 rooted!(&in(cx) let current_object = current_value.to_object());
689 let identifier_name =
690 CString::new(identifier).expect("Failed to convert key path identifier to CString");
691
692 let hop = has_own_property(
694 cx.into(),
695 current_object.handle(),
696 identifier_name.as_c_str(),
697 )
698 .map_err(|_| Error::JSFailed)?;
699
700 if !hop {
705 return Ok(true);
706 }
707
708 get_property_jsval(
710 cx,
711 current_object.handle(),
712 identifier_name.as_c_str(),
713 current_value.handle_mut(),
714 )?;
715 }
716
717 Ok(current_value.is_object())
719}
720
721#[expect(unsafe_code)]
723pub(crate) fn inject_key_into_value(
724 cx: &mut JSContext,
725 value: HandleValue,
726 key: &IndexedDBKeyType,
727 key_path: &DOMString,
728) -> Result<bool, Error> {
729 let key_path_string = key_path.str();
731 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
732
733 let Some(last) = identifiers.pop() else {
735 return Ok(false);
736 };
737
738 rooted!(&in(cx) let mut current_value = *value);
742
743 for identifier in identifiers {
745 if !current_value.is_object() {
747 return Ok(false);
748 }
749
750 rooted!(&in(cx) let current_object = current_value.to_object());
751 let identifier_name =
752 CString::new(identifier).expect("Failed to convert key path identifier to CString");
753
754 let hop = has_own_property(
756 cx.into(),
757 current_object.handle(),
758 identifier_name.as_c_str(),
759 )
760 .map_err(|_| Error::JSFailed)?;
761
762 if !hop {
764 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
766 rooted!(&in(cx) let mut o_value = UndefinedValue());
767 o.safe_to_jsval(cx, o_value.handle_mut());
768
769 define_dictionary_property(
771 cx.into(),
772 current_object.handle(),
773 identifier_name.as_c_str(),
774 o_value.handle(),
775 )
776 .map_err(|_| Error::JSFailed)?;
777
778 }
780
781 get_property_jsval(
783 cx,
784 current_object.handle(),
785 identifier_name.as_c_str(),
786 current_value.handle_mut(),
787 )?;
788
789 if !current_value.is_object() {
791 return Ok(false);
792 }
793 }
794
795 rooted!(&in(cx) let mut key_value = UndefinedValue());
797 key_type_to_jsval(cx, key, key_value.handle_mut());
798
799 if !current_value.is_object() {
801 return Ok(false);
802 }
803 rooted!(&in(cx) let parent_object = current_value.to_object());
804 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
805
806 define_dictionary_property(
808 cx.into(),
809 parent_object.handle(),
810 last_name.as_c_str(),
811 key_value.handle(),
812 )
813 .map_err(|_| Error::JSFailed)?;
814
815 Ok(true)
819}
820
821pub(crate) fn extract_key(
823 cx: &mut JSContext,
824 value: HandleValue,
825 key_path: &KeyPath,
826 multi_entry: Option<bool>,
827) -> Result<ExtractionResult, Error> {
828 rooted!(&in(cx) let mut r = UndefinedValue());
832 if let EvaluationResult::Failure =
833 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
834 {
835 return Ok(ExtractionResult::Failure);
836 }
837
838 let key = match multi_entry {
842 Some(true) => {
843 unimplemented!("multiEntry keys are not yet supported");
845 },
846 _ => match convert_value_to_key(cx, r.handle(), None)? {
847 ConversionResult::Valid(key) => key,
848 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
850 },
851 };
852
853 Ok(ExtractionResult::Key(key))
855}