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 namespace WebCore { 43 44 PassRefPtr<IDBTransaction> IDBTransaction::create(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db) 45 { 46 IDBOpenDBRequest* openDBRequest = 0; 47 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata()))); 48 transaction->suspendIfNeeded(); 49 return transaction.release(); 50 } 51 52 PassRefPtr<IDBTransaction> IDBTransaction::create(ExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 53 { 54 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionVersionChange, db, openDBRequest, previousMetadata))); 55 transaction->suspendIfNeeded(); 56 return transaction.release(); 57 } 58 59 const AtomicString& IDBTransaction::modeReadOnly() 60 { 61 DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral)); 62 return readonly; 63 } 64 65 const AtomicString& IDBTransaction::modeReadWrite() 66 { 67 DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral)); 68 return readwrite; 69 } 70 71 const AtomicString& IDBTransaction::modeVersionChange() 72 { 73 DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral)); 74 return versionchange; 75 } 76 77 IDBTransaction::IDBTransaction(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 78 : ActiveDOMObject(context) 79 , m_id(id) 80 , m_database(db) 81 , m_objectStoreNames(objectStoreNames) 82 , m_openDBRequest(openDBRequest) 83 , m_mode(mode) 84 , m_state(Active) 85 , m_hasPendingActivity(true) 86 , m_contextStopped(false) 87 , m_previousMetadata(previousMetadata) 88 { 89 ScriptWrappable::init(this); 90 if (mode == IndexedDB::TransactionVersionChange) { 91 // Not active until the callback. 92 m_state = Inactive; 93 } 94 95 // We pass a reference of this object before it can be adopted. 96 relaxAdoptionRequirement(); 97 if (m_state == Active) 98 IDBPendingTransactionMonitor::addNewTransaction(this); 99 m_database->transactionCreated(this); 100 } 101 102 IDBTransaction::~IDBTransaction() 103 { 104 ASSERT(m_state == Finished || m_contextStopped); 105 ASSERT(m_requestList.isEmpty() || m_contextStopped); 106 } 107 108 const String& IDBTransaction::mode() const 109 { 110 return modeToString(m_mode); 111 } 112 113 void IDBTransaction::setError(PassRefPtr<DOMError> error) 114 { 115 ASSERT(m_state != Finished); 116 ASSERT(error); 117 118 // The first error to be set is the true cause of the 119 // transaction abort. 120 if (!m_error) { 121 m_error = error; 122 } 123 } 124 125 PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionState& exceptionState) 126 { 127 if (m_state == Finished) { 128 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 129 return 0; 130 } 131 132 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 133 if (it != m_objectStoreMap.end()) 134 return it->value; 135 136 if (!isVersionChange() && !m_objectStoreNames.contains(name)) { 137 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 138 return 0; 139 } 140 141 int64_t objectStoreId = m_database->findObjectStoreId(name); 142 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 143 ASSERT(isVersionChange()); 144 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 145 return 0; 146 } 147 148 const IDBDatabaseMetadata& metadata = m_database->metadata(); 149 150 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this); 151 objectStoreCreated(name, objectStore); 152 return objectStore.release(); 153 } 154 155 void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore) 156 { 157 ASSERT(m_state != Finished); 158 RefPtr<IDBObjectStore> objectStore = prpObjectStore; 159 m_objectStoreMap.set(name, objectStore); 160 if (isVersionChange()) 161 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 162 } 163 164 void IDBTransaction::objectStoreDeleted(const String& name) 165 { 166 ASSERT(m_state != Finished); 167 ASSERT(isVersionChange()); 168 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 169 if (it != m_objectStoreMap.end()) { 170 RefPtr<IDBObjectStore> objectStore = it->value; 171 m_objectStoreMap.remove(name); 172 objectStore->markDeleted(); 173 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 174 m_deletedObjectStores.add(objectStore); 175 } 176 } 177 178 void IDBTransaction::setActive(bool active) 179 { 180 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false"); 181 if (m_state == Finishing) 182 return; 183 ASSERT(active != (m_state == Active)); 184 m_state = active ? Active : Inactive; 185 186 if (!active && m_requestList.isEmpty()) 187 backendDB()->commit(m_id); 188 } 189 190 void IDBTransaction::abort(ExceptionState& exceptionState) 191 { 192 if (m_state == Finishing || m_state == Finished) { 193 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 194 return; 195 } 196 197 m_state = Finishing; 198 199 if (!m_contextStopped) { 200 while (!m_requestList.isEmpty()) { 201 RefPtr<IDBRequest> request = *m_requestList.begin(); 202 m_requestList.remove(request); 203 request->abort(); 204 } 205 } 206 207 RefPtr<IDBTransaction> selfRef = this; 208 backendDB()->abort(m_id); 209 } 210 211 void IDBTransaction::registerRequest(IDBRequest* request) 212 { 213 ASSERT(request); 214 ASSERT(m_state == Active); 215 m_requestList.add(request); 216 } 217 218 void IDBTransaction::unregisterRequest(IDBRequest* request) 219 { 220 ASSERT(request); 221 // If we aborted the request, it will already have been removed. 222 m_requestList.remove(request); 223 } 224 225 void IDBTransaction::onAbort(PassRefPtr<DOMError> prpError) 226 { 227 IDB_TRACE("IDBTransaction::onAbort"); 228 RefPtr<DOMError> error = prpError; 229 ASSERT(m_state != Finished); 230 231 if (m_state != Finishing) { 232 ASSERT(error.get()); 233 setError(error.release()); 234 235 // Abort was not triggered by front-end, so outstanding requests must 236 // be aborted now. 237 while (!m_requestList.isEmpty()) { 238 RefPtr<IDBRequest> request = *m_requestList.begin(); 239 m_requestList.remove(request); 240 request->abort(); 241 } 242 m_state = Finishing; 243 } 244 245 if (isVersionChange()) { 246 for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it) 247 it->key->setMetadata(it->value); 248 m_database->setMetadata(m_previousMetadata); 249 m_database->close(); 250 } 251 m_objectStoreCleanupMap.clear(); 252 253 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 254 enqueueEvent(Event::createBubble(EventTypeNames::abort)); 255 256 // If script has stopped and GC has completed, database may have last reference to this object. 257 RefPtr<IDBTransaction> protect(this); 258 m_database->transactionFinished(this); 259 } 260 261 void IDBTransaction::onComplete() 262 { 263 IDB_TRACE("IDBTransaction::onComplete"); 264 ASSERT(m_state != Finished); 265 m_state = Finishing; 266 m_objectStoreCleanupMap.clear(); 267 268 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 269 enqueueEvent(Event::create(EventTypeNames::complete)); 270 271 // If script has stopped and GC has completed, database may have last reference to this object. 272 RefPtr<IDBTransaction> protect(this); 273 m_database->transactionFinished(this); 274 } 275 276 bool IDBTransaction::hasPendingActivity() const 277 { 278 // FIXME: In an ideal world, we should return true as long as anyone has a or can 279 // get a handle to us or any child request object and any of those have 280 // event listeners. This is in order to handle user generated events properly. 281 return m_hasPendingActivity && !m_contextStopped; 282 } 283 284 IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionState& exceptionState) 285 { 286 if (modeString.isNull() 287 || modeString == IDBTransaction::modeReadOnly()) 288 return IndexedDB::TransactionReadOnly; 289 if (modeString == IDBTransaction::modeReadWrite()) 290 return IndexedDB::TransactionReadWrite; 291 292 exceptionState.throwUninformativeAndGenericTypeError(); 293 return IndexedDB::TransactionReadOnly; 294 } 295 296 const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode) 297 { 298 switch (mode) { 299 case IndexedDB::TransactionReadOnly: 300 return IDBTransaction::modeReadOnly(); 301 break; 302 303 case IndexedDB::TransactionReadWrite: 304 return IDBTransaction::modeReadWrite(); 305 break; 306 307 case IndexedDB::TransactionVersionChange: 308 return IDBTransaction::modeVersionChange(); 309 break; 310 } 311 312 ASSERT_NOT_REACHED(); 313 return IDBTransaction::modeReadOnly(); 314 } 315 316 const AtomicString& IDBTransaction::interfaceName() const 317 { 318 return EventTargetNames::IDBTransaction; 319 } 320 321 ExecutionContext* IDBTransaction::executionContext() const 322 { 323 return ActiveDOMObject::executionContext(); 324 } 325 326 bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event) 327 { 328 IDB_TRACE("IDBTransaction::dispatchEvent"); 329 ASSERT(m_state != Finished); 330 ASSERT(m_hasPendingActivity); 331 ASSERT(executionContext()); 332 ASSERT(event->target() == this); 333 m_state = Finished; 334 335 // Break reference cycles. 336 for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it) 337 it->value->transactionFinished(); 338 m_objectStoreMap.clear(); 339 for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it) 340 (*it)->transactionFinished(); 341 m_deletedObjectStores.clear(); 342 343 Vector<RefPtr<EventTarget> > targets; 344 targets.append(this); 345 targets.append(db()); 346 347 // FIXME: When we allow custom event dispatching, this will probably need to change. 348 ASSERT(event->type() == EventTypeNames::complete || event->type() == EventTypeNames::abort); 349 bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets); 350 // FIXME: Try to construct a test where |this| outlives openDBRequest and we 351 // get a crash. 352 if (m_openDBRequest) { 353 ASSERT(isVersionChange()); 354 m_openDBRequest->transactionDidFinishAndDispatch(); 355 } 356 m_hasPendingActivity = false; 357 return returnValue; 358 } 359 360 void IDBTransaction::stop() 361 { 362 if (m_contextStopped) 363 return; 364 365 m_contextStopped = true; 366 367 abort(IGNORE_EXCEPTION); 368 } 369 370 void IDBTransaction::enqueueEvent(PassRefPtr<Event> event) 371 { 372 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data()); 373 if (m_contextStopped || !executionContext()) 374 return; 375 376 EventQueue* eventQueue = executionContext()->eventQueue(); 377 event->setTarget(this); 378 eventQueue->enqueueEvent(event); 379 } 380 381 blink::WebIDBDatabase* IDBTransaction::backendDB() const 382 { 383 return m_database->backend(); 384 } 385 386 } // namespace WebCore 387