script/
indexeddb.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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// https://www.w3.org/TR/IndexedDB-3/#convert-key-to-value
42#[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
67/// <https://www.w3.org/TR/IndexedDB-3/#valid-key-path>
68pub(crate) fn is_valid_key_path(
69    cx: &mut JSContext,
70    key_path: &StrOrStringSequence,
71) -> Result<bool, Error> {
72    // <https://tc39.es/ecma262/#prod-IdentifierName>
73    #[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    // A valid key path is one of:
89    let is_valid = |cx: &mut JSContext, path: &DOMString| -> Result<bool, Error> {
90        // An empty string.
91        let is_empty_string = path.is_empty();
92
93        // An identifier, which is a string matching the IdentifierName production from the
94        // ECMAScript Language Specification [ECMA-262].
95        let is_identifier = is_identifier_name(cx, &path.str())?;
96
97        // A string consisting of two or more identifiers separated by periods (U+002E FULL STOP).
98        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            // A non-empty list containing only strings conforming to the above requirements.
112            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// https://www.w3.org/TR/IndexedDB-3/#convert-value-to-key
142#[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    // Step 1: If seen was not given, then let seen be a new empty set.
149    let mut seen = seen.unwrap_or_default();
150
151    // Step 2: If seen contains input, then return invalid.
152    // FIXME:(arihant2math) implement this
153    // Check if we have seen this key
154    // Does not currently work with HandleValue,
155    // as it does not implement PartialEq
156
157    // Step 3
158    // FIXME:(arihant2math) Accept array as well
159    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                // FIXME:(arihant2math) implement it the correct way (is this correct?)
196                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/// <https://www.w3.org/TR/IndexedDB-3/#convert-a-value-to-a-key-range>
245#[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    // Step 1. If value is a key range, return value.
252    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    // Step 2. If value is undefined or is null, then throw a "DataError" DOMException if null
263    // disallowed flag is set, or return an unbounded key range otherwise.
264    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    // Step 3. Let key be the result of running the steps to convert a value to a key with value.
278    // Rethrow any exceptions.
279    let key = convert_value_to_key(cx, input, None)?;
280
281    // Step 4. If key is invalid, throw a "DataError" DOMException.
282    let key = key.into_result()?;
283
284    // Step 5. Return a key range containing only key.
285    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
301/// The result of steps in
302/// <https://www.w3.org/TR/IndexedDB-3/#evaluate-a-key-path-on-a-value>
303pub(crate) enum EvaluationResult {
304    Success,
305    Failure,
306}
307
308/// <https://www.w3.org/TR/IndexedDB-3/#evaluate-a-key-path-on-a-value>
309#[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        // Step 1. If keyPath is a list of strings, then:
318        KeyPath::StringSequence(key_path) => {
319            // Step 1.1. Let result be a new Array object created as if by the expression [].
320            rooted!(&in(cx) let mut result = unsafe { JS_NewObject(cx, ptr::null()) });
321
322            // Step 1.2. Let i be 0.
323            // Step 1.3. For each item in keyPath:
324            for (i, item) in key_path.iter().enumerate() {
325                // Step 1.3.1. Let key be the result of recursively running the steps to evaluate a key
326                // path on a value using item as keyPath and value as value.
327                // Step 1.3.2. Assert: key is not an abrupt completion.
328                // Step 1.3.3. If key is failure, abort the overall algorithm and return failure.
329                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                // Step 1.3.4. Let p be ! ToString(i).
340                // Step 1.3.5. Let status be CreateDataProperty(result, p, key).
341                // Step 1.3.6. Assert: status is true.
342                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                // Step 1.3.7. Increase i by 1.
352                // Done by for loop with enumerate()
353            }
354
355            // Step 1.4. Return result.
356            result.safe_to_jsval(cx, return_val);
357        },
358        KeyPath::String(key_path) => {
359            // Step 2. If keyPath is the empty string, return value and skip the remaining steps.
360            if key_path.is_empty() {
361                return_val.set(*value);
362                return Ok(EvaluationResult::Success);
363            }
364
365            // NOTE: Use current_value, instead of value described in spec, in the following steps.
366            rooted!(&in(cx) let mut current_value = *value);
367
368            // Step 3. Let identifiers be the result of strictly splitting keyPath on U+002E
369            // FULL STOP characters (.).
370            // Step 4. For each identifier of identifiers, jump to the appropriate step below:
371            for identifier in key_path.str().split('.') {
372                // If Type(value) is String, and identifier is "length"
373                if identifier == "length" && current_value.is_string() {
374                    // Let value be a Number equal to the number of elements in value.
375                    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 value is an Array and identifier is "length"
384                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                            // Let value be ! ToLength(! Get(value, "length")).
392                            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 value is a Blob and identifier is "size"
406                if identifier == "size" {
407                    if let Ok(blob) =
408                        root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
409                    {
410                        // Let value be a Number equal to value’s size.
411                        blob.Size().safe_to_jsval(cx, current_value.handle_mut());
412
413                        continue;
414                    }
415                }
416
417                // If value is a Blob and identifier is "type"
418                if identifier == "type" {
419                    if let Ok(blob) =
420                        root_from_handlevalue::<Blob>(current_value.handle(), cx.into())
421                    {
422                        // Let value be a String equal to value’s type.
423                        blob.Type().safe_to_jsval(cx, current_value.handle_mut());
424
425                        continue;
426                    }
427                }
428
429                // If value is a File and identifier is "name"
430                if identifier == "name" {
431                    if let Ok(file) =
432                        root_from_handlevalue::<File>(current_value.handle(), cx.into())
433                    {
434                        // Let value be a String equal to value’s name.
435                        file.name().safe_to_jsval(cx, current_value.handle_mut());
436
437                        continue;
438                    }
439                }
440
441                // If value is a File and identifier is "lastModified"
442                if identifier == "lastModified" {
443                    if let Ok(file) =
444                        root_from_handlevalue::<File>(current_value.handle(), cx.into())
445                    {
446                        // Let value be a Number equal to value’s lastModified.
447                        file.LastModified()
448                            .safe_to_jsval(cx, current_value.handle_mut());
449
450                        continue;
451                    }
452                }
453
454                // If value is a File and identifier is "lastModifiedDate"
455                if identifier == "lastModifiedDate" {
456                    if let Ok(file) =
457                        root_from_handlevalue::<File>(current_value.handle(), cx.into())
458                    {
459                        // Let value be a new Date object with [[DateValue]] internal slot equal to value’s lastModified.
460                        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                // Otherwise
472                unsafe {
473                    // If Type(value) is not Object, return failure.
474                    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 be ! HasOwnProperty(value, identifier).
483                    let hop =
484                        has_own_property(cx.into(), object.handle(), identifier_name.as_c_str())
485                            .map_err(|_| Error::JSFailed)?;
486
487                    // If hop is false, return failure.
488                    if !hop {
489                        return Ok(EvaluationResult::Failure);
490                    }
491
492                    // Let value be ! Get(value, identifier).
493                    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 value is undefined, return failure.
506                    if current_value.get().is_undefined() {
507                        return Ok(EvaluationResult::Failure);
508                    }
509                }
510            }
511
512            // Step 5. Assert: value is not an abrupt completion.
513            // Done within Step 4.
514
515            // Step 6. Return value.
516            return_val.set(*current_value);
517        },
518    }
519    Ok(EvaluationResult::Success)
520}
521
522/// The result of steps in
523/// <https://www.w3.org/TR/IndexedDB-3/#extract-a-key-from-a-value-using-a-key-path>
524pub(crate) enum ExtractionResult {
525    Key(IndexedDBKeyType),
526    Invalid,
527    Failure,
528}
529
530/// <https://w3c.github.io/IndexedDB/#check-that-a-key-could-be-injected-into-a-value>
531#[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    // Step 1. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP
538    // characters (.).
539    let key_path_string = key_path.str();
540    let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
541
542    // Step 2. Assert: identifiers is not empty.
543    let Some(_) = identifiers.pop() else {
544        return Ok(false);
545    };
546
547    rooted!(&in(cx) let mut current_value = *value);
548
549    // Step 3. For each remaining identifier of identifiers:
550    for identifier in identifiers {
551        // Step 3.1. If value is not an Object or an Array, return false.
552        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        // Step 3.2. Let hop be ? HasOwnProperty(value, identifier).
561        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        // Step 3.3. If hop is false, set value to a new Object created as if by the expression
569        // ({}).
570        // We avoid mutating `value` during this check and can return true immediately because the
571        // remaining path can be created from scratch.
572        if !hop {
573            return Ok(true);
574        }
575
576        // Step 3.4. Set value to ? Get(value, identifier).
577        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    // Step 4. Return true if value is an Object or an Array, and false otherwise.
593    Ok(current_value.is_object())
594}
595
596/// <https://w3c.github.io/IndexedDB/#inject-a-key-into-a-value-using-a-key-path>
597#[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    // Step 1. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP characters (.).
605    let key_path_string = key_path.str();
606    let mut identifiers: Vec<&str> = key_path_string.split('.').collect();
607
608    // Step 2. Assert: identifiers is not empty.
609    let Some(last) = identifiers.pop() else {
610        return Ok(false);
611    };
612
613    // Step 3. Let last be the last item of identifiers and remove it from the list.
614    // Done by `pop()` above.
615
616    rooted!(&in(cx) let mut current_value = *value);
617
618    // Step 4. For each remaining identifier of identifiers:
619    for identifier in identifiers {
620        // Step 4.1 Assert: value is an Object or an Array.
621        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        // Step 4.2 Let hop be ! HasOwnProperty(value, identifier).
630        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        // Step 4.3 If hop is false, then:
638        if !hop {
639            // Step 4.3.1 Let o be a new Object created as if by the expression ({}).
640            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            // Step 4.3.2 Let status be CreateDataProperty(value, identifier, o).
645            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            // Step 4.3.3 Assert: status is true.
654        }
655
656        // Step 4.3 Let value be ! Get(value, identifier).
657        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        // Step 5 "Assert: value is an Object or an Array."
672        if !current_value.is_object() {
673            return Ok(false);
674        }
675    }
676
677    // Step 6. Let keyValue be the result of converting a key to a value with key.
678    rooted!(&in(cx) let mut key_value = UndefinedValue());
679    key_type_to_jsval(cx, key, key_value.handle_mut());
680
681    // `current_value` is the parent object where `last` will be defined.
682    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    // Step 7. Let status be CreateDataProperty(value, last, keyValue).
689    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    // Step 8. Assert: status is true.
698    // The JS_DefineProperty success check above enforces this assertion.
699    // "NOTE: Assertions can be made in the above steps because this algorithm is only applied to values that are the output of StructuredDeserialize, and the steps to check that a key could be injected into a value have been run."
700    Ok(true)
701}
702
703/// <https://www.w3.org/TR/IndexedDB-3/#extract-a-key-from-a-value-using-a-key-path>
704pub(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    // Step 1. Let r be the result of running the steps to evaluate a key path on a value with
711    // value and keyPath. Rethrow any exceptions.
712    // Step 2. If r is failure, return failure.
713    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    // Step 3. Let key be the result of running the steps to convert a value to a key with r if the
721    // multiEntry flag is unset, and the result of running the steps to convert a value to a
722    // multiEntry key with r otherwise. Rethrow any exceptions.
723    let key = match multi_entry {
724        Some(true) => {
725            // TODO: implement convert_value_to_multientry_key
726            unimplemented!("multiEntry keys are not yet supported");
727        },
728        _ => match convert_value_to_key(cx, r.handle(), None)? {
729            ConversionResult::Valid(key) => key,
730            // Step 4. If key is invalid, return invalid.
731            ConversionResult::Invalid => return Ok(ExtractionResult::Invalid),
732        },
733    };
734
735    // Step 5. Return key.
736    Ok(ExtractionResult::Key(key))
737}