• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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