1use std::ffi::CString;
6use std::ptr;
7
8use ipc_channel::ipc::IpcSender;
9use itertools::Itertools;
10use js::conversions::jsstr_to_string;
11use js::jsapi::{
12 ClippedTime, ESClass, GetArrayLength, GetBuiltinClass, Heap, IsArrayBufferObject,
13 JS_GetStringLength, JS_HasOwnPropertyById, JS_IndexToId, JS_IsArrayBufferViewObject,
14 JS_NewObject, NewDateObject, PropertyKey,
15};
16use js::jsval::{DoubleValue, JSVal, UndefinedValue};
17use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasOwnProperty, JS_IsIdentifier};
18use js::rust::{HandleValue, IntoHandle, IntoMutableHandle, MutableHandleValue};
19use profile_traits::ipc;
20use profile_traits::ipc::IpcReceiver;
21use script_bindings::script_runtime::CanGc;
22use serde::{Deserialize, Serialize};
23use storage_traits::indexeddb_thread::{BackendResult, 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 SafeToJSValConvertible, get_property_jsval, root_from_handlevalue, root_from_object,
30};
31use crate::dom::bindings::error::Error;
32use crate::dom::bindings::import::module::SafeJSContext;
33use crate::dom::bindings::root::DomRoot;
34use crate::dom::bindings::str::DOMString;
35use crate::dom::bindings::structuredclone;
36use crate::dom::bindings::utils::set_dictionary_property;
37use crate::dom::blob::Blob;
38use crate::dom::file::File;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::indexeddb::idbkeyrange::IDBKeyRange;
41use crate::dom::indexeddb::idbobjectstore::KeyPath;
42
43pub fn create_channel<T>(
44 global: DomRoot<GlobalScope>,
45) -> (IpcSender<BackendResult<T>>, IpcReceiver<BackendResult<T>>)
46where
47 T: for<'a> Deserialize<'a> + Serialize,
48{
49 ipc::channel::<BackendResult<T>>(global.time_profiler_chan().clone()).unwrap()
50}
51
52#[allow(unsafe_code)]
54pub fn key_type_to_jsval(
55 cx: SafeJSContext,
56 key: &IndexedDBKeyType,
57 mut result: MutableHandleValue,
58 can_gc: CanGc,
59) {
60 match key {
61 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
62 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result, can_gc),
63 IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result, can_gc),
64 IndexedDBKeyType::Date(d) => {
65 let time = js::jsapi::ClippedTime { t: *d };
66 let date = unsafe { js::jsapi::NewDateObject(*cx, time) };
67 date.safe_to_jsval(cx, result, can_gc);
68 },
69 IndexedDBKeyType::Array(a) => {
70 rooted_vec!(let mut values);
71 for key in a {
72 rooted!(in(*cx) let mut value: JSVal);
73 key_type_to_jsval(cx, key, value.handle_mut(), can_gc);
74 values.push(Heap::boxed(value.get()));
75 }
76 values.safe_to_jsval(cx, result, can_gc);
77 },
78 }
79}
80
81pub(crate) fn is_valid_key_path(key_path: &StrOrStringSequence) -> Result<bool, Error> {
83 #[allow(unsafe_code)]
85 let is_identifier_name = |name: &str| -> Result<bool, Error> {
86 let cx = GlobalScope::get_cx();
87 rooted!(in(*cx) let mut value = UndefinedValue());
88 name.safe_to_jsval(cx, value.handle_mut(), CanGc::note());
89 rooted!(in(*cx) let string = value.to_string());
90
91 unsafe {
92 let mut is_identifier = false;
93 if !JS_IsIdentifier(*cx, string.handle(), &mut is_identifier) {
94 return Err(Error::JSFailed);
95 }
96 Ok(is_identifier)
97 }
98 };
99
100 let is_valid = |path: &DOMString| -> Result<bool, Error> {
102 let is_empty_string = path.is_empty();
104
105 let is_identifier = is_identifier_name(&path.str())?;
108
109 let is_identifier_list = path
111 .str()
112 .split('.')
113 .map(is_identifier_name)
114 .try_collect::<bool, Vec<bool>, Error>()?
115 .iter()
116 .all(|&value| value);
117
118 Ok(is_empty_string || is_identifier || is_identifier_list)
119 };
120
121 match key_path {
122 StrOrStringSequence::StringSequence(paths) => {
123 if paths.is_empty() {
125 Ok(false)
126 } else {
127 Ok(paths
128 .iter()
129 .map(is_valid)
130 .try_collect::<bool, Vec<bool>, Error>()?
131 .iter()
132 .all(|&value| value))
133 }
134 },
135 StrOrStringSequence::String(path) => is_valid(path),
136 }
137}
138
139pub(crate) enum ConversionResult {
140 Valid(IndexedDBKeyType),
141 Invalid,
142}
143
144impl ConversionResult {
145 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
146 match self {
147 ConversionResult::Valid(key) => Ok(key),
148 ConversionResult::Invalid => Err(Error::Data),
149 }
150 }
151}
152
153#[allow(unsafe_code)]
155pub fn convert_value_to_key(
156 cx: SafeJSContext,
157 input: HandleValue,
158 seen: Option<Vec<HandleValue>>,
159) -> Result<ConversionResult, Error> {
160 let mut seen = seen.unwrap_or_default();
162
163 if input.is_number() {
172 if input.to_number().is_nan() {
173 return Ok(ConversionResult::Invalid);
174 }
175 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
176 input.to_number(),
177 )));
178 }
179
180 if input.is_string() {
181 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
182 let key = unsafe { jsstr_to_string(*cx, string_ptr) };
183 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
184 }
185
186 if input.is_object() {
187 rooted!(in(*cx) let object = input.to_object());
188 unsafe {
189 let mut built_in_class = ESClass::Other;
190
191 if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
192 return Err(Error::JSFailed);
193 }
194
195 if let ESClass::Date = built_in_class {
196 let mut f = f64::NAN;
197 if !js::jsapi::DateGetMsecSinceEpoch(*cx, object.handle().into(), &mut f) {
198 return Err(Error::JSFailed);
199 }
200 if f.is_nan() {
201 return Err(Error::Data);
202 }
203 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(f)));
204 }
205
206 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
207 let key = structuredclone::write(cx, input, None)?;
209 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(
210 key.serialized.clone(),
211 )));
212 }
213
214 if let ESClass::Array = built_in_class {
215 let mut len = 0;
216 if !GetArrayLength(*cx, object.handle().into_handle(), &mut len) {
217 return Err(Error::JSFailed);
218 }
219 seen.push(input);
220 let mut values = vec![];
221 for i in 0..len {
222 rooted!(in(*cx) let mut id: PropertyKey);
223 if !JS_IndexToId(*cx, i, js::jsapi::MutableHandleId::from(id.handle_mut())) {
224 return Err(Error::JSFailed);
225 }
226 let mut has_own = false;
227 if !JS_HasOwnPropertyById(
228 *cx,
229 object.handle().into_handle(),
230 id.handle().into_handle(),
231 &mut has_own,
232 ) {
233 return Err(Error::JSFailed);
234 }
235 if !has_own {
236 return Ok(ConversionResult::Invalid);
237 }
238 rooted!(in(*cx) let mut item = UndefinedValue());
239 if !js::jsapi::JS_GetPropertyById(
240 *cx,
241 object.handle().into_handle(),
242 id.handle().into_handle(),
243 item.handle_mut().into_handle_mut(),
244 ) {
245 return Err(Error::JSFailed);
246 }
247 let key = match convert_value_to_key(cx, item.handle(), Some(seen.clone()))? {
248 ConversionResult::Valid(key) => key,
249 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
250 };
251 values.push(key);
252 }
253 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(values)));
254 }
255 }
256 }
257
258 Ok(ConversionResult::Invalid)
259}
260
261#[allow(unsafe_code)]
263pub fn convert_value_to_key_range(
264 cx: SafeJSContext,
265 input: HandleValue,
266 null_disallowed: Option<bool>,
267) -> Result<IndexedDBKeyRange, Error> {
268 if input.is_object() {
270 rooted!(in(*cx) let object = input.to_object());
271 unsafe {
272 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), *cx) {
273 let obj = obj.inner().clone();
274 return Ok(obj);
275 }
276 }
277 }
278
279 if input.get().is_undefined() || input.get().is_null() {
282 if null_disallowed.is_some_and(|flag| flag) {
283 return Err(Error::Data);
284 } else {
285 return Ok(IndexedDBKeyRange {
286 lower: None,
287 upper: None,
288 lower_open: Default::default(),
289 upper_open: Default::default(),
290 });
291 }
292 }
293
294 let key = convert_value_to_key(cx, input, None)?;
297
298 let key = key.into_result()?;
300
301 Ok(IndexedDBKeyRange::only(key))
303}
304
305pub(crate) enum EvaluationResult {
308 Success,
309 Failure,
310}
311
312#[allow(unsafe_code)]
314pub(crate) fn evaluate_key_path_on_value(
315 cx: SafeJSContext,
316 value: HandleValue,
317 key_path: &KeyPath,
318 mut return_val: MutableHandleValue,
319) -> Result<EvaluationResult, Error> {
320 match key_path {
321 KeyPath::StringSequence(key_path) => {
323 rooted!(in(*cx) let mut result = unsafe { JS_NewObject(*cx, ptr::null()) });
325
326 for (i, item) in key_path.iter().enumerate() {
329 rooted!(in(*cx) let mut key = UndefinedValue());
334 if let EvaluationResult::Failure = evaluate_key_path_on_value(
335 cx,
336 value,
337 &KeyPath::String(item.clone()),
338 key.handle_mut(),
339 )? {
340 return Ok(EvaluationResult::Failure);
341 };
342
343 set_dictionary_property(cx, result.handle(), &i.to_string(), key.handle())
347 .map_err(|_| Error::JSFailed)?;
348
349 }
352
353 result.safe_to_jsval(cx, return_val, CanGc::note());
355 },
356 KeyPath::String(key_path) => {
357 if key_path.is_empty() {
359 return_val.set(*value);
360 return Ok(EvaluationResult::Success);
361 }
362
363 rooted!(in(*cx) let mut current_value = *value);
365
366 for identifier in key_path.str().split('.') {
370 if identifier == "length" && current_value.is_string() {
372 rooted!(in(*cx) let string_value = current_value.to_string());
374 unsafe {
375 let string_length = JS_GetStringLength(*string_value) as u64;
376 string_length.safe_to_jsval(cx, current_value.handle_mut(), CanGc::note());
377 }
378 continue;
379 }
380
381 if identifier == "length" {
383 unsafe {
384 let mut is_array = false;
385 if !IsArrayObject(*cx, current_value.handle(), &mut is_array) {
386 return Err(Error::JSFailed);
387 }
388 if is_array {
389 rooted!(in(*cx) let object = current_value.to_object());
391 get_property_jsval(
392 cx,
393 object.handle(),
394 "length",
395 current_value.handle_mut(),
396 )?;
397
398 continue;
399 }
400 }
401 }
402
403 if identifier == "size" {
405 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), cx) {
406 blob.Size()
408 .safe_to_jsval(cx, current_value.handle_mut(), CanGc::note());
409
410 continue;
411 }
412 }
413
414 if identifier == "type" {
416 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), cx) {
417 blob.Type()
419 .safe_to_jsval(cx, current_value.handle_mut(), CanGc::note());
420
421 continue;
422 }
423 }
424
425 if identifier == "name" {
427 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), cx) {
428 file.name()
430 .safe_to_jsval(cx, current_value.handle_mut(), CanGc::note());
431
432 continue;
433 }
434 }
435
436 if identifier == "lastModified" {
438 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), cx) {
439 file.LastModified().safe_to_jsval(
441 cx,
442 current_value.handle_mut(),
443 CanGc::note(),
444 );
445
446 continue;
447 }
448 }
449
450 if identifier == "lastModifiedDate" {
452 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), cx) {
453 let time = ClippedTime {
455 t: file.LastModified() as f64,
456 };
457 unsafe {
458 NewDateObject(*cx, time).safe_to_jsval(
459 cx,
460 current_value.handle_mut(),
461 CanGc::note(),
462 );
463 }
464
465 continue;
466 }
467 }
468
469 unsafe {
471 if !current_value.is_object() {
473 return Ok(EvaluationResult::Failure);
474 }
475
476 rooted!(in(*cx) let object = current_value.to_object());
477 let identifier_name =
478 CString::new(identifier).expect("Failed to convert str to CString");
479
480 let mut hop = false;
482 if !JS_HasOwnProperty(*cx, object.handle(), identifier_name.as_ptr(), &mut hop)
483 {
484 return Err(Error::JSFailed);
485 }
486
487 if !hop {
489 return Ok(EvaluationResult::Failure);
490 }
491
492 if !JS_GetProperty(
494 *cx,
495 object.handle(),
496 identifier_name.as_ptr(),
497 current_value.handle_mut(),
498 ) {
499 return Err(Error::JSFailed);
500 }
501
502 if current_value.get().is_undefined() {
504 return Ok(EvaluationResult::Failure);
505 }
506 }
507 }
508
509 return_val.set(*current_value);
514 },
515 }
516 Ok(EvaluationResult::Success)
517}
518
519pub(crate) enum ExtractionResult {
522 Key(IndexedDBKeyType),
523 Invalid,
524 Failure,
525}
526
527pub(crate) fn extract_key(
529 cx: SafeJSContext,
530 value: HandleValue,
531 key_path: &KeyPath,
532 multi_entry: Option<bool>,
533) -> Result<ExtractionResult, Error> {
534 rooted!(in(*cx) let mut r = UndefinedValue());
538 if let EvaluationResult::Failure =
539 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
540 {
541 return Ok(ExtractionResult::Failure);
542 }
543
544 let key = match multi_entry {
548 Some(true) => {
549 unimplemented!("multiEntry keys are not yet supported");
551 },
552 _ => match convert_value_to_key(cx, r.handle(), None)? {
553 ConversionResult::Valid(key) => key,
554 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
556 },
557 };
558
559 Ok(ExtractionResult::Key(key))
561}