1use std::ffi::CString;
6use std::iter::repeat_n;
7use std::ptr;
8
9use ipc_channel::ipc::IpcSender;
10use itertools::Itertools;
11use js::conversions::jsstr_to_string;
12use js::gc::MutableHandle;
13use js::jsapi::{
14 ClippedTime, ESClass, GetArrayLength, GetBuiltinClass, IsArrayBufferObject, JS_GetStringLength,
15 JS_HasOwnPropertyById, JS_IndexToId, JS_IsArrayBufferViewObject, JS_NewObject, NewDateObject,
16 PropertyKey,
17};
18use js::jsval::{DoubleValue, UndefinedValue};
19use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasOwnProperty, JS_IsIdentifier};
20use js::rust::{HandleValue, IntoHandle, IntoMutableHandle, MutableHandleValue};
21use profile_traits::ipc;
22use profile_traits::ipc::IpcReceiver;
23use serde::{Deserialize, Serialize};
24use storage_traits::indexeddb_thread::{BackendResult, IndexedDBKeyRange, IndexedDBKeyType};
25
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::{
30 SafeToJSValConvertible, get_property_jsval, root_from_handlevalue, root_from_object,
31};
32use crate::dom::bindings::error::Error;
33use crate::dom::bindings::import::module::SafeJSContext;
34use crate::dom::bindings::root::DomRoot;
35use crate::dom::bindings::str::DOMString;
36use crate::dom::bindings::structuredclone;
37use crate::dom::bindings::utils::set_dictionary_property;
38use crate::dom::blob::Blob;
39use crate::dom::file::File;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::idbkeyrange::IDBKeyRange;
42use crate::dom::idbobjectstore::KeyPath;
43
44pub fn create_channel<T>(
45 global: DomRoot<GlobalScope>,
46) -> (IpcSender<BackendResult<T>>, IpcReceiver<BackendResult<T>>)
47where
48 T: for<'a> Deserialize<'a> + Serialize,
49{
50 ipc::channel::<BackendResult<T>>(global.time_profiler_chan().clone()).unwrap()
51}
52
53#[allow(unsafe_code)]
55pub fn key_type_to_jsval(
56 cx: SafeJSContext,
57 key: &IndexedDBKeyType,
58 mut result: MutableHandleValue,
59) {
60 match key {
61 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
62 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
63 IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result),
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);
68 },
69 IndexedDBKeyType::Array(a) => {
70 rooted_vec!(let mut values <- repeat_n(UndefinedValue(), a.len()));
71 for (key, value) in a.iter().zip(unsafe {
72 values
73 .iter_mut()
74 .map(|v| MutableHandle::from_marked_location(v))
75 }) {
76 key_type_to_jsval(cx, key, value);
77 }
78 values.safe_to_jsval(cx, result);
79 },
80 }
81}
82
83pub(crate) fn is_valid_key_path(key_path: &StrOrStringSequence) -> Result<bool, Error> {
85 #[allow(unsafe_code)]
87 let is_identifier_name = |name: &str| -> Result<bool, Error> {
88 let cx = GlobalScope::get_cx();
89 rooted!(in(*cx) let mut value = UndefinedValue());
90 name.safe_to_jsval(cx, value.handle_mut());
91 rooted!(in(*cx) let string = value.to_string());
92
93 unsafe {
94 let mut is_identifier = false;
95 if !JS_IsIdentifier(*cx, string.handle(), &mut is_identifier) {
96 return Err(Error::JSFailed);
97 }
98 Ok(is_identifier)
99 }
100 };
101
102 let is_valid = |path: &DOMString| -> Result<bool, Error> {
104 let is_empty_string = path.is_empty();
106
107 let is_identifier = is_identifier_name(&path.str())?;
110
111 let is_identifier_list = path
113 .str()
114 .split('.')
115 .map(is_identifier_name)
116 .try_collect::<bool, Vec<bool>, Error>()?
117 .iter()
118 .all(|&value| value);
119
120 Ok(is_empty_string || is_identifier || is_identifier_list)
121 };
122
123 match key_path {
124 StrOrStringSequence::StringSequence(paths) => {
125 if paths.is_empty() {
127 Ok(false)
128 } else {
129 Ok(paths
130 .iter()
131 .map(is_valid)
132 .try_collect::<bool, Vec<bool>, Error>()?
133 .iter()
134 .all(|&value| value))
135 }
136 },
137 StrOrStringSequence::String(path) => is_valid(path),
138 }
139}
140
141pub(crate) enum ConversionResult {
142 Valid(IndexedDBKeyType),
143 Invalid,
144}
145
146impl ConversionResult {
147 pub fn into_result(self) -> Result<IndexedDBKeyType, Error> {
148 match self {
149 ConversionResult::Valid(key) => Ok(key),
150 ConversionResult::Invalid => Err(Error::Data),
151 }
152 }
153}
154
155#[allow(unsafe_code)]
157pub fn convert_value_to_key(
158 cx: SafeJSContext,
159 input: HandleValue,
160 seen: Option<Vec<HandleValue>>,
161) -> Result<ConversionResult, Error> {
162 let mut seen = seen.unwrap_or_default();
164
165 if input.is_number() {
174 if input.to_number().is_nan() {
175 return Ok(ConversionResult::Invalid);
176 }
177 return Ok(ConversionResult::Valid(IndexedDBKeyType::Number(
178 input.to_number(),
179 )));
180 }
181
182 if input.is_string() {
183 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
184 let key = unsafe { jsstr_to_string(*cx, string_ptr) };
185 return Ok(ConversionResult::Valid(IndexedDBKeyType::String(key)));
186 }
187
188 if input.is_object() {
189 rooted!(in(*cx) let object = input.to_object());
190 unsafe {
191 let mut built_in_class = ESClass::Other;
192
193 if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
194 return Err(Error::JSFailed);
195 }
196
197 if let ESClass::Date = built_in_class {
198 let mut f = f64::NAN;
199 if !js::jsapi::DateGetMsecSinceEpoch(*cx, object.handle().into(), &mut f) {
200 return Err(Error::JSFailed);
201 }
202 if f.is_nan() {
203 return Err(Error::Data);
204 }
205 return Ok(ConversionResult::Valid(IndexedDBKeyType::Date(f)));
206 }
207
208 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
209 let key = structuredclone::write(cx, input, None)?;
211 return Ok(ConversionResult::Valid(IndexedDBKeyType::Binary(
212 key.serialized.clone(),
213 )));
214 }
215
216 if let ESClass::Array = built_in_class {
217 let mut len = 0;
218 if !GetArrayLength(*cx, object.handle().into_handle(), &mut len) {
219 return Err(Error::JSFailed);
220 }
221 seen.push(input);
222 let mut values = vec![];
223 for i in 0..len {
224 rooted!(in(*cx) let mut id: PropertyKey);
225 if !JS_IndexToId(*cx, i, js::jsapi::MutableHandleId::from(id.handle_mut())) {
226 return Err(Error::JSFailed);
227 }
228 let mut has_own = false;
229 if !JS_HasOwnPropertyById(
230 *cx,
231 object.handle().into_handle(),
232 id.handle().into_handle(),
233 &mut has_own,
234 ) {
235 return Err(Error::JSFailed);
236 }
237 if !has_own {
238 return Ok(ConversionResult::Invalid);
239 }
240 rooted!(in(*cx) let mut item = UndefinedValue());
241 if !js::jsapi::JS_GetPropertyById(
242 *cx,
243 object.handle().into_handle(),
244 id.handle().into_handle(),
245 item.handle_mut().into_handle_mut(),
246 ) {
247 return Err(Error::JSFailed);
248 }
249 let key = match convert_value_to_key(cx, item.handle(), Some(seen.clone()))? {
250 ConversionResult::Valid(key) => key,
251 ConversionResult::Invalid => return Ok(ConversionResult::Invalid),
252 };
253 values.push(key);
254 }
255 return Ok(ConversionResult::Valid(IndexedDBKeyType::Array(values)));
256 }
257 }
258 }
259
260 Ok(ConversionResult::Invalid)
261}
262
263#[allow(unsafe_code)]
265pub fn convert_value_to_key_range(
266 cx: SafeJSContext,
267 input: HandleValue,
268 null_disallowed: Option<bool>,
269) -> Result<IndexedDBKeyRange, Error> {
270 if input.is_object() {
272 rooted!(in(*cx) let object = input.to_object());
273 unsafe {
274 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), *cx) {
275 let obj = obj.inner().clone();
276 return Ok(obj);
277 }
278 }
279 }
280
281 if input.get().is_undefined() || input.get().is_null() {
284 if null_disallowed.is_some_and(|flag| flag) {
285 return Err(Error::Data);
286 } else {
287 return Ok(IndexedDBKeyRange {
288 lower: None,
289 upper: None,
290 lower_open: Default::default(),
291 upper_open: Default::default(),
292 });
293 }
294 }
295
296 let key = convert_value_to_key(cx, input, None)?;
299
300 let key = key.into_result()?;
302
303 Ok(IndexedDBKeyRange::only(key))
305}
306
307pub(crate) enum EvaluationResult {
310 Success,
311 Failure,
312}
313
314#[allow(unsafe_code)]
316pub(crate) fn evaluate_key_path_on_value(
317 cx: SafeJSContext,
318 value: HandleValue,
319 key_path: &KeyPath,
320 mut return_val: MutableHandleValue,
321) -> Result<EvaluationResult, Error> {
322 match key_path {
323 KeyPath::StringSequence(key_path) => {
325 rooted!(in(*cx) let mut result = unsafe { JS_NewObject(*cx, ptr::null()) });
327
328 for (i, item) in key_path.iter().enumerate() {
331 rooted!(in(*cx) let mut key = UndefinedValue());
336 if let EvaluationResult::Failure = evaluate_key_path_on_value(
337 cx,
338 value,
339 &KeyPath::String(item.clone()),
340 key.handle_mut(),
341 )? {
342 return Ok(EvaluationResult::Failure);
343 };
344
345 set_dictionary_property(cx, result.handle(), &i.to_string(), key.handle())
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,
395 object.handle(),
396 "length",
397 current_value.handle_mut(),
398 )?;
399
400 continue;
401 }
402 }
403 }
404
405 if identifier == "size" {
407 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), cx) {
408 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
410
411 continue;
412 }
413 }
414
415 if identifier == "type" {
417 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), cx) {
418 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
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().safe_to_jsval(cx, current_value.handle_mut());
430
431 continue;
432 }
433 }
434
435 if identifier == "lastModified" {
437 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), cx) {
438 file.LastModified()
440 .safe_to_jsval(cx, current_value.handle_mut());
441
442 continue;
443 }
444 }
445
446 if identifier == "lastModifiedDate" {
448 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), cx) {
449 let time = ClippedTime {
451 t: file.LastModified() as f64,
452 };
453 unsafe {
454 NewDateObject(*cx, time).safe_to_jsval(cx, current_value.handle_mut());
455 }
456
457 continue;
458 }
459 }
460
461 unsafe {
463 if !current_value.is_object() {
465 return Ok(EvaluationResult::Failure);
466 }
467
468 rooted!(in(*cx) let object = current_value.to_object());
469 let identifier_name =
470 CString::new(identifier).expect("Failed to convert str to CString");
471
472 let mut hop = false;
474 if !JS_HasOwnProperty(*cx, object.handle(), identifier_name.as_ptr(), &mut hop)
475 {
476 return Err(Error::JSFailed);
477 }
478
479 if !hop {
481 return Ok(EvaluationResult::Failure);
482 }
483
484 if !JS_GetProperty(
486 *cx,
487 object.handle(),
488 identifier_name.as_ptr(),
489 current_value.handle_mut(),
490 ) {
491 return Err(Error::JSFailed);
492 }
493
494 if current_value.get().is_undefined() {
496 return Ok(EvaluationResult::Failure);
497 }
498 }
499 }
500
501 return_val.set(*current_value);
506 },
507 }
508 Ok(EvaluationResult::Success)
509}
510
511pub(crate) enum ExtractionResult {
514 Key(IndexedDBKeyType),
515 Invalid,
516 Failure,
517}
518
519pub(crate) fn extract_key(
521 cx: SafeJSContext,
522 value: HandleValue,
523 key_path: &KeyPath,
524 multi_entry: Option<bool>,
525) -> Result<ExtractionResult, Error> {
526 rooted!(in(*cx) let mut r = UndefinedValue());
530 if let EvaluationResult::Failure =
531 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
532 {
533 return Ok(ExtractionResult::Failure);
534 }
535
536 let key = match multi_entry {
540 Some(true) => {
541 unimplemented!("multiEntry keys are not yet supported");
543 },
544 _ => match convert_value_to_key(cx, r.handle(), None)? {
545 ConversionResult::Valid(key) => key,
546 ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
548 },
549 };
550
551 Ok(ExtractionResult::Key(key))
553}