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::wrappers2::{
17 GetArrayLength, IsArrayObject, JS_HasOwnPropertyById, JS_IndexToId, JS_IsIdentifier,
18 JS_NewObject, NewDateObject, ObjectIsDate, SameValue,
19};
20use js::rust::{HandleValue, MutableHandleValue};
21use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith};
22use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
23
24use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
25use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
26use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
27use crate::dom::bindings::conversions::{
28 get_property_jsval, root_from_handlevalue, root_from_object,
29};
30use crate::dom::bindings::error::Error;
31use crate::dom::bindings::str::DOMString;
32use crate::dom::bindings::utils::{define_dictionary_property, has_own_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 {
49 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
51
52 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
54
55 IndexedDBKeyType::Date(d) => unsafe {
56 let date = NewDateObject(cx, ClippedTime { t: *d });
59
60 assert!(
62 !date.is_null(),
63 "Failed to convert IndexedDB date key into a Date"
64 );
65
66 date.safe_to_jsval(cx, result);
68 },
69
70 IndexedDBKeyType::Binary(b) => unsafe {
71 let len = b.len();
73
74 rooted!(&in(cx) let mut buffer = ptr::null_mut::<js::jsapi::JSObject>());
77 assert!(
78 ArrayBuffer::create(cx.raw_cx(), CreateWith::Length(len), buffer.handle_mut())
79 .is_ok(),
80 "Failed to convert IndexedDB binary key into an ArrayBuffer"
81 );
82
83 let mut array_buffer = ArrayBuffer::from(buffer.get())
88 .expect("ArrayBuffer::create should create an ArrayBuffer object");
89 array_buffer
90 .as_mut_slice_safe(cx.no_gc())
91 .expect("Can't be detached")
92 .copy_from_slice(b);
93
94 result.set(ObjectValue(buffer.get()));
96 },
97
98 IndexedDBKeyType::Array(a) => unsafe {
99 rooted!(&in(cx) let array = NewArrayObject1(cx.raw_cx(), 0));
102
103 assert!(
105 !array.get().is_null(),
106 "Failed to convert IndexedDB array key into an Array"
107 );
108
109 let len = a.len();
111
112 let mut index = 0;
114
115 while index < len {
117 rooted!(&in(cx) let mut entry = UndefinedValue());
120 key_type_to_jsval(cx, &a[index], entry.handle_mut());
121
122 let index_property = CString::new(index.to_string());
124 assert!(
125 index_property.is_ok(),
126 "Failed to convert IndexedDB array index to CString"
127 );
128 let index_property = index_property.unwrap();
129 let status = define_dictionary_property(
130 cx,
131 array.handle(),
132 index_property.as_c_str(),
133 entry.handle(),
134 );
135
136 assert!(
138 status.is_ok(),
139 "CreateDataProperty on a fresh JS array should not fail"
140 );
141
142 index += 1;
144 }
145
146 result.set(ObjectValue(array.get()));
148 },
149 }
150}
151
152pub(crate) fn is_valid_key_path(
154 cx: &mut JSContext,
155 key_path: &StrOrStringSequence,
156) -> Result<bool, Error> {
157 #[expect(unsafe_code)]
159 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
160 rooted!(&in(cx) let mut value = UndefinedValue());
161 name.safe_to_jsval(cx, value.handle_mut());
162 rooted!(&in(cx) let string = value.to_string());
163
164 unsafe {
165 let mut is_identifier = false;
166 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
167 return Err(Error::JSFailed);
168 }
169 Ok(is_identifier)
170 }
171 };
172
173 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
175 let is_empty_string = path.is_empty();
177
178 let is_identifier = is_identifier_name(cx, &path.str())?;
181
182 let is_identifier_list = path
184 .str()
185 .split('.')
186 .map(|s| is_identifier_name(cx, s))
187 .try_collect::<bool, Vec<bool>, Error>()?
188 .iter()
189 .all(|&value| value);
190
191 Ok(is_empty_string || is_identifier || is_identifier_list)
192 };
193
194 match key_path {
195 StrOrStringSequence::StringSequence(paths) => {
196 if paths.is_empty() {
198 Ok(false)
199 } else {
200 Ok(paths
201 .iter()
202 .map(|s| is_valid(cx, s))
203 .try_collect::<bool, Vec<bool>, Error>()?
204 .iter()
205 .all(|&value| value))
206 }
207 },
208 StrOrStringSequence::String(path) => is_valid(cx, path),
209 }
210}
211
212pub(crate) enum ConversionResult {
213 Valid(IndexedDBKeyType),
214 Invalid,
215}
216
217impl ConversionResult {
218 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
219 match self {
220 ConversionResult::Valid(key) => Ok(key),
221 ConversionResult::Invalid => Err(Error::Data(None)),
222 }
223 }
224}
225
226#[expect(unsafe_code)]
228pub fn convert_value_to_key(
229 cx: &mut JSContext,
230 input: HandleValue,
231 seen: Option<Vec<HandleValue>>,
232) -> Result<ConversionResult, Error> {
233 let mut seen = seen.unwrap_or_default();
235
236 for seen_input in &seen {
238 let mut same = false;
239 if unsafe { !SameValue(cx, *seen_input, input, &mut same) } {
240 return Err(Error::JSFailed);
241 }
242 if same {
243 return Ok(ConversionResult::Invalid);
244 }
245 }
246
247 if input.is_number() {
251 if input.to_number().is_nan() {
253 return Ok(ConversionResult::Invalid);
254 }
255 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
257 input.to_number(),
258 )));
259 }
260
261 if input.is_string() {
263 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
265 let key = unsafe { jsstr_to_string(cx, string_ptr) };
266 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
267 }
268
269 if input.is_object() {
270 rooted!(&in(cx) let object = input.to_object());
271 unsafe {
272 let mut is_date = false;
273 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
274 return Err(Error::JSFailed);
275 }
276
277 if is_date {
279 let mut ms = f64::NAN;
281 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
282 return Err(Error::JSFailed);
283 }
284 if ms.is_nan() {
286 return Ok(ConversionResult::Invalid);
287 }
288 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
290 }
291
292 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
294 let is_detached = if IsArrayBufferObject(*object) {
295 IsDetachedArrayBufferObject(*object)
296 } else {
297 let mut is_shared = false;
299 rooted!(
300 in (cx.raw_cx()) let view_buffer =
301 JS_GetArrayBufferViewBuffer(
302 cx.raw_cx(),
303 object.handle().into(),
304 &mut is_shared
305 )
306 );
307 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
308 };
309 if is_detached {
311 return Ok(ConversionResult::Invalid);
312 }
313 let bytes = if IsArrayBufferObject(*object) {
316 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
317 array_buffer.to_vec()
318 } else {
319 let array_buffer_view =
320 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
321 array_buffer_view.to_vec()
322 }
323 .expect("Already checked for detached buffers");
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>(cx, object.get()) {
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(cx, result.handle(), i_cstr.as_c_str(), key.handle())
495 .map_err(|_| Error::JSFailed)?;
496
497 }
500
501 result.safe_to_jsval(cx, return_val);
503 },
504 KeyPath::String(key_path) => {
505 if key_path.is_empty() {
507 return_val.set(*value);
508 return Ok(EvaluationResult::Success);
509 }
510
511 rooted!(&in(cx) let mut current_value = *value);
513
514 for identifier in key_path.str().split('.') {
518 if identifier == "length" && current_value.is_string() {
520 rooted!(&in(cx) let string_value = current_value.to_string());
522 unsafe {
523 let string_length = JS_GetStringLength(*string_value) as u64;
524 string_length.safe_to_jsval(cx, current_value.handle_mut());
525 }
526 continue;
527 }
528
529 if identifier == "length" {
531 let mut is_array = false;
532 if unsafe { !IsArrayObject(cx, current_value.handle(), &mut is_array) } {
533 return Err(Error::JSFailed);
534 }
535 if is_array {
536 rooted!(&in(cx) let object = current_value.to_object());
538 get_property_jsval(
539 cx,
540 object.handle(),
541 c"length",
542 current_value.handle_mut(),
543 )?;
544
545 continue;
546 }
547 }
548
549 if identifier == "size" &&
551 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
552 {
553 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
555
556 continue;
557 }
558
559 if identifier == "type" &&
561 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
562 {
563 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
565
566 continue;
567 }
568
569 if identifier == "name" &&
571 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
572 {
573 file.name().safe_to_jsval(cx, current_value.handle_mut());
575
576 continue;
577 }
578
579 if identifier == "lastModified" &&
581 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
582 {
583 file.LastModified()
585 .safe_to_jsval(cx, current_value.handle_mut());
586
587 continue;
588 }
589
590 if identifier == "lastModifiedDate" &&
592 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
593 {
594 let time = ClippedTime {
596 t: file.LastModified() as f64,
597 };
598 unsafe {
599 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
600 }
601
602 continue;
603 }
604
605 if !current_value.is_object() {
608 return Ok(EvaluationResult::Failure);
609 }
610
611 rooted!(&in(cx) let object = current_value.to_object());
612 let identifier_name =
613 CString::new(identifier).expect("Failed to convert str to CString");
614
615 let hop = has_own_property(cx, object.handle(), identifier_name.as_c_str())
617 .map_err(|_| Error::JSFailed)?;
618
619 if !hop {
621 return Ok(EvaluationResult::Failure);
622 }
623
624 get_property_jsval(
626 cx,
627 object.handle(),
628 identifier_name.as_c_str(),
629 current_value.handle_mut(),
630 )?;
631
632 if current_value.get().is_undefined() {
634 return Ok(EvaluationResult::Failure);
635 }
636 }
637
638 return_val.set(*current_value);
643 },
644 }
645 Ok(EvaluationResult::Success)
646}
647
648pub(crate) enum ExtractionResult {
651 Key(IndexedDBKeyType),
652 Invalid,
653 Failure,
654}
655
656pub(crate) fn can_inject_key_into_value(
658 cx: &mut JSContext,
659 value: HandleValue,
660 key_path: &DOMString,
661) -> Result<bool, Error> {
662 let key_path_string = key_path.str();
665 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
666
667 let Some(_) = identifiers.pop() else {
669 return Ok(false);
670 };
671
672 rooted!(&in(cx) let mut current_value = *value);
673
674 for identifier in identifiers {
676 if !current_value.is_object() {
678 return Ok(false);
679 }
680
681 rooted!(&in(cx) let current_object = current_value.to_object());
682 let identifier_name =
683 CString::new(identifier).expect("Failed to convert key path identifier to CString");
684
685 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
687 .map_err(|_| Error::JSFailed)?;
688
689 if !hop {
694 return Ok(true);
695 }
696
697 get_property_jsval(
699 cx,
700 current_object.handle(),
701 identifier_name.as_c_str(),
702 current_value.handle_mut(),
703 )?;
704 }
705
706 Ok(current_value.is_object())
708}
709
710#[expect(unsafe_code)]
712pub(crate) fn inject_key_into_value(
713 cx: &mut JSContext,
714 value: HandleValue,
715 key: &IndexedDBKeyType,
716 key_path: &DOMString,
717) -> Result<bool, Error> {
718 let key_path_string = key_path.str();
720 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
721
722 let Some(last) = identifiers.pop() else {
724 return Ok(false);
725 };
726
727 rooted!(&in(cx) let mut current_value = *value);
731
732 for identifier in identifiers {
734 if !current_value.is_object() {
736 return Ok(false);
737 }
738
739 rooted!(&in(cx) let current_object = current_value.to_object());
740 let identifier_name =
741 CString::new(identifier).expect("Failed to convert key path identifier to CString");
742
743 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
745 .map_err(|_| Error::JSFailed)?;
746
747 if !hop {
749 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
751 rooted!(&in(cx) let mut o_value = UndefinedValue());
752 o.safe_to_jsval(cx, o_value.handle_mut());
753
754 define_dictionary_property(
756 cx,
757 current_object.handle(),
758 identifier_name.as_c_str(),
759 o_value.handle(),
760 )
761 .map_err(|_| Error::JSFailed)?;
762
763 }
765
766 get_property_jsval(
768 cx,
769 current_object.handle(),
770 identifier_name.as_c_str(),
771 current_value.handle_mut(),
772 )?;
773
774 if !current_value.is_object() {
776 return Ok(false);
777 }
778 }
779
780 rooted!(&in(cx) let mut key_value = UndefinedValue());
782 key_type_to_jsval(cx, key, key_value.handle_mut());
783
784 if !current_value.is_object() {
786 return Ok(false);
787 }
788 rooted!(&in(cx) let parent_object = current_value.to_object());
789 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
790
791 define_dictionary_property(
793 cx,
794 parent_object.handle(),
795 last_name.as_c_str(),
796 key_value.handle(),
797 )
798 .map_err(|_| Error::JSFailed)?;
799
800 Ok(true)
804}
805
806pub(crate) fn extract_key(
808 cx: &mut JSContext,
809 value: HandleValue,
810 key_path: &KeyPath,
811 multi_entry: Option<bool>,
812) -> Result<ExtractionResult, Error> {
813 rooted!(&in(cx) let mut r = UndefinedValue());
817 if let EvaluationResult::Failure =
818 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
819 {
820 return Ok(ExtractionResult::Failure);
821 }
822
823 let key = match multi_entry {
827 Some(true) => {
828 unimplemented!("multiEntry keys are not yet supported");
830 },
831 _ => match convert_value_to_key(cx, r.handle(), None)? {
832 ConversionResult::Valid(key) => key,
833 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
835 },
836 };
837
838 Ok(ExtractionResult::Key(key))
840}