indexeddb: Implement getAll and getAllKeys (#38885)

Implement getAll and getAllKeys for IDBObjectStore.

Testing: WPT & Unit testing
Fixes: Part of #6963.

---------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren 2025-09-05 22:48:11 -07:00 committed by GitHub
parent e64c53972a
commit b5d6555238
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 308 additions and 86 deletions

View file

@ -176,6 +176,55 @@ impl SqliteEngine {
Self::get(connection, store, key_range).map(|opt| opt.map(|model| model.data))
}
fn get_all(
connection: &Connection,
store: object_store_model::Model,
key_range: IndexedDBKeyRange,
count: Option<u32>,
) -> Result<Vec<object_data_model::Model>, Error> {
let query = range_to_query(key_range);
let mut sql_query = sea_query::Query::select();
sql_query
.from(object_data_model::Column::Table)
.columns(vec![
object_data_model::Column::ObjectStoreId,
object_data_model::Column::Key,
object_data_model::Column::Data,
])
.and_where(query.and(Expr::col(object_data_model::Column::ObjectStoreId).is(store.id)));
if let Some(count) = count {
sql_query.limit(count as u64);
}
let (sql, values) = sql_query.build_rusqlite(SqliteQueryBuilder);
let mut stmt = connection.prepare(&sql)?;
let models = stmt
.query_and_then(&*values.as_params(), |row| {
object_data_model::Model::try_from(row)
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(models)
}
fn get_all_keys(
connection: &Connection,
store: object_store_model::Model,
key_range: IndexedDBKeyRange,
count: Option<u32>,
) -> Result<Vec<Vec<u8>>, Error> {
Self::get_all(connection, store, key_range, count)
.map(|models| models.into_iter().map(|m| m.key).collect())
}
fn get_all_items(
connection: &Connection,
store: object_store_model::Model,
key_range: IndexedDBKeyRange,
count: Option<u32>,
) -> Result<Vec<Vec<u8>>, Error> {
Self::get_all(connection, store, key_range, count)
.map(|models| models.into_iter().map(|m| m.data).collect())
}
fn put_item(
connection: &Connection,
store: object_store_model::Model,
@ -401,6 +450,37 @@ impl KvsEngine for SqliteEngine {
.map_err(|e| BackendError::DbErr(format!("{:?}", e))),
);
},
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
sender,
key_range,
count,
}) => {
let Ok(object_store) = process_object_store(object_store, &sender) else {
continue;
};
let _ = sender.send(
Self::get_all_keys(&connection, object_store, key_range, count)
.map(|keys| {
keys.into_iter()
.map(|k| bincode::deserialize(&k).unwrap())
.collect()
})
.map_err(|e| BackendError::DbErr(format!("{:?}", e))),
);
},
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
sender,
key_range,
count,
}) => {
let Ok(object_store) = process_object_store(object_store, &sender) else {
continue;
};
let _ = sender.send(
Self::get_all_items(&connection, object_store, key_range, count)
.map_err(|e| BackendError::DbErr(format!("{:?}", e))),
);
},
AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
sender,
key,

View file

