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