script: implement IDBKeyRange (#38268)

#37684 provided the backend for this change. The key range interface
just wraps a `IndexedDBKeyRange` and exposes the methods from it as per
the spec.

Spec: https://www.w3.org/TR/IndexedDB-2/#keyrange
Testing: WPT tests (some regressions have been exposed, but that's fine)

---------

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Ashwin Naren 2025-07-26 08:29:15 +05:30 committed by GitHub
parent 8b2a5fca54
commit c2ed599eb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1281 additions and 196 deletions

View file

@ -0,0 +1,128 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 dom_struct::dom_struct;
use js::gc::MutableHandleValue;
use js::rust::HandleValue;
use net_traits::indexeddb_thread::IndexedDBKeyRange;
use script_bindings::codegen::GenericBindings::IDBKeyRangeBinding::IDBKeyRangeMethods;
use script_bindings::root::DomRoot;
use script_bindings::script_runtime::CanGc;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::import::module::SafeJSContext;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::globalscope::GlobalScope;
use crate::indexed_db::{convert_value_to_key, key_type_to_jsval};
#[dom_struct]
pub struct IDBKeyRange {
reflector_: Reflector,
#[no_trace]
inner: IndexedDBKeyRange,
}
impl IDBKeyRange {
pub fn new_inherited(inner: IndexedDBKeyRange) -> Self {
IDBKeyRange {
reflector_: Reflector::new(),
inner,
}
}
pub fn new(global: &GlobalScope, inner: IndexedDBKeyRange, can_gc: CanGc) -> DomRoot<Self> {
reflect_dom_object(Box::new(IDBKeyRange::new_inherited(inner)), global, can_gc)
}
#[expect(unused)]
pub fn inner(&self) -> &IndexedDBKeyRange {
&self.inner
}
}
impl IDBKeyRangeMethods<crate::DomTypeHolder> for IDBKeyRange {
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-lower
fn Lower(&self, cx: SafeJSContext, answer: MutableHandleValue) {
if let Some(lower) = self.inner.lower.as_ref() {
key_type_to_jsval(cx, lower, answer);
}
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-upper
fn Upper(&self, cx: SafeJSContext, answer: MutableHandleValue) {
if let Some(upper) = self.inner.upper.as_ref() {
key_type_to_jsval(cx, upper, answer);
}
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-loweropen
fn LowerOpen(&self) -> bool {
self.inner.lower_open
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-upperopen
fn UpperOpen(&self) -> bool {
self.inner.upper_open
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-only
fn Only(
cx: SafeJSContext,
global: &GlobalScope,
value: HandleValue,
) -> Fallible<DomRoot<IDBKeyRange>> {
let key = convert_value_to_key(cx, value, None)?;
let inner = IndexedDBKeyRange::only(key);
Ok(IDBKeyRange::new(global, inner, CanGc::note()))
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-lowerbound
fn LowerBound(
cx: SafeJSContext,
global: &GlobalScope,
lower: HandleValue,
open: bool,
) -> Fallible<DomRoot<IDBKeyRange>> {
let key = convert_value_to_key(cx, lower, None)?;
let inner = IndexedDBKeyRange::lower_bound(key, open);
Ok(IDBKeyRange::new(global, inner, CanGc::note()))
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-upperbound
fn UpperBound(
cx: SafeJSContext,
global: &GlobalScope,
upper: HandleValue,
open: bool,
) -> Fallible<DomRoot<IDBKeyRange>> {
let key = convert_value_to_key(cx, upper, None)?;
let inner = IndexedDBKeyRange::upper_bound(key, open);
Ok(IDBKeyRange::new(global, inner, CanGc::note()))
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-bound
fn Bound(
cx: SafeJSContext,
global: &GlobalScope,
lower: HandleValue,
upper: HandleValue,
lower_open: bool,
upper_open: bool,
) -> Fallible<DomRoot<IDBKeyRange>> {
let lower_key = convert_value_to_key(cx, lower, None)?;
let upper_key = convert_value_to_key(cx, upper, None)?;
let inner =
IndexedDBKeyRange::new(Some(lower_key), Some(upper_key), lower_open, upper_open);
Ok(IDBKeyRange::new(global, inner, CanGc::note()))
}
// https://www.w3.org/TR/IndexedDB-2/#dom-idbkeyrange-_includes
fn Includes(&self, cx: SafeJSContext, value: HandleValue) -> Fallible<bool> {
let key = convert_value_to_key(cx, value, None)?;
if self.inner.contains(&key) {
return Ok(true);
}
Ok(false)
}
}

View file

@ -422,6 +422,7 @@ pub(crate) mod htmlunknownelement;
pub(crate) mod htmlvideoelement;
pub(crate) mod idbdatabase;
pub(crate) mod idbfactory;
pub(crate) mod idbkeyrange;
pub(crate) mod idbobjectstore;
pub(crate) mod idbopendbrequest;
pub(crate) mod idbrequest;

View file

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#keyrange
*
*/
// https://w3c.github.io/IndexedDB/#keyrange
[Pref="dom_indexeddb_enabled", Exposed=(Window,Worker)]
interface IDBKeyRange {
readonly attribute any lower;
readonly attribute any upper;
readonly attribute boolean lowerOpen;
readonly attribute boolean upperOpen;
// Static construction methods:
[Throws, NewObject] static IDBKeyRange only(any value);
[Throws, NewObject] static IDBKeyRange lowerBound(any lower, optional boolean open = false);
[Throws, NewObject] static IDBKeyRange upperBound(any upper, optional boolean open = false);
[Throws, NewObject] static IDBKeyRange bound(any lower,
any upper,
optional boolean lowerOpen = false,
optional boolean upperOpen = false);
[Throws] boolean _includes(any key);
};

View file

@ -5,6 +5,7 @@
use std::cmp::{PartialEq, PartialOrd};
use ipc_channel::ipc::IpcSender;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use servo_url::origin::ImmutableOrigin;
@ -17,7 +18,7 @@ pub enum IndexedDBTxnMode {
}
/// <https://www.w3.org/TR/IndexedDB-2/#key-type>
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum IndexedDBKeyType {
Number(f64),
String(String),
@ -113,7 +114,7 @@ impl PartialEq for IndexedDBKeyType {
}
// <https://www.w3.org/TR/IndexedDB-2/#key-range>
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
#[allow(unused)]
pub struct IndexedDBKeyRange {
pub lower: Option<IndexedDBKeyType>,
@ -133,6 +134,42 @@ impl From<IndexedDBKeyType> for IndexedDBKeyRange {
}
impl IndexedDBKeyRange {
pub fn only(key: IndexedDBKeyType) -> Self {
Self::from(key)
}
pub fn new(
lower: Option<IndexedDBKeyType>,
upper: Option<IndexedDBKeyType>,
lower_open: bool,
upper_open: bool,
) -> Self {
IndexedDBKeyRange {
lower,
upper,
lower_open,
upper_open,
}
}
pub fn lower_bound(key: IndexedDBKeyType, open: bool) -> Self {
IndexedDBKeyRange {
lower: Some(key),
upper: None,
lower_open: open,
upper_open: false,
}
}
pub fn upper_bound(key: IndexedDBKeyType, open: bool) -> Self {
IndexedDBKeyRange {
lower: None,
upper: Some(key),
lower_open: false,
upper_open: open,
}
}
// <https://www.w3.org/TR/IndexedDB-2/#in>
pub fn contains(&self, key: &IndexedDBKeyType) -> bool {
// A key is in a key range if both of the following conditions are fulfilled:
@ -150,6 +187,17 @@ impl IndexedDBKeyRange {
.is_none_or(|upper| key < upper || (!self.upper_open && key == upper));
lower_bound_condition && upper_bound_condition
}
pub fn is_singleton(&self) -> bool {
self.lower == self.upper && !self.lower_open && !self.upper_open
}
pub fn as_singleton(&self) -> Option<&IndexedDBKeyType> {
if self.is_singleton() {
return Some(self.lower.as_ref().unwrap());
}
None
}
}
#[derive(Debug, Deserialize, Serialize)]