blob: 3f737c5319f491696c4538573ee10fd929967556 [file] [log] [blame] [edit]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::ffi::CString;
use std::iter::repeat_n;
use std::ptr;
use ipc_channel::ipc::IpcSender;
use js::conversions::jsstr_to_string;
use js::gc::MutableHandle;
use js::jsapi::{
ClippedTime, ESClass, GetBuiltinClass, IsArrayBufferObject, JS_GetStringLength,
JS_IsArrayBufferViewObject, JS_NewObject, NewDateObject,
};
use js::jsval::{DoubleValue, UndefinedValue};
use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasOwnProperty};
use js::rust::{HandleValue, MutableHandleValue};
use net_traits::indexeddb_thread::{BackendResult, IndexedDBKeyRange, IndexedDBKeyType};
use profile_traits::ipc;
use profile_traits::ipc::IpcReceiver;
use serde::{Deserialize, Serialize};
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods;
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
use crate::dom::bindings::conversions::{
SafeToJSValConvertible, get_property_jsval, root_from_handlevalue, root_from_object,
};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::import::module::SafeJSContext;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::structuredclone;
use crate::dom::bindings::utils::set_dictionary_property;
use crate::dom::blob::Blob;
use crate::dom::file::File;
use crate::dom::globalscope::GlobalScope;
use crate::dom::idbkeyrange::IDBKeyRange;
use crate::dom::idbobjectstore::KeyPath;
pub fn create_channel<T>(
global: DomRoot<GlobalScope>,
) -> (IpcSender<BackendResult<T>>, IpcReceiver<BackendResult<T>>)
where
T: for<'a> Deserialize<'a> + Serialize,
{
ipc::channel::<BackendResult<T>>(global.time_profiler_chan().clone()).unwrap()
}
// https://www.w3.org/TR/IndexedDB-2/#convert-key-to-value
#[allow(unsafe_code)]
pub fn key_type_to_jsval(
cx: SafeJSContext,
key: &IndexedDBKeyType,
mut result: MutableHandleValue,
) {
match key {
IndexedDBKeyType::Number(n) => result.set(DoubleValue(*n)),
IndexedDBKeyType::String(s) => s.safe_to_jsval(cx, result),
IndexedDBKeyType::Binary(b) => b.safe_to_jsval(cx, result),
IndexedDBKeyType::Date(d) => {
let time = js::jsapi::ClippedTime { t: *d };
let date = unsafe { js::jsapi::NewDateObject(*cx, time) };
date.safe_to_jsval(cx, result);
},
IndexedDBKeyType::Array(a) => {
rooted_vec!(let mut values <- repeat_n(UndefinedValue(), a.len()));
for (key, value) in a.iter().zip(unsafe {
values
.iter_mut()
.map(|v| MutableHandle::from_marked_location(v))
}) {
key_type_to_jsval(cx, key, value);
}
values.safe_to_jsval(cx, result);
},
}
}
// https://www.w3.org/TR/IndexedDB-2/#valid-key-path
pub fn is_valid_key_path(key_path: &StrOrStringSequence) -> bool {
fn is_identifier(_s: &str) -> bool {
// FIXME: (arihant2math)
true
}
let is_valid = |path: &DOMString| {
path.is_empty() || is_identifier(path) || path.split(".").all(is_identifier)
};
match key_path {
StrOrStringSequence::StringSequence(paths) => {
if paths.is_empty() {
return false;
}
paths.iter().all(is_valid)
},
StrOrStringSequence::String(path) => is_valid(path),
}
}
// https://www.w3.org/TR/IndexedDB-2/#convert-value-to-key
#[allow(unsafe_code)]
pub fn convert_value_to_key(
cx: SafeJSContext,
input: HandleValue,
seen: Option<Vec<HandleValue>>,
) -> Result<IndexedDBKeyType, Error> {
// Step 1: If seen was not given, then let seen be a new empty set.
let _seen = seen.unwrap_or_default();
// Step 2: If seen contains input, then return invalid.
// FIXME:(arihant2math) implement this
// Check if we have seen this key
// Does not currently work with HandleValue,
// as it does not implement PartialEq
// Step 3
// FIXME:(arihant2math) Accept array as well
if input.is_number() {
if input.to_number().is_nan() {
return Err(Error::Data);
}
return Ok(IndexedDBKeyType::Number(input.to_number()));
}
if input.is_string() {
let string_ptr = std::ptr::NonNull::new(input.to_string()).unwrap();
let key = unsafe { jsstr_to_string(*cx, string_ptr) };
return Ok(IndexedDBKeyType::String(key));
}
if input.is_object() {
rooted!(in(*cx) let object = input.to_object());
unsafe {
let mut built_in_class = ESClass::Other;
if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
return Err(Error::Data);
}
if let ESClass::Date = built_in_class {
let mut f = f64::NAN;
if !js::jsapi::DateGetMsecSinceEpoch(*cx, object.handle().into(), &mut f) {
return Err(Error::Data);
}
if f.is_nan() {
return Err(Error::Data);
}
return Ok(IndexedDBKeyType::Date(f));
}
if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
// FIXME:(arihant2math) implement it the correct way (is this correct?)
let key = structuredclone::write(cx, input, None)?;
return Ok(IndexedDBKeyType::Binary(key.serialized.clone()));
}
if let ESClass::Array = built_in_class {
// FIXME:(arihant2math)
error!("Arrays as keys is currently unsupported");
return Err(Error::NotSupported);
}
}
}
Err(Error::Data)
}
// https://www.w3.org/TR/IndexedDB-2/#convert-a-value-to-a-key-range
#[allow(unsafe_code)]
pub fn convert_value_to_key_range(
cx: SafeJSContext,
input: HandleValue,
null_disallowed: Option<bool>,
) -> Result<IndexedDBKeyRange, Error> {
let null_disallowed = null_disallowed.unwrap_or(false);
// Step 1.
if input.is_object() {
rooted!(in(*cx) let object = input.to_object());
unsafe {
if let Ok(obj) = root_from_object::<IDBKeyRange>(object.get(), *cx) {
let obj = obj.inner().clone();
return Ok(obj);
}
}
}
// Step 2.
if (input.get().is_undefined() || input.get().is_null()) && null_disallowed {
return Err(Error::Data);
}
let key = convert_value_to_key(cx, input, None)?;
Ok(IndexedDBKeyRange::only(key))
}
/// The result of steps in
/// <https://www.w3.org/TR/IndexedDB-2/#evaluate-a-key-path-on-a-value>
pub(crate) enum EvaluationResult {
Success,
Failure,
}
/// <https://www.w3.org/TR/IndexedDB-2/#evaluate-a-key-path-on-a-value>
#[allow(unsafe_code)]
pub(crate) fn evaluate_key_path_on_value(
cx: SafeJSContext,
value: HandleValue,
key_path: &KeyPath,
mut return_val: MutableHandleValue,
) -> Result<EvaluationResult, Error> {
match key_path {
// Step 1. If keyPath is a list of strings, then:
KeyPath::StringSequence(key_path) => {
// Step 1.1. Let result be a new Array object created as if by the expression [].
rooted!(in(*cx) let mut result = unsafe { JS_NewObject(*cx, ptr::null()) });
// Step 1.2. Let i be 0.
// Step 1.3. For each item in keyPath:
for (i, item) in key_path.iter().enumerate() {
// Step 1.3.1. Let key be the result of recursively running the steps to evaluate a key
// path on a value using item as keyPath and value as value.
// Step 1.3.2. Assert: key is not an abrupt completion.
// Step 1.3.3. If key is failure, abort the overall algorithm and return failure.
rooted!(in(*cx) let mut key = UndefinedValue());
if let EvaluationResult::Failure = evaluate_key_path_on_value(
cx,
value,
&KeyPath::String(item.clone()),
key.handle_mut(),
)? {
return Ok(EvaluationResult::Failure);
};
// Step 1.3.4. Let p be ! ToString(i).
// Step 1.3.5. Let status be CreateDataProperty(result, p, key).
// Step 1.3.6. Assert: status is true.
set_dictionary_property(cx, result.handle(), &i.to_string(), key.handle())
.map_err(|_| Error::JSFailed)?;
// Step 1.3.7. Increase i by 1.
// Done by for loop with enumerate()
}
// Step 1.4. Return result.
result.safe_to_jsval(cx, return_val);
},
KeyPath::String(key_path) => {
// Step 2. If keyPath is the empty string, return value and skip the remaining steps.
if key_path.is_empty() {
return_val.set(*value);
return Ok(EvaluationResult::Success);
}
// NOTE: Use current_value, instead of value described in spec, in the following steps.
rooted!(in(*cx) let mut current_value = *value);
// Step 3. Let identifiers be the result of strictly splitting keyPath on U+002E
// FULL STOP characters (.).
// Step 4. For each identifier of identifiers, jump to the appropriate step below:
for identifier in key_path.split('.') {
// If Type(value) is String, and identifier is "length"
if identifier == "length" && current_value.is_string() {
// Let value be a Number equal to the number of elements in value.
rooted!(in(*cx) let string_value = current_value.to_string());
unsafe {
let string_length = JS_GetStringLength(*string_value) as u64;
string_length.safe_to_jsval(cx, current_value.handle_mut());
}
continue;
}
// If value is an Array and identifier is "length"
if identifier == "length" {
unsafe {
let mut is_array = false;
if !IsArrayObject(*cx, current_value.handle(), &mut is_array) {
return Err(Error::JSFailed);
}
if is_array {
// Let value be ! ToLength(! Get(value, "length")).
rooted!(in(*cx) let object = current_value.to_object());
get_property_jsval(
cx,
object.handle(),
"length",
current_value.handle_mut(),
)?;
continue;
}
}
}
// If value is a Blob and identifier is "size"
if identifier == "size" {
unsafe {
if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), *cx)
{
// Let value be a Number equal to value’s size.
blob.Size().safe_to_jsval(cx, current_value.handle_mut());
continue;
}
}
}
// If value is a Blob and identifier is "type"
if identifier == "type" {
unsafe {
if let Ok(blob) = root_from_handlevalue::<Blob>(current_value.handle(), *cx)
{
// Let value be a String equal to value’s type.
blob.Type().safe_to_jsval(cx, current_value.handle_mut());
continue;
}
}
}
// If value is a File and identifier is "name"
if identifier == "name" {
unsafe {
if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
{
// Let value be a String equal to value’s name.
file.name().safe_to_jsval(cx, current_value.handle_mut());
continue;
}
}
}
// If value is a File and identifier is "lastModified"
if identifier == "lastModified" {
unsafe {
if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
{
// Let value be a Number equal to value’s lastModified.
file.LastModified()
.safe_to_jsval(cx, current_value.handle_mut());
continue;
}
}
}
// If value is a File and identifier is "lastModifiedDate"
if identifier == "lastModifiedDate" {
unsafe {
if let Ok(file) = root_from_handlevalue::<File>(current_value.handle(), *cx)
{
// Let value be a new Date object with [[DateValue]] internal slot equal to value’s lastModified.
let time = ClippedTime {
t: file.LastModified() as f64,
};
NewDateObject(*cx, time).safe_to_jsval(cx, current_value.handle_mut());
continue;
}
}
}
// Otherwise
unsafe {
// If Type(value) is not Object, return failure.
if !current_value.is_object() {
return Ok(EvaluationResult::Failure);
}
rooted!(in(*cx) let object = current_value.to_object());
let identifier_name =
CString::new(identifier).expect("Failed to convert str to CString");
// Let hop be ! HasOwnProperty(value, identifier).
let mut hop = false;
if !JS_HasOwnProperty(*cx, object.handle(), identifier_name.as_ptr(), &mut hop)
{
return Err(Error::JSFailed);
}
// If hop is false, return failure.
if !hop {
return Ok(EvaluationResult::Failure);
}
// Let value be ! Get(value, identifier).
if !JS_GetProperty(
*cx,
object.handle(),
identifier_name.as_ptr(),
current_value.handle_mut(),
) {
return Err(Error::JSFailed);
}
// If value is undefined, return failure.
if current_value.get().is_undefined() {
return Ok(EvaluationResult::Failure);
}
}
}
// Step 5. Assert: value is not an abrupt completion.
// Done within Step 4.
// Step 6. Return value.
return_val.set(*current_value);
},
}
Ok(EvaluationResult::Success)
}
/// The result of steps in
/// <https://www.w3.org/TR/IndexedDB-2/#extract-a-key-from-a-value-using-a-key-path>
pub(crate) enum ExtractionResult {
Key(IndexedDBKeyType),
// NOTE: Invalid is not used for now. Remove the unused annotation when it is used.
#[expect(unused)]
Invalid,
Failure,
}
/// <https://www.w3.org/TR/IndexedDB-2/#extract-a-key-from-a-value-using-a-key-path>
pub(crate) fn extract_key(
cx: SafeJSContext,
value: HandleValue,
key_path: &KeyPath,
multi_entry: Option<bool>,
) -> Result<ExtractionResult, Error> {
// Step 1. Let r be the result of running the steps to evaluate a key path on a value with
// value and keyPath. Rethrow any exceptions.
// Step 2. If r is failure, return failure.
rooted!(in(*cx) let mut r = UndefinedValue());
if let EvaluationResult::Failure =
evaluate_key_path_on_value(cx, value, key_path, r.handle_mut())?
{
return Ok(ExtractionResult::Failure);
}
// Step 3. Let key be the result of running the steps to convert a value to a key with r if the
// multiEntry flag is unset, and the result of running the steps to convert a value to a
// multiEntry key with r otherwise. Rethrow any exceptions.
let key = match multi_entry {
Some(true) => {
// TODO: implement convert_value_to_multientry_key
unimplemented!("multiEntry keys are not yet supported");
},
_ => convert_value_to_key(cx, r.handle(), None)?,
};
// TODO: Step 4. If key is invalid, return invalid.
// Step 5. Return key.
Ok(ExtractionResult::Key(key))
}