@ -9,8 +9,9 @@ use net::indexeddb::idb_thread::IndexedDBDescription;
use net::resource_thread::CoreResourceThreadPool;
use net_traits::indexeddb_thread::{
AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, CreateObjectResult,
IndexedDBKeyRange, IndexedDBKeyType, IndexedDBTxnMode, KeyPath,
IndexedDBKeyRange, IndexedDBKeyType, IndexedDBTxnMode, KeyPath, PutItemResult,
};
use serde::{Deserialize, Serialize};
use servo_url::ImmutableOrigin;
use url::Host;
@ -183,6 +184,16 @@ fn test_delete_store() {
#[test]
fn test_async_operations() {
fn get_channel<T>() -> (
ipc_channel::ipc::IpcSender<T>,
ipc_channel::ipc::IpcReceiver<T>,
)
where
T: for<'de> Deserialize<'de> + Serialize,
{
ipc_channel::ipc::channel().unwrap()
}
let base_dir = tempfile::tempdir().expect("Failed to create temp dir");
let thread_pool = get_pool();
let db = SqliteEngine::new(
@ -197,68 +208,121 @@ fn test_async_operations() {
let store_name = "test_store";
db.create_store(store_name, None, false)
.expect("Failed to create store");
let channel = ipc_channel::ipc::channel().unwrap();
let channel2 = ipc_channel::ipc::channel().unwrap();
let channel3 = ipc_channel::ipc::channel().unwrap();
let channel4 = ipc_channel::ipc::channel().unwrap();
let channel5 = ipc_channel::ipc::channel().unwrap();
let channel6 = ipc_channel::ipc::channel().unwrap();
let put = get_channel();
let put2 = get_channel();
let put3 = get_channel();
let put_dup = get_channel();
let get_item_some = get_channel();
let get_item_none = get_channel();
let get_all_items = get_channel();
let count = get_channel();
let remove = get_channel();
let clear = get_channel();
let rx = db.process_transaction(KvsTransaction {
mode: IndexedDBTxnMode::Readwrite,
requests: VecDeque::from(vec![
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
sender: channel.0,
sender: put.0,
key: Some(IndexedDBKeyType::Number(1.0)),
value: vec![1, 2, 3],
should_overwrite: false,
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
sender: put2.0,
key: Some(IndexedDBKeyType::String("2.0".to_string())),
value: vec![4, 5, 6],
should_overwrite: false,
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
sender: put3.0,
key: Some(IndexedDBKeyType::Array(vec![
IndexedDBKeyType::String("3".to_string()),
IndexedDBKeyType::Number(0.0),
])),
value: vec![7, 8, 9],
should_overwrite: false,
}),
},
// Try to put a duplicate key without overwrite
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem {
sender: put_dup.0,
key: Some(IndexedDBKeyType::Number(1.0)),
value: vec![10, 11, 12],
should_overwrite: false,
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
sender: channel2.0,
sender: get_item_some.0,
key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(1.0)),
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem {
sender: channel3.0,
sender: get_item_none.0,
key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(5.0)),
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
sender: get_all_items.0,
key_range: IndexedDBKeyRange::lower_bound(IndexedDBKeyType::Number(0.0), false),
count: None,
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count {
sender: channel4.0,
sender: count.0,
key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(1.0)),
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem {
sender: channel5.0,
sender: remove.0,
key: IndexedDBKeyType::Number(1.0),
}),
},
KvsOperation {
store_name: store_name.to_owned(),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(channel6.0)),
operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(clear.0)),
},
]),
});
let _ = rx.blocking_recv().unwrap();
channel.1.recv().unwrap().unwrap();
let get_result = channel2.1.recv().unwrap();
put.1.recv().unwrap().unwrap();
put2.1.recv().unwrap().unwrap();
put3.1.recv().unwrap().unwrap();
let err = put_dup.1.recv().unwrap().unwrap();
assert_eq!(err, PutItemResult::CannotOverwrite);
let get_result = get_item_some.1.recv().unwrap();
let value = get_result.unwrap();
assert_eq!(value, Some(vec![1, 2, 3]));
let get_result = channel3.1.recv().unwrap();
let get_result = get_item_none.1.recv().unwrap();
let value = get_result.unwrap();
assert_eq!(value, None);
let amount = channel4.1.recv().unwrap().unwrap();
let all_items = get_all_items.1.recv().unwrap().unwrap();
assert_eq!(all_items.len(), 3);
// Check that all three items are present
assert!(all_items.contains(&vec![1, 2, 3]));
assert!(all_items.contains(&vec![4, 5, 6]));
assert!(all_items.contains(&vec![7, 8, 9]));
let amount = count.1.recv().unwrap().unwrap();
assert_eq!(amount, 1);
channel5.1.recv().unwrap().unwrap();
channel6.1.recv().unwrap().unwrap();
remove.1.recv().unwrap().unwrap();
clear.1.recv().unwrap().unwrap();
}

View file

