Skip to main content

script/dom/indexeddb/
idbindex.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4use dom_struct::dom_struct;
5use js::context::JSContext;
6use js::conversions::ToJSValConvertible;
7use js::gc::MutableHandleValue;
8use js::rust::HandleValue;
9use script_bindings::cell::DomRefCell;
10use script_bindings::codegen::GenericBindings::IDBIndexBinding::IDBIndexMethods;
11use script_bindings::codegen::GenericBindings::IDBTransactionBinding::IDBTransactionMode;
12use script_bindings::error::{Error, ErrorResult, Fallible};
13use script_bindings::reflector::{Reflector, reflect_dom_object};
14use script_bindings::str::DOMString;
15use storage_traits::indexeddb::{AsyncOperation, AsyncReadOnlyOperation};
16
17use crate::dom::bindings::codegen::Bindings::IDBCursorBinding::IDBCursorDirection;
18use crate::dom::bindings::refcounted::Trusted;
19use crate::dom::bindings::reflector::DomGlobal;
20use crate::dom::bindings::root::{Dom, DomRoot};
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::idbobjectstore::KeyPath;
23use crate::dom::indexeddb::idbcursor::{IDBCursor, IterationParam, ObjectStoreOrIndex};
24use crate::dom::indexeddb::idbcursorwithvalue::IDBCursorWithValue;
25use crate::dom::indexeddb::idbobjectstore::IDBObjectStore;
26use crate::dom::indexeddb::idbrequest::IDBRequest;
27use crate::dom::indexeddb::key::convert_value_to_key_range;
28
29#[dom_struct]
30pub(crate) struct IDBIndex {
31    reflector_: Reflector,
32    object_store: Dom<IDBObjectStore>,
33    name: DomRefCell<DOMString>,
34    multi_entry: bool,
35    unique: bool,
36    key_path: KeyPath,
37}
38
39impl IDBIndex {
40    pub fn new_inherited(
41        object_store: &IDBObjectStore,
42        name: DOMString,
43        multi_entry: bool,
44        unique: bool,
45        key_path: KeyPath,
46    ) -> IDBIndex {
47        IDBIndex {
48            reflector_: Reflector::new(),
49            object_store: Dom::from_ref(object_store),
50            name: DomRefCell::new(name),
51            multi_entry,
52            unique,
53            key_path,
54        }
55    }
56
57    pub fn new(
58        cx: &mut JSContext,
59        global: &GlobalScope,
60        object_store: &IDBObjectStore,
61        name: DOMString,
62        multi_entry: bool,
63        unique: bool,
64        key_path: KeyPath,
65    ) -> DomRoot<IDBIndex> {
66        reflect_dom_object(
67            cx,
68            Box::new(IDBIndex::new_inherited(
69                object_store,
70                name,
71                multi_entry,
72                unique,
73                key_path,
74            )),
75            global,
76        )
77    }
78
79    pub(crate) fn object_store(&self) -> DomRoot<IDBObjectStore> {
80        DomRoot::from_ref(&self.object_store)
81    }
82
83    pub(crate) fn verify_not_deleted(&self) -> ErrorResult {
84        let stored_name = self.name.borrow().clone();
85        let transaction = self.object_store.transaction();
86        if !self.object_store.has_index(&stored_name) ||
87            !transaction
88                .get_db()
89                .object_store_exists(&self.object_store.get_name())
90        {
91            return Err(Error::InvalidState(Some(
92                "Index or its object store has been deleted".to_owned(),
93            )));
94        }
95        Ok(())
96    }
97
98    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbindex-opencursor>
99    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbindex-openkeycursor>
100    fn open_cursor(
101        &self,
102        cx: &mut JSContext,
103        query: HandleValue,
104        direction: IDBCursorDirection,
105        key_only: bool,
106    ) -> Fallible<DomRoot<IDBRequest>> {
107        // Step 1. Let transaction be this’s object store's transaction.
108        let transaction = self.object_store.transaction();
109
110        // Step 2. If index or its object store has been deleted, throw an "InvalidStateError" DOMException.
111        self.verify_not_deleted()?;
112
113        // Step 3. If transaction is not active, throw a "TransactionInactiveError" DOMException.
114        if !transaction.is_active() || !transaction.is_usable() {
115            return Err(Error::TransactionInactive(Some(
116                "Transaction is not active".to_owned(),
117            )));
118        }
119
120        // Step 4. Let range be the result of running the steps to convert a value to a key range with query.
121        let range = convert_value_to_key_range(cx, query, Some(false))?;
122
123        // Step 5. Let cursor be a new cursor with transaction, direction, source: Index, range.
124        let cursor = if key_only {
125            IDBCursor::new(
126                cx,
127                &self.global(),
128                &transaction,
129                direction,
130                false,
131                ObjectStoreOrIndex::Index(Dom::from_ref(self)),
132                range.clone(),
133                key_only,
134            )
135        } else {
136            DomRoot::upcast(IDBCursorWithValue::new(
137                cx,
138                &self.global(),
139                &transaction,
140                direction,
141                false,
142                ObjectStoreOrIndex::Index(Dom::from_ref(self)),
143                range.clone(),
144                key_only,
145            ))
146        };
147
148        // Step 6. Asynchronously execute a request with iteration_param.
149        let iteration_param = IterationParam {
150            cursor: Trusted::new(&cursor),
151            key: None,
152            primary_key: None,
153            count: None,
154        };
155
156        IDBRequest::execute_async(
157            cx,
158            &self.object_store,
159            |callback| {
160                AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Iterate {
161                    callback,
162                    key_range: range,
163                })
164            },
165            None,
166            Some(iteration_param),
167        )
168        .inspect(|request| cursor.set_request(request))
169    }
170}
171
172impl IDBIndexMethods<crate::DomTypeHolder> for IDBIndex {
173    /// <https://www.w3.org/TR/IndexedDB/#dom-idbindex-name>
174    fn Name(&self) -> DOMString {
175        self.name.borrow().clone()
176    }
177
178    /// <https://www.w3.org/TR/IndexedDB/#ref-for-dom-idbindex-name%E2%91%A2>
179    fn SetName(&self, name: DOMString) -> ErrorResult {
180        // Step 1: Let name be the given value.
181        // Step 2: Let transaction be this’s transaction.
182        let transaction = self.object_store.transaction();
183
184        // Step 3: Let index be this’s index.
185        // We do not have an explicit object representing the underlying index.
186
187        // Step 4: If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
188        if transaction.get_mode() != IDBTransactionMode::Versionchange {
189            return Err(Error::InvalidState(Some(
190                "Transaction is not an upgrade transaction".to_owned(),
191            )));
192        }
193
194        // Step 5: If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException.
195        if !transaction.is_active() {
196            return Err(Error::TransactionInactive(Some(
197                "Transaction is not active while updating index name".to_owned(),
198            )));
199        }
200
201        // Step 6: If index or index’s object store has been deleted, throw an "InvalidStateError" DOMException.
202        let mut stored_name = self.name.borrow_mut();
203        if !self.object_store.has_index(&stored_name) ||
204            !transaction
205                .get_db()
206                .object_store_exists(&self.object_store.get_name())
207        {
208            return Err(Error::InvalidState(Some(
209                "Index or its object store has been deleted".to_owned(),
210            )));
211        }
212
213        // Step 7: If index’s name is equal to name, terminate these steps.
214        if *stored_name == name {
215            return Ok(());
216        }
217
218        // Step 8: If an index named name already exists in index’s object store, throw a "ConstraintError" DOMException.
219        if self.object_store.has_index(&name) {
220            return Err(Error::Constraint(Some(
221                "An index with the given name already exists".to_owned(),
222            )));
223        }
224
225        // Step 9: Set index’s name to name.
226        self.object_store.rename_index(&stored_name, &name);
227
228        // Step 10: Set this’s name to name.
229        *stored_name = name;
230        Ok(())
231    }
232
233    /// <https://www.w3.org/TR/IndexedDB/#dom-idbindex-objectstore>
234    fn ObjectStore(&self) -> DomRoot<IDBObjectStore> {
235        self.object_store.as_rooted()
236    }
237
238    /// <https://www.w3.org/TR/IndexedDB/#dom-idbindex-multientry>
239    fn MultiEntry(&self) -> bool {
240        self.multi_entry
241    }
242
243    /// <https://www.w3.org/TR/IndexedDB/#dom-idbindex-unique>
244    fn Unique(&self) -> bool {
245        self.unique
246    }
247
248    /// <https://www.w3.org/TR/IndexedDB/#dom-idbindex-keypath>
249    fn KeyPath(&self, cx: &mut JSContext, retval: MutableHandleValue) {
250        match &self.key_path {
251            KeyPath::String(string) => {
252                string.to_jsval(cx, retval);
253            },
254            KeyPath::StringSequence(sequence) => {
255                sequence.to_jsval(cx, retval);
256            },
257        }
258    }
259
260    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbindex-opencursor>
261    fn OpenCursor(
262        &self,
263        cx: &mut JSContext,
264        query: HandleValue,
265        direction: IDBCursorDirection,
266    ) -> Fallible<DomRoot<IDBRequest>> {
267        self.open_cursor(cx, query, direction, false)
268    }
269
270    /// <https://www.w3.org/TR/IndexedDB-3/#dom-idbindex-openkeycursor>
271    fn OpenKeyCursor(
272        &self,
273        cx: &mut JSContext,
274        query: HandleValue,
275        direction: IDBCursorDirection,
276    ) -> Fallible<DomRoot<IDBRequest>> {
277        self.open_cursor(cx, query, direction, true)
278    }
279}