1/*
2 * Copyright (C) 2015 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "IDBCursor.h"
28
29#if ENABLE(INDEXED_DATABASE)
30
31#include "IDBBindingUtilities.h"
32#include "IDBDatabase.h"
33#include "IDBGetResult.h"
34#include "IDBIndex.h"
35#include "IDBIterateCursorData.h"
36#include "IDBObjectStore.h"
37#include "IDBRequest.h"
38#include "IDBTransaction.h"
39#include "Logging.h"
40#include "ScriptExecutionContext.h"
41#include <JavaScriptCore/HeapInlines.h>
42#include <JavaScriptCore/JSCJSValueInlines.h>
43#include <JavaScriptCore/StrongInlines.h>
44#include <wtf/IsoMallocInlines.h>
45
46namespace WebCore {
47using namespace JSC;
48
49WTF_MAKE_ISO_ALLOCATED_IMPL(IDBCursor);
50
51Ref<IDBCursor> IDBCursor::create(IDBObjectStore& objectStore, const IDBCursorInfo& info)
52{
53 return adoptRef(*new IDBCursor(objectStore, info));
54}
55
56Ref<IDBCursor> IDBCursor::create(IDBIndex& index, const IDBCursorInfo& info)
57{
58 return adoptRef(*new IDBCursor(index, info));
59}
60
61IDBCursor::IDBCursor(IDBObjectStore& objectStore, const IDBCursorInfo& info)
62 : m_info(info)
63 , m_source(&objectStore)
64{
65 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
66}
67
68IDBCursor::IDBCursor(IDBIndex& index, const IDBCursorInfo& info)
69 : m_info(info)
70 , m_source(&index)
71{
72 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
73}
74
75IDBCursor::~IDBCursor()
76{
77 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
78}
79
80bool IDBCursor::sourcesDeleted() const
81{
82 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
83
84 return WTF::switchOn(m_source,
85 [] (const RefPtr<IDBObjectStore>& objectStore) { return objectStore->isDeleted(); },
86 [] (const RefPtr<IDBIndex>& index) { return index->isDeleted() || index->objectStore().isDeleted(); }
87 );
88}
89
90IDBObjectStore& IDBCursor::effectiveObjectStore() const
91{
92 return WTF::switchOn(m_source,
93 [] (const RefPtr<IDBObjectStore>& objectStore) -> IDBObjectStore& { return *objectStore; },
94 [] (const RefPtr<IDBIndex>& index) -> IDBObjectStore& { return index->objectStore(); }
95 );
96}
97
98IDBTransaction& IDBCursor::transaction() const
99{
100 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
101 return effectiveObjectStore().transaction();
102}
103
104ExceptionOr<Ref<IDBRequest>> IDBCursor::update(ExecState& state, JSValue value)
105{
106 LOG(IndexedDB, "IDBCursor::update");
107 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
108
109 if (sourcesDeleted())
110 return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s };
111
112 if (!transaction().isActive())
113 return Exception { TransactionInactiveError, "Failed to execute 'update' on 'IDBCursor': The transaction is inactive or finished."_s };
114
115 if (transaction().isReadOnly())
116 return Exception { ReadonlyError, "Failed to execute 'update' on 'IDBCursor': The record may not be updated inside a read-only transaction."_s };
117
118 if (!m_gotValue)
119 return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s };
120
121 if (!isKeyCursorWithValue())
122 return Exception { InvalidStateError, "Failed to execute 'update' on 'IDBCursor': The cursor is a key cursor."_s };
123
124 auto& objectStore = effectiveObjectStore();
125 auto& optionalKeyPath = objectStore.info().keyPath();
126 const bool usesInLineKeys = !!optionalKeyPath;
127 if (usesInLineKeys) {
128 RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, optionalKeyPath.value());
129 IDBKeyData keyPathKeyData(keyPathKey.get());
130 if (!keyPathKey || keyPathKeyData != m_primaryKeyData)
131 return Exception { DataError, "Failed to execute 'update' on 'IDBCursor': The effective object store of this cursor uses in-line keys and evaluating the key path of the value parameter results in a different value than the cursor's effective key."_s };
132 }
133
134 auto putResult = effectiveObjectStore().putForCursorUpdate(state, value, m_primaryKey.copyRef());
135 if (putResult.hasException())
136 return putResult.releaseException();
137
138 auto request = putResult.releaseReturnValue();
139 request->setSource(*this);
140
141 return request;
142}
143
144ExceptionOr<void> IDBCursor::advance(unsigned count)
145{
146 LOG(IndexedDB, "IDBCursor::advance");
147 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
148
149 if (!m_request)
150 return Exception { InvalidStateError };
151
152 if (!count)
153 return Exception { TypeError, "Failed to execute 'advance' on 'IDBCursor': A count argument with value 0 (zero) was supplied, must be greater than 0."_s };
154
155 if (!transaction().isActive())
156 return Exception { TransactionInactiveError, "Failed to execute 'advance' on 'IDBCursor': The transaction is inactive or finished."_s };
157
158 if (sourcesDeleted())
159 return Exception { InvalidStateError, "Failed to execute 'advance' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s };
160
161 if (!m_gotValue)
162 return Exception { InvalidStateError, "Failed to execute 'advance' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s };
163
164 m_gotValue = false;
165
166 uncheckedIterateCursor(IDBKeyData(), count);
167
168 return { };
169}
170
171ExceptionOr<void> IDBCursor::continuePrimaryKey(ExecState& state, JSValue keyValue, JSValue primaryKeyValue)
172{
173 if (!m_request)
174 return Exception { InvalidStateError };
175
176 if (!transaction().isActive())
177 return Exception { TransactionInactiveError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The transaction is inactive or finished."_s };
178
179 if (sourcesDeleted())
180 return Exception { InvalidStateError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s };
181
182 if (!WTF::holds_alternative<RefPtr<IDBIndex>>(m_source))
183 return Exception { InvalidAccessError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source is not an index."_s };
184
185 auto direction = m_info.cursorDirection();
186 if (direction != IndexedDB::CursorDirection::Next && direction != IndexedDB::CursorDirection::Prev)
187 return Exception { InvalidAccessError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's direction must be either \"next\" or \"prev\"."_s };
188
189 if (!m_gotValue)
190 return Exception { InvalidStateError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s };
191
192 RefPtr<IDBKey> key = scriptValueToIDBKey(state, keyValue);
193 if (!key->isValid())
194 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is not a valid key."_s };
195
196 RefPtr<IDBKey> primaryKey = scriptValueToIDBKey(state, primaryKeyValue);
197 if (!primaryKey->isValid())
198 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The second parameter is not a valid key."_s };
199
200 IDBKeyData keyData = { key.get() };
201 IDBKeyData primaryKeyData = { primaryKey.get() };
202
203 if (keyData < m_keyData && direction == IndexedDB::CursorDirection::Next)
204 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is less than this cursor's position and this cursor's direction is \"next\"."_s };
205
206 if (keyData > m_keyData && direction == IndexedDB::CursorDirection::Prev)
207 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is greater than this cursor's position and this cursor's direction is \"prev\"."_s };
208
209 if (keyData == m_keyData) {
210 if (primaryKeyData <= m_primaryKeyData && direction == IndexedDB::CursorDirection::Next)
211 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position less-than-or-equal-to this cursor's position and this cursor's direction is \"next\"."_s };
212 if (primaryKeyData >= m_primaryKeyData && direction == IndexedDB::CursorDirection::Prev)
213 return Exception { DataError, "Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position greater-than-or-equal-to this cursor's position and this cursor's direction is \"prev\"."_s };
214 }
215
216 m_gotValue = false;
217
218 uncheckedIterateCursor(keyData, primaryKeyData);
219
220 return { };
221}
222
223ExceptionOr<void> IDBCursor::continueFunction(ExecState& execState, JSValue keyValue)
224{
225 RefPtr<IDBKey> key;
226 if (!keyValue.isUndefined())
227 key = scriptValueToIDBKey(execState, keyValue);
228
229 return continueFunction(key.get());
230}
231
232ExceptionOr<void> IDBCursor::continueFunction(const IDBKeyData& key)
233{
234 LOG(IndexedDB, "IDBCursor::continueFunction (to key %s)", key.loggingString().utf8().data());
235 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
236
237 if (!m_request)
238 return Exception { InvalidStateError };
239
240 if (!transaction().isActive())
241 return Exception { TransactionInactiveError, "Failed to execute 'continue' on 'IDBCursor': The transaction is inactive or finished."_s };
242
243 if (sourcesDeleted())
244 return Exception { InvalidStateError, "Failed to execute 'continue' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s };
245
246 if (!m_gotValue)
247 return Exception { InvalidStateError, "Failed to execute 'continue' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s };
248
249 if (!key.isNull() && !key.isValid())
250 return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is not a valid key."_s };
251
252 if (m_info.isDirectionForward()) {
253 if (!key.isNull() && key.compare(m_keyData) <= 0)
254 return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is less than or equal to this cursor's position."_s };
255 } else {
256 if (!key.isNull() && key.compare(m_keyData) >= 0)
257 return Exception { DataError, "Failed to execute 'continue' on 'IDBCursor': The parameter is greater than or equal to this cursor's position."_s };
258 }
259
260 m_gotValue = false;
261
262 uncheckedIterateCursor(key, 0);
263
264 return { };
265}
266
267void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, unsigned count)
268{
269 ASSERT(m_request);
270 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
271
272 m_request->willIterateCursor(*this);
273 transaction().iterateCursor(*this, { key, { }, count });
274}
275
276void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, const IDBKeyData& primaryKey)
277{
278 ASSERT(m_request);
279 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
280
281 m_request->willIterateCursor(*this);
282 transaction().iterateCursor(*this, { key, primaryKey, 0 });
283}
284
285ExceptionOr<Ref<WebCore::IDBRequest>> IDBCursor::deleteFunction(ExecState& state)
286{
287 LOG(IndexedDB, "IDBCursor::deleteFunction");
288 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
289
290 if (sourcesDeleted())
291 return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor's source or effective object store has been deleted."_s };
292
293 if (!transaction().isActive())
294 return Exception { TransactionInactiveError, "Failed to execute 'delete' on 'IDBCursor': The transaction is inactive or finished."_s };
295
296 if (transaction().isReadOnly())
297 return Exception { ReadonlyError, "Failed to execute 'delete' on 'IDBCursor': The record may not be deleted inside a read-only transaction."_s };
298
299 if (!m_gotValue)
300 return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor is being iterated or has iterated past its end."_s };
301
302 if (!isKeyCursorWithValue())
303 return Exception { InvalidStateError, "Failed to execute 'delete' on 'IDBCursor': The cursor is a key cursor."_s };
304
305 auto result = effectiveObjectStore().deleteFunction(state, IDBKeyRange::create(m_primaryKey.copyRef()).ptr());
306 if (result.hasException())
307 return result.releaseException();
308
309 auto request = result.releaseReturnValue();
310 request->setSource(*this);
311
312 return request;
313}
314
315bool IDBCursor::setGetResult(IDBRequest& request, const IDBGetResult& getResult)
316{
317 LOG(IndexedDB, "IDBCursor::setGetResult - current key %s", getResult.keyData().loggingString().substring(0, 100).utf8().data());
318 ASSERT(&effectiveObjectStore().transaction().database().originThread() == &Thread::current());
319
320 auto* context = request.scriptExecutionContext();
321 if (!context)
322 return false;
323
324 VM& vm = context->vm();
325 JSLockHolder lock(vm);
326
327 m_keyWrapper = { };
328 m_primaryKeyWrapper = { };
329 m_valueWrapper = { };
330
331 if (!getResult.isDefined()) {
332 m_keyData = { };
333 m_key = nullptr;
334 m_primaryKeyData = { };
335 m_primaryKey = nullptr;
336 m_value = { };
337
338 m_gotValue = false;
339 return false;
340 }
341
342 m_keyData = getResult.keyData();
343 m_key = m_keyData.maybeCreateIDBKey();
344 m_primaryKeyData = getResult.primaryKeyData();
345 m_primaryKey = m_primaryKeyData.maybeCreateIDBKey();
346
347 if (isKeyCursorWithValue()) {
348 m_value = getResult.value();
349 m_keyPath = getResult.keyPath();
350 }
351
352 m_gotValue = true;
353 return true;
354}
355
356void IDBCursor::clearWrappers()
357{
358 m_keyWrapper.clear();
359 m_primaryKeyWrapper.clear();
360 m_valueWrapper.clear();
361}
362
363} // namespace WebCore
364
365#endif // ENABLE(INDEXED_DATABASE)
366