@ -283,7 +283,7 @@ impl IDBObjectStore {
}
impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put>
fn Put(
&self,
cx: SafeJSContext,
@ -293,7 +293,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
self.put(cx, value, key, true, CanGc::note())
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add>
fn Add(
&self,
cx: SafeJSContext,
@ -303,7 +303,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
self.put(cx, value, key, false, CanGc::note())
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete>
fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
@ -331,7 +331,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
})
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear>
fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
@ -355,7 +355,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
)
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get>
fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
@ -385,7 +385,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
})
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey>
fn GetKey(&self, cx: SafeJSContext, query: HandleValue) -> Result<DomRoot<IDBRequest>, Error> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
@ -416,27 +416,81 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
})
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall
// fn GetAll(
// &self,
// _cx: SafeJSContext,
// _query: HandleValue,
// _count: Option<u32>,
// ) -> DomRoot<IDBRequest> {
// unimplemented!();
// }
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall>
fn GetAll(
&self,
cx: SafeJSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys
// fn GetAllKeys(
// &self,
// _cx: SafeJSContext,
// _query: HandleValue,
// _count: Option<u32>,
// ) -> DomRoot<IDBRequest> {
// unimplemented!();
// }
// Step 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count
// Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
// The steps are run with this object store handle as source and the steps to retrieve a key from an object
// store as operation, using store and range.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems {
sender,
key_range: q,
count,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys>
fn GetAllKeys(
&self,
cx: SafeJSContext,
query: HandleValue,
count: Option<u32>,
) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
self.verify_not_deleted()?;
// Step 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
self.check_transaction_active()?;
// Step 5. Let range be the result of running the steps to convert a value to a key range with query and null disallowed flag set. Rethrow any exceptions.
let serialized_query = convert_value_to_key_range(cx, query, None);
// Step 6. Run the steps to asynchronously execute a request and return the IDBRequest created by these steps.
// The steps are run with this object store handle as source and the steps to retrieve a key from an object
// store as operation, using store and range.
let (sender, receiver) = indexed_db::create_channel(self.global());
serialized_query.and_then(|q| {
IDBRequest::execute_async(
self,
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllKeys {
sender,
key_range: q,
count,
}),
receiver,
None,
CanGc::note(),
)
})
}
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count>
fn Count(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
// Step 1. Let transaction be thiss transaction.
// Step 2. Let store be this's object store.
@ -466,12 +520,12 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
})
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name>
fn Name(&self) -> DOMString {
self.name.borrow().clone()
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname>
fn SetName(&self, value: DOMString) -> ErrorResult {
// Step 2. Let transaction be thiss transaction.
let transaction = &self.transaction;
@ -501,12 +555,12 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
// unimplemented!();
// }
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction>
fn Transaction(&self) -> DomRoot<IDBTransaction> {
self.transaction()
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement>
fn AutoIncrement(&self) -> bool {
self.has_key_generator()
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::iter::repeat_n;
use dom_struct::dom_struct;
use ipc_channel::router::ROUTER;
@ -15,6 +16,7 @@ use net_traits::indexeddb_thread::{
IndexedDBTxnMode, PutItemResult,
};
use profile_traits::ipc::IpcReceiver;
use script_bindings::conversions::SafeToJSValConvertible;
use serde::{Deserialize, Serialize};
use stylo_atoms::Atom;
@ -45,7 +47,9 @@ struct RequestListener {
pub enum IdbResult {
Key(IndexedDBKeyType),
Data(Vec<u8>),
Keys(Vec<IndexedDBKeyType>),
Value(Vec<u8>),
Values(Vec<Vec<u8>>),
Count(u64),
Error(Error),
None,
@ -57,9 +61,21 @@ impl From<IndexedDBKeyType> for IdbResult {
}
}
impl From<Vec<IndexedDBKeyType>> for IdbResult {
fn from(value: Vec<IndexedDBKeyType>) -> Self {
IdbResult::Keys(value)
}
}
impl From<Vec<u8>> for IdbResult {
fn from(value: Vec<u8>) -> Self {
IdbResult::Data(value)
IdbResult::Value(value)
}
}
impl From<Vec<Vec<u8>>> for IdbResult {
fn from(value: Vec<Vec<u8>>) -> Self {
IdbResult::Values(value)
}
}
@ -115,7 +131,16 @@ impl RequestListener {
IdbResult::Key(key) => {
key_type_to_jsval(GlobalScope::get_cx(), &key, answer.handle_mut())
},
IdbResult::Data(serialized_data) => {
IdbResult::Keys(keys) => {
rooted_vec!(let mut array <- repeat_n(UndefinedValue(), keys.len()));
for (count, key) in keys.into_iter().enumerate() {
rooted!(in(*cx) let mut val = UndefinedValue());
key_type_to_jsval(GlobalScope::get_cx(), &key, val.handle_mut());
array[count] = val.get();
}
array.safe_to_jsval(cx, answer.handle_mut());
},
IdbResult::Value(serialized_data) => {
let result = bincode::deserialize(&serialized_data)
.map_err(|_| Error::Data)
.and_then(|data| structuredclone::read(&global, data, answer.handle_mut()));
@ -125,6 +150,24 @@ impl RequestListener {
return;
};
},
IdbResult::Values(serialized_values) => {
rooted_vec!(let mut values <- repeat_n(UndefinedValue(), serialized_values.len()));
for (count, serialized_data) in serialized_values.into_iter().enumerate() {
rooted!(in(*cx) let mut val = UndefinedValue());
let result = bincode::deserialize(&serialized_data)
.map_err(|_| Error::Data)
.and_then(|data| {
structuredclone::read(&global, data, val.handle_mut())
});
if let Err(e) = result {
warn!("Error reading structuredclone data");
Self::handle_async_request_error(&global, cx, request, e);
return;
};
values[count] = val.get();
}
values.safe_to_jsval(cx, answer.handle_mut());
},
IdbResult::Count(count) => {
answer.handle_mut().set(DoubleValue(count as f64));
},

View file

@ -22,10 +22,10 @@ interface IDBObjectStore {
[NewObject, Throws] IDBRequest clear();
[NewObject, Throws] IDBRequest get(any query);
[NewObject, Throws] IDBRequest getKey(any query);
// [NewObject] IDBRequest getAll(optional any query,
// optional [EnforceRange] unsigned long count);
// [NewObject] IDBRequest getAllKeys(optional any query,
// optional [EnforceRange] unsigned long count);
[NewObject, Throws] IDBRequest getAll(optional any query,
optional [EnforceRange] unsigned long count);
[NewObject, Throws] IDBRequest getAllKeys(optional any query,
optional [EnforceRange] unsigned long count);
[NewObject, Throws] IDBRequest count(optional any query);
// [NewObject] IDBRequest openCursor(optional any query,

View file

@ -248,7 +248,7 @@ fn test_as_singleton() {
assert!(full_range.as_singleton().is_none());
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum PutItemResult {
Success,
CannotOverwrite,
@ -266,6 +266,17 @@ pub enum AsyncReadOnlyOperation {
key_range: IndexedDBKeyRange,
},
GetAllKeys {
sender: IpcSender<BackendResult<Vec<IndexedDBKeyType>>>,
key_range: IndexedDBKeyRange,
count: Option<u32>,
},
GetAllItems {
sender: IpcSender<BackendResult<Vec<Vec<u8>>>>,
key_range: IndexedDBKeyRange,
count: Option<u32>,
},
Count {
sender: IpcSender<BackendResult<u64>>,
key_range: IndexedDBKeyRange,

View file

@ -12,12 +12,6 @@
[Cross-realm IDBObjectStore::clear() method from detached <iframe> works as expected]
expected: FAIL
[Cross-realm IDBObjectStore::getAll() method from detached <iframe> works as expected]
expected: FAIL
[Cross-realm IDBObjectStore::getAllKeys() method from detached <iframe> works as expected]
expected: FAIL
[Cross-realm IDBObjectStore::count() method from detached <iframe> works as expected]
expected: FAIL

View file

@ -2,15 +2,9 @@
[IDBObjectStore.get exception order: TransactionInactiveError vs. DataError]
expected: FAIL
[IDBObjectStore.getAll exception order: InvalidStateError vs. TransactionInactiveError]
expected: FAIL
[IDBObjectStore.getAll exception order: TransactionInactiveError vs. DataError]
expected: FAIL
[IDBObjectStore.getAllKeys exception order: InvalidStateError vs. TransactionInactiveError]
expected: FAIL
[IDBObjectStore.getAllKeys exception order: TransactionInactiveError vs. DataError]
expected: FAIL
@ -37,15 +31,9 @@
[IDBObjectStore.get exception order: TransactionInactiveError vs. DataError]
expected: FAIL
[IDBObjectStore.getAll exception order: InvalidStateError vs. TransactionInactiveError]
expected: FAIL
[IDBObjectStore.getAll exception order: TransactionInactiveError vs. DataError]
expected: FAIL
[IDBObjectStore.getAllKeys exception order: InvalidStateError vs. TransactionInactiveError]
expected: FAIL
[IDBObjectStore.getAllKeys exception order: TransactionInactiveError vs. DataError]
expected: FAIL

View file

@ -11,12 +11,6 @@
[IDBObjectStore interface: attribute indexNames]
expected: FAIL
[IDBObjectStore interface: operation getAll(optional any, optional unsigned long)]
expected: FAIL
[IDBObjectStore interface: operation getAllKeys(optional any, optional unsigned long)]
expected: FAIL
[IDBObjectStore interface: operation openCursor(optional any, optional IDBCursorDirection)]
expected: FAIL
@ -127,12 +121,6 @@
[IDBObjectStore interface: attribute indexNames]
expected: FAIL
[IDBObjectStore interface: operation getAll(optional any, optional unsigned long)]
expected: FAIL
[IDBObjectStore interface: operation getAllKeys(optional any, optional unsigned long)]
expected: FAIL
[IDBObjectStore interface: operation openCursor(optional any, optional IDBCursorDirection)]
expected: FAIL