IDBDatabase.cpp 12.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
27
#include "IDBDatabase.h"
28

29 30
#if ENABLE(INDEXED_DATABASE)

31
#include "DOMStringList.h"
32
#include "EventQueue.h"
33
#include "ExceptionCode.h"
34
#include "HistogramSupport.h"
35
#include "IDBAny.h"
36
#include "IDBDatabaseCallbacks.h"
37 38
#include "IDBDatabaseError.h"
#include "IDBDatabaseException.h"
39
#include "IDBEventDispatcher.h"
40
#include "IDBHistograms.h"
41
#include "IDBIndex.h"
42
#include "IDBKeyPath.h"
43
#include "IDBObjectStore.h"
44
#include "IDBTransaction.h"
45
#include "IDBVersionChangeEvent.h"
beidson@apple.com's avatar
beidson@apple.com committed
46
#include "Logging.h"
47
#include "ScriptCallStack.h"
48
#include "ScriptExecutionContext.h"
49
#include <limits>
50
#include <wtf/Atomics.h>
51 52 53

namespace WebCore {

54
PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> database, PassRefPtr<IDBDatabaseCallbacks> callbacks)
55
{
56
    RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks)));
57 58
    idbDatabase->suspendIfNeeded();
    return idbDatabase.release();
59 60
}

61
IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks)
62
    : ActiveDOMObject(context)
63
    , m_backend(backend)
64 65
    , m_closePending(false)
    , m_contextStopped(false)
66
    , m_databaseCallbacks(callbacks)
67
{
68
    // We pass a reference of this object before it can be adopted.
69
    relaxAdoptionRequirement();
70 71
}

72
IDBDatabase::~IDBDatabase()
73
{
74
    close();
75 76
}

77 78 79 80 81 82 83 84
int64_t IDBDatabase::nextTransactionId()
{
    // Only keep a 32-bit counter to allow ports to use the other 32
    // bits of the id.
    AtomicallyInitializedStatic(int, currentTransactionId = 0);
    return atomicIncrement(&currentTransactionId);
}

85
void IDBDatabase::transactionCreated(IDBTransaction* transaction)
86
{
87
    ASSERT(transaction);
88 89
    ASSERT(!m_transactions.contains(transaction->id()));
    m_transactions.add(transaction->id(), transaction);
90 91 92 93 94

    if (transaction->isVersionChange()) {
        ASSERT(!m_versionChangeTransaction);
        m_versionChangeTransaction = transaction;
    }
95 96
}

97
void IDBDatabase::transactionFinished(IDBTransaction* transaction)
98
{
99
    ASSERT(transaction);
100 101 102
    ASSERT(m_transactions.contains(transaction->id()));
    ASSERT(m_transactions.get(transaction->id()) == transaction);
    m_transactions.remove(transaction->id());
103 104 105 106 107 108 109 110

    if (transaction->isVersionChange()) {
        ASSERT(m_versionChangeTransaction == transaction);
        m_versionChangeTransaction = 0;
    }

    if (m_closePending && m_transactions.isEmpty())
        closeConnection();
111
}
112

113 114
void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<IDBDatabaseError> error)
{
115 116
    ASSERT(m_transactions.contains(transactionId));
    m_transactions.get(transactionId)->onAbort(error);
117 118 119 120
}

void IDBDatabase::onComplete(int64_t transactionId)
{
121 122
    ASSERT(m_transactions.contains(transactionId));
    m_transactions.get(transactionId)->onComplete();
123 124
}

125 126 127 128
PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const
{
    RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
    for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it)
129
        objectStoreNames->append(it->value.name);
130 131 132 133
    objectStoreNames->sort();
    return objectStoreNames.release();
}

beidson@apple.com's avatar
beidson@apple.com committed
134
uint64_t IDBDatabase::version() const
135
{
beidson@apple.com's avatar
beidson@apple.com committed
136
    return m_metadata.version;
137 138
}

139
PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec)
140
{
141
    IDBKeyPath keyPath;
142
    bool autoIncrement = false;
143 144
    if (!options.isUndefinedOrNull()) {
        String keyPathString;
145 146 147 148
        Vector<String> keyPathArray;
        if (options.get("keyPath", keyPathArray))
            keyPath = IDBKeyPath(keyPathArray);
        else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString))
149
            keyPath = IDBKeyPath(keyPathString);
150 151 152 153 154 155 156 157 158

        options.get("autoIncrement", autoIncrement);
    }

    return createObjectStore(name, keyPath, autoIncrement, ec);
}

PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec)
{
beidson@apple.com's avatar
beidson@apple.com committed
159
    LOG(StorageAPI, "IDBDatabase::createObjectStore");
160
    HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax);
161 162 163 164 165 166 167
    if (!m_versionChangeTransaction) {
        ec = IDBDatabaseException::InvalidStateError;
        return 0;
    }
    if (!m_versionChangeTransaction->isActive()) {
        ec = IDBDatabaseException::TransactionInactiveError;
        return 0;
168 169
    }

170
    if (containsObjectStore(name)) {
171
        ec = IDBDatabaseException::ConstraintError;
172 173 174
        return 0;
    }

175
    if (!keyPath.isNull() && !keyPath.isValid()) {
176
        ec = IDBDatabaseException::SyntaxError;
177 178 179
        return 0;
    }

180
    if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) {
181
        ec = IDBDatabaseException::InvalidAccessError;
182 183
        return 0;
    }
184

185
    int64_t objectStoreId = m_metadata.maxObjectStoreId + 1;
186
    m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement);
187

188
    IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackendInterface::MinimumIndexId);
189
    RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get());
190
    m_metadata.objectStores.set(metadata.id, metadata);
191
    ++m_metadata.maxObjectStoreId;
192

193 194
    m_versionChangeTransaction->objectStoreCreated(name, objectStore);
    return objectStore.release();
195 196
}

197
void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec)
198
{
beidson@apple.com's avatar
beidson@apple.com committed
199
    LOG(StorageAPI, "IDBDatabase::deleteObjectStore");
200
    HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax);
201
    if (!m_versionChangeTransaction) {
202
        ec = IDBDatabaseException::InvalidStateError;
203
        return;
204
    }
205
    if (!m_versionChangeTransaction->isActive()) {
206
        ec = IDBDatabaseException::TransactionInactiveError;
207 208
        return;
    }
209

210
    int64_t objectStoreId = findObjectStoreId(name);
211
    if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
212
        ec = IDBDatabaseException::NotFoundError;
213 214
        return;
    }
215

216 217 218
    m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId);
    m_versionChangeTransaction->objectStoreDeleted(name);
    m_metadata.objectStores.remove(objectStoreId);
219 220
}

221
PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec)
222
{
beidson@apple.com's avatar
beidson@apple.com committed
223
    LOG(StorageAPI, "IDBDatabase::transaction");
224
    HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax);
225
    if (!scope.size()) {
226
        ec = IDBDatabaseException::InvalidAccessError;
227 228
        return 0;
    }
229

230
    IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec);
231
    if (ec)
232
        return 0;
233

234
    if (m_versionChangeTransaction || m_closePending) {
235
        ec = IDBDatabaseException::InvalidStateError;
236 237
        return 0;
    }
238

239
    Vector<int64_t> objectStoreIds;
240 241
    for (size_t i = 0; i < scope.size(); ++i) {
        int64_t objectStoreId = findObjectStoreId(scope[i]);
242
        if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
243
            ec = IDBDatabaseException::NotFoundError;
244 245
            return 0;
        }
246
        objectStoreIds.append(objectStoreId);
247 248
    }

249
    int64_t transactionId = nextTransactionId();
250
    m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode);
251

252
    RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this);
