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, HandleValueArray, IsArrayBufferObject, IsDetachedArrayBufferObject,
13 JS_GetArrayBufferViewBuffer, JS_GetStringLength, JS_IsArrayBufferViewObject, NewArrayObject,
14 PropertyKey,
15};
16use js::jsval::{DoubleValue, ObjectValue, UndefinedValue};
17use js::rust::wrappers::SameValue;
18use js::rust::wrappers2::{
19 GetArrayLength, IsArrayObject, JS_HasOwnPropertyById, JS_IndexToId, JS_IsIdentifier,
20 JS_NewObject, NewDateObject, ObjectIsDate,
21};
22use js::rust::{HandleValue, MutableHandleValue};
23use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith};
24use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
25
26use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
27use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
28use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
29use crate::dom::bindings::conversions::{
30 get_property_jsval, root_from_handlevalue, root_from_object,
31};
32use crate::dom::bindings::error::Error;
33use crate::dom::bindings::str::DOMString;
34use crate::dom::bindings::utils::{
35 define_dictionary_property, get_dictionary_property, has_own_property,
36};
37use crate::dom::blob::Blob;
38use crate::dom::file::File;
39use crate::dom::idbkeyrange::IDBKeyRange;
40use crate::dom::idbobjectstore::KeyPath;
41use crate::script_runtime::CanGc;
42
43#[expect(unsafe_code)]
45pub fn key_type_to_jsval(
46 cx: &mut JSContext,
47 key: &IndexedDBKeyType,
48 mut result: MutableHandleValue,
49) {
50 match key {
54 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
56
57 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
59
60 IndexedDBKeyType::Date(d) => unsafe {
61 let date = NewDateObject(cx, ClippedTime { t: *d });
64
65 assert!(
67 !date.is_null(),
68 "Failed to convert IndexedDB date key into a Date"
69 );
70
71 date.safe_to_jsval(cx, result);
73 },
74
75 IndexedDBKeyType::Binary(b) => unsafe {
76 let len = b.len();
78
79 rooted!(&in(cx) let mut buffer = ptr::null_mut::<js::jsapi::JSObject>());
82 assert!(
83 ArrayBuffer::create(cx.raw_cx(), CreateWith::Length(len), buffer.handle_mut())
84 .is_ok(),
85 "Failed to convert IndexedDB binary key into an ArrayBuffer"
86 );
87
88 let mut array_buffer = ArrayBuffer::from(buffer.get())
93 .expect("ArrayBuffer::create should create an ArrayBuffer object");
94 array_buffer.as_mut_slice().copy_from_slice(b);
95
96 result.set(ObjectValue(buffer.get()));
98 },
99
100 IndexedDBKeyType::Array(a) => unsafe {
101 let empty_args = HandleValueArray::empty();
104 rooted!(&in(cx) let array = NewArrayObject(cx.raw_cx(), &empty_args));
105
106 assert!(
108 !array.get().is_null(),
109 "Failed to convert IndexedDB array key into an Array"
110 );
111
112 let len = a.len();
114
115 let mut index = 0;
117
118 while index < len {
120 rooted!(&in(cx) let mut entry = UndefinedValue());
123 key_type_to_jsval(cx, &a[index], entry.handle_mut());
124
125 let index_property = CString::new(index.to_string());
127 assert!(
128 index_property.is_ok(),
129 "Failed to convert IndexedDB array index to CString"
130 );
131 let index_property = index_property.unwrap();
132 let status = define_dictionary_property(
133 cx.into(),
134 array.handle(),
135 index_property.as_c_str(),
136 entry.handle(),
137 );
138
139 assert!(
141 status.is_ok(),
142 "CreateDataProperty on a fresh JS array should not fail"
143 );
144
145 index += 1;
147 }
148
149 result.set(ObjectValue(array.get()));
151 },
152 }
153}
154
155pub(crate) fn is_valid_key_path(
157 cx: &mut JSContext,
158 key_path: &StrOrStringSequence,
159) -> Result<bool, Error> {
160 #[expect(unsafe_code)]
162 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
163 rooted!(&in(cx) let mut value = UndefinedValue());
164 name.safe_to_jsval(cx, value.handle_mut());
165 rooted!(&in(cx) let string = value.to_string());
166
167 unsafe {
168 let mut is_identifier = false;
169 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
170 return Err(Error::JSFailed);
171 }
172 Ok(is_identifier)
173 }
174 };
175
176 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
178 let is_empty_string = path.is_empty();
180
181 let is_identifier = is_identifier_name(cx, &path.str())?;
184
185 let is_identifier_list = path
187 .str()
188 .split('.')
189 .map(|s| is_identifier_name(cx, s))
190 .try_collect::<bool, Vec<bool>, Error>()?
191 .iter()
192 .all(|&value| value);
193
194 Ok(is_empty_string || is_identifier || is_identifier_list)
195 };
196
197 match key_path {
198 StrOrStringSequence::StringSequence(paths) => {
199 if paths.is_empty() {
201 Ok(false)
202 } else {
203 Ok(paths
204 .iter()
205 .map(|s| is_valid(cx, s))
206 .try_collect::<bool, Vec<bool>, Error>()?
207 .iter()
208 .all(|&value| value))
209 }
210 },
211 StrOrStringSequence::String(path) => is_valid(cx, path),
212 }
213}
214
215pub(crate) enum ConversionResult {
216 Valid(IndexedDBKeyType),
217 Invalid,
218}
219
220impl ConversionResult {
221 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
222 match self {
223 ConversionResult::Valid(key) => Ok(key),
224 ConversionResult::Invalid => Err(Error::Data(None)),
225 }
226 }
227}
228
229#[expect(unsafe_code)]
231pub fn convert_value_to_key(
232 cx: &mut JSContext,
233 input: HandleValue,
234 seen: Option<Vec<HandleValue>>,
235) -> Result<ConversionResult, Error> {
236 let mut seen = seen.unwrap_or_default();
238
239 for seen_input in &seen {
241 let mut same = false;
242 if unsafe { !SameValue(cx.raw_cx(), *seen_input, input, &mut same) } {
243 return Err(Error::JSFailed);
244 }
245 if same {
246 return Ok(ConversionResult::Invalid);
247 }
248 }
249
250 if input.is_number() {
254 if input.to_number().is_nan() {
256 return Ok(ConversionResult::Invalid);
257 }
258 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
260 input.to_number(),
261 )));
262 }
263
264 if input.is_string() {
266 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
268 let key = unsafe { jsstr_to_string(cx.raw_cx(), string_ptr) };
269 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
270 }
271
272 if input.is_object() {
273 rooted!(&in(cx) let object = input.to_object());
274 unsafe {
275 let mut is_date = false;
276 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
277 return Err(Error::JSFailed);
278 }
279
280 if is_date {
282 let mut ms = f64::NAN;
284 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
285 return Err(Error::JSFailed);
286 }
287 if ms.is_nan() {
289 return Ok(ConversionResult::Invalid);
290 }
291 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
293 }
294
295 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
297 let is_detached = if IsArrayBufferObject(*object) {
298 IsDetachedArrayBufferObject(*object)
299 } else {
300 let mut is_shared = false;
302 rooted!(
303 in (cx.raw_cx()) let view_buffer =
304 JS_GetArrayBufferViewBuffer(
305 cx.raw_cx(),
306 object.handle().into(),
307 &mut is_shared
308 )
309 );
310 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
311 };
312 if is_detached {
314 return Ok(ConversionResult::Invalid);
315 }
316 let bytes = if IsArrayBufferObject(*object) {
319 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
320 array_buffer.to_vec()
321 } else {
322 let array_buffer_view =
323 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
324 array_buffer_view.to_vec()
325 };
326 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(bytes)));
328 }
329
330 let mut is_array = false;
332 if !IsArrayObject(cx, input, &mut is_array) {
333 return Err(Error::JSFailed);
334 }
335 if is_array {
336 let mut len = 0;
338 if !GetArrayLength(cx, object.handle(), &mut len) {
339 return Err(Error::JSFailed);
340 }
341 seen.push(input);
343 let mut keys = vec![];
345 let mut index: u32 = 0;
347 while index < len {
349 rooted!(&in(cx) let mut id: PropertyKey);
350 if !JS_IndexToId(cx, index, id.handle_mut()) {
351 return Err(Error::JSFailed);
352 }
353 let mut hop = false;
355 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut hop) {
356 return Err(Error::JSFailed);
357 }
358 if !hop {
360 return Ok(ConversionResult::Invalid);
361 }
362 rooted!(&in(cx) let mut entry = UndefinedValue());
364 if !js::rust::wrappers2::JS_GetPropertyById(
365 cx,
366 object.handle(),
367 id.handle(),
368 entry.handle_mut(),
369 ) {
370 return Err(Error::JSFailed);
371 }
372
373 let key = match convert_value_to_key(cx, entry.handle(), Some(seen.clone()))? {
377 ConversionResult::Valid(key) => key,
378 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
381 };
382 keys.push(key);
384 index += 1;
386 }
387 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(keys)));
389 }
390 }
391 }
392
393 Ok(ConversionResult::Invalid)
395}
396
397#[expect(unsafe_code)]
399pub fn convert_value_to_key_range(
400 cx: &mut JSContext,
401 input: HandleValue,
402 null_disallowed: Option<bool>,
403) -> Result<IndexedDBKeyRange, Error> {
404 if input.is_object() {
406 rooted!(&in(cx) let object = input.to_object());
407 unsafe {
408 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), cx.raw_cx()) {
409 let obj = obj.inner().clone();
410 return Ok(obj);
411 }
412 }
413 }
414
415 if input.get().is_undefined() || input.get().is_null() {
418 if null_disallowed.is_some_and(|flag| flag) {
419 return Err(Error::Data(None));
420 } else {
421 return Ok(IndexedDBKeyRange {
422 lower: None,
423 upper: None,
424 lower_open: Default::default(),
425 upper_open: Default::default(),
426 });
427 }
428 }
429
430 let key = convert_value_to_key(cx, input, None)?;
433
434 let key = key.into_result()?;
436
437 Ok(IndexedDBKeyRange::only(key))
439}
440
441pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
442 match error {
443 BackendError::QuotaExceeded => Error::QuotaExceeded {
444 quota: None,
445 requested: None,
446 },
447 BackendError::DbErr(details) => {
448 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
449 },
450 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
451 }
452}
453
454pub(crate) enum EvaluationResult {
457 Success,
458 Failure,
459}
460
461#[expect(unsafe_code)]
463pub(crate) fn evaluate_key_path_on_value(
464 cx: &mut JSContext,
465 value: HandleValue,
466 key_path: &KeyPath,
467 mut return_val: MutableHandleValue,
468) -> Result<EvaluationResult, Error> {
469 match key_path {
470 KeyPath::StringSequence(key_path) => {
472 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
474
475 for (i, item) in key_path.iter().enumerate() {
478 rooted!(&in(cx) let mut key = UndefinedValue());
483 if let EvaluationResult::Failure = evaluate_key_path_on_value(
484 cx,
485 value,
486 &KeyPath::String(item.clone()),
487 key.handle_mut(),
488 )? {
489 return Ok(EvaluationResult::Failure);
490 };
491
492 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
496 define_dictionary_property(
497 cx.into(),
498 result.handle(),
499 i_cstr.as_c_str(),
500 key.handle(),
501 )
502 .map_err(|_| Error::JSFailed)?;
503
504 }
507
508 result.safe_to_jsval(cx, return_val);
510 },
511 KeyPath::String(key_path) => {
512 if key_path.is_empty() {
514 return_val.set(*value);
515 return Ok(EvaluationResult::Success);
516 }
517
518 rooted!(&in(cx) let mut current_value = *value);
520
521 for identifier in key_path.str().split('.') {
525 if identifier == "length" && current_value.is_string() {
527 rooted!(&in(cx) let string_value = current_value.to_string());
529 unsafe {
530 let string_length = JS_GetStringLength(*string_value) as u64;
531 string_length.safe_to_jsval(cx, current_value.handle_mut());
532 }
533 continue;
534 }
535
536 if identifier == "length" {
538 unsafe {
539 let mut is_array = false;
540 if !IsArrayObject(cx, current_value.handle(), &mut is_array) {
541 return Err(Error::JSFailed);
542 }
543 if is_array {
544 rooted!(&in(cx) let object = current_value.to_object());
546 get_property_jsval(
547 cx.into(),
548 object.handle(),
549 c"length",
550 current_value.handle_mut(),
551 )?;
552
553 continue;
554 }
555 }
556 }
557
558 if identifier == "size" {
560 if let Ok(blob) =
561 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
562 {
563 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
565
566 continue;
567 }
568 }
569
570 if identifier == "type" {
572 if let Ok(blob) =
573 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
574 {
575 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
577
578 continue;
579 }
580 }
581
582 if identifier == "name" {
584 if let Ok(file) =
585 root_from_handlevalue::<File>(current_value.handle(), cx.into())
586 {
587 file.name().safe_to_jsval(cx, current_value.handle_mut());
589
590 continue;
591 }
592 }
593
594 if identifier == "lastModified" {
596 if let Ok(file) =
597 root_from_handlevalue::<File>(current_value.handle(), cx.into())
598 {
599 file.LastModified()
601 .safe_to_jsval(cx, current_value.handle_mut());
602
603 continue;
604 }
605 }
606
607 if identifier == "lastModifiedDate" {
609 if let Ok(file) =
610 root_from_handlevalue::<File>(current_value.handle(), cx.into())
611 {
612 let time = ClippedTime {
614 t: file.LastModified() as f64,
615 };
616 unsafe {
617 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
618 }
619
620 continue;
621 }
622 }
623
624 unsafe {
626 if !current_value.is_object() {
628 return Ok(EvaluationResult::Failure);
629 }
630
631 rooted!(&in(cx) let object = current_value.to_object());
632 let identifier_name =
633 CString::new(identifier).expect("Failed to convert str to CString");
634
635 let hop =
637 has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
638 .map_err(|_| Error::JSFailed)?;
639
640 if !hop {
642 return Ok(EvaluationResult::Failure);
643 }
644
645 match get_dictionary_property(
647 cx.raw_cx(),
648 object.handle(),
649 identifier_name.as_c_str(),
650 current_value.handle_mut(),
651 CanGc::note(),
652 ) {
653 Ok(true) => {},
654 Ok(false) => return Ok(EvaluationResult::Failure),
655 Err(()) => return Err(Error::JSFailed),
656 }
657
658 if current_value.get().is_undefined() {
660 return Ok(EvaluationResult::Failure);
661 }
662 }
663 }
664
665 return_val.set(*current_value);
670 },
671 }
672 Ok(EvaluationResult::Success)
673}
674
675pub(crate) enum ExtractionResult {
678 Key(IndexedDBKeyType),
679 Invalid,
680 Failure,
681}
682
683#[expect(unsafe_code)]
685pub(crate) fn can_inject_key_into_value(
686 cx: &mut JSContext,
687 value: HandleValue,
688 key_path: &DOMString,
689) -> Result<bool, Error> {
690 let key_path_string = key_path.str();
693 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
694
695 let Some(_) = identifiers.pop() else {
697 return Ok(false);
698 };
699
700 rooted!(&in(cx) let mut current_value = *value);
701
702 for identifier in identifiers {
704 if !current_value.is_object() {
706 return Ok(false);
707 }
708
709 rooted!(&in(cx) let current_object = current_value.to_object());
710 let identifier_name =
711 CString::new(identifier).expect("Failed to convert key path identifier to CString");
712
713 let hop = has_own_property(
715 cx.into(),
716 current_object.handle(),
717 identifier_name.as_c_str(),
718 )
719 .map_err(|_| Error::JSFailed)?;
720
721 if !hop {
726 return Ok(true);
727 }
728
729 match unsafe {
731 get_dictionary_property(
732 cx.raw_cx(),
733 current_object.handle(),
734 identifier_name.as_c_str(),
735 current_value.handle_mut(),
736 CanGc::note(),
737 )
738 } {
739 Ok(true) => {},
740 Ok(false) => return Ok(false),
741 Err(()) => return Err(Error::JSFailed),
742 }
743 }
744
745 Ok(current_value.is_object())
747}
748
749#[expect(unsafe_code)]
751pub(crate) fn inject_key_into_value(
752 cx: &mut JSContext,
753 value: HandleValue,
754 key: &IndexedDBKeyType,
755 key_path: &DOMString,
756) -> Result<bool, Error> {
757 let key_path_string = key_path.str();
759 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
760
761 let Some(last) = identifiers.pop() else {
763 return Ok(false);
764 };
765
766 rooted!(&in(cx) let mut current_value = *value);
770
771 for identifier in identifiers {
773 if !current_value.is_object() {
775 return Ok(false);
776 }
777
778 rooted!(&in(cx) let current_object = current_value.to_object());
779 let identifier_name =
780 CString::new(identifier).expect("Failed to convert key path identifier to CString");
781
782 let hop = has_own_property(
784 cx.into(),
785 current_object.handle(),
786 identifier_name.as_c_str(),
787 )
788 .map_err(|_| Error::JSFailed)?;
789
790 if !hop {
792 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
794 rooted!(&in(cx) let mut o_value = UndefinedValue());
795 o.safe_to_jsval(cx, o_value.handle_mut());
796
797 define_dictionary_property(
799 cx.into(),
800 current_object.handle(),
801 identifier_name.as_c_str(),
802 o_value.handle(),
803 )
804 .map_err(|_| Error::JSFailed)?;
805
806 }
808
809 match unsafe {
811 get_dictionary_property(
812 cx.raw_cx(),
813 current_object.handle(),
814 identifier_name.as_c_str(),
815 current_value.handle_mut(),
816 CanGc::note(),
817 )
818 } {
819 Ok(true) => {},
820 Ok(false) => return Ok(false),
821 Err(()) => return Err(Error::JSFailed),
822 }
823
824 if !current_value.is_object() {
826 return Ok(false);
827 }
828 }
829
830 rooted!(&in(cx) let mut key_value = UndefinedValue());
832 key_type_to_jsval(cx, key, key_value.handle_mut());
833
834 if !current_value.is_object() {
836 return Ok(false);
837 }
838 rooted!(&in(cx) let parent_object = current_value.to_object());
839 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
840
841 define_dictionary_property(
843 cx.into(),
844 parent_object.handle(),
845 last_name.as_c_str(),
846 key_value.handle(),
847 )
848 .map_err(|_| Error::JSFailed)?;
849
850 Ok(true)
854}
855
856pub(crate) fn extract_key(
858 cx: &mut JSContext,
859 value: HandleValue,
860 key_path: &KeyPath,
861 multi_entry: Option<bool>,
862) -> Result<ExtractionResult, Error> {
863 rooted!(&in(cx) let mut r = UndefinedValue());
867 if let EvaluationResult::Failure =
868 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
869 {
870 return Ok(ExtractionResult::Failure);
871 }
872
873 let key = match multi_entry {
877 Some(true) => {
878 unimplemented!("multiEntry keys are not yet supported");
880 },
881 _ => match convert_value_to_key(cx, r.handle(), None)? {
882 ConversionResult::Valid(key) => key,
883 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
885 },
886 };
887
888 Ok(ExtractionResult::Key(key))
890}