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, ESClass, IsArrayBufferObject, JS_GetStringLength, JS_IsArrayBufferViewObject,
13 PropertyKey,
14};
15use js::jsval::{DoubleValue, JSVal, UndefinedValue};
16use js::rust::wrappers2::{
17 GetArrayLength, GetBuiltinClass, IsArrayObject, JS_HasOwnPropertyById, JS_IndexToId,
18 JS_IsIdentifier, JS_NewObject, NewDateObject,
19};
20use js::rust::{HandleValue, MutableHandleValue};
21use storage_traits::indexeddb::{BackendError, IndexedDBKeyRange, IndexedDBKeyType};
22
23use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
24use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
25use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
26use crate::dom::bindings::conversions::{
27 get_property_jsval, root_from_handlevalue, root_from_object,
28};
29use crate::dom::bindings::error::Error;
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::structuredclone;
32use crate::dom::bindings::utils::{
33 define_dictionary_property, get_dictionary_property, has_own_property,
34};
35use crate::dom::blob::Blob;
36use crate::dom::file::File;
37use crate::dom::idbkeyrange::IDBKeyRange;
38use crate::dom::idbobjectstore::KeyPath;
39use crate::script_runtime::CanGc;
40
41#[expect(unsafe_code)]
43pub fn key_type_to_jsval(
44 cx: &mut JSContext,
45 key: &IndexedDBKeyType,
46 mut result: MutableHandleValue,
47) {
48 match key {
49 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
50 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
51 IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result),
52 IndexedDBKeyType::Date(d) => {
53 let time = js::jsapi::ClippedTime { t: *d };
54 let date = unsafe { js::rust::wrappers2::NewDateObject(cx, time) };
55 date.safe_to_jsval(cx, result);
56 },
57 IndexedDBKeyType::Array(a) => {
58 rooted!(&in(cx) let mut values = vec![JSVal::default(); a.len()]);
59 for (i, key) in a.iter().enumerate() {
60 key_type_to_jsval(cx, key, values.handle_mut_at(i));
61 }
62 values.safe_to_jsval(cx, result);
63 },
64 }
65}
66
67pub(crate) fn is_valid_key_path(
69 cx: &mut JSContext,
70 key_path: &StrOrStringSequence,
71) -> Result<bool, Error> {
72 #[expect(unsafe_code)]
74 let is_identifier_name = |cx: &mut JSContext, name: &str| -> Result<bool, Error> {
75 rooted!(&in(cx) let mut value = UndefinedValue());
76 name.safe_to_jsval(cx, value.handle_mut());
77 rooted!(&in(cx) let string = value.to_string());
78
79 unsafe {
80 let mut is_identifier = false;
81 if !JS_IsIdentifier(cx, string.handle(), &mut is_identifier) {
82 return Err(Error::JSFailed);
83 }
84 Ok(is_identifier)
85 }
86 };
87
88 let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
90 let is_empty_string = path.is_empty();
92
93 let is_identifier = is_identifier_name(cx, &path.str())?;
96
97 let is_identifier_list = path
99 .str()
100 .split('.')
101 .map(|s| is_identifier_name(cx, s))
102 .try_collect::<bool, Vec<bool>, Error>()?
103 .iter()
104 .all(|&value| value);
105
106 Ok(is_empty_string || is_identifier || is_identifier_list)
107 };
108
109 match key_path {
110 StrOrStringSequence::StringSequence(paths) => {
111 if paths.is_empty() {
113 Ok(false)
114 } else {
115 Ok(paths
116 .iter()
117 .map(|s| is_valid(cx, s))
118 .try_collect::<bool, Vec<bool>, Error>()?
119 .iter()
120 .all(|&value| value))
121 }
122 },
123 StrOrStringSequence::String(path) => is_valid(cx, path),
124 }
125}
126
127pub(crate) enum ConversionResult {
128 Valid(IndexedDBKeyType),
129 Invalid,
130}
131
132impl ConversionResult {
133 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
134 match self {
135 ConversionResult::Valid(key) => Ok(key),
136 ConversionResult::Invalid => Err(Error::Data(None)),
137 }
138 }
139}
140
141#[expect(unsafe_code)]
143pub fn convert_value_to_key(
144 cx: &mut JSContext,
145 input: HandleValue,
146 seen: Option<Vec<HandleValue>>,
147) -> Result<ConversionResult, Error> {
148 let mut seen = seen.unwrap_or_default();
150
151 if input.is_number() {
160 if input.to_number().is_nan() {
161 return Ok(ConversionResult::Invalid);
162 }
163 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
164 input.to_number(),
165 )));
166 }
167
168 if input.is_string() {
169 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
170 let key = unsafe { jsstr_to_string(cx.raw_cx(), string_ptr) };
171 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
172 }
173
174 if input.is_object() {
175 rooted!(&in(cx) let object = input.to_object());
176 unsafe {
177 let mut built_in_class = ESClass::Other;
178
179 if !GetBuiltinClass(cx, object.handle(), &mut built_in_class) {
180 return Err(Error::JSFailed);
181 }
182
183 if let ESClass::Date = built_in_class {
184 let mut f = f64::NAN;
185 if !js::rust::wrappers2::DateGetMsecSinceEpoch(cx, object.handle(), &mut f) {
186 return Err(Error::JSFailed);
187 }
188 if f.is_nan() {
189 return Err(Error::Data(None));
190 }
191 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(f)));
192 }
193
194 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
195 let key = structuredclone::write(cx.into(), input, None)?;
197 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(
198 key.serialized,
199 )));
200 }
201
202 if let ESClass::Array = built_in_class {
203 let mut len = 0;
204 if !GetArrayLength(cx, object.handle(), &mut len) {
205 return Err(Error::JSFailed);
206 }
207 seen.push(input);
208 let mut values = vec![];
209 for i in 0..len {
210 rooted!(&in(cx) let mut id: PropertyKey);
211 if !JS_IndexToId(cx, i, id.handle_mut()) {
212 return Err(Error::JSFailed);
213 }
214 let mut has_own = false;
215 if !JS_HasOwnPropertyById(cx, object.handle(), id.handle(), &mut has_own) {
216 return Err(Error::JSFailed);
217 }
218 if !has_own {
219 return Ok(ConversionResult::Invalid);
220 }
221 rooted!(&in(cx) let mut item = UndefinedValue());
222 if !js::rust::wrappers2::JS_GetPropertyById(
223 cx,
224 object.handle(),
225 id.handle(),
226 item.handle_mut(),
227 ) {
228 return Err(Error::JSFailed);
229 }
230 let key = match convert_value_to_key(cx, item.handle(), Some(seen.clone()))? {
231 ConversionResult::Valid(key) => key,
232 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
233 };
234 values.push(key);
235 }
236 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(values)));
237 }
238 }
239 }
240
241 Ok(ConversionResult::Invalid)
242}
243
244#[expect(unsafe_code)]
246pub fn convert_value_to_key_range(
247 cx: &mut JSContext,
248 input: HandleValue,
249 null_disallowed: Option<bool>,
250) -> Result<IndexedDBKeyRange, Error> {
251 if input.is_object() {
253 rooted!(&in(cx) let object = input.to_object());
254 unsafe {
255 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), cx.raw_cx()) {
256 let obj = obj.inner().clone();
257 return Ok(obj);
258 }
259 }
260 }
261
262 if input.get().is_undefined() || input.get().is_null() {
265 if null_disallowed.is_some_and(|flag| flag) {
266 return Err(Error::Data(None));
267 } else {
268 return Ok(IndexedDBKeyRange {
269 lower: None,
270 upper: None,
271 lower_open: Default::default(),
272 upper_open: Default::default(),
273 });
274 }
275 }
276
277 let key = convert_value_to_key(cx, input, None)?;
280
281 let key = key.into_result()?;
283
284 Ok(IndexedDBKeyRange::only(key))
286}
287
288pub(crate) fn map_backend_error_to_dom_error(error: BackendError) -> Error {
289 match error {
290 BackendError::QuotaExceeded => Error::QuotaExceeded {
291 quota: None,
292 requested: None,
293 },
294 BackendError::DbErr(details) => {
295 Error::Operation(Some(format!("IndexedDB open failed: {details}")))
296 },
297 other => Error::Operation(Some(format!("IndexedDB open failed: {other:?}"))),
298 }
299}
300
301pub(crate) enum EvaluationResult {
304 Success,
305 Failure,
306}
307
308#[expect(unsafe_code)]
310pub(crate) fn evaluate_key_path_on_value(
311 cx: &mut JSContext,
312 value: HandleValue,
313 key_path: &KeyPath,
314 mut return_val: MutableHandleValue,
315) -> Result<EvaluationResult, Error> {
316 match key_path {
317 KeyPath::StringSequence(key_path) => {
319 rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
321
322 for (i, item) in key_path.iter().enumerate() {
325 rooted!(&in(cx) let mut key = UndefinedValue());
330 if let EvaluationResult::Failure = evaluate_key_path_on_value(
331 cx,
332 value,
333 &KeyPath::String(item.clone()),
334 key.handle_mut(),
335 )? {
336 return Ok(EvaluationResult::Failure);
337 };
338
339 let i_cstr = std::ffi::CString::new(i.to_string()).unwrap();
343 define_dictionary_property(
344 cx.into(),
345 result.handle(),
346 i_cstr.as_c_str(),
347 key.handle(),
348 )
349 .map_err(|_| Error::JSFailed)?;
350
351 }
354
355 result.safe_to_jsval(cx, return_val);
357 },
358 KeyPath::String(key_path) => {
359 if key_path.is_empty() {
361 return_val.set(*value);
362 return Ok(EvaluationResult::Success);
363 }
364
365 rooted!(&in(cx) let mut current_value = *value);
367
368 for identifier in key_path.str().split('.') {
372 if identifier == "length" && current_value.is_string() {
374 rooted!(&in(cx) let string_value = current_value.to_string());
376 unsafe {
377 let string_length = JS_GetStringLength(*string_value) as u64;
378 string_length.safe_to_jsval(cx, current_value.handle_mut());
379 }
380 continue;
381 }
382
383 if identifier == "length" {
385 unsafe {
386 let mut is_array = false;
387 if !IsArrayObject(cx, current_value.handle(), &mut is_array) {
388 return Err(Error::JSFailed);
389 }
390 if is_array {
391 rooted!(&in(cx) let object = current_value.to_object());
393 get_property_jsval(
394 cx.into(),
395 object.handle(),
396 c"length",
397 current_value.handle_mut(),
398 )?;
399
400 continue;
401 }
402 }
403 }
404
405 if identifier == "size" {
407 if let Ok(blob) =
408 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
409 {
410 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
412
413 continue;
414 }
415 }
416
417 if identifier == "type" {
419 if let Ok(blob) =
420 root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
421 {
422 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
424
425 continue;
426 }
427 }
428
429 if identifier == "name" {
431 if let Ok(file) =
432 root_from_handlevalue::<File>(current_value.handle(), cx.into())
433 {
434 file.name().safe_to_jsval(cx, current_value.handle_mut());
436
437 continue;
438 }
439 }
440
441 if identifier == "lastModified" {
443 if let Ok(file) =
444 root_from_handlevalue::<File>(current_value.handle(), cx.into())
445 {
446 file.LastModified()
448 .safe_to_jsval(cx, current_value.handle_mut());
449
450 continue;
451 }
452 }
453
454 if identifier == "lastModifiedDate" {
456 if let Ok(file) =
457 root_from_handlevalue::<File>(current_value.handle(), cx.into())
458 {
459 let time = ClippedTime {
461 t: file.LastModified() as f64,
462 };
463 unsafe {
464 NewDateObject(cx, time).safe_to_jsval(cx, current_value.handle_mut());
465 }
466
467 continue;
468 }
469 }
470
471 unsafe {
473 if !current_value.is_object() {
475 return Ok(EvaluationResult::Failure);
476 }
477
478 rooted!(&in(cx) let object = current_value.to_object());
479 let identifier_name =
480 CString::new(identifier).expect("Failed to convert str to CString");
481
482 let hop =
484 has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
485 .map_err(|_| Error::JSFailed)?;
486
487 if !hop {
489 return Ok(EvaluationResult::Failure);
490 }
491
492 match get_dictionary_property(
494 cx.raw_cx(),
495 object.handle(),
496 identifier_name.as_c_str(),
497 current_value.handle_mut(),
498 CanGc::note(),
499 ) {
500 Ok(true) => {},
501 Ok(false) => return Ok(EvaluationResult::Failure),
502 Err(()) => return Err(Error::JSFailed),
503 }
504
505 if current_value.get().is_undefined() {
507 return Ok(EvaluationResult::Failure);
508 }
509 }
510 }
511
512 return_val.set(*current_value);
517 },
518 }
519 Ok(EvaluationResult::Success)
520}
521
522pub(crate) enum ExtractionResult {
525 Key(IndexedDBKeyType),
526 Invalid,
527 Failure,
528}
529
530#[expect(unsafe_code)]
532pub(crate) fn can_inject_key_into_value(
533 cx: &mut JSContext,
534 value: HandleValue,
535 key_path: &DOMString,
536) -> Result<bool, Error> {
537 let key_path_string = key_path.str();
540 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
541
542 let Some(_) = identifiers.pop() else {
544 return Ok(false);
545 };
546
547 rooted!(&in(cx) let mut current_value = *value);
548
549 for identifier in identifiers {
551 if !current_value.is_object() {
553 return Ok(false);
554 }
555
556 rooted!(&in(cx) let current_object = current_value.to_object());
557 let identifier_name =
558 CString::new(identifier).expect("Failed to convert key path identifier to CString");
559
560 let hop = has_own_property(
562 cx.into(),
563 current_object.handle(),
564 identifier_name.as_c_str(),
565 )
566 .map_err(|_| Error::JSFailed)?;
567
568 if !hop {
573 return Ok(true);
574 }
575
576 match unsafe {
578 get_dictionary_property(
579 cx.raw_cx(),
580 current_object.handle(),
581 identifier_name.as_c_str(),
582 current_value.handle_mut(),
583 CanGc::note(),
584 )
585 } {
586 Ok(true) => {},
587 Ok(false) => return Ok(false),
588 Err(()) => return Err(Error::JSFailed),
589 }
590 }
591
592 Ok(current_value.is_object())
594}
595
596#[expect(unsafe_code)]
598pub(crate) fn inject_key_into_value(
599 cx: &mut JSContext,
600 value: HandleValue,
601 key: &IndexedDBKeyType,
602 key_path: &DOMString,
603) -> Result<bool, Error> {
604 let key_path_string = key_path.str();
606 let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
607
608 let Some(last) = identifiers.pop() else {
610 return Ok(false);
611 };
612
613 rooted!(&in(cx) let mut current_value = *value);
617
618 for identifier in identifiers {
620 if !current_value.is_object() {
622 return Ok(false);
623 }
624
625 rooted!(&in(cx) let current_object = current_value.to_object());
626 let identifier_name =
627 CString::new(identifier).expect("Failed to convert key path identifier to CString");
628
629 let hop = has_own_property(
631 cx.into(),
632 current_object.handle(),
633 identifier_name.as_c_str(),
634 )
635 .map_err(|_| Error::JSFailed)?;
636
637 if !hop {
639 rooted!(&in(cx) let o = unsafe { JS_NewObject(cx, ptr::null()) });
641 rooted!(&in(cx) let mut o_value = UndefinedValue());
642 o.safe_to_jsval(cx, o_value.handle_mut());
643
644 define_dictionary_property(
646 cx.into(),
647 current_object.handle(),
648 identifier_name.as_c_str(),
649 o_value.handle(),
650 )
651 .map_err(|_| Error::JSFailed)?;
652
653 }
655
656 match unsafe {
658 get_dictionary_property(
659 cx.raw_cx(),
660 current_object.handle(),
661 identifier_name.as_c_str(),
662 current_value.handle_mut(),
663 CanGc::note(),
664 )
665 } {
666 Ok(true) => {},
667 Ok(false) => return Ok(false),
668 Err(()) => return Err(Error::JSFailed),
669 }
670
671 if !current_value.is_object() {
673 return Ok(false);
674 }
675 }
676
677 rooted!(&in(cx) let mut key_value = UndefinedValue());
679 key_type_to_jsval(cx, key, key_value.handle_mut());
680
681 if !current_value.is_object() {
683 return Ok(false);
684 }
685 rooted!(&in(cx) let parent_object = current_value.to_object());
686 let last_name = CString::new(last).expect("Failed to convert final key path identifier");
687
688 define_dictionary_property(
690 cx.into(),
691 parent_object.handle(),
692 last_name.as_c_str(),
693 key_value.handle(),
694 )
695 .map_err(|_| Error::JSFailed)?;
696
697 Ok(true)
701}
702
703pub(crate) fn extract_key(
705 cx: &mut JSContext,
706 value: HandleValue,
707 key_path: &KeyPath,
708 multi_entry: Option<bool>,
709) -> Result<ExtractionResult, Error> {
710 rooted!(&in(cx) let mut r = UndefinedValue());
714 if let EvaluationResult::Failure =
715 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
716 {
717 return Ok(ExtractionResult::Failure);
718 }
719
720 let key = match multi_entry {
724 Some(true) => {
725 unimplemented!("multiEntry keys are not yet supported");
727 },
728 _ => match convert_value_to_key(cx, r.handle(), None)? {
729 ConversionResult::Valid(key) => key,
730 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
732 },
733 };
734
735 Ok(ExtractionResult::Key(key))
737}