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, CreateWith::Length(len), buffer.handle_mut()).is_ok(),
79 "Failed to convert IndexedDB binary key into an ArrayBuffer"
80 );
81
82 let mut array_buffer = ArrayBuffer::from(buffer.get())
87 .expect("ArrayBuffer::create should create an ArrayBuffer object");
88 array_buffer
89 .as_mut_slice_safe(cx.no_gc())
90 .expect("Can't be detached")
91 .copy_from_slice(b);
92
93 result.set(ObjectValue(buffer.get()));
95 },
96
97 IndexedDBKeyType::Array(a) => unsafe {
98 rooted!(&in(cx) let array = NewArrayObject1(cx.raw_cx(), 0));
101
102 assert!(
104 !array.get().is_null(),
105 "Failed to convert IndexedDB array key into an Array"
106 );
107
108 let len = a.len();
110
111 let mut index = 0;
113
114 while index < len {
116 rooted!(&in(cx) let mut entry = UndefinedValue());
119 key_type_to_jsval(cx, &a[index], entry.handle_mut());
120
121 let index_property = CString::new(index.to_string());
123 assert!(
124 index_property.is_ok(),
125 "Failed to convert IndexedDB array index to CString"
126 );
127 let index_property = index_property.unwrap();
128 let status = define_dictionary_property(
129 cx,
130 array.handle(),
131 index_property.as_c_str(),
132 entry.handle(),
133 );
134
135 assert!(
137 status.is_ok(),
138 "CreateDataProperty on a fresh JS array should not fail"
139 );
140
141 index += 1;
143 }
144
145 result.set(ObjectValue(array.get()));
147 },
148 }
149}
150
151pub(crate) fn is_valid_key_path(
153 cx: &mut JSContext,
154 key_path: &StrOrStringSequence,
155) -> Result<bool, Error> {
156 #[expect(unsafe_code)]
158 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
159 rooted!(&in(cx) let mut value = UndefinedValue());
160 name.safe_to_jsval(cx, value.handle_mut());
161 rooted!(&in(cx) let string = value.to_string());
162
163 unsafe {
164 let mut is_identifier = false;
165 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
166 return Err(Error::JSFailed);
167 }
168 Ok(is_identifier)
169 }
170 };
171
172 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
174 let is_empty_string = path.is_empty();
176
177 let is_identifier = is_identifier_name(cx, &path.str())?;
180
181 let is_identifier_list = path
183 .str()
184 .split('.')
185 .map(|s| is_identifier_name(cx, s))
186 .try_collect::<bool, Vec<bool>, Error>()?
187 .iter()
188 .all(|&value| value);
189
190 Ok(is_empty_string || is_identifier || is_identifier_list)
191 };
192
193 match key_path {
194 StrOrStringSequence::StringSequence(paths) => {
195 if paths.is_empty() {
197 Ok(false)
198 } else {
199 Ok(paths
200 .iter()
201 .map(|s| is_valid(cx, s))
202 .try_collect::<bool, Vec<bool>, Error>()?
203 .iter()
204 .all(|&value| value))
205 }
206 },
207 StrOrStringSequence::String(path) => is_valid(cx, path),
208 }
209}
210
211pub(crate) enum ConversionResult {
212 Valid(IndexedDBKeyType),
213 Invalid,
214}
215
216impl ConversionResult {
217 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
218 match self {
219 ConversionResult::Valid(key) => Ok(key),
220 ConversionResult::Invalid => Err(Error::Data(None)),
221 }
222 }
223}
224
225#[expect(unsafe_code)]
227pub fn convert_value_to_key(
228 cx: &mut JSContext,
229 input: HandleValue,
230 seen: Option<Vec<HandleValue>>,
231) -> Result<ConversionResult, Error> {
232 let mut seen = seen.unwrap_or_default();
234
235 for seen_input in &seen {
237 let mut same = false;
238 if unsafe { !SameValue(cx, *seen_input, input, &mut same) } {
239 return Err(Error::JSFailed);
240 }
241 if same {
242 return Ok(ConversionResult::Invalid);
243 }
244 }
245
246 if input.is_number() {
250 if input.to_number().is_nan() {
252 return Ok(ConversionResult::Invalid);
253 }
254 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
256 input.to_number(),
257 )));
258 }
259
260 if input.is_string() {
262 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
264 let key = unsafe { jsstr_to_string(cx, string_ptr) };
265 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
266 }
267
268 if input.is_object() {
269 rooted!(&in(cx) let object = input.to_object());
270 unsafe {
271 let mut is_date = false;
272 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
273 return Err(Error::JSFailed);
274 }
275
276 if is_date {
278 let mut ms = f64::NAN;
280 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
281 return Err(Error::JSFailed);
282 }
283 if ms.is_nan() {
285 return Ok(ConversionResult::Invalid);
286 }
287 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
289 }
290
291 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
293 let is_detached = if IsArrayBufferObject(*object) {
294 IsDetachedArrayBufferObject(*object)
295 } else {
296 let mut is_shared = false;
298 rooted!(
299 in (cx.raw_cx()) let view_buffer =
300 JS_GetArrayBufferViewBuffer(
301 cx.raw_cx(),
302 object.handle().into(),
303 &mut is_shared
304 )
305 );
306 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
307 };
308 if is_detached {
310 return Ok(ConversionResult::Invalid);
311 }
312 let bytes = if IsArrayBufferObject(*object) {
315 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
316 array_buffer.to_vec()
317 } else {
318 let array_buffer_view =
319 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
320 array_buffer_view.to_vec()
321 }
322 .expect("Already checked for detached buffers");
323 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(bytes)));
325 }
326
327 let mut is_array = false;
329 if !IsArrayObject(cx, input, &mut is_array) {
330 return Err(Error::JSFailed);
331 }
332 if is_array {
333 let mut len = 0;
335 if !GetArrayLength(cx, object.handle(), &mut len) {
336 return Err(Error::JSFailed);
337 }
338 seen.push(input);
340 let mut keys = vec![];
342 let mut index: u32 = 0;
344 while index < len {
346 rooted!(&in(cx) let mut id: PropertyKey);
347 if !JS_IndexToId(cx, index, id.handle_mut()) {
348 return Err(Error::JSFailed);
349 }
350 let mut hop = false;
352 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut hop) {
353 return Err(Error::JSFailed);
354 }
355 if !hop {
357 return Ok(ConversionResult::Invalid);
358 }
359 rooted!(&in(cx) let mut entry = UndefinedValue());
361 if !js::rust::wrappers2::JS_GetPropertyById(
362 cx,
363 object.handle(),
364 id.handle(),
365 entry.handle_mut(),
366 ) {
367 return Err(Error::JSFailed);
368 }
369
370 let key = match convert_value_to_key(cx, entry.handle(), Some(seen.clone()))? {
374 ConversionResult::Valid(key) => key,
375 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
378 };
379 keys.push(key);
381 index += 1;
383 }
384 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(keys)));
386 }
387 }
388 }
389
390 Ok(ConversionResult::Invalid)
392}
393
394#[expect(unsafe_code)]
396pub fn convert_value_to_key_range(
397 cx: &mut JSContext,
398 input: HandleValue,
399 null_disallowed: Option<bool>,
400) -> Result<IndexedDBKeyRange, Error> {
401 if input.is_object() {
403 rooted!(&in(cx) let object = input.to_object());
404 unsafe {
405 if let Ok(obj) = root_from_object::<IDBKeyRange>(cx, object.get()) {
406 let obj = obj.inner().clone();
407 return Ok(obj);
408 }
409 }
410 }
411
412 if input.get().is_undefined() || input.get().is_null() {
415 if null_disallowed.is_some_and(|flag| flag) {
416 return Err(Error::Data(None));
417 } else {
418 return Ok(IndexedDBKeyRange {
419 lower: None,
420 upper: None,
421 lower_open: Default::default(),
422 upper_open: Default::default(),
423 });
424 }
425 }
426
427 let key = convert_value_to_key(cx, input, None)?;
430
431 let key = key.into_result()?;
433
434 Ok(IndexedDBKeyRange::only(key))
436}
437
438pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
439 match error {
440 BackendError::QuotaExceeded => Error::QuotaExceeded {
441 quota: None,
442 requested: None,
443 },
444 BackendError::DbErr(details) => {
445 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
446 },
447 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
448 }
449}
450
451pub(crate) enum EvaluationResult {
454 Success,
455 Failure,
456}
457
458#[expect(unsafe_code)]
460pub(crate) fn evaluate_key_path_on_value(
461 cx: &mut JSContext,
462 value: HandleValue,
463 key_path: &KeyPath,
464 mut return_val: MutableHandleValue,
465) -> Result<EvaluationResult, Error> {
466 match key_path {
467 KeyPath::StringSequence(key_path) => {
469 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
471
472 for (i, item) in key_path.iter().enumerate() {
475 rooted!(&in(cx) let mut key = UndefinedValue());
480 if let EvaluationResult::Failure = evaluate_key_path_on_value(
481 cx,
482 value,
483 &KeyPath::String(item.clone()),
484 key.handle_mut(),
485 )? {
486 return Ok(EvaluationResult::Failure);
487 };
488
489 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
493 define_dictionary_property(cx, result.handle(), i_cstr.as_c_str(), key.handle())
494 .map_err(|_| Error::JSFailed)?;
495
496 }
499
500 result.safe_to_jsval(cx, return_val);
502 },
503 KeyPath::String(key_path) => {
504 if key_path.is_empty() {
506 return_val.set(*value);
507 return Ok(EvaluationResult::Success);
508 }
509
510 rooted!(&in(cx) let mut current_value = *value);
512
513 for identifier in key_path.str().split('.') {
517 if identifier == "length" && current_value.is_string() {
519 rooted!(&in(cx) let string_value = current_value.to_string());
521 unsafe {
522 let string_length = JS_GetStringLength(*string_value) as u64;
523 string_length.safe_to_jsval(cx, current_value.handle_mut());
524 }
525 continue;
526 }
527
528 if identifier == "length" {
530 let mut is_array = false;
531 if unsafe { !IsArrayObject(cx, current_value.handle(), &mut is_array) } {
532 return Err(Error::JSFailed);
533 }
534 if is_array {
535 rooted!(&in(cx) let object = current_value.to_object());
537 get_property_jsval(
538 cx,
539 object.handle(),
540 c"length",
541 current_value.handle_mut(),
542 )?;
543
544 continue;
545 }
546 }
547
548 if identifier == "size" &&
550 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
551 {
552 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
554
555 continue;
556 }
557
558 if identifier == "type" &&
560 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
561 {
562 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
564
565 continue;
566 }
567
568 if identifier == "name" &&
570 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
571 {
572 file.name().safe_to_jsval(cx, current_value.handle_mut());
574
575 continue;
576 }
577
578 if identifier == "lastModified" &&
580 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
581 {
582 file.LastModified()
584 .safe_to_jsval(cx, current_value.handle_mut());
585
586 continue;
587 }
588
589 if identifier == "lastModifiedDate" &&
591 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
592 {
593 let time = ClippedTime {
595 t: file.LastModified() as f64,
596 };
597 unsafe {
598 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
599 }
600
601 continue;
602 }
603
604 if !current_value.is_object() {
607 return Ok(EvaluationResult::Failure);
608 }
609
610 rooted!(&in(cx) let object = current_value.to_object());
611 let identifier_name =
612 CString::new(identifier).expect("Failed to convert str to CString");
613
614 let hop = has_own_property(cx, object.handle(), identifier_name.as_c_str())
616 .map_err(|_| Error::JSFailed)?;
617
618 if !hop {
620 return Ok(EvaluationResult::Failure);
621 }
622
623 get_property_jsval(
625 cx,
626 object.handle(),
627 identifier_name.as_c_str(),
628 current_value.handle_mut(),
629 )?;
630
631 if current_value.get().is_undefined() {
633 return Ok(EvaluationResult::Failure);
634 }
635 }
636
637 return_val.set(*current_value);
642 },
643 }
644 Ok(EvaluationResult::Success)
645}
646
647pub(crate) enum ExtractionResult {
650 Key(IndexedDBKeyType),
651 Invalid,
652 Failure,
653}
654
655pub(crate) fn can_inject_key_into_value(
657 cx: &mut JSContext,
658 value: HandleValue,
659 key_path: &DOMString,
660) -> Result<bool, Error> {
661 let key_path_string = key_path.str();
664 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
665
666 let Some(_) = identifiers.pop() else {
668 return Ok(false);
669 };
670
671 rooted!(&in(cx) let mut current_value = *value);
672
673 for identifier in identifiers {
675 if !current_value.is_object() {
677 return Ok(false);
678 }
679
680 rooted!(&in(cx) let current_object = current_value.to_object());
681 let identifier_name =
682 CString::new(identifier).expect("Failed to convert key path identifier to CString");
683
684 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
686 .map_err(|_| Error::JSFailed)?;
687
688 if !hop {
693 return Ok(true);
694 }
695
696 get_property_jsval(
698 cx,
699 current_object.handle(),
700 identifier_name.as_c_str(),
701 current_value.handle_mut(),
702 )?;
703 }
704
705 Ok(current_value.is_object())
707}
708
709#[expect(unsafe_code)]
711pub(crate) fn inject_key_into_value(
712 cx: &mut JSContext,
713 value: HandleValue,
714 key: &IndexedDBKeyType,
715 key_path: &DOMString,
716) -> Result<bool, Error> {
717 let key_path_string = key_path.str();
719 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
720
721 let Some(last) = identifiers.pop() else {
723 return Ok(false);
724 };
725
726 rooted!(&in(cx) let mut current_value = *value);
730
731 for identifier in identifiers {
733 if !current_value.is_object() {
735 return Ok(false);
736 }
737
738 rooted!(&in(cx) let current_object = current_value.to_object());
739 let identifier_name =
740 CString::new(identifier).expect("Failed to convert key path identifier to CString");
741
742 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
744 .map_err(|_| Error::JSFailed)?;
745
746 if !hop {
748 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
750 rooted!(&in(cx) let mut o_value = UndefinedValue());
751 o.safe_to_jsval(cx, o_value.handle_mut());
752
753 define_dictionary_property(
755 cx,
756 current_object.handle(),
757 identifier_name.as_c_str(),
758 o_value.handle(),
759 )
760 .map_err(|_| Error::JSFailed)?;
761
762 }
764
765 get_property_jsval(
767 cx,
768 current_object.handle(),
769 identifier_name.as_c_str(),
770 current_value.handle_mut(),
771 )?;
772
773 if !current_value.is_object() {
775 return Ok(false);
776 }
777 }
778
779 rooted!(&in(cx) let mut key_value = UndefinedValue());
781 key_type_to_jsval(cx, key, key_value.handle_mut());
782
783 if !current_value.is_object() {
785 return Ok(false);
786 }
787 rooted!(&in(cx) let parent_object = current_value.to_object());
788 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
789
790 define_dictionary_property(
792 cx,
793 parent_object.handle(),
794 last_name.as_c_str(),
795 key_value.handle(),
796 )
797 .map_err(|_| Error::JSFailed)?;
798
799 Ok(true)
803}
804
805pub(crate) fn extract_key(
807 cx: &mut JSContext,
808 value: HandleValue,
809 key_path: &KeyPath,
810 multi_entry: Option<bool>,
811) -> Result<ExtractionResult, Error> {
812 rooted!(&in(cx) let mut r = UndefinedValue());
816 if let EvaluationResult::Failure =
817 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
818 {
819 return Ok(ExtractionResult::Failure);
820 }
821
822 let key = match multi_entry {
826 Some(true) => {
827 unimplemented!("multiEntry keys are not yet supported");
829 },
830 _ => match convert_value_to_key(cx, r.handle(), None)? {
831 ConversionResult::Valid(key) => key,
832 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
834 },
835 };
836
837 Ok(ExtractionResult::Key(key))
839}