1use std::ffi::CString;
6use std::iter::repeat_n;
7use std::ptr;
8
9use ipc_channel::ipc::IpcSender;
10use js::conversions::jsstr_to_string;
11use js::gc::MutableHandle;
12use js::jsapi::{
13 ClippedTime, ESClass, GetBuiltinClass, IsArrayBufferObject, JS_GetStringLength,
14 JS_IsArrayBufferViewObject, JS_NewObject, NewDateObject,
15};
16use js::jsval::{DoubleValue, UndefinedValue};
17use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasOwnProperty};
18use js::rust::{HandleValue, MutableHandleValue};
19use net_traits::indexeddb_thread::{BackendResult, IndexedDBKeyRange, IndexedDBKeyType};
20use profile_traits::ipc;
21use profile_traits::ipc::IpcReceiver;
22use serde::{Deserialize, Serialize};
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 SafeToJSValConvertible, get_property_jsval, root_from_handlevalue, root_from_object,
29};
30use crate::dom::bindings::error::Error;
31use crate::dom::bindings::import::module::SafeJSContext;
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::bindings::str::DOMString;
34use crate::dom::bindings::structuredclone;
35use crate::dom::bindings::utils::set_dictionary_property;
36use crate::dom::blob::Blob;
37use crate::dom::file::File;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::idbkeyrange::IDBKeyRange;
40use crate::dom::idbobjectstore::KeyPath;
41
42pub fn create_channel<T>(
43 global: DomRoot<GlobalScope>,
44) -> (IpcSender<BackendResult<T>>, IpcReceiver<BackendResult<T>>)
45where
46 T: for<'a> Deserialize<'a> + Serialize,
47{
48 ipc::channel::<BackendResult<T>>(global.time_profiler_chan().clone()).unwrap()
49}
50
51#[allow(unsafe_code)]
53pub fn key_type_to_jsval(
54 cx: SafeJSContext,
55 key: &IndexedDBKeyType,
56 mut result: MutableHandleValue,
57) {
58 match key {
59 IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
60 IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
61 IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result),
62 IndexedDBKeyType::Date(d) => {
63 let time = js::jsapi::ClippedTime { t: *d };
64 let date = unsafe { js::jsapi::NewDateObject(*cx, time) };
65 date.safe_to_jsval(cx, result);
66 },
67 IndexedDBKeyType::Array(a) => {
68 rooted_vec!(let mut values <- repeat_n(UndefinedValue(), a.len()));
69 for (key, value) in a.iter().zip(unsafe {
70 values
71 .iter_mut()
72 .map(|v| MutableHandle::from_marked_location(v))
73 }) {
74 key_type_to_jsval(cx, key, value);
75 }
76 values.safe_to_jsval(cx, result);
77 },
78 }
79}
80
81pub fn is_valid_key_path(key_path: &StrOrStringSequence) -> bool {
83 fn is_identifier(_s: &str) -> bool {
84 true
86 }
87
88 let is_valid = |path: &DOMString| {
89 path.is_empty() || is_identifier(path) || path.split(".").all(is_identifier)
90 };
91
92 match key_path {
93 StrOrStringSequence::StringSequence(paths) => {
94 if paths.is_empty() {
95 return false;
96 }
97
98 paths.iter().all(is_valid)
99 },
100 StrOrStringSequence::String(path) => is_valid(path),
101 }
102}
103
104#[allow(unsafe_code)]
106pub fn convert_value_to_key(
107 cx: SafeJSContext,
108 input: HandleValue,
109 seen: Option<Vec<HandleValue>>,
110) -> Result<IndexedDBKeyType, Error> {
111 let _seen = seen.unwrap_or_default();
113
114 if input.is_number() {
123 if input.to_number().is_nan() {
124 return Err(Error::Data);
125 }
126 return Ok(IndexedDBKeyType::Number(input.to_number()));
127 }
128
129 if input.is_string() {
130 let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
131 let key = unsafe { jsstr_to_string(*cx, string_ptr) };
132 return Ok(IndexedDBKeyType::String(key));
133 }
134
135 if input.is_object() {
136 rooted!(in(*cx) let object = input.to_object());
137 unsafe {
138 let mut built_in_class = ESClass::Other;
139
140 if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
141 return Err(Error::Data);
142 }
143
144 if let ESClass::Date = built_in_class {
145 let mut f = f64::NAN;
146 if !js::jsapi::DateGetMsecSinceEpoch(*cx, object.handle().into(), &mut f) {
147 return Err(Error::Data);
148 }
149 if f.is_nan() {
150 return Err(Error::Data);
151 }
152 return Ok(IndexedDBKeyType::Date(f));
153 }
154
155 if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
156 let key = structuredclone::write(cx, input, None)?;
158 return Ok(IndexedDBKeyType::Binary(key.serialized.clone()));
159 }
160
161 if let ESClass::Array = built_in_class {
162 error!("Arrays as keys is currently unsupported");
164 return Err(Error::NotSupported);
165 }
166 }
167 }
168
169 Err(Error::Data)
170}
171
172#[allow(unsafe_code)]
174pub fn convert_value_to_key_range(
175 cx: SafeJSContext,
176 input: HandleValue,
177 null_disallowed: Option<bool>,
178) -> Result<IndexedDBKeyRange, Error> {
179 let null_disallowed = null_disallowed.unwrap_or(false);
180 if input.is_object() {
182 rooted!(in(*cx) let object = input.to_object());
183 unsafe {
184 if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), *cx) {
185 let obj = obj.inner().clone();
186 return Ok(obj);
187 }
188 }
189 }
190 if (input.get().is_undefined() || input.get().is_null()) && null_disallowed {
192 return Err(Error::Data);
193 }
194 let key = convert_value_to_key(cx, input, None)?;
195 Ok(IndexedDBKeyRange::only(key))
196}
197
198pub(crate) enum EvaluationResult {
201 Success,
202 Failure,
203}
204
205#[allow(unsafe_code)]
207pub(crate) fn evaluate_key_path_on_value(
208 cx: SafeJSContext,
209 value: HandleValue,
210 key_path: &KeyPath,
211 mut return_val: MutableHandleValue,
212) -> Result<EvaluationResult, Error> {
213 match key_path {
214 KeyPath::StringSequence(key_path) => {
216 rooted!(in(*cx) let mut result = unsafe { JS_NewObject(*cx, ptr::null()) });
218
219 for (i, item) in key_path.iter().enumerate() {
222 rooted!(in(*cx) let mut key = UndefinedValue());
227 if let EvaluationResult::Failure = evaluate_key_path_on_value(
228 cx,
229 value,
230 &KeyPath::String(item.clone()),
231 key.handle_mut(),
232 )? {
233 return Ok(EvaluationResult::Failure);
234 };
235
236 unsafe {
240 set_dictionary_property(*cx, result.handle(), &i.to_string(), key.handle())
241 .map_err(|_| Error::JSFailed)?;
242 }
243
244 }
247
248 result.safe_to_jsval(cx, return_val);
250 },
251 KeyPath::String(key_path) => {
252 if key_path.is_empty() {
254 return_val.set(*value);
255 return Ok(EvaluationResult::Success);
256 }
257
258 rooted!(in(*cx) let mut current_value = *value);
260
261 for identifier in key_path.split('.') {
265 if identifier == "length" && current_value.is_string() {
267 rooted!(in(*cx) let string_value = current_value.to_string());
269 unsafe {
270 let string_length = JS_GetStringLength(*string_value) as u64;
271 string_length.safe_to_jsval(cx, current_value.handle_mut());
272 }
273 continue;
274 }
275
276 if identifier == "length" {
278 unsafe {
279 let mut is_array = false;
280 if !IsArrayObject(*cx, current_value.handle(), &mut is_array) {
281 return Err(Error::JSFailed);
282 }
283 if is_array {
284 rooted!(in(*cx) let object = current_value.to_object());
286 get_property_jsval(
287 cx,
288 object.handle(),
289 "length",
290 current_value.handle_mut(),
291 )?;
292
293 continue;
294 }
295 }
296 }
297
298 if identifier == "size" {
300 unsafe {
301 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), *cx)
302 {
303 blob.Size().safe_to_jsval(cx, current_value.handle_mut());
305
306 continue;
307 }
308 }
309 }
310
311 if identifier == "type" {
313 unsafe {
314 if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), *cx)
315 {
316 blob.Type().safe_to_jsval(cx, current_value.handle_mut());
318
319 continue;
320 }
321 }
322 }
323
324 if identifier == "name" {
326 unsafe {
327 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
328 {
329 file.name().safe_to_jsval(cx, current_value.handle_mut());
331
332 continue;
333 }
334 }
335 }
336
337 if identifier == "lastModified" {
339 unsafe {
340 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
341 {
342 file.LastModified()
344 .safe_to_jsval(cx, current_value.handle_mut());
345
346 continue;
347 }
348 }
349 }
350
351 if identifier == "lastModifiedDate" {
353 unsafe {
354 if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
355 {
356 let time = ClippedTime {
358 t: file.LastModified() as f64,
359 };
360 NewDateObject(*cx, time).safe_to_jsval(cx, current_value.handle_mut());
361
362 continue;
363 }
364 }
365 }
366
367 unsafe {
369 if !current_value.is_object() {
371 return Ok(EvaluationResult::Failure);
372 }
373
374 rooted!(in(*cx) let object = current_value.to_object());
375 let identifier_name =
376 CString::new(identifier).expect("Failed to convert str to CString");
377
378 let mut hop = false;
380 if !JS_HasOwnProperty(*cx, object.handle(), identifier_name.as_ptr(), &mut hop)
381 {
382 return Err(Error::JSFailed);
383 }
384
385 if !hop {
387 return Ok(EvaluationResult::Failure);
388 }
389
390 if !JS_GetProperty(
392 *cx,
393 object.handle(),
394 identifier_name.as_ptr(),
395 current_value.handle_mut(),
396 ) {
397 return Err(Error::JSFailed);
398 }
399
400 if current_value.get().is_undefined() {
402 return Ok(EvaluationResult::Failure);
403 }
404 }
405 }
406
407 return_val.set(*current_value);
412 },
413 }
414 Ok(EvaluationResult::Success)
415}
416
417pub(crate) enum ExtractionResult {
420 Key(IndexedDBKeyType),
421 #[expect(unused)]
423 Invalid,
424 Failure,
425}
426
427pub(crate) fn extract_key(
429 cx: SafeJSContext,
430 value: HandleValue,
431 key_path: &KeyPath,
432 multi_entry: Option<bool>,
433) -> Result<ExtractionResult, Error> {
434 rooted!(in(*cx) let mut r = UndefinedValue());
438 if let EvaluationResult::Failure =
439 evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
440 {
441 return Ok(ExtractionResult::Failure);
442 }
443
444 let key = match multi_entry {
448 Some(true) => {
449 unimplemented!("multiEntry keys are not yet supported");
451 },
452 _ => convert_value_to_key(cx, r.handle(), None)?,
453 };
454
455 Ok(ExtractionResult::Key(key))
459}