IndexedDB 2.0: Clean up some exception ordering.
[WebKit-https.git] / Source / WebCore / Modules / indexeddb / IDBCursor.cpp
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 "ExceptionCode.h"
32 #include "IDBBindingUtilities.h"
33 #include "IDBDatabase.h"
34 #include "IDBDatabaseException.h"
35 #include "IDBGetResult.h"
36 #include "IDBIndex.h"
37 #include "IDBIterateCursorData.h"
38 #include "IDBObjectStore.h"
39 #include "IDBRequest.h"
40 #include "IDBTransaction.h"
41 #include "Logging.h"
42 #include "ScriptExecutionContext.h"
43 #include <heap/StrongInlines.h>
44 #include <runtime/JSCJSValueInlines.h>
45 #include <wtf/NeverDestroyed.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
51 const AtomicString& IDBCursor::directionNext()
52 {
53     static NeverDestroyed<AtomicString> next("next", AtomicString::ConstructFromLiteral);
54     return next;
55 }
56
57 const AtomicString& IDBCursor::directionNextUnique()
58 {
59     static NeverDestroyed<AtomicString> nextunique("nextunique", AtomicString::ConstructFromLiteral);
60     return nextunique;
61 }
62
63 const AtomicString& IDBCursor::directionPrev()
64 {
65     static NeverDestroyed<AtomicString> prev("prev", AtomicString::ConstructFromLiteral);
66     return prev;
67 }
68
69 const AtomicString& IDBCursor::directionPrevUnique()
70 {
71     static NeverDestroyed<AtomicString> prevunique("prevunique", AtomicString::ConstructFromLiteral);
72     return prevunique;
73 }
74
75 Optional<IndexedDB::CursorDirection> IDBCursor::stringToDirection(const String& directionString)
76 {
77     if (directionString == directionNext())
78         return IndexedDB::CursorDirection::Next;
79     if (directionString == directionNextUnique())
80         return IndexedDB::CursorDirection::NextNoDuplicate;
81     if (directionString == directionPrev())
82         return IndexedDB::CursorDirection::Prev;
83     if (directionString == directionPrevUnique())
84         return IndexedDB::CursorDirection::PrevNoDuplicate;
85
86     return Nullopt;
87 }
88
89 const AtomicString& IDBCursor::directionToString(IndexedDB::CursorDirection direction)
90 {
91     switch (direction) {
92     case IndexedDB::CursorDirection::Next:
93         return directionNext();
94
95     case IndexedDB::CursorDirection::NextNoDuplicate:
96         return directionNextUnique();
97
98     case IndexedDB::CursorDirection::Prev:
99         return directionPrev();
100
101     case IndexedDB::CursorDirection::PrevNoDuplicate:
102         return directionPrevUnique();
103
104     default:
105         ASSERT_NOT_REACHED();
106         return directionNext();
107     }
108 }
109
110 Ref<IDBCursor> IDBCursor::create(IDBTransaction& transaction, IDBObjectStore& objectStore, const IDBCursorInfo& info)
111 {
112     return adoptRef(*new IDBCursor(transaction, objectStore, info));
113 }
114
115 Ref<IDBCursor> IDBCursor::create(IDBTransaction& transaction, IDBIndex& index, const IDBCursorInfo& info)
116 {
117     return adoptRef(*new IDBCursor(transaction, index, info));
118 }
119
120 IDBCursor::IDBCursor(IDBTransaction& transaction, IDBObjectStore& objectStore, const IDBCursorInfo& info)
121     : ActiveDOMObject(transaction.scriptExecutionContext())
122     , m_info(info)
123     , m_objectStore(&objectStore)
124 {
125     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
126
127     suspendIfNeeded();
128 }
129
130 IDBCursor::IDBCursor(IDBTransaction& transaction, IDBIndex& index, const IDBCursorInfo& info)
131     : ActiveDOMObject(transaction.scriptExecutionContext())
132     , m_info(info)
133     , m_index(&index)
134 {
135     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
136
137     suspendIfNeeded();
138 }
139
140 IDBCursor::~IDBCursor()
141 {
142     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
143 }
144
145 bool IDBCursor::sourcesDeleted() const
146 {
147     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
148
149     if (m_objectStore)
150         return m_objectStore->isDeleted();
151
152     ASSERT(m_index);
153     return m_index->isDeleted() || m_index->objectStore().isDeleted();
154 }
155
156 IDBObjectStore& IDBCursor::effectiveObjectStore() const
157 {
158     if (m_objectStore)
159         return *m_objectStore;
160
161     ASSERT(m_index);
162     return m_index->objectStore();
163 }
164
165 IDBTransaction& IDBCursor::transaction() const
166 {
167     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
168     return effectiveObjectStore().transaction();
169 }
170
171 const String& IDBCursor::direction() const
172 {
173     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
174     return directionToString(m_info.cursorDirection());
175 }
176
177 ExceptionOr<Ref<IDBRequest>> IDBCursor::update(ExecState& state, JSValue value)
178 {
179     LOG(IndexedDB, "IDBCursor::update");
180     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
181
182     if (sourcesDeleted())
183         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
184
185     if (!transaction().isActive())
186         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The transaction is inactive or finished.") };
187
188     if (transaction().isReadOnly())
189         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The record may not be updated inside a read-only transaction.") };
190
191     if (!m_gotValue)
192         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
193
194     if (!isKeyCursorWithValue())
195         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'update' on 'IDBCursor': The cursor is a key cursor.") };
196
197     auto& objectStore = effectiveObjectStore();
198     auto& optionalKeyPath = objectStore.info().keyPath();
199     const bool usesInLineKeys = !!optionalKeyPath;
200     if (usesInLineKeys) {
201         RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, optionalKeyPath.value());
202         IDBKeyData keyPathKeyData(keyPathKey.get());
203         if (!keyPathKey || keyPathKeyData != m_currentPrimaryKeyData)
204             return Exception { IDBDatabaseException::DataError, ASCIILiteral("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.") };
205     }
206
207     auto putResult = effectiveObjectStore().putForCursorUpdate(state, value, m_currentPrimaryKey.get());
208     if (putResult.hasException())
209         return putResult.releaseException();
210
211     auto request = putResult.releaseReturnValue();
212     request->setSource(*this);
213     ++m_outstandingRequestCount;
214
215     return WTFMove(request);
216 }
217
218 ExceptionOr<void> IDBCursor::advance(unsigned count)
219 {
220     LOG(IndexedDB, "IDBCursor::advance");
221     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
222
223     if (!m_request)
224         return Exception { IDBDatabaseException::InvalidStateError };
225
226     if (!count)
227         return Exception { TypeError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': A count argument with value 0 (zero) was supplied, must be greater than 0.") };
228
229     if (!transaction().isActive())
230         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The transaction is inactive or finished.") };
231
232     if (sourcesDeleted())
233         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
234
235     if (!m_gotValue)
236         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'advance' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
237
238     m_gotValue = false;
239
240     uncheckedIterateCursor(IDBKeyData(), count);
241
242     return { };
243 }
244
245 ExceptionOr<void> IDBCursor::continuePrimaryKey(ExecState& state, JSValue keyValue, JSValue primaryKeyValue)
246 {
247     if (!transaction().isActive())
248         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The transaction is inactive or finished.") };
249
250     if (sourcesDeleted())
251         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
252
253     if (!m_index)
254         return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source is not an index.") };
255
256     auto direction = m_info.cursorDirection();
257     if (direction != IndexedDB::CursorDirection::Next && direction != IndexedDB::CursorDirection::Prev)
258         return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's direction must be either \"next\" or \"prev\".") };
259
260     if (!m_gotValue)
261         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
262
263     RefPtr<IDBKey> key = scriptValueToIDBKey(state, keyValue);
264     if (!key->isValid())
265         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is not a valid key.") };
266
267     RefPtr<IDBKey> primaryKey = scriptValueToIDBKey(state, primaryKeyValue);
268     if (!primaryKey->isValid())
269         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The second parameter is not a valid key.") };
270
271     IDBKeyData keyData = { key.get() };
272     IDBKeyData primaryKeyData = { primaryKey.get() };
273
274     if (keyData < m_currentKeyData && direction == IndexedDB::CursorDirection::Next)
275         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is less than this cursor's position and this cursor's direction is \"next\".") };
276
277     if (keyData > m_currentKeyData && direction == IndexedDB::CursorDirection::Prev)
278         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is greater than this cursor's position and this cursor's direction is \"prev\".") };
279
280     if (keyData == m_currentKeyData) {
281         if (primaryKeyData <= m_currentPrimaryKeyData && direction == IndexedDB::CursorDirection::Next)
282             return Exception { IDBDatabaseException::DataError, ASCIILiteral("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\".") };
283         if (primaryKeyData >= m_currentPrimaryKeyData && direction == IndexedDB::CursorDirection::Prev)
284             return Exception { IDBDatabaseException::DataError, ASCIILiteral("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\".") };
285     }
286
287     m_gotValue = false;
288
289     uncheckedIterateCursor(keyData, primaryKeyData);
290
291     return { };
292 }
293
294 ExceptionOr<void> IDBCursor::continueFunction(ExecState& execState, JSValue keyValue)
295 {
296     RefPtr<IDBKey> key;
297     if (!keyValue.isUndefined())
298         key = scriptValueToIDBKey(execState, keyValue);
299
300     return continueFunction(key.get());
301 }
302
303 ExceptionOr<void> IDBCursor::continueFunction(const IDBKeyData& key)
304 {
305     LOG(IndexedDB, "IDBCursor::continueFunction (to key %s)", key.loggingString().utf8().data());
306     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
307
308     if (!m_request)
309         return Exception { IDBDatabaseException::InvalidStateError };
310
311     if (!transaction().isActive())
312         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The transaction is inactive or finished.") };
313
314     if (sourcesDeleted())
315         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
316
317     if (!m_gotValue)
318         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
319
320     if (!key.isNull() && !key.isValid())
321         return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is not a valid key.") };
322
323     if (m_info.isDirectionForward()) {
324         if (!key.isNull() && key.compare(m_currentKeyData) <= 0)
325             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is less than or equal to this cursor's position.") };
326     } else {
327         if (!key.isNull() && key.compare(m_currentKeyData) >= 0)
328             return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continue' on 'IDBCursor': The parameter is greater than or equal to this cursor's position.") };
329     }
330
331     m_gotValue = false;
332
333     uncheckedIterateCursor(key, 0);
334
335     return { };
336 }
337
338 void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, unsigned count)
339 {
340     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
341
342     ++m_outstandingRequestCount;
343
344     m_request->willIterateCursor(*this);
345     transaction().iterateCursor(*this, { key, { }, count });
346 }
347
348 void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, const IDBKeyData& primaryKey)
349 {
350     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
351
352     ++m_outstandingRequestCount;
353
354     m_request->willIterateCursor(*this);
355     transaction().iterateCursor(*this, { key, primaryKey, 0 });
356 }
357
358 ExceptionOr<Ref<WebCore::IDBRequest>> IDBCursor::deleteFunction(ExecState& state)
359 {
360     LOG(IndexedDB, "IDBCursor::deleteFunction");
361     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
362
363     if (sourcesDeleted())
364         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
365
366     if (!transaction().isActive())
367         return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The transaction is inactive or finished.") };
368
369     if (transaction().isReadOnly())
370         return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The record may not be deleted inside a read-only transaction.") };
371
372     if (!m_gotValue)
373         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
374
375     if (!isKeyCursorWithValue())
376         return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBCursor': The cursor is a key cursor.") };
377
378     auto result = effectiveObjectStore().deleteFunction(state, m_currentPrimaryKey.get());
379     if (result.hasException())
380         return result.releaseException();
381
382     auto request = result.releaseReturnValue();
383     request->setSource(*this);
384     ++m_outstandingRequestCount;
385
386     return WTFMove(request);
387 }
388
389 void IDBCursor::setGetResult(IDBRequest& request, const IDBGetResult& getResult)
390 {
391     LOG(IndexedDB, "IDBCursor::setGetResult - current key %s", getResult.keyData().loggingString().substring(0, 100).utf8().data());
392     ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
393
394     auto* context = request.scriptExecutionContext();
395     if (!context)
396         return;
397
398     auto* exec = context->execState();
399     if (!exec)
400         return;
401
402     if (!getResult.isDefined()) {
403         m_currentKey = { };
404         m_currentKeyData = { };
405         m_currentPrimaryKey = { };
406         m_currentPrimaryKeyData = { };
407         m_currentValue = { };
408
409         m_gotValue = false;
410         return;
411     }
412
413     auto& vm = context->vm();
414
415     m_currentKey = { vm, idbKeyDataToScriptValue(*exec, getResult.keyData()) };
416     m_currentKeyData = getResult.keyData();
417     m_currentPrimaryKey = { vm, idbKeyDataToScriptValue(*exec, getResult.primaryKeyData()) };
418     m_currentPrimaryKeyData = getResult.primaryKeyData();
419
420     if (isKeyCursorWithValue())
421         m_currentValue = { vm, deserializeIDBValueToJSValue(*exec, getResult.value()) };
422     else
423         m_currentValue = { };
424
425     m_gotValue = true;
426 }
427
428 const char* IDBCursor::activeDOMObjectName() const
429 {
430     return "IDBCursor";
431 }
432
433 bool IDBCursor::canSuspendForDocumentSuspension() const
434 {
435     return false;
436 }
437
438 bool IDBCursor::hasPendingActivity() const
439 {
440     return m_outstandingRequestCount;
441 }
442
443 void IDBCursor::decrementOutstandingRequestCount()
444 {
445     ASSERT(m_outstandingRequestCount);
446     --m_outstandingRequestCount;
447 }
448
449 } // namespace WebCore
450
451 #endif // ENABLE(INDEXED_DATABASE)