1 /*
2 * Copyright (C) 2010 Google 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "modules/indexeddb/IDBTransaction.h"
28
29 #include "bindings/v8/ExceptionState.h"
30 #include "bindings/v8/ExceptionStatePlaceholder.h"
31 #include "core/dom/ExecutionContext.h"
32 #include "core/events/EventQueue.h"
33 #include "core/inspector/ScriptCallStack.h"
34 #include "modules/indexeddb/IDBDatabase.h"
35 #include "modules/indexeddb/IDBEventDispatcher.h"
36 #include "modules/indexeddb/IDBIndex.h"
37 #include "modules/indexeddb/IDBObjectStore.h"
38 #include "modules/indexeddb/IDBOpenDBRequest.h"
39 #include "modules/indexeddb/IDBPendingTransactionMonitor.h"
40 #include "modules/indexeddb/IDBTracing.h"
41
42 using blink::WebIDBDatabase;
43
44 namespace WebCore {
45
create(ExecutionContext * context,int64_t id,const Vector<String> & objectStoreNames,blink::WebIDBTransactionMode mode,IDBDatabase * db)46 IDBTransaction* IDBTransaction::create(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, blink::WebIDBTransactionMode mode, IDBDatabase* db)
47 {
48 IDBOpenDBRequest* openDBRequest = 0;
49 IDBTransaction* transaction = adoptRefCountedGarbageCollectedWillBeNoop(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata()));
50 transaction->suspendIfNeeded();
51 return transaction;
52 }
53
create(ExecutionContext * context,int64_t id,IDBDatabase * db,IDBOpenDBRequest * openDBRequest,const IDBDatabaseMetadata & previousMetadata)54 IDBTransaction* IDBTransaction::create(ExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
55 {
56 IDBTransaction* transaction = adoptRefCountedGarbageCollectedWillBeNoop(new IDBTransaction(context, id, Vector<String>(), blink::WebIDBTransactionModeVersionChange, db, openDBRequest, previousMetadata));
57 transaction->suspendIfNeeded();
58 return transaction;
59 }
60
modeReadOnly()61 const AtomicString& IDBTransaction::modeReadOnly()
62 {
63 DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral));
64 return readonly;
65 }
66
modeReadWrite()67 const AtomicString& IDBTransaction::modeReadWrite()
68 {
69 DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral));
70 return readwrite;
71 }
72
modeVersionChange()73 const AtomicString& IDBTransaction::modeVersionChange()
74 {
75 DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral));
76 return versionchange;
77 }
78
IDBTransaction(ExecutionContext * context,int64_t id,const Vector<String> & objectStoreNames,blink::WebIDBTransactionMode mode,IDBDatabase * db,IDBOpenDBRequest * openDBRequest,const IDBDatabaseMetadata & previousMetadata)79 IDBTransaction::IDBTransaction(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, blink::WebIDBTransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
80 : ActiveDOMObject(context)
81 , m_id(id)
82 , m_database(db)
83 , m_objectStoreNames(objectStoreNames)
84 , m_openDBRequest(openDBRequest)
85 , m_mode(mode)
86 , m_state(Active)
87 , m_hasPendingActivity(true)
88 , m_contextStopped(false)
89 , m_previousMetadata(previousMetadata)
90 {
91 ScriptWrappable::init(this);
92 if (mode == blink::WebIDBTransactionModeVersionChange) {
93 // Not active until the callback.
94 m_state = Inactive;
95 }
96
97 if (m_state == Active)
98 IDBPendingTransactionMonitor::from(*context).addNewTransaction(*this);
99 m_database->transactionCreated(this);
100 }
101
~IDBTransaction()102 IDBTransaction::~IDBTransaction()
103 {
104 ASSERT(m_state == Finished || m_contextStopped);
105 ASSERT(m_requestList.isEmpty() || m_contextStopped);
106 }
107
trace(Visitor * visitor)108 void IDBTransaction::trace(Visitor* visitor)
109 {
110 visitor->trace(m_database);
111 visitor->trace(m_openDBRequest);
112 visitor->trace(m_error);
113 visitor->trace(m_requestList);
114 visitor->trace(m_objectStoreMap);
115 visitor->trace(m_deletedObjectStores);
116 visitor->trace(m_objectStoreCleanupMap);
117 EventTargetWithInlineData::trace(visitor);
118 }
119
mode() const120 const String& IDBTransaction::mode() const
121 {
122 return modeToString(m_mode);
123 }
124
setError(PassRefPtrWillBeRawPtr<DOMError> error)125 void IDBTransaction::setError(PassRefPtrWillBeRawPtr<DOMError> error)
126 {
127 ASSERT(m_state != Finished);
128 ASSERT(error);
129
130 // The first error to be set is the true cause of the
131 // transaction abort.
132 if (!m_error) {
133 m_error = error;
134 }
135 }
136
objectStore(const String & name,ExceptionState & exceptionState)137 IDBObjectStore* IDBTransaction::objectStore(const String& name, ExceptionState& exceptionState)
138 {
139 if (m_state == Finished) {
140 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
141 return 0;
142 }
143
144 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
145 if (it != m_objectStoreMap.end())
146 return it->value;
147
148 if (!isVersionChange() && !m_objectStoreNames.contains(name)) {
149 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
150 return 0;
151 }
152
153 int64_t objectStoreId = m_database->findObjectStoreId(name);
154 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
155 ASSERT(isVersionChange());
156 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
157 return 0;
158 }
159
160 const IDBDatabaseMetadata& metadata = m_database->metadata();
161
162 IDBObjectStore* objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this);
163 objectStoreCreated(name, objectStore);
164 return objectStore;
165 }
166
objectStoreCreated(const String & name,IDBObjectStore * objectStore)167 void IDBTransaction::objectStoreCreated(const String& name, IDBObjectStore* objectStore)
168 {
169 ASSERT(m_state != Finished);
170 m_objectStoreMap.set(name, objectStore);
171 if (isVersionChange())
172 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
173 }
174
objectStoreDeleted(const String & name)175 void IDBTransaction::objectStoreDeleted(const String& name)
176 {
177 ASSERT(m_state != Finished);
178 ASSERT(isVersionChange());
179 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
180 if (it != m_objectStoreMap.end()) {
181 IDBObjectStore* objectStore = it->value;
182 m_objectStoreMap.remove(name);
183 objectStore->markDeleted();
184 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
185 m_deletedObjectStores.add(objectStore);
186 }
187 }
188
setActive(bool active)189 void IDBTransaction::setActive(bool active)
190 {
191 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false");
192 if (m_state == Finishing)
193 return;
194 ASSERT(active != (m_state == Active));
195 m_state = active ? Active : Inactive;
196
197 if (!active && m_requestList.isEmpty() && backendDB())
198 backendDB()->commit(m_id);
199 }
200
abort(ExceptionState & exceptionState)201 void IDBTransaction::abort(ExceptionState& exceptionState)
202 {
203 if (m_state == Finishing || m_state == Finished) {
204 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
205 return;
206 }
207
208 m_state = Finishing;
209
210 if (m_contextStopped)
211 return;
212
213 while (!m_requestList.isEmpty()) {
214 IDBRequest* request = *m_requestList.begin();
215 m_requestList.remove(request);
216 request->abort();
217 }
218
219 if (backendDB())
220 backendDB()->abort(m_id);
221 }
222
registerRequest(IDBRequest * request)223 void IDBTransaction::registerRequest(IDBRequest* request)
224 {
225 ASSERT(request);
226 ASSERT(m_state == Active);
227 m_requestList.add(request);
228 }
229
unregisterRequest(IDBRequest * request)230 void IDBTransaction::unregisterRequest(IDBRequest* request)
231 {
232 ASSERT(request);
233 // If we aborted the request, it will already have been removed.
234 m_requestList.remove(request);
235 }
236
onAbort(PassRefPtrWillBeRawPtr<DOMError> prpError)237 void IDBTransaction::onAbort(PassRefPtrWillBeRawPtr<DOMError> prpError)
238 {
239 IDB_TRACE("IDBTransaction::onAbort");
240 if (m_contextStopped) {
241 m_database->transactionFinished(this);
242 return;
243 }
244
245 RefPtrWillBeRawPtr<DOMError> error = prpError;
246 ASSERT(m_state != Finished);
247
248 if (m_state != Finishing) {
249 ASSERT(error.get());
250 setError(error.release());
251
252 // Abort was not triggered by front-end, so outstanding requests must
253 // be aborted now.
254 while (!m_requestList.isEmpty()) {
255 IDBRequest* request = *m_requestList.begin();
256 m_requestList.remove(request);
257 request->abort();
258 }
259 m_state = Finishing;
260 }
261
262 if (isVersionChange()) {
263 for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it)
264 it->key->setMetadata(it->value);
265 m_database->setMetadata(m_previousMetadata);
266 m_database->close();
267 }
268 m_objectStoreCleanupMap.clear();
269
270 // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
271 enqueueEvent(Event::createBubble(EventTypeNames::abort));
272
273 m_database->transactionFinished(this);
274 }
275
onComplete()276 void IDBTransaction::onComplete()
277 {
278 IDB_TRACE("IDBTransaction::onComplete");
279 if (m_contextStopped) {
280 m_database->transactionFinished(this);
281 return;
282 }
283
284 ASSERT(m_state != Finished);
285 m_state = Finishing;
286 m_objectStoreCleanupMap.clear();
287
288 // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
289 enqueueEvent(Event::create(EventTypeNames::complete));
290
291 m_database->transactionFinished(this);
292 }
293
hasPendingActivity() const294 bool IDBTransaction::hasPendingActivity() const
295 {
296 // FIXME: In an ideal world, we should return true as long as anyone has a or can
297 // get a handle to us or any child request object and any of those have
298 // event listeners. This is in order to handle user generated events properly.
299 return m_hasPendingActivity && !m_contextStopped;
300 }
301
stringToMode(const String & modeString,ExceptionState & exceptionState)302 blink::WebIDBTransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionState& exceptionState)
303 {
304 if (modeString.isNull()
305 || modeString == IDBTransaction::modeReadOnly())
306 return blink::WebIDBTransactionModeReadOnly;
307 if (modeString == IDBTransaction::modeReadWrite())
308 return blink::WebIDBTransactionModeReadWrite;
309
310 exceptionState.throwTypeError("The mode provided ('" + modeString + "') is not one of 'readonly' or 'readwrite'.");
311 return blink::WebIDBTransactionModeReadOnly;
312 }
313
modeToString(blink::WebIDBTransactionMode mode)314 const AtomicString& IDBTransaction::modeToString(blink::WebIDBTransactionMode mode)
315 {
316 switch (mode) {
317 case blink::WebIDBTransactionModeReadOnly:
318 return IDBTransaction::modeReadOnly();
319 break;
320
321 case blink::WebIDBTransactionModeReadWrite:
322 return IDBTransaction::modeReadWrite();
323 break;
324
325 case blink::WebIDBTransactionModeVersionChange:
326 return IDBTransaction::modeVersionChange();
327 break;
328 }
329
330 ASSERT_NOT_REACHED();
331 return IDBTransaction::modeReadOnly();
332 }
333
interfaceName() const334 const AtomicString& IDBTransaction::interfaceName() const
335 {
336 return EventTargetNames::IDBTransaction;
337 }
338
executionContext() const339 ExecutionContext* IDBTransaction::executionContext() const
340 {
341 return ActiveDOMObject::executionContext();
342 }
343
dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)344 bool IDBTransaction::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
345 {
346 IDB_TRACE("IDBTransaction::dispatchEvent");
347 if (m_contextStopped || !executionContext()) {
348 m_state = Finished;
349 return false;
350 }
351 ASSERT(m_state != Finished);
352 ASSERT(m_hasPendingActivity);
353 ASSERT(executionContext());
354 ASSERT(event->target() == this);
355 m_state = Finished;
356
357 // Break reference cycles.
358 for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it)
359 it->value->transactionFinished();
360 m_objectStoreMap.clear();
361 for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it)
362 (*it)->transactionFinished();
363 m_deletedObjectStores.clear();
364
365 WillBeHeapVector<RefPtrWillBeMember<EventTarget> > targets;
366 targets.append(this);
367 targets.append(db());
368
369 // FIXME: When we allow custom event dispatching, this will probably need to change.
370 ASSERT(event->type() == EventTypeNames::complete || event->type() == EventTypeNames::abort);
371 bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets);
372 // FIXME: Try to construct a test where |this| outlives openDBRequest and we
373 // get a crash.
374 if (m_openDBRequest) {
375 ASSERT(isVersionChange());
376 m_openDBRequest->transactionDidFinishAndDispatch();
377 }
378 m_hasPendingActivity = false;
379 return returnValue;
380 }
381
stop()382 void IDBTransaction::stop()
383 {
384 if (m_contextStopped)
385 return;
386
387 m_contextStopped = true;
388
389 abort(IGNORE_EXCEPTION);
390 }
391
enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)392 void IDBTransaction::enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)
393 {
394 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().utf8().data());
395 if (m_contextStopped || !executionContext())
396 return;
397
398 EventQueue* eventQueue = executionContext()->eventQueue();
399 event->setTarget(this);
400 eventQueue->enqueueEvent(event);
401 }
402
backendDB() const403 blink::WebIDBDatabase* IDBTransaction::backendDB() const
404 {
405 return m_database->backend();
406 }
407
408 } // namespace WebCore
409