Move common indexeddb methods out of dom implementations (#38101)

A lot of shared functions were scattered around the dom files; I moved
them into `indexed_db.rs` for clarity.

Fixes: Nothing to my knowledge, just a cleanup

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren 2025-07-16 21:19:03 -07:00 committed by GitHub
parent e10466b4c4
commit f70a4eb4ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 314 additions and 299 deletions

View file

@ -2,24 +2,14 @@
* 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::ptr;
use dom_struct::dom_struct;
use js::jsapi::{
ESClass, GetBuiltinClass, IsArrayBufferObject, JS_DeleteUCProperty,
JS_GetOwnUCPropertyDescriptor, JS_GetStringLength, JS_IsArrayBufferViewObject, JSObject,
ObjectOpResult, ObjectOpResult_SpecialCodes, PropertyDescriptor,
};
use js::jsval::UndefinedValue;
use js::rust::{HandleValue, MutableHandleValue};
use log::error;
use js::rust::HandleValue;
use net_traits::IpcSend;
use net_traits::indexeddb_thread::{
AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, IndexedDBKeyType,
IndexedDBThreadMsg, SyncOperation,
};
use profile_traits::ipc;
use script_bindings::conversions::SafeToJSValConvertible;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
@ -27,7 +17,6 @@ use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectSto
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
// We need to alias this name, otherwise test-tidy complains at &String reference.
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
use crate::dom::bindings::conversions::jsstring_to_str;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
@ -37,6 +26,7 @@ use crate::dom::domstringlist::DOMStringList;
use crate::dom::globalscope::GlobalScope;
use crate::dom::idbrequest::IDBRequest;
use crate::dom::idbtransaction::IDBTransaction;
use crate::indexed_db::{convert_value_to_key, extract_key};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
#[derive(JSTraceable, MallocSizeOf)]
@ -119,229 +109,6 @@ impl IDBObjectStore {
self.transaction.get()
}
// 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),
}
}
#[allow(unsafe_code)]
// https://www.w3.org/TR/IndexedDB-2/#convert-value-to-key
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 buffer, array and date as well
if input.is_number() {
// FIXME:(arihant2math) check for NaN
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 { jsstring_to_str(*cx, string_ptr).str().to_string() };
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 {
// FIXME:(arihant2math) implement it the correct way
let key =
structuredclone::write(cx, input, None).expect("Could not serialize key");
return Ok(IndexedDBKeyType::Date(key.serialized.clone()));
}
if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
// FIXME:(arihant2math)
error!("Array buffers as keys is currently unsupported");
return Err(Error::NotSupported);
}
if let ESClass::Array = built_in_class {
// FIXME:(arihant2math)
unimplemented!("Arrays as keys is currently unsupported");
}
}
}
Err(Error::Data)
}
// https://www.w3.org/TR/IndexedDB-2/#evaluate-a-key-path-on-a-value
#[allow(unsafe_code)]
fn evaluate_key_path_on_value(
cx: SafeJSContext,
value: HandleValue,
mut return_val: MutableHandleValue,
key_path: &KeyPath,
) {
// The implementation is translated from gecko:
// https://github.com/mozilla/gecko-dev/blob/master/dom/indexedDB/KeyPath.cpp
return_val.set(*value);
rooted!(in(*cx) let mut target_object = ptr::null_mut::<JSObject>());
rooted!(in(*cx) let mut current_val = *value);
rooted!(in(*cx) let mut object = ptr::null_mut::<JSObject>());
let mut target_object_prop_name: Option<String> = None;
match key_path {
KeyPath::String(path) => {
// Step 3
let path_as_string = path.to_string();
let mut tokenizer = path_as_string.split('.').peekable();
while let Some(token) = tokenizer.next() {
if target_object.get().is_null() {
if token == "length" &&
tokenizer.peek().is_none() &&
current_val.is_string()
{
rooted!(in(*cx) let input_val = current_val.to_string());
unsafe {
let string_len = JS_GetStringLength(*input_val) as u64;
string_len.safe_to_jsval(cx, return_val);
}
break;
}
if !current_val.is_object() {
// FIXME:(rasviitanen) Return a proper error
return;
}
object.handle_mut().set(current_val.to_object());
rooted!(in(*cx) let mut desc = PropertyDescriptor::default());
rooted!(in(*cx) let mut intermediate = UndefinedValue());
// So rust says that this value is never read, but it is.
#[allow(unused)]
let mut has_prop = false;
unsafe {
let prop_name_as_utf16: Vec<u16> = token.encode_utf16().collect();
let mut is_descriptor_none: bool = false;
let ok = JS_GetOwnUCPropertyDescriptor(
*cx,
object.handle().into(),
prop_name_as_utf16.as_ptr(),
prop_name_as_utf16.len(),
desc.handle_mut().into(),
&mut is_descriptor_none,
);
if !ok {
// FIXME:(arihant2math) Handle this
return;
}
if desc.hasWritable_() || desc.hasValue_() {
intermediate.handle_mut().set(desc.handle().value_);
has_prop = true;
} else {
// If we get here it means the object doesn't have the property or the
// property is available throuch a getter. We don't want to call any
// getters to avoid potential re-entrancy.
// The blob object is special since its properties are available
// only through getters but we still want to support them for key
// extraction. So they need to be handled manually.
unimplemented!("Blob tokens are not yet supported");
}
}
if has_prop {
// Treat undefined as an error
if intermediate.is_undefined() {
// FIXME:(rasviitanen) Throw/return error
return;
}
if tokenizer.peek().is_some() {
// ...and walk to it if there are more steps...
current_val.handle_mut().set(*intermediate);
} else {
// ...otherwise use it as key
return_val.set(*intermediate);
}
} else {
target_object.handle_mut().set(*object);
target_object_prop_name = Some(token.to_string());
}
}
if !target_object.get().is_null() {
// We have started inserting new objects or are about to just insert
// the first one.
// FIXME:(rasviitanen) Implement this piece
unimplemented!("keyPath tokens that requires insertion are not supported.");
}
} // All tokens processed
if !target_object.get().is_null() {
// If this fails, we lose, and the web page sees a magical property
// appear on the object :-(
unsafe {
let prop_name_as_utf16: Vec<u16> =
target_object_prop_name.unwrap().encode_utf16().collect();
#[allow(clippy::cast_enum_truncation)]
let mut succeeded = ObjectOpResult {
code_: ObjectOpResult_SpecialCodes::Uninitialized as usize,
};
if !JS_DeleteUCProperty(
*cx,
target_object.handle().into(),
prop_name_as_utf16.as_ptr(),
prop_name_as_utf16.len(),
&mut succeeded,
) {
// FIXME:(rasviitanen) Throw/return error
// return;
}
}
}
},
KeyPath::StringSequence(_) => {
unimplemented!("String sequence keyPath is currently unsupported");
},
}
}
fn has_key_generator(&self) -> bool {
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
@ -361,26 +128,6 @@ impl IDBObjectStore {
receiver.recv().unwrap()
}
// https://www.w3.org/TR/IndexedDB-2/#extract-a-key-from-a-value-using-a-key-path
fn extract_key(
cx: SafeJSContext,
input: HandleValue,
key_path: &KeyPath,
multi_entry: Option<bool>,
) -> Result<IndexedDBKeyType, Error> {
// Step 1: Evaluate key path
// FIXME:(rasviitanen) Do this propertly
rooted!(in(*cx) let mut r = UndefinedValue());
IDBObjectStore::evaluate_key_path_on_value(cx, input, r.handle_mut(), key_path);
if let Some(_multi_entry) = multi_entry {
// FIXME:(rasviitanen) handle multi_entry cases
unimplemented!("multiEntry keys are not yet supported");
} else {
IDBObjectStore::convert_value_to_key(cx, r.handle(), None)
}
}
// https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys
fn uses_inline_keys(&self) -> bool {
self.key_path.is_some()
@ -450,10 +197,10 @@ impl IDBObjectStore {
let serialized_key: IndexedDBKeyType;
if !key.is_undefined() {
serialized_key = IDBObjectStore::convert_value_to_key(cx, key, None)?;
serialized_key = convert_value_to_key(cx, key, None)?;
} else {
// Step 11: We should use in-line keys instead
if let Ok(kpk) = IDBObjectStore::extract_key(
if let Ok(kpk) = extract_key(
cx,
value,
self.key_path.as_ref().expect("No key path"),
@ -514,7 +261,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
self.check_readwrite_transaction_active()?;
// Step 6
// TODO: Convert to key range instead
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
let serialized_query = convert_value_to_key(cx, query, None);
// Step 7
serialized_query.and_then(|q| {
IDBRequest::execute_async(
@ -550,7 +297,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
self.check_transaction_active()?;
// Step 5
// TODO: Convert to key range instead
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
let serialized_query = convert_value_to_key(cx, query, None);
// Step 6
serialized_query.and_then(|q| {
IDBRequest::execute_async(
@ -604,7 +351,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
self.check_transaction_active()?;
// Step 5
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
let serialized_query = convert_value_to_key(cx, query, None);
// Step 6
serialized_query.and_then(|q| {