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 NewDateObject, ObjectIsDate, SameValue,
19};
20use js::rust::{HandleValue, MutableHandleValue};
21use js::typedarray::{ArrayBuffer, ArrayBufferU8, ArrayBufferView};
22use script_bindings::js::{JS_GetPropertyById, JS_NewObject};
23use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
24
25use crate::dom::bindings::buffer_source::create_buffer_source;
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::{get_property_jsval, root_from_handlevalue};
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.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.to_jsval(cx, result);
68 },
69
70 IndexedDBKeyType::Binary(b) => {
71 rooted!(&in(cx) let mut buffer = ptr::null_mut::<js::jsapi::JSObject>());
76
77 assert!(
82 create_buffer_source::<ArrayBufferU8>(cx, b, buffer.handle_mut()).is_ok(),
83 "Failed to convert IndexedDB binary key into an ArrayBuffer"
84 );
85
86 result.set(ObjectValue(buffer.get()));
88 },
89
90 IndexedDBKeyType::Array(a) => unsafe {
91 rooted!(&in(cx) let array = NewArrayObject1(cx.raw_cx(), 0));
94
95 assert!(
97 !array.get().is_null(),
98 "Failed to convert IndexedDB array key into an Array"
99 );
100
101 let len = a.len();
103
104 let mut index = 0;
106
107 while index < len {
109 rooted!(&in(cx) let mut entry = UndefinedValue());
112 key_type_to_jsval(cx, &a[index], entry.handle_mut());
113
114 let index_property = CString::new(index.to_string());
116 assert!(
117 index_property.is_ok(),
118 "Failed to convert IndexedDB array index to CString"
119 );
120 let index_property = index_property.unwrap();
121 let status = define_dictionary_property(
122 cx,
123 array.handle(),
124 index_property.as_c_str(),
125 entry.handle(),
126 );
127
128 assert!(
130 status.is_ok(),
131 "CreateDataProperty on a fresh JS array should not fail"
132 );
133
134 index += 1;
136 }
137
138 result.set(ObjectValue(array.get()));
140 },
141 }
142}
143
144pub(crate) fn is_valid_key_path(
146 cx: &mut JSContext,
147 key_path: &StrOrStringSequence,
148) -> Result<bool, Error> {
149 #[expect(unsafe_code)]
151 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
152 rooted!(&in(cx) let mut value = UndefinedValue());
153 name.to_jsval(cx, value.handle_mut());
154 rooted!(&in(cx) let string = value.to_string());
155
156 unsafe {
157 let mut is_identifier = false;
158 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
159 return Err(Error::JSFailed);
160 }
161 Ok(is_identifier)
162 }
163 };
164
165 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
167 let is_empty_string = path.is_empty();
169
170 let is_identifier = is_identifier_name(cx, &path.str())?;
173
174 let is_identifier_list = path
176 .str()
177 .split('.')
178 .map(|s| is_identifier_name(cx, s))
179 .try_collect::<bool, Vec<bool>, Error>()?
180 .iter()
181 .all(|&value| value);
182
183 Ok(is_empty_string || is_identifier || is_identifier_list)
184 };
185
186 match key_path {
187 StrOrStringSequence::StringSequence(paths) => {
188 if paths.is_empty() {
190 Ok(false)
191 } else {
192 Ok(paths
193 .iter()
194 .map(|s| is_valid(cx, s))
195 .try_collect::<bool, Vec<bool>, Error>()?
196 .iter()
197 .all(|&value| value))
198 }
199 },
200 StrOrStringSequence::String(path) => is_valid(cx, path),
201 }
202}
203
204pub(crate) enum ConversionResult {
205 Valid(IndexedDBKeyType),
206 Invalid,
207}
208
209impl ConversionResult {
210 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
211 match self {
212 ConversionResult::Valid(key) => Ok(key),
213 ConversionResult::Invalid => Err(Error::Data(None)),
214 }
215 }
216}
217
218#[expect(unsafe_code)]
220pub fn convert_value_to_key(
221 cx: &mut JSContext,
222 input: HandleValue,
223 seen: Option<Vec<HandleValue>>,
224) -> Result<ConversionResult, Error> {
225 let mut seen = seen.unwrap_or_default();
227
228 for seen_input in &seen {
230 let mut same = false;
231 if unsafe { !SameValue(cx, *seen_input, input, &mut same) } {
232 return Err(Error::JSFailed);
233 }
234 if same {
235 return Ok(ConversionResult::Invalid);
236 }
237 }
238
239 if input.is_number() {
243 if input.to_number().is_nan() {
245 return Ok(ConversionResult::Invalid);
246 }
247 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
249 input.to_number(),
250 )));
251 }
252
253 if input.is_string() {
255 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
257 let key = unsafe { jsstr_to_string(cx, string_ptr) };
258 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
259 }
260
261 if input.is_object() {
262 rooted!(&in(cx) let object = input.to_object());
263 unsafe {
264 let mut is_date = false;
265 if !ObjectIsDate(cx, object.handle(), &mut is_date) {
266 return Err(Error::JSFailed);
267 }
268
269 if is_date {
271 let mut ms = f64::NAN;
273 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut ms) {
274 return Err(Error::JSFailed);
275 }
276 if ms.is_nan() {
278 return Ok(ConversionResult::Invalid);
279 }
280 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(ms)));
282 }
283
284 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
286 let is_detached = if IsArrayBufferObject(*object) {
287 IsDetachedArrayBufferObject(*object)
288 } else {
289 let mut is_shared = false;
291 rooted!(
292 in (cx.raw_cx()) let view_buffer =
293 JS_GetArrayBufferViewBuffer(
294 cx.raw_cx(),
295 object.handle().into(),
296 &mut is_shared
297 )
298 );
299 !is_shared && IsDetachedArrayBufferObject(*view_buffer.handle())
300 };
301 if is_detached {
303 return Ok(ConversionResult::Invalid);
304 }
305 let bytes = if IsArrayBufferObject(*object) {
308 let array_buffer = ArrayBuffer::from(*object).map_err(|()| Error::JSFailed)?;
309 array_buffer.to_vec()
310 } else {
311 let array_buffer_view =
312 ArrayBufferView::from(*object).map_err(|()| Error::JSFailed)?;
313 array_buffer_view.to_vec()
314 }
315 .expect("Already checked for detached buffers");
316 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(bytes)));
318 }
319
320 let mut is_array = false;
322 if !IsArrayObject(cx, input, &mut is_array) {
323 return Err(Error::JSFailed);
324 }
325 if is_array {
326 let mut len = 0;
328 if !GetArrayLength(cx, object.handle(), &mut len) {
329 return Err(Error::JSFailed);
330 }
331 seen.push(input);
333 let mut keys = vec![];
335 let mut index: u32 = 0;
337 while index < len {
339 rooted!(&in(cx) let mut id: PropertyKey);
340 if !JS_IndexToId(cx, index, id.handle_mut()) {
341 return Err(Error::JSFailed);
342 }
343 let mut hop = false;
345 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut hop) {
346 return Err(Error::JSFailed);
347 }
348 if !hop {
350 return Ok(ConversionResult::Invalid);
351 }
352 rooted!(&in(cx) let mut entry = UndefinedValue());
354 if !JS_GetPropertyById(cx, object.handle(), id.handle(), entry.handle_mut()) {
355 return Err(Error::JSFailed);
356 }
357
358 let key = match convert_value_to_key(cx, entry.handle(), Some(seen.clone()))? {
362 ConversionResult::Valid(key) => key,
363 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
366 };
367 keys.push(key);
369 index += 1;
371 }
372 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(keys)));
374 }
375 }
376 }
377
378 Ok(ConversionResult::Invalid)
380}
381
382pub fn convert_value_to_key_range(
384 cx: &mut JSContext,
385 input: HandleValue,
386 null_disallowed: Option<bool>,
387) -> Result<IndexedDBKeyRange, Error> {
388 if let Ok(obj) = root_from_handlevalue::<IDBKeyRange>(cx, input) {
390 let obj = obj.inner().clone();
391 return Ok(obj);
392 }
393
394 if input.get().is_undefined() || input.get().is_null() {
397 if null_disallowed.is_some_and(|flag| flag) {
398 return Err(Error::Data(None));
399 } else {
400 return Ok(IndexedDBKeyRange {
401 lower: None,
402 upper: None,
403 lower_open: Default::default(),
404 upper_open: Default::default(),
405 });
406 }
407 }
408
409 let key = convert_value_to_key(cx, input, None)?;
412
413 let key = key.into_result()?;
415
416 Ok(IndexedDBKeyRange::only(key))
418}
419
420pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
421 match error {
422 BackendError::QuotaExceeded => Error::QuotaExceeded {
423 quota: None,
424 requested: None,
425 },
426 BackendError::DbErr(details) => {
427 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
428 },
429 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
430 }
431}
432
433pub(crate) enum EvaluationResult {
436 Success,
437 Failure,
438}
439
440#[expect(unsafe_code)]
442pub(crate) fn evaluate_key_path_on_value(
443 cx: &mut JSContext,
444 value: HandleValue,
445 key_path: &KeyPath,
446 mut return_val: MutableHandleValue,
447) -> Result<EvaluationResult, Error> {
448 match key_path {
449 KeyPath::StringSequence(key_path) => {
451 rooted!(&in(cx) let mut result = JS_NewObject(cx, None));
453
454 for (i, item) in key_path.iter().enumerate() {
457 rooted!(&in(cx) let mut key = UndefinedValue());
462 if let EvaluationResult::Failure = evaluate_key_path_on_value(
463 cx,
464 value,
465 &KeyPath::String(item.clone()),
466 key.handle_mut(),
467 )? {
468 return Ok(EvaluationResult::Failure);
469 };
470
471 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
475 define_dictionary_property(cx, result.handle(), i_cstr.as_c_str(), key.handle())
476 .map_err(|_| Error::JSFailed)?;
477
478 }
481
482 result.to_jsval(cx, return_val);
484 },
485 KeyPath::String(key_path) => {
486 if key_path.is_empty() {
488 return_val.set(*value);
489 return Ok(EvaluationResult::Success);
490 }
491
492 rooted!(&in(cx) let mut current_value = *value);
494
495 for identifier in key_path.str().split('.') {
499 if identifier == "length" && current_value.is_string() {
501 rooted!(&in(cx) let string_value = current_value.to_string());
503 unsafe {
504 let string_length = JS_GetStringLength(*string_value) as u64;
505 string_length.to_jsval(cx, current_value.handle_mut());
506 }
507 continue;
508 }
509
510 if identifier == "length" {
512 let mut is_array = false;
513 if unsafe { !IsArrayObject(cx, current_value.handle(), &mut is_array) } {
514 return Err(Error::JSFailed);
515 }
516 if is_array {
517 rooted!(&in(cx) let object = current_value.to_object());
519 get_property_jsval(
520 cx,
521 object.handle(),
522 c"length",
523 current_value.handle_mut(),
524 )?;
525
526 continue;
527 }
528 }
529
530 if identifier == "size" &&
532 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
533 {
534 blob.Size().to_jsval(cx, current_value.handle_mut());
536
537 continue;
538 }
539
540 if identifier == "type" &&
542 let Ok(blob) = root_from_handlevalue::<Blob>(cx, current_value.handle())
543 {
544 blob.Type().to_jsval(cx, current_value.handle_mut());
546
547 continue;
548 }
549
550 if identifier == "name" &&
552 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
553 {
554 file.name()
556 .clone()
557 .root()
558 .to_jsval(cx, current_value.handle_mut());
559
560 continue;
561 }
562
563 if identifier == "lastModified" &&
565 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
566 {
567 file.LastModified().to_jsval(cx, current_value.handle_mut());
569
570 continue;
571 }
572
573 if identifier == "lastModifiedDate" &&
575 let Ok(file) = root_from_handlevalue::<File>(cx, current_value.handle())
576 {
577 let time = ClippedTime {
579 t: file.LastModified() as f64,
580 };
581 unsafe {
582 NewDateObject(cx, time).to_jsval(cx, current_value.handle_mut());
583 }
584
585 continue;
586 }
587
588 if !current_value.is_object() {
591 return Ok(EvaluationResult::Failure);
592 }
593
594 rooted!(&in(cx) let object = current_value.to_object());
595 let identifier_name =
596 CString::new(identifier).expect("Failed to convert str to CString");
597
598 let hop = has_own_property(cx, object.handle(), identifier_name.as_c_str())
600 .map_err(|_| Error::JSFailed)?;
601
602 if !hop {
604 return Ok(EvaluationResult::Failure);
605 }
606
607 get_property_jsval(
609 cx,
610 object.handle(),
611 identifier_name.as_c_str(),
612 current_value.handle_mut(),
613 )?;
614
615 if current_value.get().is_undefined() {
617 return Ok(EvaluationResult::Failure);
618 }
619 }
620
621 return_val.set(*current_value);
626 },
627 }
628 Ok(EvaluationResult::Success)
629}
630
631pub(crate) enum ExtractionResult {
634 Key(IndexedDBKeyType),
635 Invalid,
636 Failure,
637}
638
639pub(crate) fn can_inject_key_into_value(
641 cx: &mut JSContext,
642 value: HandleValue,
643 key_path: &DOMString,
644) -> Result<bool, Error> {
645 let key_path_string = key_path.str();
648 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
649
650 let Some(_) = identifiers.pop() else {
652 return Ok(false);
653 };
654
655 rooted!(&in(cx) let mut current_value = *value);
656
657 for identifier in identifiers {
659 if !current_value.is_object() {
661 return Ok(false);
662 }
663
664 rooted!(&in(cx) let current_object = current_value.to_object());
665 let identifier_name =
666 CString::new(identifier).expect("Failed to convert key path identifier to CString");
667
668 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
670 .map_err(|_| Error::JSFailed)?;
671
672 if !hop {
677 return Ok(true);
678 }
679
680 get_property_jsval(
682 cx,
683 current_object.handle(),
684 identifier_name.as_c_str(),
685 current_value.handle_mut(),
686 )?;
687 }
688
689 Ok(current_value.is_object())
691}
692
693pub(crate) fn inject_key_into_value(
695 cx: &mut JSContext,
696 value: HandleValue,
697 key: &IndexedDBKeyType,
698 key_path: &DOMString,
699) -> Result<bool, Error> {
700 let key_path_string = key_path.str();
702 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
703
704 let Some(last) = identifiers.pop() else {
706 return Ok(false);
707 };
708
709 rooted!(&in(cx) let mut current_value = *value);
713
714 for identifier in identifiers {
716 if !current_value.is_object() {
718 return Ok(false);
719 }
720
721 rooted!(&in(cx) let current_object = current_value.to_object());
722 let identifier_name =
723 CString::new(identifier).expect("Failed to convert key path identifier to CString");
724
725 let hop = has_own_property(cx, current_object.handle(), identifier_name.as_c_str())
727 .map_err(|_| Error::JSFailed)?;
728
729 if !hop {
731 rooted!(&in(cx) let o = JS_NewObject(cx, None));
733 rooted!(&in(cx) let mut o_value = UndefinedValue());
734 o.to_jsval(cx, o_value.handle_mut());
735
736 define_dictionary_property(
738 cx,
739 current_object.handle(),
740 identifier_name.as_c_str(),
741 o_value.handle(),
742 )
743 .map_err(|_| Error::JSFailed)?;
744
745 }
747
748 get_property_jsval(
750 cx,
751 current_object.handle(),
752 identifier_name.as_c_str(),
753 current_value.handle_mut(),
754 )?;
755
756 if !current_value.is_object() {
758 return Ok(false);
759 }
760 }
761
762 rooted!(&in(cx) let mut key_value = UndefinedValue());
764 key_type_to_jsval(cx, key, key_value.handle_mut());
765
766 if !current_value.is_object() {
768 return Ok(false);
769 }
770 rooted!(&in(cx) let parent_object = current_value.to_object());
771 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
772
773 define_dictionary_property(
775 cx,
776 parent_object.handle(),
777 last_name.as_c_str(),
778 key_value.handle(),
779 )
780 .map_err(|_| Error::JSFailed)?;
781
782 Ok(true)
786}
787
788pub(crate) fn extract_key(
790 cx: &mut JSContext,
791 value: HandleValue,
792 key_path: &KeyPath,
793 multi_entry: Option<bool>,
794) -> Result<ExtractionResult, Error> {
795 rooted!(&in(cx) let mut r = UndefinedValue());
799 if let EvaluationResult::Failure =
800 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
801 {
802 return Ok(ExtractionResult::Failure);
803 }
804
805 let key = match multi_entry {
809 Some(true) => {
810 unimplemented!("multiEntry keys are not yet supported");
812 },
813 _ => match convert_value_to_key(cx, r.handle(), None)? {
814 ConversionResult::Valid(key) => key,
815 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
817 },
818 };
819
820 Ok(ExtractionResult::Key(key))
822}