mirror of
https://github.com/servo/servo.git
synced 2025-09-23 05:10:09 +01:00
indexeddb: Implement openCursor
and openKeyCursor
for object store (#39080)
Continue on implementing indexeddb's cursor. This patch focuses on implementing the `openCursor` [1] and `openKeyCursor` [2] methods of the `IDBObjectStore` interface, which create and initialize cursors by running the iterate-a-cursor algorithm [3]. It also adds struct `IndexedDBRecord` to `components/shared/net/indexeddb_thread.rs`. This struct can later be used to implement the new `IDBRecord` interface [4]. [1] https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor [2] https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor [3] https://www.w3.org/TR/IndexedDB-2/#iterate-a-cursor [4] https://w3c.github.io/IndexedDB/#record-interface Testing: Pass WPT tests that were expected to fail. Fixes: Part of #38111 --------- Signed-off-by: Kingsley Yung <kingsley@kkoyung.dev>
This commit is contained in:
parent
1f63116bdd
commit
250c4cda00
26 changed files with 580 additions and 279 deletions
|
@ -8,8 +8,8 @@ use ipc_channel::ipc::IpcSender;
|
|||
use log::{error, info};
|
||||
use net_traits::indexeddb_thread::{
|
||||
AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, BackendError, BackendResult,
|
||||
CreateObjectResult, IndexedDBKeyRange, IndexedDBKeyType, IndexedDBTxnMode, KeyPath,
|
||||
PutItemResult,
|
||||
CreateObjectResult, IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord, IndexedDBTxnMode,
|
||||
KeyPath, PutItemResult,
|
||||
};
|
||||
use rusqlite::{Connection, Error, OptionalExtension, params};
|
||||
use sea_query::{Condition, Expr, ExprTrait, IntoCondition, SqliteQueryBuilder};
|
||||
|
@ -225,6 +225,16 @@ impl SqliteEngine {
|
|||
.map(|models| models.into_iter().map(|m| m.data).collect())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn get_all_records(
|
||||
connection: &Connection,
|
||||
store: object_store_model::Model,
|
||||
key_range: IndexedDBKeyRange,
|
||||
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, Error> {
|
||||
Self::get_all(connection, store, key_range, None)
|
||||
.map(|models| models.into_iter().map(|m| (m.key, m.data)).collect())
|
||||
}
|
||||
|
||||
fn put_item(
|
||||
connection: &Connection,
|
||||
store: object_store_model::Model,
|
||||
|
@ -507,6 +517,28 @@ impl KvsEngine for SqliteEngine {
|
|||
.map_err(|e| BackendError::DbErr(format!("{:?}", e))),
|
||||
);
|
||||
},
|
||||
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
|
||||
sender,
|
||||
key_range,
|
||||
}) => {
|
||||
let Ok(object_store) = process_object_store(object_store, &sender) else {
|
||||
continue;
|
||||
};
|
||||
let _ = sender.send(
|
||||
Self::get_all_records(&connection, object_store, key_range)
|
||||
.map(|records| {
|
||||
records
|
||||
.into_iter()
|
||||
.map(|(key, data)| IndexedDBRecord {
|
||||
key: bincode::deserialize(&key).unwrap(),
|
||||
primary_key: bincode::deserialize(&key).unwrap(),
|
||||
value: data,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.map_err(|e| BackendError::DbErr(format!("{:?}", e))),
|
||||
);
|
||||
},
|
||||
AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(sender)) => {
|
||||
let Ok(object_store) = process_object_store(object_store, &sender) else {
|
||||
continue;
|
||||
|
|
|
@ -8,15 +8,18 @@ use dom_struct::dom_struct;
|
|||
use js::jsapi::Heap;
|
||||
use js::jsval::{JSVal, UndefinedValue};
|
||||
use js::rust::MutableHandleValue;
|
||||
use net_traits::indexeddb_thread::{IndexedDBKeyRange, IndexedDBKeyType};
|
||||
use net_traits::indexeddb_thread::{IndexedDBKeyRange, IndexedDBKeyType, IndexedDBRecord};
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::{
|
||||
IDBCursorDirection, IDBCursorMethods,
|
||||
};
|
||||
use crate::dom::bindings::codegen::UnionTypes::IDBObjectStoreOrIDBIndex;
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbindex::IDBIndex;
|
||||
use crate::dom::idbobjectstore::IDBObjectStore;
|
||||
|
@ -93,7 +96,6 @@ impl IDBCursor {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
|
@ -120,6 +122,22 @@ impl IDBCursor {
|
|||
)
|
||||
}
|
||||
|
||||
fn set_position(&self, position: Option<IndexedDBKeyType>) {
|
||||
*self.position.borrow_mut() = position;
|
||||
}
|
||||
|
||||
fn set_key(&self, key: Option<IndexedDBKeyType>) {
|
||||
*self.key.borrow_mut() = key;
|
||||
}
|
||||
|
||||
fn set_object_store_position(&self, object_store_position: Option<IndexedDBKeyType>) {
|
||||
*self.object_store_position.borrow_mut() = object_store_position;
|
||||
}
|
||||
|
||||
pub(crate) fn set_request(&self, request: &IDBRequest) {
|
||||
self.request.set(Some(request));
|
||||
}
|
||||
|
||||
pub(crate) fn value(&self, mut out: MutableHandleValue) {
|
||||
out.set(self.value.get());
|
||||
}
|
||||
|
@ -174,3 +192,306 @@ impl IDBCursorMethods<crate::DomTypeHolder> for IDBCursor {
|
|||
.expect("IDBCursor.request should be set when cursor is opened")
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct containing parameters for
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#iterate-a-cursor>
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct IterationParam {
|
||||
pub(crate) cursor: Trusted<IDBCursor>,
|
||||
pub(crate) key: Option<IndexedDBKeyType>,
|
||||
pub(crate) primary_key: Option<IndexedDBKeyType>,
|
||||
pub(crate) count: Option<u32>,
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#iterate-a-cursor>
|
||||
///
|
||||
/// NOTE: Be cautious: this part of the specification seems to assume the cursor’s source is an
|
||||
/// index. Therefore,
|
||||
/// "record’s key" means the key of the record,
|
||||
/// "record’s value" means the primary key of the record, and
|
||||
/// "record’s referenced value" means the value of the record.
|
||||
pub(crate) fn iterate_cursor(
|
||||
global: &GlobalScope,
|
||||
cx: SafeJSContext,
|
||||
param: &IterationParam,
|
||||
records: Vec<IndexedDBRecord>,
|
||||
) -> Result<Option<DomRoot<IDBCursor>>, Error> {
|
||||
// Unpack IterationParam
|
||||
let cursor = param.cursor.root();
|
||||
let key = param.key.clone();
|
||||
let primary_key = param.primary_key.clone();
|
||||
let count = param.count;
|
||||
|
||||
// Step 1. Let source be cursor’s source.
|
||||
let source = &cursor.source;
|
||||
|
||||
// Step 2. Let direction be cursor’s direction.
|
||||
let direction = cursor.direction;
|
||||
|
||||
// Step 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
|
||||
if primary_key.is_some() {
|
||||
assert!(matches!(source, ObjectStoreOrIndex::Index(..)));
|
||||
assert!(matches!(
|
||||
direction,
|
||||
IDBCursorDirection::Next | IDBCursorDirection::Prev
|
||||
));
|
||||
}
|
||||
|
||||
// Step 4. Let records be the list of records in source.
|
||||
// NOTE: It is given as a function parameter.
|
||||
|
||||
// Step 5. Let range be cursor’s range.
|
||||
let range = &cursor.range;
|
||||
|
||||
// Step 6. Let position be cursor’s position.
|
||||
let mut position = cursor.position.borrow().clone();
|
||||
|
||||
// Step 7. Let object store position be cursor’s object store position.
|
||||
let object_store_position = cursor.object_store_position.borrow().clone();
|
||||
|
||||
// Step 8. If count is not given, let count be 1.
|
||||
let mut count = count.unwrap_or(1);
|
||||
|
||||
let mut found_record: Option<&IndexedDBRecord> = None;
|
||||
|
||||
// Step 9. While count is greater than 0:
|
||||
while count > 0 {
|
||||
// Step 9.1. Switch on direction:
|
||||
found_record = match direction {
|
||||
// "next"
|
||||
IDBCursorDirection::Next => records.iter().find(|record| {
|
||||
// Let found record be the first record in records which satisfy all of the
|
||||
// following requirements:
|
||||
|
||||
// If key is defined, the record’s key is greater than or equal to key.
|
||||
let requirement1 = || match &key {
|
||||
Some(key) => &record.key >= key,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// If primaryKey is defined, the record’s key is equal to key and the record’s
|
||||
// value is greater than or equal to primaryKey, or the record’s key is greater
|
||||
// than key.
|
||||
let requirement2 = || match &primary_key {
|
||||
Some(primary_key) => key.as_ref().is_some_and(|key| {
|
||||
(&record.key == key && &record.primary_key >= primary_key) ||
|
||||
&record.key > key
|
||||
}),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// If position is defined, and source is an object store, the record’s key is
|
||||
// greater than position.
|
||||
let requirement3 = || match (&position, source) {
|
||||
(Some(position), ObjectStoreOrIndex::ObjectStore(_)) => &record.key > position,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// If position is defined, and source is an index, the record’s key is equal to
|
||||
// position and the record’s value is greater than object store position or the
|
||||
// record’s key is greater than position.
|
||||
let requirement4 = || match (&position, source) {
|
||||
(Some(position), ObjectStoreOrIndex::Index(_)) => {
|
||||
(&record.key == position &&
|
||||
object_store_position.as_ref().is_some_and(
|
||||
|object_store_position| &record.primary_key > object_store_position,
|
||||
)) ||
|
||||
&record.key > position
|
||||
},
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// The record’s key is in range.
|
||||
let requirement5 = || range.contains(&record.key);
|
||||
|
||||
// NOTE: Use closures here for lazy computation on requirements.
|
||||
requirement1() &&
|
||||
requirement2() &&
|
||||
requirement3() &&
|
||||
requirement4() &&
|
||||
requirement5()
|
||||
}),
|
||||
// "nextunique"
|
||||
IDBCursorDirection::Nextunique => records.iter().find(|record| {
|
||||
// Let found record be the first record in records which satisfy all of the
|
||||
// following requirements:
|
||||
|
||||
// If key is defined, the record’s key is greater than or equal to key.
|
||||
let requirement1 = || match &key {
|
||||
Some(key) => &record.key >= key,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// If position is defined, the record’s key is greater than position.
|
||||
let requirement2 = || match &position {
|
||||
Some(position) => &record.key > position,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// The record’s key is in range.
|
||||
let requirement3 = || range.contains(&record.key);
|
||||
|
||||
// NOTE: Use closures here for lazy computation on requirements.
|
||||
requirement1() && requirement2() && requirement3()
|
||||
}),
|
||||
// "prev"
|
||||
IDBCursorDirection::Prev => {
|
||||
records.iter().rev().find(|&record| {
|
||||
// Let found record be the last record in records which satisfy all of the
|
||||
// following requirements:
|
||||
|
||||
// If key is defined, the record’s key is less than or equal to key.
|
||||
let requirement1 = || match &key {
|
||||
Some(key) => &record.key <= key,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// If primaryKey is defined, the record’s key is equal to key and the record’s
|
||||
// value is less than or equal to primaryKey, or the record’s key is less than
|
||||
// key.
|
||||
let requirement2 = || match &primary_key {
|
||||
Some(primary_key) => key.as_ref().is_some_and(|key| {
|
||||
(&record.key == key && &record.primary_key <= primary_key) ||
|
||||
&record.key < key
|
||||
}),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// If position is defined, and source is an object store, the record’s key is
|
||||
// less than position.
|
||||
let requirement3 = || match (&position, source) {
|
||||
(Some(position), ObjectStoreOrIndex::ObjectStore(_)) => {
|
||||
&record.key < position
|
||||
},
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// If position is defined, and source is an index, the record’s key is equal to
|
||||
// position and the record’s value is less than object store position or the
|
||||
// record’s key is less than position.
|
||||
let requirement4 = || match (&position, source) {
|
||||
(Some(position), ObjectStoreOrIndex::Index(_)) => {
|
||||
(&record.key == position &&
|
||||
object_store_position.as_ref().is_some_and(
|
||||
|object_store_position| {
|
||||
&record.primary_key < object_store_position
|
||||
},
|
||||
)) ||
|
||||
&record.key < position
|
||||
},
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// The record’s key is in range.
|
||||
let requirement5 = || range.contains(&record.key);
|
||||
|
||||
// NOTE: Use closures here for lazy computation on requirements.
|
||||
requirement1() &&
|
||||
requirement2() &&
|
||||
requirement3() &&
|
||||
requirement4() &&
|
||||
requirement5()
|
||||
})
|
||||
},
|
||||
// "prevunique"
|
||||
IDBCursorDirection::Prevunique => records
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|&record| {
|
||||
// Let temp record be the last record in records which satisfy all of the
|
||||
// following requirements:
|
||||
|
||||
// If key is defined, the record’s key is less than or equal to key.
|
||||
let requirement1 = || match &key {
|
||||
Some(key) => &record.key <= key,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// If position is defined, the record’s key is less than position.
|
||||
let requirement2 = || match &position {
|
||||
Some(position) => &record.key < position,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// The record’s key is in range.
|
||||
let requirement3 = || range.contains(&record.key);
|
||||
|
||||
// NOTE: Use closures here for lazy computation on requirements.
|
||||
requirement1() && requirement2() && requirement3()
|
||||
})
|
||||
// If temp record is defined, let found record be the first record in records
|
||||
// whose key is equal to temp record’s key.
|
||||
.map(|temp_record| {
|
||||
records
|
||||
.iter()
|
||||
.find(|&record| record.key == temp_record.key)
|
||||
.expect(
|
||||
"Record with key equal to temp record's key should exist in records",
|
||||
)
|
||||
}),
|
||||
};
|
||||
|
||||
match found_record {
|
||||
// Step 9.2. If found record is not defined, then:
|
||||
None => {
|
||||
// Step 9.2.1. Set cursor’s key to undefined.
|
||||
cursor.set_key(None);
|
||||
|
||||
// Step 9.2.2. If source is an index, set cursor’s object store position to undefined.
|
||||
if matches!(source, ObjectStoreOrIndex::Index(_)) {
|
||||
cursor.set_object_store_position(None);
|
||||
}
|
||||
|
||||
// Step 9.2.3. If cursor’s key only flag is unset, set cursor’s value to undefined.
|
||||
if !cursor.key_only {
|
||||
cursor.value.set(UndefinedValue());
|
||||
}
|
||||
|
||||
// Step 9.2.4. Return null.
|
||||
return Ok(None);
|
||||
},
|
||||
Some(found_record) => {
|
||||
// Step 9.3. Let position be found record’s key.
|
||||
position = Some(found_record.key.clone());
|
||||
|
||||
// Step 9.4. If source is an index, let object store position be found record’s value.
|
||||
if matches!(source, ObjectStoreOrIndex::Index(_)) {
|
||||
cursor.set_object_store_position(Some(found_record.primary_key.clone()));
|
||||
}
|
||||
|
||||
// Step 9.5. Decrease count by 1.
|
||||
count -= 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
let found_record =
|
||||
found_record.expect("The while loop above guarantees found_record is defined");
|
||||
|
||||
// Step 10. Set cursor’s position to position.
|
||||
cursor.set_position(position);
|
||||
|
||||
// Step 11. If source is an index, set cursor’s object store position to object store position.
|
||||
if let ObjectStoreOrIndex::Index(_) = source {
|
||||
cursor.set_object_store_position(object_store_position);
|
||||
}
|
||||
|
||||
// Step 12. Set cursor’s key to found record’s key.
|
||||
cursor.set_key(Some(found_record.key.clone()));
|
||||
|
||||
// Step 13. If cursor’s key only flag is unset, then:
|
||||
if !cursor.key_only {
|
||||
// Step 13.1. Let serialized be found record’s referenced value.
|
||||
// Step 13.2. Set cursor’s value to ! StructuredDeserialize(serialized, targetRealm)
|
||||
rooted!(in(*cx) let mut new_cursor_value = UndefinedValue());
|
||||
bincode::deserialize(&found_record.value)
|
||||
.map_err(|_| Error::Data)
|
||||
.and_then(|data| structuredclone::read(global, data, new_cursor_value.handle_mut()))?;
|
||||
cursor.value.set(new_cursor_value.get());
|
||||
}
|
||||
|
||||
// Step 14. Set cursor’s got value flag.
|
||||
cursor.got_value.set(true);
|
||||
|
||||
// Step 15. Return cursor.
|
||||
Ok(Some(cursor))
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ impl IDBCursorWithValue {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
|
|
|
@ -16,6 +16,7 @@ use script_bindings::conversions::SafeToJSValConvertible;
|
|||
use script_bindings::error::ErrorResult;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::IDBCursorDirection;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
|
||||
|
@ -24,12 +25,15 @@ use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
|
|||
// 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::error::{Error, Fallible};
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::domstringlist::DOMStringList;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbcursor::{IDBCursor, IterationParam, ObjectStoreOrIndex};
|
||||
use crate::dom::idbcursorwithvalue::IDBCursorWithValue;
|
||||
use crate::dom::idbrequest::IDBRequest;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::indexed_db::{
|
||||
|
@ -248,9 +252,91 @@ impl IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
|
||||
fn open_cursor(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
query: HandleValue,
|
||||
direction: IDBCursorDirection,
|
||||
key_only: bool,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
// Step 1. Let transaction be this object store handle's transaction.
|
||||
// Step 2. Let store be this object store handle's object store.
|
||||
|
||||
// Step 3. If store has been deleted, throw an "InvalidStateError" DOMException.
|
||||
self.verify_not_deleted()?;
|
||||
|
||||
// Step 4. If transaction is not active, 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. Rethrow any exceptions.
|
||||
//
|
||||
// The query parameter may be a key or an IDBKeyRange to use as the cursor's range. If null
|
||||
// or not given, an unbounded key range is used.
|
||||
let range = convert_value_to_key_range(cx, query, Some(false))?;
|
||||
|
||||
// Step 6. Let cursor be a new cursor with transaction set to transaction, an undefined
|
||||
// position, direction set to direction, got value flag unset, and undefined key and value.
|
||||
// The source of cursor is store. The range of cursor is range.
|
||||
//
|
||||
// NOTE: A cursor that has the key only flag unset implements the IDBCursorWithValue
|
||||
// interface as well.
|
||||
let cursor = if key_only {
|
||||
IDBCursor::new(
|
||||
&self.global(),
|
||||
&self.transaction,
|
||||
direction,
|
||||
false,
|
||||
ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
|
||||
range.clone(),
|
||||
key_only,
|
||||
can_gc,
|
||||
)
|
||||
} else {
|
||||
DomRoot::upcast(IDBCursorWithValue::new(
|
||||
&self.global(),
|
||||
&self.transaction,
|
||||
direction,
|
||||
false,
|
||||
ObjectStoreOrIndex::ObjectStore(Dom::from_ref(self)),
|
||||
range.clone(),
|
||||
key_only,
|
||||
can_gc,
|
||||
))
|
||||
};
|
||||
|
||||
// Step 7. 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 iterate a cursor as operation, using the current Realm as targetRealm, and
|
||||
// cursor.
|
||||
let iteration_param = IterationParam {
|
||||
cursor: Trusted::new(&cursor),
|
||||
key: None,
|
||||
primary_key: None,
|
||||
count: None,
|
||||
};
|
||||
let (sender, receiver) = indexed_db::create_channel(self.global());
|
||||
IDBRequest::execute_async(
|
||||
self,
|
||||
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
|
||||
sender,
|
||||
key_range: range,
|
||||
}),
|
||||
receiver,
|
||||
None,
|
||||
Some(iteration_param),
|
||||
can_gc,
|
||||
)
|
||||
.inspect(|request| cursor.set_request(request))
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
||||
|
@ -297,6 +383,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem { sender, key: q }),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
|
@ -322,6 +409,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(sender)),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
@ -351,6 +439,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
|
@ -382,6 +471,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
|
@ -419,6 +509,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
|
@ -456,6 +547,7 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
|
@ -486,11 +578,32 @@ impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|||
}),
|
||||
receiver,
|
||||
None,
|
||||
None,
|
||||
CanGc::note(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-opencursor>
|
||||
fn OpenCursor(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
query: HandleValue,
|
||||
direction: IDBCursorDirection,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
self.open_cursor(cx, query, direction, false, CanGc::note())
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-openkeycursor>
|
||||
fn OpenKeyCursor(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
query: HandleValue,
|
||||
direction: IDBCursorDirection,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
self.open_cursor(cx, query, direction, true, CanGc::note())
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name>
|
||||
fn Name(&self) -> DOMString {
|
||||
self.name.borrow().clone()
|
||||
|
|
|
@ -8,12 +8,12 @@ use std::iter::repeat_n;
|
|||
use dom_struct::dom_struct;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsval::{DoubleValue, JSVal, UndefinedValue};
|
||||
use js::jsval::{DoubleValue, JSVal, ObjectValue, UndefinedValue};
|
||||
use js::rust::HandleValue;
|
||||
use net_traits::IpcSend;
|
||||
use net_traits::indexeddb_thread::{
|
||||
AsyncOperation, BackendError, BackendResult, IndexedDBKeyType, IndexedDBThreadMsg,
|
||||
IndexedDBTxnMode, PutItemResult,
|
||||
AsyncOperation, AsyncReadOnlyOperation, BackendError, BackendResult, IndexedDBKeyType,
|
||||
IndexedDBRecord, IndexedDBThreadMsg, IndexedDBTxnMode, PutItemResult,
|
||||
};
|
||||
use profile_traits::ipc::IpcReceiver;
|
||||
use script_bindings::conversions::SafeToJSValConvertible;
|
||||
|
@ -27,13 +27,15 @@ use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransacti
|
|||
use crate::dom::bindings::error::{Error, Fallible, create_dom_exception};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
|
||||
use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object};
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::domexception::DOMException;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbcursor::{IterationParam, iterate_cursor};
|
||||
use crate::dom::idbcursorwithvalue::IDBCursorWithValue;
|
||||
use crate::dom::idbobjectstore::IDBObjectStore;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::indexed_db::key_type_to_jsval;
|
||||
|
@ -43,6 +45,7 @@ use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
|||
#[derive(Clone)]
|
||||
struct RequestListener {
|
||||
request: Trusted<IDBRequest>,
|
||||
iteration_param: Option<IterationParam>,
|
||||
}
|
||||
|
||||
pub enum IdbResult {
|
||||
|
@ -51,6 +54,7 @@ pub enum IdbResult {
|
|||
Value(Vec<u8>),
|
||||
Values(Vec<Vec<u8>>),
|
||||
Count(u64),
|
||||
Iterate(Vec<IndexedDBRecord>),
|
||||
Error(Error),
|
||||
None,
|
||||
}
|
||||
|
@ -88,6 +92,12 @@ impl From<PutItemResult> for IdbResult {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec<IndexedDBRecord>> for IdbResult {
|
||||
fn from(value: Vec<IndexedDBRecord>) -> Self {
|
||||
Self::Iterate(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for IdbResult {
|
||||
fn from(_value: ()) -> Self {
|
||||
Self::None
|
||||
|
@ -171,6 +181,33 @@ impl RequestListener {
|
|||
IdbResult::Count(count) => {
|
||||
answer.handle_mut().set(DoubleValue(count as f64));
|
||||
},
|
||||
IdbResult::Iterate(records) => {
|
||||
let param = self.iteration_param.as_ref().expect(
|
||||
"iteration_param must be provided by IDBRequest::execute_async for Iterate",
|
||||
);
|
||||
let cursor = match iterate_cursor(&global, cx, param, records) {
|
||||
Ok(cursor) => cursor,
|
||||
Err(e) => {
|
||||
warn!("Error reading structuredclone data");
|
||||
Self::handle_async_request_error(&global, cx, request, e);
|
||||
return;
|
||||
},
|
||||
};
|
||||
if let Some(cursor) = cursor {
|
||||
match cursor.downcast::<IDBCursorWithValue>() {
|
||||
Some(cursor_with_value) => {
|
||||
answer.handle_mut().set(ObjectValue(
|
||||
*cursor_with_value.reflector().get_jsobject(),
|
||||
));
|
||||
},
|
||||
None => {
|
||||
answer
|
||||
.handle_mut()
|
||||
.set(ObjectValue(*cursor.reflector().get_jsobject()));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
IdbResult::None => {
|
||||
// no-op
|
||||
},
|
||||
|
@ -313,6 +350,7 @@ impl IDBRequest {
|
|||
operation: AsyncOperation,
|
||||
receiver: IpcReceiver<BackendResult<T>>,
|
||||
request: Option<DomRoot<IDBRequest>>,
|
||||
iteration_param: Option<IterationParam>,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<DomRoot<IDBRequest>>
|
||||
where
|
||||
|
@ -346,8 +384,24 @@ impl IDBRequest {
|
|||
IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
|
||||
};
|
||||
|
||||
if matches!(
|
||||
operation,
|
||||
AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate { .. })
|
||||
) {
|
||||
assert!(
|
||||
iteration_param.is_some(),
|
||||
"iteration_param must be provided for Iterate"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
iteration_param.is_none(),
|
||||
"iteration_param should not be provided for operation other than Iterate"
|
||||
);
|
||||
}
|
||||
|
||||
let response_listener = RequestListener {
|
||||
request: Trusted::new(&request),
|
||||
iteration_param,
|
||||
};
|
||||
|
||||
let task_source = global
|
||||
|
|
|
@ -222,15 +222,14 @@ pub fn convert_value_to_key(
|
|||
Ok(ConversionResult::Invalid)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#convert-a-value-to-a-key-range
|
||||
/// <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.
|
||||
// Step 1. If value is a key range, return value.
|
||||
if input.is_object() {
|
||||
rooted!(in(*cx) let object = input.to_object());
|
||||
unsafe {
|
||||
|
@ -240,11 +239,30 @@ pub fn convert_value_to_key_range(
|
|||
}
|
||||
}
|
||||
}
|
||||
// Step 2.
|
||||
if (input.get().is_undefined() || input.get().is_null()) && null_disallowed {
|
||||
return Err(Error::Data);
|
||||
|
||||
// Step 2. If value is undefined or is null, then throw a "DataError" DOMException if null
|
||||
// disallowed flag is set, or return an unbounded key range otherwise.
|
||||
if input.get().is_undefined() || input.get().is_null() {
|
||||
if null_disallowed.is_some_and(|flag| flag) {
|
||||
return Err(Error::Data);
|
||||
} else {
|
||||
return Ok(IndexedDBKeyRange {
|
||||
lower: None,
|
||||
upper: None,
|
||||
lower_open: Default::default(),
|
||||
upper_open: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let key = convert_value_to_key(cx, input, None)?.into_result()?;
|
||||
|
||||
// Step 3. Let key be the result of running the steps to convert a value to a key with value.
|
||||
// Rethrow any exceptions.
|
||||
let key = convert_value_to_key(cx, input, None)?;
|
||||
|
||||
// Step 4. If key is invalid, throw a "DataError" DOMException.
|
||||
let key = key.into_result()?;
|
||||
|
||||
// Step 5. Return a key range containing only key.
|
||||
Ok(IndexedDBKeyRange::only(key))
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ interface IDBObjectStore {
|
|||
optional [EnforceRange] unsigned long count);
|
||||
[NewObject, Throws] IDBRequest count(optional any query);
|
||||
|
||||
// [NewObject] IDBRequest openCursor(optional any query,
|
||||
// optional IDBCursorDirection direction = "next");
|
||||
// [NewObject] IDBRequest openKeyCursor(optional any query,
|
||||
// optional IDBCursorDirection direction = "next");
|
||||
[NewObject, Throws] IDBRequest openCursor(optional any query,
|
||||
optional IDBCursorDirection direction = "next");
|
||||
[NewObject, Throws] IDBRequest openKeyCursor(optional any query,
|
||||
optional IDBCursorDirection direction = "next");
|
||||
|
||||
// IDBIndex index(DOMString name);
|
||||
|
||||
|
|
|
@ -233,6 +233,14 @@ impl IndexedDBKeyRange {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IndexedDB/#record-snapshot>
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct IndexedDBRecord {
|
||||
pub key: IndexedDBKeyType,
|
||||
pub primary_key: IndexedDBKeyType,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_singleton() {
|
||||
let key = IndexedDBKeyType::Number(1.0);
|
||||
|
@ -281,6 +289,10 @@ pub enum AsyncReadOnlyOperation {
|
|||
sender: IpcSender<BackendResult<u64>>,
|
||||
key_range: IndexedDBKeyRange,
|
||||
},
|
||||
Iterate {
|
||||
sender: IpcSender<BackendResult<Vec<IndexedDBRecord>>>,
|
||||
key_range: IndexedDBKeyRange,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue