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 "IDBDatabase.h" 28 29#if ENABLE(INDEXED_DATABASE) 30 31#include "DOMStringList.h" 32#include "EventQueue.h" 33#include "ExceptionCode.h" 34#include "IDBAny.h" 35#include "IDBDatabaseCallbacks.h" 36#include "IDBDatabaseError.h" 37#include "IDBDatabaseException.h" 38#include "IDBEventDispatcher.h" 39#include "IDBIndex.h" 40#include "IDBKeyPath.h" 41#include "IDBObjectStore.h" 42#include "IDBTransaction.h" 43#include "IDBVersionChangeEvent.h" 44#include "Logging.h" 45#include "ScriptExecutionContext.h" 46#include <atomic> 47#include <inspector/ScriptCallStack.h> 48#include <limits> 49#include <wtf/Atomics.h> 50 51namespace WebCore { 52 53PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> database, PassRefPtr<IDBDatabaseCallbacks> callbacks) 54{ 55 RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks))); 56 idbDatabase->suspendIfNeeded(); 57 return idbDatabase.release(); 58} 59 60IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks) 61 : ActiveDOMObject(context) 62 , m_backend(backend) 63 , m_closePending(false) 64 , m_contextStopped(false) 65 , m_databaseCallbacks(callbacks) 66{ 67 // We pass a reference of this object before it can be adopted. 68 relaxAdoptionRequirement(); 69} 70 71IDBDatabase::~IDBDatabase() 72{ 73 close(); 74} 75 76int64_t IDBDatabase::nextTransactionId() 77{ 78 // Only keep a 32-bit counter to allow ports to use the other 32 79 // bits of the id. 80 static std::atomic<uint32_t> currentTransactionId; 81 82 return ++currentTransactionId; 83} 84 85void IDBDatabase::transactionCreated(IDBTransaction* transaction) 86{ 87 ASSERT(transaction); 88 ASSERT(!m_transactions.contains(transaction->id())); 89 m_transactions.add(transaction->id(), transaction); 90 91 if (transaction->isVersionChange()) { 92 ASSERT(!m_versionChangeTransaction); 93 m_versionChangeTransaction = transaction; 94 } 95} 96 97void IDBDatabase::transactionFinished(IDBTransaction* transaction) 98{ 99 ASSERT(transaction); 100 ASSERT(m_transactions.contains(transaction->id())); 101 ASSERT(m_transactions.get(transaction->id()) == transaction); 102 m_transactions.remove(transaction->id()); 103 104 if (transaction->isVersionChange()) { 105 ASSERT(m_versionChangeTransaction == transaction); 106 m_versionChangeTransaction = 0; 107 } 108 109 if (m_closePending && m_transactions.isEmpty()) 110 closeConnection(); 111} 112 113void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<IDBDatabaseError> error) 114{ 115 ASSERT(m_transactions.contains(transactionId)); 116 m_transactions.get(transactionId)->onAbort(error); 117} 118 119void IDBDatabase::onComplete(int64_t transactionId) 120{ 121 ASSERT(m_transactions.contains(transactionId)); 122 m_transactions.get(transactionId)->onComplete(); 123} 124 125PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const 126{ 127 RefPtr<DOMStringList> objectStoreNames = DOMStringList::create(); 128 for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) 129 objectStoreNames->append(it->value.name); 130 objectStoreNames->sort(); 131 return objectStoreNames.release(); 132} 133 134uint64_t IDBDatabase::version() const 135{ 136 // NoIntVersion is a special value for internal use only and shouldn't be exposed to script. 137 // DefaultIntVersion should be exposed instead. 138 return m_metadata.version != IDBDatabaseMetadata::NoIntVersion ? m_metadata.version : static_cast<uint64_t>(IDBDatabaseMetadata::DefaultIntVersion); 139} 140 141PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec) 142{ 143 IDBKeyPath keyPath; 144 bool autoIncrement = false; 145 if (!options.isUndefinedOrNull()) { 146 String keyPathString; 147 Vector<String> keyPathArray; 148 if (options.get("keyPath", keyPathArray)) 149 keyPath = IDBKeyPath(keyPathArray); 150 else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString)) 151 keyPath = IDBKeyPath(keyPathString); 152 153 options.get("autoIncrement", autoIncrement); 154 } 155 156 return createObjectStore(name, keyPath, autoIncrement, ec); 157} 158 159PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec) 160{ 161 LOG(StorageAPI, "IDBDatabase::createObjectStore"); 162 if (!m_versionChangeTransaction) { 163 ec = IDBDatabaseException::InvalidStateError; 164 return 0; 165 } 166 if (!m_versionChangeTransaction->isActive()) { 167 ec = IDBDatabaseException::TransactionInactiveError; 168 return 0; 169 } 170 171 if (containsObjectStore(name)) { 172 ec = IDBDatabaseException::ConstraintError; 173 return 0; 174 } 175 176 if (!keyPath.isNull() && !keyPath.isValid()) { 177 ec = IDBDatabaseException::SyntaxError; 178 return 0; 179 } 180 181 if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) { 182 ec = IDBDatabaseException::InvalidAccessError; 183 return 0; 184 } 185 186 int64_t objectStoreId = m_metadata.maxObjectStoreId + 1; 187 m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement); 188 189 IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackend::MinimumIndexId); 190 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get()); 191 m_metadata.objectStores.set(metadata.id, metadata); 192 ++m_metadata.maxObjectStoreId; 193 194 m_versionChangeTransaction->objectStoreCreated(name, objectStore); 195 return objectStore.release(); 196} 197 198void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec) 199{ 200 LOG(StorageAPI, "IDBDatabase::deleteObjectStore"); 201 if (!m_versionChangeTransaction) { 202 ec = IDBDatabaseException::InvalidStateError; 203 return; 204 } 205 if (!m_versionChangeTransaction->isActive()) { 206 ec = IDBDatabaseException::TransactionInactiveError; 207 return; 208 } 209 210 int64_t objectStoreId = findObjectStoreId(name); 211 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 212 ec = IDBDatabaseException::NotFoundError; 213 return; 214 } 215 216 m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId); 217 m_versionChangeTransaction->objectStoreDeleted(name); 218 m_metadata.objectStores.remove(objectStoreId); 219} 220 221PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec) 222{ 223 LOG(StorageAPI, "IDBDatabase::transaction"); 224 if (!scope.size()) { 225 ec = IDBDatabaseException::InvalidAccessError; 226 return 0; 227 } 228 229 IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec); 230 if (ec) 231 return 0; 232 233 if (m_versionChangeTransaction || m_closePending) { 234 ec = IDBDatabaseException::InvalidStateError; 235 return 0; 236 } 237 238 Vector<int64_t> objectStoreIds; 239 for (size_t i = 0; i < scope.size(); ++i) { 240 int64_t objectStoreId = findObjectStoreId(scope[i]); 241 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 242 ec = IDBDatabaseException::NotFoundError; 243 return 0; 244 } 245 objectStoreIds.append(objectStoreId); 246 } 247 248 int64_t transactionId = nextTransactionId(); 249 m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode); 250 251 RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this); 252 return transaction.release(); 253} 254 255PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec) 256{ 257 RefPtr<DOMStringList> storeNames = DOMStringList::create(); 258 storeNames->append(storeName); 259 return transaction(context, storeNames, mode, ec); 260} 261 262void IDBDatabase::forceClose() 263{ 264 for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it) 265 (*it)->abort(IGNORE_EXCEPTION); 266 this->close(); 267} 268 269void IDBDatabase::close() 270{ 271 LOG(StorageAPI, "IDBDatabase::close"); 272 if (m_closePending) 273 return; 274 275 m_closePending = true; 276 277 if (m_transactions.isEmpty()) 278 closeConnection(); 279} 280 281void IDBDatabase::closeConnection() 282{ 283 ASSERT(m_closePending); 284 ASSERT(m_transactions.isEmpty()); 285 286 m_backend->close(m_databaseCallbacks); 287 288 if (m_contextStopped || !scriptExecutionContext()) 289 return; 290 291 EventQueue& eventQueue = scriptExecutionContext()->eventQueue(); 292 // Remove any pending versionchange events scheduled to fire on this 293 // connection. They would have been scheduled by the backend when another 294 // connection called setVersion, but the frontend connection is being 295 // closed before they could fire. 296 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { 297 bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]); 298 ASSERT_UNUSED(removed, removed); 299 } 300} 301 302void IDBDatabase::onVersionChange(uint64_t oldVersion, uint64_t newVersion, IndexedDB::VersionNullness newVersionNullness) 303{ 304 LOG(StorageAPI, "IDBDatabase::onVersionChange"); 305 if (m_contextStopped || !scriptExecutionContext()) 306 return; 307 308 if (m_closePending) 309 return; 310 311 enqueueEvent(IDBVersionChangeEvent::create(oldVersion, newVersion, newVersionNullness)); 312} 313 314void IDBDatabase::enqueueEvent(PassRefPtr<Event> event) 315{ 316 ASSERT(!m_contextStopped); 317 ASSERT(scriptExecutionContext()); 318 event->setTarget(this); 319 scriptExecutionContext()->eventQueue().enqueueEvent(event.get()); 320 m_enqueuedEvents.append(event); 321} 322 323bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event) 324{ 325 LOG(StorageAPI, "IDBDatabase::dispatchEvent"); 326 ASSERT(event->type() == eventNames().versionchangeEvent); 327 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { 328 if (m_enqueuedEvents[i].get() == event.get()) 329 m_enqueuedEvents.remove(i); 330 } 331 return EventTarget::dispatchEvent(event.get()); 332} 333 334int64_t IDBDatabase::findObjectStoreId(const String& name) const 335{ 336 for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) { 337 if (it->value.name == name) { 338 ASSERT(it->key != IDBObjectStoreMetadata::InvalidId); 339 return it->key; 340 } 341 } 342 return IDBObjectStoreMetadata::InvalidId; 343} 344 345bool IDBDatabase::hasPendingActivity() const 346{ 347 // The script wrapper must not be collected before the object is closed or 348 // we can't fire a "versionchange" event to let script manually close the connection. 349 return !m_closePending && hasEventListeners() && !m_contextStopped; 350} 351 352void IDBDatabase::stop() 353{ 354 // Stop fires at a deterministic time, so we need to call close in it. 355 close(); 356 357 m_contextStopped = true; 358} 359 360} // namespace WebCore 361 362#endif // ENABLE(INDEXED_DATABASE) 363