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 if 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
568 if identifier == "type" {
570 if let Ok(blob) =
571 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
572 {
573 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
575
576 continue;
577 }
578 }
579
580 if identifier == "name" {
582 if let Ok(file) =
583 root_from_handlevalue::<File>(current_value.handle(), cx.into())
584 {
585 file.name().safe_to_jsval(cx, current_value.handle_mut());
587
588 continue;
589 }
590 }
591
592 if identifier == "lastModified" {
594 if let Ok(file) =
595 root_from_handlevalue::<File>(current_value.handle(), cx.into())
596 {
597 file.LastModified()
599 .safe_to_jsval(cx, current_value.handle_mut());
600
601 continue;
602 }
603 }
604
605 if identifier == "lastModifiedDate" {
607 if let Ok(file) =
608 root_from_handlevalue::<File>(current_value.handle(), cx.into())
609 {
610 let time = ClippedTime {
612 t: file.LastModified() as f64,
613 };
614 unsafe {
615 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
616 }
617
618 continue;
619 }
620 }
621
622 unsafe {
624 if !current_value.is_object() {
626 return Ok(EvaluationResult::Failure);
627 }
628
629 rooted!(&in(cx) let object = current_value.to_object());
630 let identifier_name =
631 CString::new(identifier).expect("Failed to convert str to CString");
632
633 let hop =
635 has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
636 .map_err(|_| Error::JSFailed)?;
637
638 if !hop {
640 return Ok(EvaluationResult::Failure);
641 }
642
643 match get_dictionary_property(
645 cx.raw_cx(),
646 object.handle(),
647 identifier_name.as_c_str(),
648 current_value.handle_mut(),
649 CanGc::deprecated_note(),
650 ) {
651 Ok(true) => {},
652 Ok(false) => return Ok(EvaluationResult::Failure),
653 Err(()) => return Err(Error::JSFailed),
654 }
655
656 if current_value.get().is_undefined() {
658 return Ok(EvaluationResult::Failure);
659 }
660 }
661 }
662
663 return_val.set(*current_value);
668 },
669 }
670 Ok(EvaluationResult::Success)
671}
672
673pub(crate) enum ExtractionResult {
676 Key(IndexedDBKeyType),
677 Invalid,
678 Failure,
679}
680
681#[expect(unsafe_code)]
683pub(crate) fn can_inject_key_into_value(
684 cx: &mut JSContext,
685 value: HandleValue,
686 key_path: &DOMString,
687) -> Result<bool, Error> {
688 let key_path_string = key_path.str();
691 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
692
693 let Some(_) = identifiers.pop() else {
695 return Ok(false);
696 };
697
698 rooted!(&in(cx) let mut current_value = *value);
699
700 for identifier in identifiers {
702 if !current_value.is_object() {
704 return Ok(false);
705 }
706
707 rooted!(&in(cx) let current_object = current_value.to_object());
708 let identifier_name =
709 CString::new(identifier).expect("Failed to convert key path identifier to CString");
710
711 let hop = has_own_property(
713 cx.into(),
714 current_object.handle(),
715 identifier_name.as_c_str(),
716 )
717 .map_err(|_| Error::JSFailed)?;
718
719 if !hop {
724 return Ok(true);
725 }
726
727 match unsafe {
729 get_dictionary_property(
730 cx.raw_cx(),
731 current_object.handle(),
732 identifier_name.as_c_str(),
733 current_value.handle_mut(),
734 CanGc::deprecated_note(),
735 )
736 } {
737 Ok(true) => {},
738 Ok(false) => return Ok(false),
739 Err(()) => return Err(Error::JSFailed),
740 }
741 }
742
743 Ok(current_value.is_object())
745}
746
747#[expect(unsafe_code)]
749pub(crate) fn inject_key_into_value(
750 cx: &mut JSContext,
751 value: HandleValue,
752 key: &IndexedDBKeyType,
753 key_path: &DOMString,
754) -> Result<bool, Error> {
755 let key_path_string = key_path.str();
757 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
758
759 let Some(last) = identifiers.pop() else {
761 return Ok(false);
762 };
763
764 rooted!(&in(cx) let mut current_value = *value);
768
769 for identifier in identifiers {
771 if !current_value.is_object() {
773 return Ok(false);
774 }
775
776 rooted!(&in(cx) let current_object = current_value.to_object());
777 let identifier_name =
778 CString::new(identifier).expect("Failed to convert key path identifier to CString");
779
780 let hop = has_own_property(
782 cx.into(),
783 current_object.handle(),
784 identifier_name.as_c_str(),
785 )
786 .map_err(|_| Error::JSFailed)?;
787
788 if !hop {
790 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
792 rooted!(&in(cx) let mut o_value = UndefinedValue());
793 o.safe_to_jsval(cx, o_value.handle_mut());
794
795 define_dictionary_property(
797 cx.into(),
798 current_object.handle(),
799 identifier_name.as_c_str(),
800 o_value.handle(),
801 )
802 .map_err(|_| Error::JSFailed)?;
803
804 }
806
807 match unsafe {
809 get_dictionary_property(
810 cx.raw_cx(),
811 current_object.handle(),
812 identifier_name.as_c_str(),
813 current_value.handle_mut(),
814 CanGc::deprecated_note(),
815 )
816 } {
817 Ok(true) => {},
818 Ok(false) => return Ok(false),
819 Err(()) => return Err(Error::JSFailed),
820 }
821
822 if !current_value.is_object() {
824 return Ok(false);
825 }
826 }
827
828 rooted!(&in(cx) let mut key_value = UndefinedValue());
830 key_type_to_jsval(cx, key, key_value.handle_mut());
831
832 if !current_value.is_object() {
834 return Ok(false);
835 }
836 rooted!(&in(cx) let parent_object = current_value.to_object());
837 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
838
839 define_dictionary_property(
841 cx.into(),
842 parent_object.handle(),
843 last_name.as_c_str(),
844 key_value.handle(),
845 )
846 .map_err(|_| Error::JSFailed)?;
847
848 Ok(true)
852}
853
854pub(crate) fn extract_key(
856 cx: &mut JSContext,
857 value: HandleValue,
858 key_path: &KeyPath,
859 multi_entry: Option<bool>,
860) -> Result<ExtractionResult, Error> {
861 rooted!(&in(cx) let mut r = UndefinedValue());
865 if let EvaluationResult::Failure =
866 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
867 {
868 return Ok(ExtractionResult::Failure);
869 }
870
871 let key = match multi_entry {
875 Some(true) => {
876 unimplemented!("multiEntry keys are not yet supported");
878 },
879 _ => match convert_value_to_key(cx, r.handle(), None)? {
880 ConversionResult::Valid(key) => key,
881 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
883 },
884 };
885
886 Ok(ExtractionResult::Key(key))
888}