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::{
34 define_dictionary_property, get_dictionary_property, has_own_property,
35};
36use crate::dom::blob::Blob;
37use crate::dom::file::File;
38use crate::dom::idbkeyrange::IDBKeyRange;
39use crate::dom::idbobjectstore::KeyPath;
40use crate::script_runtime::CanGc;
41
42#[expect(unsafe_code)]
44pub fn key_type_to_jsval(
45 cx: &mut JSContext,
46 key: &IndexedDBKeyType,
47 mut result: MutableHandleValue,
48) {
49 match key {
53 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
55
56 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
58
59 IndexedDBKeyType::Date(d) => unsafe {
60 let date = NewDateObject(cx, ClippedTime { t: *d });
63
64 assert!(
66 !date.is_null(),
67 "Failed to convert IndexedDB date key into a Date"
68 );
69
70 date.safe_to_jsval(cx, result);
72 },
73
74 IndexedDBKeyType::Binary(b) => unsafe {
75 let len = b.len();
77
78 rooted!(&in(cx) let mut buffer = ptr::null_mut::<js::jsapi::JSObject>());
81 assert!(
82 ArrayBuffer::create(cx.raw_cx(), CreateWith::Length(len), buffer.handle_mut())
83 .is_ok(),
84 "Failed to convert IndexedDB binary key into an ArrayBuffer"
85 );
86
87 let mut array_buffer = ArrayBuffer::from(buffer.get())
92 .expect("ArrayBuffer::create should create an ArrayBuffer object");
93 array_buffer.as_mut_slice().copy_from_slice(b);
94
95 result.set(ObjectValue(buffer.get()));
97 },
98
99 IndexedDBKeyType::Array(a) => unsafe {
100 rooted!(&in(cx) let array = NewArrayObject1(cx.raw_cx(), 0));
103
104 assert!(
106 !array.get().is_null(),
107 "Failed to convert IndexedDB array key into an Array"
108 );
109
110 let len = a.len();
112
113 let mut index = 0;
115
116 while index < len {
118 rooted!(&in(cx) let mut entry = UndefinedValue());
121 key_type_to_jsval(cx, &a[index], entry.handle_mut());
122
123 let index_property = CString::new(index.to_string());
125 assert!(
126 index_property.is_ok(),
127 "Failed to convert IndexedDB array index to CString"
128 );
129 let index_property = index_property.unwrap();
130 let status = define_dictionary_property(
131 cx.into(),
132 array.handle(),
133 index_property.as_c_str(),
134 entry.handle(),
135 );
136
137 assert!(
139 status.is_ok(),
140 "CreateDataProperty on a fresh JS array should not fail"
141 );
142
143 index += 1;
145 }
146
147 result.set(ObjectValue(array.get()));
149 },
150 }
151}
152
153pub(crate) fn is_valid_key_path(
155 cx: &mut JSContext,
156 key_path: &StrOrStringSequence,
157) -> Result<bool, Error> {
158 #[expect(unsafe_code)]
160 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
161 rooted!(&in(cx) let mut value = UndefinedValue());
162 name.safe_to_jsval(cx, value.handle_mut());
163 rooted!(&in(cx) let string = value.to_string());
164
165 unsafe {
166 let mut is_identifier = false;
167 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
168 return Err(Error::JSFailed);
169 }
170 Ok(is_identifier)
171 }
172 };
173
174 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
176 let is_empty_string = path.is_empty();
178
179 let is_identifier = is_identifier_name(cx, &path.str())?;
182
183 let is_identifier_list = path
185 .str()
186 .split('.')
187 .map(|s| is_identifier_name(cx, s))
188 .try_collect::<bool, Vec<bool>, Error>()?
189 .iter()
190 .all(|&value| value);
191
192 Ok(is_empty_string || is_identifier || is_identifier_list)
193 };
194
195 match key_path {
196 StrOrStringSequence::StringSequence(paths) => {
197 if paths.is_empty() {
199 Ok(false)
200 } else {
201 Ok(paths
202 .iter()
203 .map(|s| is_valid(cx, s))
204 .try_collect::<bool, Vec<bool>, Error>()?
205 .iter()
206 .all(|&value| value))
207 }
208 },
209 StrOrStringSequence::String(path) => is_valid(cx, path),
210 }
211}
212
213pub(crate) enum ConversionResult {
214 Valid(IndexedDBKeyType),
215 Invalid,
216}
217
218impl ConversionResult {
219 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
220 match self {
221 ConversionResult::Valid(key) => Ok(key),
222 ConversionResult::Invalid => Err(Error::Data(None)),
223 }
224 }
225}
226
227#[expect(unsafe_code)]
229pub fn convert_value_to_key(
230 cx: &mut JSContext,
231 input: HandleValue,
232 seen: Option<Vec<HandleValue>>,
233) -> Result<ConversionResult, Error> {
234 let mut seen = seen.unwrap_or_default();
236
237 for seen_input in &seen {
239 let mut same = false;
240 if unsafe { !SameValue(cx.raw_cx(), *seen_input, input, &mut same) } {
241 return Err(Error::JSFailed);
242 }
243 if same {
244 return Ok(ConversionResult::Invalid);
245 }
246 }
247
248 if input.is_number() {
252 if input.to_number().is_nan() {
254 return Ok(ConversionResult::Invalid);
255 }
256 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
258 input.to_number(),
259 )));
260 }
261
262 if input.is_string() {
264 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
266 let key = unsafe { jsstr_to_string(cx.raw_cx(), string_ptr) };
267 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
268 }
269
270 if input.is_object() {
271 rooted!(&in(cx) let object = input.to_object());
272 unsafe {
273 let mut is_date = false;
274 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
275 return Err(Error::JSFailed);
276 }
277
278 if is_date {
280 let mut ms = f64::NAN;
282 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
283 return Err(Error::JSFailed);
284 }
285 if ms.is_nan() {
287 return Ok(ConversionResult::Invalid);
288 }
289 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
291 }
292
293 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
295 let is_detached = if IsArrayBufferObject(*object) {
296 IsDetachedArrayBufferObject(*object)
297 } else {
298 let mut is_shared = false;
300 rooted!(
301 in (cx.raw_cx()) let view_buffer =
302 JS_GetArrayBufferViewBuffer(
303 cx.raw_cx(),
304 object.handle().into(),
305 &mut is_shared
306 )
307 );
308 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
309 };
310 if is_detached {
312 return Ok(ConversionResult::Invalid);
313 }
314 let bytes = if IsArrayBufferObject(*object) {
317 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
318 array_buffer.to_vec()
319 } else {
320 let array_buffer_view =
321 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
322 array_buffer_view.to_vec()
323 };
324 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(bytes)));
326 }
327
328 let mut is_array = false;
330 if !IsArrayObject(cx, input, &mut is_array) {
331 return Err(Error::JSFailed);
332 }
333 if is_array {
334 let mut len = 0;
336 if !GetArrayLength(cx, object.handle(), &mut len) {
337 return Err(Error::JSFailed);
338 }
339 seen.push(input);
341 let mut keys = vec![];
343 let mut index: u32 = 0;
345 while index < len {
347 rooted!(&in(cx) let mut id: PropertyKey);
348 if !JS_IndexToId(cx, index, id.handle_mut()) {
349 return Err(Error::JSFailed);
350 }
351 let mut hop = false;
353 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut hop) {
354 return Err(Error::JSFailed);
355 }
356 if !hop {
358 return Ok(ConversionResult::Invalid);
359 }
360 rooted!(&in(cx) let mut entry = UndefinedValue());
362 if !js::rust::wrappers2::JS_GetPropertyById(
363 cx,
364 object.handle(),
365 id.handle(),
366 entry.handle_mut(),
367 ) {
368 return Err(Error::JSFailed);
369 }
370
371 let key = match convert_value_to_key(cx, entry.handle(), Some(seen.clone()))? {
375 ConversionResult::Valid(key) => key,
376 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
379 };
380 keys.push(key);
382 index += 1;
384 }
385 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(keys)));
387 }
388 }
389 }
390
391 Ok(ConversionResult::Invalid)
393}
394
395#[expect(unsafe_code)]
397pub fn convert_value_to_key_range(
398 cx: &mut JSContext,
399 input: HandleValue,
400 null_disallowed: Option<bool>,
401) -> Result<IndexedDBKeyRange, Error> {
402 if input.is_object() {
404 rooted!(&in(cx) let object = input.to_object());
405 unsafe {
406 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), cx.raw_cx()) {
407 let obj = obj.inner().clone();
408 return Ok(obj);
409 }
410 }
411 }
412
413 if input.get().is_undefined() || input.get().is_null() {
416 if null_disallowed.is_some_and(|flag| flag) {
417 return Err(Error::Data(None));
418 } else {
419 return Ok(IndexedDBKeyRange {
420 lower: None,
421 upper: None,
422 lower_open: Default::default(),
423 upper_open: Default::default(),
424 });
425 }
426 }
427
428 let key = convert_value_to_key(cx, input, None)?;
431
432 let key = key.into_result()?;
434
435 Ok(IndexedDBKeyRange::only(key))
437}
438
439pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
440 match error {
441 BackendError::QuotaExceeded => Error::QuotaExceeded {
442 quota: None,
443 requested: None,
444 },
445 BackendError::DbErr(details) => {
446 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
447 },
448 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
449 }
450}
451
452pub(crate) enum EvaluationResult {
455 Success,
456 Failure,
457}
458
459#[expect(unsafe_code)]
461pub(crate) fn evaluate_key_path_on_value(
462 cx: &mut JSContext,
463 value: HandleValue,
464 key_path: &KeyPath,
465 mut return_val: MutableHandleValue,
466) -> Result<EvaluationResult, Error> {
467 match key_path {
468 KeyPath::StringSequence(key_path) => {
470 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
472
473 for (i, item) in key_path.iter().enumerate() {
476 rooted!(&in(cx) let mut key = UndefinedValue());
481 if let EvaluationResult::Failure = evaluate_key_path_on_value(
482 cx,
483 value,
484 &KeyPath::String(item.clone()),
485 key.handle_mut(),
486 )? {
487 return Ok(EvaluationResult::Failure);
488 };
489
490 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
494 define_dictionary_property(
495 cx.into(),
496 result.handle(),
497 i_cstr.as_c_str(),
498 key.handle(),
499 )
500 .map_err(|_| Error::JSFailed)?;
501
502 }
505
506 result.safe_to_jsval(cx, return_val);
508 },
509 KeyPath::String(key_path) => {
510 if key_path.is_empty() {
512 return_val.set(*value);
513 return Ok(EvaluationResult::Success);
514 }
515
516 rooted!(&in(cx) let mut current_value = *value);
518
519 for identifier in key_path.str().split('.') {
523 if identifier == "length" && current_value.is_string() {
525 rooted!(&in(cx) let string_value = current_value.to_string());
527 unsafe {
528 let string_length = JS_GetStringLength(*string_value) as u64;
529 string_length.safe_to_jsval(cx, current_value.handle_mut());
530 }
531 continue;
532 }
533
534 if identifier == "length" {
536 unsafe {
537 let mut is_array = false;
538 if !IsArrayObject(cx, current_value.handle(), &mut is_array) {
539 return Err(Error::JSFailed);
540 }
541 if is_array {
542 rooted!(&in(cx) let object = current_value.to_object());
544 get_property_jsval(
545 cx.into(),
546 object.handle(),
547 c"length",
548 current_value.handle_mut(),
549 )?;
550
551 continue;
552 }
553 }
554 }
555
556 if identifier == "size" &&
558 let Ok(blob) =
559 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
560 {
561 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
563
564 continue;
565 }
566
567 if identifier == "type" &&
569 let Ok(blob) =
570 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
571 {
572 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
574
575 continue;
576 }
577
578 if identifier == "name" &&
580 let Ok(file) =
581 root_from_handlevalue::<File>(current_value.handle(), cx.into())
582 {
583 file.name().safe_to_jsval(cx, current_value.handle_mut());
585
586 continue;
587 }
588
589 if identifier == "lastModified" &&
591 let Ok(file) =
592 root_from_handlevalue::<File>(current_value.handle(), cx.into())
593 {
594 file.LastModified()
596 .safe_to_jsval(cx, current_value.handle_mut());
597
598 continue;
599 }
600
601 if identifier == "lastModifiedDate" &&
603 let Ok(file) =
604 root_from_handlevalue::<File>(current_value.handle(), cx.into())
605 {
606 let time = ClippedTime {
608 t: file.LastModified() as f64,
609 };
610 unsafe {
611 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
612 }
613
614 continue;
615 }
616
617 unsafe {
619 if !current_value.is_object() {
621 return Ok(EvaluationResult::Failure);
622 }
623
624 rooted!(&in(cx) let object = current_value.to_object());
625 let identifier_name =
626 CString::new(identifier).expect("Failed to convert str to CString");
627
628 let hop =
630 has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
631 .map_err(|_| Error::JSFailed)?;
632
633 if !hop {
635 return Ok(EvaluationResult::Failure);
636 }
637
638 match get_dictionary_property(
640 cx.raw_cx(),
641 object.handle(),
642 identifier_name.as_c_str(),
643 current_value.handle_mut(),
644 CanGc::deprecated_note(),
645 ) {
646 Ok(true) => {},
647 Ok(false) => return Ok(EvaluationResult::Failure),
648 Err(()) => return Err(Error::JSFailed),
649 }
650
651 if current_value.get().is_undefined() {
653 return Ok(EvaluationResult::Failure);
654 }
655 }
656 }
657
658 return_val.set(*current_value);
663 },
664 }
665 Ok(EvaluationResult::Success)
666}
667
668pub(crate) enum ExtractionResult {
671 Key(IndexedDBKeyType),
672 Invalid,
673 Failure,
674}
675
676#[expect(unsafe_code)]
678pub(crate) fn can_inject_key_into_value(
679 cx: &mut JSContext,
680 value: HandleValue,
681 key_path: &DOMString,
682) -> Result<bool, Error> {
683 let key_path_string = key_path.str();
686 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
687
688 let Some(_) = identifiers.pop() else {
690 return Ok(false);
691 };
692
693 rooted!(&in(cx) let mut current_value = *value);
694
695 for identifier in identifiers {
697 if !current_value.is_object() {
699 return Ok(false);
700 }
701
702 rooted!(&in(cx) let current_object = current_value.to_object());
703 let identifier_name =
704 CString::new(identifier).expect("Failed to convert key path identifier to CString");
705
706 let hop = has_own_property(
708 cx.into(),
709 current_object.handle(),
710 identifier_name.as_c_str(),
711 )
712 .map_err(|_| Error::JSFailed)?;
713
714 if !hop {
719 return Ok(true);
720 }
721
722 match unsafe {
724 get_dictionary_property(
725 cx.raw_cx(),
726 current_object.handle(),
727 identifier_name.as_c_str(),
728 current_value.handle_mut(),
729 CanGc::deprecated_note(),
730 )
731 } {
732 Ok(true) => {},
733 Ok(false) => return Ok(false),
734 Err(()) => return Err(Error::JSFailed),
735 }
736 }
737
738 Ok(current_value.is_object())
740}
741
742#[expect(unsafe_code)]
744pub(crate) fn inject_key_into_value(
745 cx: &mut JSContext,
746 value: HandleValue,
747 key: &IndexedDBKeyType,
748 key_path: &DOMString,
749) -> Result<bool, Error> {
750 let key_path_string = key_path.str();
752 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
753
754 let Some(last) = identifiers.pop() else {
756 return Ok(false);
757 };
758
759 rooted!(&in(cx) let mut current_value = *value);
763
764 for identifier in identifiers {
766 if !current_value.is_object() {
768 return Ok(false);
769 }
770
771 rooted!(&in(cx) let current_object = current_value.to_object());
772 let identifier_name =
773 CString::new(identifier).expect("Failed to convert key path identifier to CString");
774
775 let hop = has_own_property(
777 cx.into(),
778 current_object.handle(),
779 identifier_name.as_c_str(),
780 )
781 .map_err(|_| Error::JSFailed)?;
782
783 if !hop {
785 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
787 rooted!(&in(cx) let mut o_value = UndefinedValue());
788 o.safe_to_jsval(cx, o_value.handle_mut());
789
790 define_dictionary_property(
792 cx.into(),
793 current_object.handle(),
794 identifier_name.as_c_str(),
795 o_value.handle(),
796 )
797 .map_err(|_| Error::JSFailed)?;
798
799 }
801
802 match unsafe {
804 get_dictionary_property(
805 cx.raw_cx(),
806 current_object.handle(),
807 identifier_name.as_c_str(),
808 current_value.handle_mut(),
809 CanGc::deprecated_note(),
810 )
811 } {
812 Ok(true) => {},
813 Ok(false) => return Ok(false),
814 Err(()) => return Err(Error::JSFailed),
815 }
816
817 if !current_value.is_object() {
819 return Ok(false);
820 }
821 }
822
823 rooted!(&in(cx) let mut key_value = UndefinedValue());
825 key_type_to_jsval(cx, key, key_value.handle_mut());
826
827 if !current_value.is_object() {
829 return Ok(false);
830 }
831 rooted!(&in(cx) let parent_object = current_value.to_object());
832 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
833
834 define_dictionary_property(
836 cx.into(),
837 parent_object.handle(),
838 last_name.as_c_str(),
839 key_value.handle(),
840 )
841 .map_err(|_| Error::JSFailed)?;
842
843 Ok(true)
847}
848
849pub(crate) fn extract_key(
851 cx: &mut JSContext,
852 value: HandleValue,
853 key_path: &KeyPath,
854 multi_entry: Option<bool>,
855) -> Result<ExtractionResult, Error> {
856 rooted!(&in(cx) let mut r = UndefinedValue());
860 if let EvaluationResult::Failure =
861 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
862 {
863 return Ok(ExtractionResult::Failure);
864 }
865
866 let key = match multi_entry {
870 Some(true) => {
871 unimplemented!("multiEntry keys are not yet supported");
873 },
874 _ => match convert_value_to_key(cx, r.handle(), None)? {
875 ConversionResult::Valid(key) => key,
876 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
878 },
879 };
880
881 Ok(ExtractionResult::Key(key))
883}