mirror of
https://github.com/servo/servo.git
synced 2025-09-30 00:29:14 +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,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))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue