fix(indexeddb): convert iterable key paths only once

This commit is contained in:
ldm0
2026-09-30 00:53:12 +08:00
parent d92d4da858
commit 225030f30e
2 changed files with 43 additions and 22 deletions
@@ -7,9 +7,12 @@ pub(in crate::context_bootstrap::indexed_db) fn parse_idb_key_path<'s>(
value: v8::Local<'s, v8::Value>,
context: webidl::Context,
) -> Result<KeyPath, webidl::WebIdlError> {
if should_parse_key_path_sequence(scope, value, context)? {
let key_path =
webidl::convert::<webidl::Sequence<webidl::DomString>>(scope, value, context)?;
if let Some(key_path) = webidl::convert_optional_sequence::<webidl::DomString>(
scope,
value,
context,
&Default::default(),
)? {
return Ok(KeyPath::Sequence(
key_path.0.into_iter().map(Into::into).collect(),
));
@@ -60,22 +63,3 @@ pub(in crate::context_bootstrap::indexed_db) fn key_path_from_js_value<'s>(
.to_string(scope)
.map(|value| KeyPath::String(value.to_rust_string_lossy(scope)))
}
fn should_parse_key_path_sequence<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
context: webidl::Context,
) -> Result<bool, webidl::WebIdlError> {
if value.is_string() {
return Ok(false);
}
let Ok(object) = v8::Local::<v8::Object>::try_from(value) else {
return Ok(false);
};
let iterator_key = v8::Symbol::get_iterator(scope);
let Some(iterator) = webidl::symbol_property_result(scope, object, iterator_key, context)?
else {
return Ok(false);
};
Ok(!iterator.is_null_or_undefined())
}
@@ -5773,3 +5773,40 @@ fn indexed_db_put_rejects_non_serializable_platform_objects() {
assert_eq!(result, "DataCloneError|DataCloneError|DataCloneError");
}
#[test]
fn indexed_db_key_path_reads_iterator_once() {
let mut vm = new_storage_page_task_executor_test_vm("https://key-path-iterator.test/");
vm.eval(
r#"
globalThis.keyPathResult = 'pending';
const open = indexedDB.open('key-path-iterator', 1);
open.onupgradeneeded = () => {
try {
let reads = 0;
const keyPath = {
get [Symbol.iterator]() {
if (++reads > 1) throw new Error('iterator read twice');
return function* () { yield 'id'; };
}
};
const store = open.result.createObjectStore('values', {keyPath});
if (reads !== 1 || String(store.keyPath) !== 'id') throw new Error('store key path');
reads = 0;
const index = store.createIndex('by-id', keyPath);
if (reads !== 1 || String(index.keyPath) !== 'id') throw new Error('index key path');
const sentinel = {};
const throwing = {get [Symbol.iterator]() { throw sentinel; }};
try { store.createIndex('throws', throwing); throw new Error('missing exception'); }
catch (error) { if (error !== sentinel) throw error; }
globalThis.keyPathResult = 'pass';
} catch (error) { globalThis.keyPathResult = String(error); }
};
"#,
)
.unwrap();
assert_eq!(
vm.eval_after_selected_page_tasks("keyPathResult").unwrap(),
"pass"
);
}