253 254 255
    return transaction.release();
}

256 257 258 259 260 261 262
PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec)
{
    RefPtr<DOMStringList> storeNames = DOMStringList::create();
    storeNames->append(storeName);
    return transaction(context, storeNames, mode, ec);
}

263 264
void IDBDatabase::forceClose()
{
265
    for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it)
266
        (*it)->abort(IGNORE_EXCEPTION);
267 268 269
    this->close();
}

270 271
void IDBDatabase::close()
{
beidson@apple.com's avatar
beidson@apple.com committed
272
    LOG(StorageAPI, "IDBDatabase::close");
273 274 275 276
    if (m_closePending)
        return;

    m_closePending = true;
277 278 279 280 281 282 283 284 285 286

    if (m_transactions.isEmpty())
        closeConnection();
}

void IDBDatabase::closeConnection()
{
    ASSERT(m_closePending);
    ASSERT(m_transactions.isEmpty());

287 288 289
    m_backend->close(m_databaseCallbacks);

    if (m_contextStopped || !scriptExecutionContext())
290
        return;
291

292
    EventQueue& eventQueue = scriptExecutionContext()->eventQueue();
293 294 295 296 297
    // Remove any pending versionchange events scheduled to fire on this
    // connection. They would have been scheduled by the backend when another
    // connection called setVersion, but the frontend connection is being
    // closed before they could fire.
    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
298
        bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]);
299 300 301 302
        ASSERT_UNUSED(removed, removed);
    }
}

beidson@apple.com's avatar
beidson@apple.com committed
303
void IDBDatabase::onVersionChange(uint64_t oldVersion, uint64_t newVersion, IndexedDB::VersionNullness newVersionNullness)
304
{
beidson@apple.com's avatar
beidson@apple.com committed
305
    LOG(StorageAPI, "IDBDatabase::onVersionChange");
306 307 308 309 310 311
    if (m_contextStopped || !scriptExecutionContext())
        return;

    if (m_closePending)
        return;

beidson@apple.com's avatar
beidson@apple.com committed
312
    enqueueEvent(IDBVersionChangeEvent::create(oldVersion, newVersion, newVersionNullness));
313 314
}

315 316
void IDBDatabase::enqueueEvent(PassRefPtr<Event> event)
{
317 318
    ASSERT(!m_contextStopped);
    ASSERT(scriptExecutionContext());
319
    event->setTarget(this);
320
    scriptExecutionContext()->eventQueue().enqueueEvent(event.get());
321 322 323 324 325
    m_enqueuedEvents.append(event);
}

bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event)
{
beidson@apple.com's avatar
beidson@apple.com committed
326
    LOG(StorageAPI, "IDBDatabase::dispatchEvent");
327 328 329 330 331 332 333 334
    ASSERT(event->type() == eventNames().versionchangeEvent);
    for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
        if (m_enqueuedEvents[i].get() == event.get())
            m_enqueuedEvents.remove(i);
    }
    return EventTarget::dispatchEvent(event.get());
}

335 336 337 338 339 340 341 342 343 344 345
int64_t IDBDatabase::findObjectStoreId(const String& name) const
{
    for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) {
        if (it->value.name == name) {
            ASSERT(it->key != IDBObjectStoreMetadata::InvalidId);
            return it->key;
        }
    }
    return IDBObjectStoreMetadata::InvalidId;
}

346 347 348 349
bool IDBDatabase::hasPendingActivity() const
{
    // The script wrapper must not be collected before the object is closed or
    // we can't fire a "versionchange" event to let script manually close the connection.
350
    return !m_closePending && hasEventListeners() && !m_contextStopped;
351 352
}

353 354 355 356 357
void IDBDatabase::stop()
{
    // Stop fires at a deterministic time, so we need to call close in it.
    close();

358
    m_contextStopped = true;
359 360
}

361 362 363
} // namespace WebCore

#endif // ENABLE(INDEXED_DATABASE)