diff options
Diffstat (limited to 'WebCore/storage')
62 files changed, 7146 insertions, 0 deletions
diff --git a/WebCore/storage/ChangeVersionWrapper.cpp b/WebCore/storage/ChangeVersionWrapper.cpp new file mode 100644 index 0000000..54c317c --- /dev/null +++ b/WebCore/storage/ChangeVersionWrapper.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "ChangeVersionWrapper.h" + +#include "Database.h" + +namespace WebCore { + +ChangeVersionWrapper::ChangeVersionWrapper(const String& oldVersion, const String& newVersion) + : m_oldVersion(oldVersion.copy()) + , m_newVersion(newVersion.copy()) +{ +} + +bool ChangeVersionWrapper::performPreflight(SQLTransaction* transaction) +{ + ASSERT(transaction && transaction->database()); + + String actualVersion; + + if (!transaction->database()->getVersionFromDatabase(actualVersion)) { + LOG_ERROR("Unable to retrieve actual current version from database"); + m_sqlError = SQLError::create(0, "unable to verify current version of database"); + return false; + } + + if (actualVersion != m_oldVersion) { + LOG_ERROR("Old version doesn't match actual version"); + m_sqlError = SQLError::create(2, "current version of the database and `oldVersion` argument do not match"); + return false; + } + + return true; +} + +bool ChangeVersionWrapper::performPostflight(SQLTransaction* transaction) +{ + ASSERT(transaction && transaction->database()); + + if (!transaction->database()->setVersionInDatabase(m_newVersion)) { + LOG_ERROR("Unable to set new version in database"); + m_sqlError = SQLError::create(0, "unable to set new version in database"); + return false; + } + + transaction->database()->setExpectedVersion(m_newVersion); + + return true; +} + +} // namespace WebCore diff --git a/WebCore/storage/ChangeVersionWrapper.h b/WebCore/storage/ChangeVersionWrapper.h new file mode 100644 index 0000000..c2983eb --- /dev/null +++ b/WebCore/storage/ChangeVersionWrapper.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef ChangeVersionWrapper_h +#define ChangeVersionWrapper_h + +#include "PlatformString.h" +#include "SQLTransaction.h" + +namespace WebCore { + +class ChangeVersionWrapper : public SQLTransactionWrapper { +public: + static PassRefPtr<ChangeVersionWrapper> create(const String& oldVersion, const String& newVersion) { return adoptRef(new ChangeVersionWrapper(oldVersion, newVersion)); } + + virtual bool performPreflight(SQLTransaction*); + virtual bool performPostflight(SQLTransaction*); + + virtual SQLError* sqlError() const { return m_sqlError.get(); } + +private: + ChangeVersionWrapper(const String& oldVersion, const String& newVersion); + + String m_oldVersion; + String m_newVersion; + RefPtr<SQLError> m_sqlError; +}; + +} // namespace WebCore + +#endif // ChangeVersionWrapper_h diff --git a/WebCore/storage/Database.cpp b/WebCore/storage/Database.cpp new file mode 100644 index 0000000..1055504 --- /dev/null +++ b/WebCore/storage/Database.cpp @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "Database.h" + +#include "ChangeVersionWrapper.h" +#include "CString.h" +#include "DatabaseAuthorizer.h" +#include "DatabaseTask.h" +#include "DatabaseThread.h" +#include "DatabaseTracker.h" +#include "Document.h" +#include "ExceptionCode.h" +#include "FileSystem.h" +#include "Frame.h" +#include "InspectorController.h" +#include "JSDOMWindow.h" +#include "Logging.h" +#include "NotImplemented.h" +#include "Page.h" +#include "OriginQuotaManager.h" +#include "SQLiteDatabase.h" +#include "SQLiteStatement.h" +#include "SQLResultSet.h" +#include <runtime/InitializeThreading.h> +#include <wtf/MainThread.h> + +namespace WebCore { + +static Mutex& guidMutex() +{ + // FIXME: this is not a thread-safe way to initialize a shared global. + static Mutex mutex; + return mutex; +} + +static HashMap<int, String>& guidToVersionMap() +{ + static HashMap<int, String> map; + return map; +} + +static HashMap<int, HashSet<Database*>*>& guidToDatabaseMap() +{ + static HashMap<int, HashSet<Database*>*> map; + return map; +} + +const String& Database::databaseInfoTableName() +{ + static String name = "__WebKitDatabaseInfoTable__"; + return name; +} + +static const String& databaseVersionKey() +{ + static String key = "WebKitDatabaseVersionKey"; + return key; +} + +static int guidForOriginAndName(const String& origin, const String& name); + +PassRefPtr<Database> Database::openDatabase(Document* document, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, ExceptionCode& e) +{ + if (!DatabaseTracker::tracker().canEstablishDatabase(document, name, displayName, estimatedSize)) { + // FIXME: There should be an exception raised here in addition to returning a null Database object. The question has been raised with the WHATWG. + LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), document->securityOrigin()->toString().ascii().data()); + return 0; + } + + RefPtr<Database> database = adoptRef(new Database(document, name, expectedVersion)); + + if (!database->openAndVerifyVersion(e)) { + LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data()); + return 0; + } + + DatabaseTracker::tracker().setDatabaseDetails(document->securityOrigin(), name, displayName, estimatedSize); + + document->setHasOpenDatabases(); + + if (Page* page = document->frame()->page()) + page->inspectorController()->didOpenDatabase(database.get(), document->domain(), name, expectedVersion); + + return database; +} + +Database::Database(Document* document, const String& name, const String& expectedVersion) + : m_transactionInProgress(false) + , m_document(document) + , m_name(name.copy()) + , m_guid(0) + , m_expectedVersion(expectedVersion) + , m_deleted(false) + , m_stopped(false) +{ + ASSERT(document); + m_securityOrigin = document->securityOrigin(); + + if (m_name.isNull()) + m_name = ""; + + JSC::initializeThreading(); + // Database code violates the normal JSCore contract by calling jsUnprotect from a secondary thread, and thus needs additional locking. + JSDOMWindow::commonJSGlobalData()->heap.setGCProtectNeedsLocking(); + + m_guid = guidForOriginAndName(m_securityOrigin->toString(), name); + + { + MutexLocker locker(guidMutex()); + + HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); + if (!hashSet) { + hashSet = new HashSet<Database*>; + guidToDatabaseMap().set(m_guid, hashSet); + } + + hashSet->add(this); + } + + ASSERT(m_document->databaseThread()); + + m_filename = DatabaseTracker::tracker().fullPathForDatabase(m_securityOrigin.get(), m_name); + + DatabaseTracker::tracker().addOpenDatabase(this); + m_document->addOpenDatabase(this); +} + +Database::~Database() +{ + { + MutexLocker locker(guidMutex()); + + HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); + ASSERT(hashSet); + ASSERT(hashSet->contains(this)); + hashSet->remove(this); + if (hashSet->isEmpty()) { + guidToDatabaseMap().remove(m_guid); + delete hashSet; + guidToVersionMap().remove(m_guid); + } + } + + if (m_document->databaseThread()) + m_document->databaseThread()->unscheduleDatabaseTasks(this); + + DatabaseTracker::tracker().removeOpenDatabase(this); + m_document->removeOpenDatabase(this); +} + +bool Database::openAndVerifyVersion(ExceptionCode& e) +{ + m_databaseAuthorizer = DatabaseAuthorizer::create(); + + RefPtr<DatabaseOpenTask> task = DatabaseOpenTask::create(this); + + task->lockForSynchronousScheduling(); + m_document->databaseThread()->scheduleImmediateTask(task); + task->waitForSynchronousCompletion(); + + ASSERT(task->isComplete()); + e = task->exceptionCode(); + return task->openSuccessful(); +} + + +static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) +{ + SQLiteStatement statement(db, query); + int result = statement.prepare(); + + if (result != SQLResultOk) { + LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); + return false; + } + + result = statement.step(); + if (result == SQLResultRow) { + resultString = statement.getColumnText(0); + return true; + } else if (result == SQLResultDone) { + resultString = String(); + return true; + } else { + LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); + return false; + } +} + +bool Database::getVersionFromDatabase(String& version) +{ + static String getVersionQuery = "SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';"; + + m_databaseAuthorizer->disable(); + + bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.copy(), version); + if (!result) + LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); + + m_databaseAuthorizer->enable(); + + return result; +} + +static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) +{ + SQLiteStatement statement(db, query); + int result = statement.prepare(); + + if (result != SQLResultOk) { + LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); + return false; + } + + statement.bindText(1, value); + + result = statement.step(); + if (result != SQLResultDone) { + LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); + return false; + } + + return true; +} + +bool Database::setVersionInDatabase(const String& version) +{ + static String setVersionQuery = "INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);"; + + m_databaseAuthorizer->disable(); + + bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.copy(), version); + if (!result) + LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); + + m_databaseAuthorizer->enable(); + + return result; +} + +bool Database::versionMatchesExpected() const +{ + if (!m_expectedVersion.isEmpty()) { + MutexLocker locker(guidMutex()); + return m_expectedVersion == guidToVersionMap().get(m_guid); + } + + return true; +} + +void Database::markAsDeletedAndClose() +{ + if (m_deleted) + return; + + LOG(StorageAPI, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this); + m_deleted = true; + + if (m_document->databaseThread()->terminationRequested()) { + LOG(StorageAPI, "Database handle %p is on a terminated DatabaseThread, cannot be marked for normal closure\n", this); + return; + } + + document()->databaseThread()->unscheduleDatabaseTasks(this); + + RefPtr<DatabaseCloseTask> task = DatabaseCloseTask::create(this); + + task->lockForSynchronousScheduling(); + m_document->databaseThread()->scheduleImmediateTask(task); + task->waitForSynchronousCompletion(); +} + +void Database::close() +{ + m_sqliteDatabase.close(); +} + +void Database::stop() +{ + // FIXME: The net effect of the following code is to remove all pending transactions and statements, but allow the current statement + // to run to completion. In the future we can use the sqlite3_progress_handler or sqlite3_interrupt interfaces to cancel the current + // statement in response to close(), as well. + + // This method is meant to be used as an analog to cancelling a loader, and is used when a document is shut down as the result of + // a page load or closing the page + m_stopped = true; + + { + MutexLocker locker(m_transactionInProgressMutex); + m_transactionQueue.kill(); + m_transactionInProgress = false; + } +} + +unsigned long long Database::databaseSize() const +{ + long long size; + if (!getFileSize(m_filename, size)) + size = 0; + return size; +} + +unsigned long long Database::maximumSize() const +{ + // The maximum size for this database is the full quota for this origin, minus the current usage within this origin, + // except for the current usage of this database + + OriginQuotaManager& manager(DatabaseTracker::tracker().originQuotaManager()); + Locker<OriginQuotaManager> locker(manager); + + return DatabaseTracker::tracker().quotaForOrigin(m_securityOrigin.get()) - manager.diskUsage(m_securityOrigin.get()) + databaseSize(); +} + +void Database::disableAuthorizer() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->disable(); +} + +void Database::enableAuthorizer() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->enable(); +} + +static int guidForOriginAndName(const String& origin, const String& name) +{ + static int currentNewGUID = 1; + static Mutex stringIdentifierMutex; + static HashMap<String, int> stringIdentifierToGUIDMap; + + String stringID; + if (origin.endsWith("/")) + stringID = origin + name; + else + stringID = origin + "/" + name; + + MutexLocker locker(stringIdentifierMutex); + int guid = stringIdentifierToGUIDMap.get(stringID); + if (!guid) { + guid = currentNewGUID++; + stringIdentifierToGUIDMap.set(stringID, guid); + } + + return guid; +} + +void Database::resetAuthorizer() +{ + if (m_databaseAuthorizer) + m_databaseAuthorizer->reset(); +} + +void Database::performPolicyChecks() +{ + // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates. + // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to + // increase the size limit + + /* + if (m_databaseAuthorizer->lastActionIncreasedSize()) + DatabaseTracker::scheduleFileSizeCheckOnMainThread(this); + */ + + notImplemented(); +} + +bool Database::performOpenAndVerify(ExceptionCode& e) +{ + if (!m_sqliteDatabase.open(m_filename)) { + LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); + e = INVALID_STATE_ERR; + return false; + } + + ASSERT(m_databaseAuthorizer); + m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); + + if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { + if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { + LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); + e = INVALID_STATE_ERR; + return false; + } + } + + + String currentVersion; + { + MutexLocker locker(guidMutex()); + currentVersion = guidToVersionMap().get(m_guid); + + if (currentVersion.isNull()) + LOG(StorageAPI, "Current cached version for guid %i is null", m_guid); + else + LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); + + if (currentVersion.isNull()) { + if (!getVersionFromDatabase(currentVersion)) { + LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); + e = INVALID_STATE_ERR; + return false; + } + if (currentVersion.length()) { + LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); + } else { + LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); + if (!setVersionInDatabase(m_expectedVersion)) { + LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); + e = INVALID_STATE_ERR; + return false; + } + + currentVersion = m_expectedVersion; + } + + guidToVersionMap().set(m_guid, currentVersion.copy()); + } + } + + if (currentVersion.isNull()) { + LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); + currentVersion = ""; + } + + // FIXME: For now, the spec says that if the database has no version, it is valid for any "Expected version" string. That seems silly and I think it should be + // changed, and here's where we would change it + if (m_expectedVersion.length()) { + if (currentVersion.length() && m_expectedVersion != currentVersion) { + LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), + databaseDebugName().ascii().data(), currentVersion.ascii().data()); + e = INVALID_STATE_ERR; + return false; + } + } + + return true; +} + +void Database::changeVersion(const String& oldVersion, const String& newVersion, + PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, + PassRefPtr<VoidCallback> successCallback) +{ + m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, ChangeVersionWrapper::create(oldVersion, newVersion))); + MutexLocker locker(m_transactionInProgressMutex); + if (!m_transactionInProgress) + scheduleTransaction(); +} + +void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, + PassRefPtr<VoidCallback> successCallback) +{ + m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, 0)); + MutexLocker locker(m_transactionInProgressMutex); + if (!m_transactionInProgress) + scheduleTransaction(); +} + +void Database::scheduleTransaction() +{ + ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller. + RefPtr<SQLTransaction> transaction; + if (m_transactionQueue.tryGetMessage(transaction) && m_document->databaseThread()) { + RefPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); + LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task.get(), task->transaction()); + m_transactionInProgress = true; + m_document->databaseThread()->scheduleTask(task.release()); + } else + m_transactionInProgress = false; +} + +void Database::scheduleTransactionStep(SQLTransaction* transaction) +{ + if (m_document->databaseThread()) { + RefPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); + LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task.get()); + m_document->databaseThread()->scheduleTask(task.release()); + } +} + +void Database::scheduleTransactionCallback(SQLTransaction* transaction) +{ + transaction->ref(); + callOnMainThread(deliverPendingCallback, transaction); +} + +Vector<String> Database::performGetTableNames() +{ + disableAuthorizer(); + + SQLiteStatement statement(m_sqliteDatabase, "SELECT name FROM sqlite_master WHERE type='table';"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data()); + enableAuthorizer(); + return Vector<String>(); + } + + Vector<String> tableNames; + int result; + while ((result = statement.step()) == SQLResultRow) { + String name = statement.getColumnText(0); + if (name != databaseInfoTableName()) + tableNames.append(name); + } + + enableAuthorizer(); + + if (result != SQLResultDone) { + LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data()); + return Vector<String>(); + } + + return tableNames; +} + +String Database::version() const +{ + if (m_deleted) + return String(); + MutexLocker locker(guidMutex()); + return guidToVersionMap().get(m_guid).copy(); +} + +void Database::deliverPendingCallback(void* context) +{ + SQLTransaction* transaction = static_cast<SQLTransaction*>(context); + transaction->performPendingCallback(); + transaction->deref(); // Was ref'd in scheduleTransactionCallback(). +} + +Vector<String> Database::tableNames() +{ + RefPtr<DatabaseTableNamesTask> task = DatabaseTableNamesTask::create(this); + + task->lockForSynchronousScheduling(); + m_document->databaseThread()->scheduleImmediateTask(task); + task->waitForSynchronousCompletion(); + + return task->tableNames(); +} + +void Database::setExpectedVersion(const String& version) +{ + m_expectedVersion = version.copy(); +} + +PassRefPtr<SecurityOrigin> Database::securityOriginCopy() const +{ + return m_securityOrigin->copy(); +} + +String Database::stringIdentifier() const +{ + // Return a deep copy for ref counting thread safety + return m_name.copy(); +} + +} diff --git a/WebCore/storage/Database.h b/WebCore/storage/Database.h new file mode 100644 index 0000000..955f023 --- /dev/null +++ b/WebCore/storage/Database.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef Database_h +#define Database_h + +#include <wtf/MessageQueue.h> +#include "PlatformString.h" +#include "SecurityOrigin.h" +#include "SQLiteDatabase.h" +#include "SQLTransaction.h" +#include "StringHash.h" +#include "Timer.h" +#include "VoidCallback.h" + +#include <wtf/Forward.h> +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Deque.h> + +namespace WebCore { + +class DatabaseAuthorizer; +class DatabaseThread; +class Document; +class SQLResultSet; +class SQLTransactionCallback; +class SQLTransactionErrorCallback; +class SQLValue; + +typedef int ExceptionCode; + +class Database : public ThreadSafeShared<Database> { + friend class DatabaseTransactionTask; + friend class SQLStatement; + friend class SQLTransaction; +public: + ~Database(); + +// Direct support for the DOM API + static PassRefPtr<Database> openDatabase(Document* document, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, ExceptionCode&); + String version() const; + void changeVersion(const String& oldVersion, const String& newVersion, + PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, + PassRefPtr<VoidCallback> successCallback); + void transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, + PassRefPtr<VoidCallback> successCallback); + +// Internal engine support + static const String& databaseInfoTableName(); + + void disableAuthorizer(); + void enableAuthorizer(); + + Vector<String> tableNames(); + + Document* document() const { return m_document.get(); } + PassRefPtr<SecurityOrigin> securityOriginCopy() const; + String stringIdentifier() const; + + bool getVersionFromDatabase(String&); + bool setVersionInDatabase(const String&); + void setExpectedVersion(const String&); + bool versionMatchesExpected() const; + + void markAsDeletedAndClose(); + bool deleted() const { return m_deleted; } + + void close(); + + void stop(); + bool stopped() const { return m_stopped; } + + unsigned long long databaseSize() const; + unsigned long long maximumSize() const; + +// Called from DatabaseThread, must be prepared to work on the background thread + void resetAuthorizer(); + void performPolicyChecks(); + + bool performOpenAndVerify(ExceptionCode&); + + Vector<String> performGetTableNames(); + +private: + Database(Document* document, const String& name, const String& expectedVersion); + + bool openAndVerifyVersion(ExceptionCode&); + + void scheduleTransaction(); + void scheduleTransactionCallback(SQLTransaction*); + void scheduleTransactionStep(SQLTransaction* transaction); + + MessageQueue<RefPtr<SQLTransaction> > m_transactionQueue; + Mutex m_transactionInProgressMutex; + bool m_transactionInProgress; + + static void deliverPendingCallback(void*); + + RefPtr<Document> m_document; + RefPtr<SecurityOrigin> m_securityOrigin; + String m_name; + int m_guid; + String m_expectedVersion; + String m_filename; + + bool m_deleted; + + bool m_stopped; + + SQLiteDatabase m_sqliteDatabase; + RefPtr<DatabaseAuthorizer> m_databaseAuthorizer; + +#ifndef NDEBUG + String databaseDebugName() const { return m_securityOrigin->toString() + "::" + m_name; } +#endif +}; + +} // namespace WebCore + +#endif // Database_h diff --git a/WebCore/storage/Database.idl b/WebCore/storage/Database.idl new file mode 100644 index 0000000..e28db9b --- /dev/null +++ b/WebCore/storage/Database.idl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface Database { + readonly attribute DOMString version; + [Custom] void changeVersion(in DOMString oldVersion, in DOMString newVersion, in SQLTransactionCallback callback, in SQLTransactionErrorCallback errorCallback, in VoidCallback successCallback); + [Custom] void transaction(in SQLTransactionCallback callback, in SQLTransactionErrorCallback errorCallback, in VoidCallback successCallback); + }; + +} diff --git a/WebCore/storage/DatabaseAuthorizer.cpp b/WebCore/storage/DatabaseAuthorizer.cpp new file mode 100644 index 0000000..333dd52 --- /dev/null +++ b/WebCore/storage/DatabaseAuthorizer.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "DatabaseAuthorizer.h" + +#include "Database.h" +#include "PlatformString.h" + +namespace WebCore { + +DatabaseAuthorizer::DatabaseAuthorizer() + : m_securityEnabled(false) +{ + reset(); +} + +void DatabaseAuthorizer::reset() +{ + m_lastActionWasInsert = false; + m_lastActionChangedDatabase = false; +} + +int DatabaseAuthorizer::createTable(const String& tableName) +{ + m_lastActionChangedDatabase = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createTempTable(const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropTable(const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropTempTable(const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowAlterTable(const String& databaseName, const String& tableName) +{ + m_lastActionChangedDatabase = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createIndex(const String& indexName, const String& tableName) +{ + m_lastActionChangedDatabase = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createTempIndex(const String& indexName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropIndex(const String& indexName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropTempIndex(const String& indexName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createTrigger(const String& triggerName, const String& tableName) +{ + m_lastActionChangedDatabase = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createTempTrigger(const String& triggerName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropTrigger(const String& triggerName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::dropTempTrigger(const String& triggerName, const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::createVTable(const String& tableName, const String& moduleName) +{ + m_lastActionChangedDatabase = true; + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::dropVTable(const String& tableName, const String& moduleName) +{ + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::allowDelete(const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowInsert(const String& tableName) +{ + m_lastActionChangedDatabase = true; + m_lastActionWasInsert = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowUpdate(const String& tableName, const String& columnName) +{ + m_lastActionChangedDatabase = true; + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowTransaction() +{ + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::allowRead(const String& tableName, const String& columnName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowAnalyze(const String& tableName) +{ + return denyBasedOnTableName(tableName); +} + +int DatabaseAuthorizer::allowPragma(const String& pragmaName, const String& firstArgument) +{ + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::allowAttach(const String& filename) +{ + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::allowDetach(const String& databaseName) +{ + return m_securityEnabled ? SQLAuthDeny : SQLAuthAllow; +} + +int DatabaseAuthorizer::allowFunction(const String& functionName) +{ + // FIXME: Are there any of these we need to prevent? One might guess current_date, current_time, current_timestamp because + // they would violate the "sandbox environment" part of 4.11.3, but scripts can generate the local client side information via + // javascript directly, anyways. Are there any other built-ins we need to be worried about? + return SQLAuthAllow; +} + +void DatabaseAuthorizer::disable() +{ + m_securityEnabled = false; +} + +void DatabaseAuthorizer::enable() +{ + m_securityEnabled = true; +} + +int DatabaseAuthorizer::denyBasedOnTableName(const String& tableName) +{ + if (!m_securityEnabled) + return SQLAuthAllow; + + // Sadly, normal creates and drops end up affecting sqlite_master in an authorizer callback, so + // it will be tough to enforce all of the following policies + //if (equalIgnoringCase(tableName, "sqlite_master") || equalIgnoringCase(tableName, "sqlite_temp_master") || + // equalIgnoringCase(tableName, "sqlite_sequence") || equalIgnoringCase(tableName, Database::databaseInfoTableName())) + // return SQLAuthDeny; + + if (equalIgnoringCase(tableName, Database::databaseInfoTableName())) + return SQLAuthDeny; + + return SQLAuthAllow; +} + +} // namespace WebCore diff --git a/WebCore/storage/DatabaseAuthorizer.h b/WebCore/storage/DatabaseAuthorizer.h new file mode 100644 index 0000000..d02d810 --- /dev/null +++ b/WebCore/storage/DatabaseAuthorizer.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef DatabaseAuthorizer_h +#define DatabaseAuthorizer_h + +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class String; + +extern const int SQLAuthAllow; +extern const int SQLAuthIgnore; +extern const int SQLAuthDeny; + +class DatabaseAuthorizer : public ThreadSafeShared<DatabaseAuthorizer> { +public: + static PassRefPtr<DatabaseAuthorizer> create() { return adoptRef(new DatabaseAuthorizer); } + + int createTable(const String& tableName); + int createTempTable(const String& tableName); + int dropTable(const String& tableName); + int dropTempTable(const String& tableName); + int allowAlterTable(const String& databaseName, const String& tableName); + + int createIndex(const String& indexName, const String& tableName); + int createTempIndex(const String& indexName, const String& tableName); + int dropIndex(const String& indexName, const String& tableName); + int dropTempIndex(const String& indexName, const String& tableName); + + int createTrigger(const String& triggerName, const String& tableName); + int createTempTrigger(const String& triggerName, const String& tableName); + int dropTrigger(const String& triggerName, const String& tableName); + int dropTempTrigger(const String& triggerName, const String& tableName); + + int createView(const String& viewName) { return SQLAuthAllow; } + int createTempView(const String& viewName) { return SQLAuthAllow; } + int dropView(const String& viewName) { return SQLAuthAllow; } + int dropTempView(const String& viewName) { return SQLAuthAllow; } + + int createVTable(const String& tableName, const String& moduleName); + int dropVTable(const String& tableName, const String& moduleName); + + int allowDelete(const String& tableName); + int allowInsert(const String& tableName); + int allowUpdate(const String& tableName, const String& columnName); + int allowTransaction(); + + int allowSelect() { return SQLAuthAllow; } + int allowRead(const String& tableName, const String& columnName); + + int allowReindex(const String& indexName) { return SQLAuthAllow; } + int allowAnalyze(const String& tableName); + int allowFunction(const String& functionName); + int allowPragma(const String& pragmaName, const String& firstArgument); + + int allowAttach(const String& filename); + int allowDetach(const String& databaseName); + + void disable(); + void enable(); + + void reset(); + + bool lastActionWasInsert() const { return m_lastActionWasInsert; } + bool lastActionChangedDatabase() const { return m_lastActionChangedDatabase; } + +private: + DatabaseAuthorizer(); + int denyBasedOnTableName(const String&); + + bool m_securityEnabled; + bool m_lastActionWasInsert; + bool m_lastActionChangedDatabase; +}; + +} // namespace WebCore + +#endif // DatabaseAuthorizer_h diff --git a/WebCore/storage/DatabaseDetails.h b/WebCore/storage/DatabaseDetails.h new file mode 100644 index 0000000..677362b --- /dev/null +++ b/WebCore/storage/DatabaseDetails.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef DatabaseDetails_h +#define DatabaseDetails_h + +#include "PlatformString.h" + +namespace WebCore { + +class DatabaseDetails { +public: + DatabaseDetails() + : m_expectedUsage(0) + , m_currentUsage(0) + { + } + + DatabaseDetails(const String& databaseName, const String& displayName, unsigned long long expectedUsage, unsigned long long currentUsage) + : m_name(databaseName) + , m_displayName(displayName) + , m_expectedUsage(expectedUsage) + , m_currentUsage(currentUsage) + { + } + + const String& name() const { return m_name; } + const String& displayName() const { return m_displayName; } + unsigned long long expectedUsage() const { return m_expectedUsage; } + unsigned long long currentUsage() const { return m_currentUsage; } + +private: + String m_name; + String m_displayName; + unsigned long long m_expectedUsage; + unsigned long long m_currentUsage; + +}; + +} // namespace WebCore + +#endif // DatabaseDetails_h diff --git a/WebCore/storage/DatabaseTask.cpp b/WebCore/storage/DatabaseTask.cpp new file mode 100644 index 0000000..7957d2a --- /dev/null +++ b/WebCore/storage/DatabaseTask.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "DatabaseTask.h" + +#include "Database.h" +#include "Logging.h" + +namespace WebCore { + +DatabaseTask::DatabaseTask(Database* database) + : m_database(database) + , m_complete(false) +{ +} + +DatabaseTask::~DatabaseTask() +{ +} + +void DatabaseTask::performTask() +{ + // Database tasks are meant to be used only once, so make sure this one hasn't been performed before. + ASSERT(!m_complete); + + LOG(StorageAPI, "Performing %s %p\n", debugTaskName(), this); + + m_database->resetAuthorizer(); + doPerformTask(); + m_database->performPolicyChecks(); + + if (m_synchronousMutex) + m_synchronousMutex->lock(); + + m_complete = true; + + if (m_synchronousMutex) { + m_synchronousCondition->signal(); + m_synchronousMutex->unlock(); + } +} + +void DatabaseTask::lockForSynchronousScheduling() +{ + // Called from main thread. + ASSERT(!m_synchronousMutex); + ASSERT(!m_synchronousCondition); + m_synchronousMutex.set(new Mutex); + m_synchronousCondition.set(new ThreadCondition); +} + +void DatabaseTask::waitForSynchronousCompletion() +{ + // Called from main thread. + m_synchronousMutex->lock(); + if (!m_complete) + m_synchronousCondition->wait(*m_synchronousMutex); + m_synchronousMutex->unlock(); +} + +// *** DatabaseOpenTask *** +// Opens the database file and verifies the version matches the expected version. + +DatabaseOpenTask::DatabaseOpenTask(Database* database) + : DatabaseTask(database) + , m_code(0) + , m_success(false) +{ +} + +void DatabaseOpenTask::doPerformTask() +{ + m_success = database()->performOpenAndVerify(m_code); +} + +#ifndef NDEBUG +const char* DatabaseOpenTask::debugTaskName() const +{ + return "DatabaseOpenTask"; +} +#endif + +// *** DatabaseCloseTask *** +// Closes the database. + +DatabaseCloseTask::DatabaseCloseTask(Database* database) + : DatabaseTask(database) +{ +} + +void DatabaseCloseTask::doPerformTask() +{ + database()->close(); +} + +#ifndef NDEBUG +const char* DatabaseCloseTask::debugTaskName() const +{ + return "DatabaseCloseTask"; +} +#endif + +// *** DatabaseTransactionTask *** +// Starts a transaction that will report its results via a callback. + +DatabaseTransactionTask::DatabaseTransactionTask(PassRefPtr<SQLTransaction> transaction) + : DatabaseTask(transaction->database()) + , m_transaction(transaction) +{ +} + +DatabaseTransactionTask::~DatabaseTransactionTask() +{ +} + +void DatabaseTransactionTask::doPerformTask() +{ + if (m_transaction->performNextStep()) { + // The transaction is complete, we can move on to the next one. + MutexLocker locker(m_transaction->database()->m_transactionInProgressMutex); + m_transaction->database()->scheduleTransaction(); + } +} + +#ifndef NDEBUG +const char* DatabaseTransactionTask::debugTaskName() const +{ + return "DatabaseTransactionTask"; +} +#endif + +// *** DatabaseTableNamesTask *** +// Retrieves a list of all tables in the database - for WebInspector support. + +DatabaseTableNamesTask::DatabaseTableNamesTask(Database* database) + : DatabaseTask(database) +{ +} + +void DatabaseTableNamesTask::doPerformTask() +{ + m_tableNames = database()->performGetTableNames(); +} + +#ifndef NDEBUG +const char* DatabaseTableNamesTask::debugTaskName() const +{ + return "DatabaseTableNamesTask"; +} +#endif + +} // namespace WebCore diff --git a/WebCore/storage/DatabaseTask.h b/WebCore/storage/DatabaseTask.h new file mode 100644 index 0000000..50d297f --- /dev/null +++ b/WebCore/storage/DatabaseTask.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef DatabaseTask_h +#define DatabaseTask_h + +#include "ExceptionCode.h" +#include "PlatformString.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Database; +class DatabaseThread; +class SQLValue; +class SQLCallback; +class SQLTransaction; +class VersionChangeCallback; + +class DatabaseTask : public ThreadSafeShared<DatabaseTask> +{ + friend class Database; +public: + virtual ~DatabaseTask(); + + void performTask(); + + Database* database() const { return m_database; } + bool isComplete() const { return m_complete; } + +protected: + DatabaseTask(Database*); + +private: + virtual void doPerformTask() = 0; +#ifndef NDEBUG + virtual const char* debugTaskName() const = 0; +#endif + + void lockForSynchronousScheduling(); + void waitForSynchronousCompletion(); + + Database* m_database; + + bool m_complete; + + OwnPtr<Mutex> m_synchronousMutex; + OwnPtr<ThreadCondition> m_synchronousCondition; +}; + +class DatabaseOpenTask : public DatabaseTask +{ +public: + static PassRefPtr<DatabaseOpenTask> create(Database* db) { return adoptRef(new DatabaseOpenTask(db)); } + + ExceptionCode exceptionCode() const { return m_code; } + bool openSuccessful() const { return m_success; } + +private: + DatabaseOpenTask(Database*); + + virtual void doPerformTask(); +#ifndef NDEBUG + virtual const char* debugTaskName() const; +#endif + + ExceptionCode m_code; + bool m_success; +}; + +class DatabaseCloseTask : public DatabaseTask +{ +public: + static PassRefPtr<DatabaseCloseTask> create(Database* db) { return adoptRef(new DatabaseCloseTask(db)); } + +private: + DatabaseCloseTask(Database*); + + virtual void doPerformTask(); +#ifndef NDEBUG + virtual const char* debugTaskName() const; +#endif +}; + +class DatabaseTransactionTask : public DatabaseTask +{ +public: + static PassRefPtr<DatabaseTransactionTask> create(PassRefPtr<SQLTransaction> transaction) { return adoptRef(new DatabaseTransactionTask(transaction)); } + + SQLTransaction* transaction() const { return m_transaction.get(); } + + virtual ~DatabaseTransactionTask(); +private: + DatabaseTransactionTask(PassRefPtr<SQLTransaction>); + + virtual void doPerformTask(); +#ifndef NDEBUG + virtual const char* debugTaskName() const; +#endif + + RefPtr<SQLTransaction> m_transaction; +}; + +class DatabaseTableNamesTask : public DatabaseTask +{ +public: + static PassRefPtr<DatabaseTableNamesTask> create(Database* db) { return adoptRef(new DatabaseTableNamesTask(db)); } + + Vector<String>& tableNames() { return m_tableNames; } + +private: + DatabaseTableNamesTask(Database*); + + virtual void doPerformTask(); +#ifndef NDEBUG + virtual const char* debugTaskName() const; +#endif + + Vector<String> m_tableNames; +}; + +} // namespace WebCore + +#endif // DatabaseTask_h diff --git a/WebCore/storage/DatabaseThread.cpp b/WebCore/storage/DatabaseThread.cpp new file mode 100644 index 0000000..7eeed9b --- /dev/null +++ b/WebCore/storage/DatabaseThread.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "DatabaseThread.h" + +#include "AutodrainedPool.h" +#include "Database.h" +#include "DatabaseTask.h" +#include "Logging.h" + +namespace WebCore { + +DatabaseThread::DatabaseThread(Document* document) + : m_threadID(0) +{ + m_selfRef = this; +} + +DatabaseThread::~DatabaseThread() +{ + // FIXME: Any cleanup required here? Since the thread deletes itself after running its detached course, I don't think so. Lets be sure. +} + +bool DatabaseThread::start() +{ + if (m_threadID) + return true; + + m_threadID = createThread(DatabaseThread::databaseThreadStart, this, "WebCore::Database"); + + return m_threadID; +} + +void DatabaseThread::requestTermination() +{ + LOG(StorageAPI, "DatabaseThread %p was asked to terminate\n", this); + m_queue.kill(); +} + +bool DatabaseThread::terminationRequested() const +{ + return m_queue.killed(); +} + +void* DatabaseThread::databaseThreadStart(void* vDatabaseThread) +{ + DatabaseThread* dbThread = static_cast<DatabaseThread*>(vDatabaseThread); + return dbThread->databaseThread(); +} + +void* DatabaseThread::databaseThread() +{ + LOG(StorageAPI, "Starting DatabaseThread %p", this); + + AutodrainedPool pool; + while (true) { + RefPtr<DatabaseTask> task; + if (!m_queue.waitForMessage(task)) + break; + + task->performTask(); + + pool.cycle(); + } + + LOG(StorageAPI, "About to detach thread %i and clear the ref to DatabaseThread %p, which currently has %i ref(s)", m_threadID, this, refCount()); + + // Detach the thread so its resources are no longer of any concern to anyone else + detachThread(m_threadID); + + // Clear the self refptr, possibly resulting in deletion + m_selfRef = 0; + + return 0; +} + +void DatabaseThread::scheduleTask(PassRefPtr<DatabaseTask> task) +{ + m_queue.append(task); +} + +void DatabaseThread::scheduleImmediateTask(PassRefPtr<DatabaseTask> task) +{ + m_queue.prepend(task); +} + +void DatabaseThread::unscheduleDatabaseTasks(Database* database) +{ + // Note that the thread loop is running, so some tasks for the database + // may still be executed. This is unavoidable. + + Deque<RefPtr<DatabaseTask> > filteredReverseQueue; + RefPtr<DatabaseTask> task; + while (m_queue.tryGetMessage(task)) { + if (task->database() != database) + filteredReverseQueue.append(task); + } + + while (!filteredReverseQueue.isEmpty()) { + m_queue.append(filteredReverseQueue.first()); + filteredReverseQueue.removeFirst(); + } +} + +} // namespace WebCore diff --git a/WebCore/storage/DatabaseThread.h b/WebCore/storage/DatabaseThread.h new file mode 100644 index 0000000..654c2dd --- /dev/null +++ b/WebCore/storage/DatabaseThread.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef DatabaseThread_h +#define DatabaseThread_h + +#include <wtf/Deque.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/MessageQueue.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Database; +class DatabaseTask; +class Document; + +class DatabaseThread : public ThreadSafeShared<DatabaseThread> +{ +public: + static PassRefPtr<DatabaseThread> create(Document* doc) { return adoptRef(new DatabaseThread(doc)); } + ~DatabaseThread(); + + bool start(); + void requestTermination(); + bool terminationRequested() const; + + void scheduleTask(PassRefPtr<DatabaseTask>); + void scheduleImmediateTask(PassRefPtr<DatabaseTask>); // This just adds the task to the front of the queue - the caller needs to be extremely careful not to create deadlocks when waiting for completion. + void unscheduleDatabaseTasks(Database*); + +private: + DatabaseThread(Document*); + + static void* databaseThreadStart(void*); + void* databaseThread(); + + ThreadIdentifier m_threadID; + RefPtr<DatabaseThread> m_selfRef; + + MessageQueue<RefPtr<DatabaseTask> > m_queue; +}; + +} // namespace WebCore + +#endif // DatabaseThread_h diff --git a/WebCore/storage/DatabaseTracker.cpp b/WebCore/storage/DatabaseTracker.cpp new file mode 100644 index 0000000..9a78dc8 --- /dev/null +++ b/WebCore/storage/DatabaseTracker.cpp @@ -0,0 +1,827 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "DatabaseTracker.h" + +#include "ChromeClient.h" +#include "Database.h" +#include "DatabaseTrackerClient.h" +#include "Document.h" +#include "FileSystem.h" +#include "Logging.h" +#include "OriginQuotaManager.h" +#include "Page.h" +#include "SecurityOrigin.h" +#include "SecurityOriginHash.h" +#include "SQLiteStatement.h" +#include <wtf/MainThread.h> + +using namespace std; + +namespace WebCore { + +OriginQuotaManager& DatabaseTracker::originQuotaManager() +{ + populateOrigins(); + ASSERT(m_quotaManager); + return *m_quotaManager; +} + +DatabaseTracker& DatabaseTracker::tracker() +{ + static DatabaseTracker tracker; + return tracker; +} + +DatabaseTracker::DatabaseTracker() + : m_client(0) + , m_proposedDatabase(0) +#ifndef NDEBUG + , m_thread(currentThread()) +#endif +{ +} + +void DatabaseTracker::setDatabaseDirectoryPath(const String& path) +{ + ASSERT(currentThread() == m_thread); + ASSERT(!m_database.isOpen()); + m_databaseDirectoryPath = path; +} + +const String& DatabaseTracker::databaseDirectoryPath() const +{ + ASSERT(currentThread() == m_thread); + return m_databaseDirectoryPath; +} + +String DatabaseTracker::trackerDatabasePath() const +{ + ASSERT(currentThread() == m_thread); + if (m_databaseDirectoryPath.isEmpty()) + return String(); + return pathByAppendingComponent(m_databaseDirectoryPath, "Databases.db"); +} + +void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist) +{ + ASSERT(currentThread() == m_thread); + + if (m_database.isOpen()) + return; + + String databasePath = trackerDatabasePath(); + if (databasePath.isEmpty()) + return; + + if (!createIfDoesNotExist && !fileExists(databasePath)) + return; + + makeAllDirectories(m_databaseDirectoryPath); + if (!m_database.open(databasePath)) { + // FIXME: What do do here? + return; + } + if (!m_database.tableExists("Origins")) { + if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) { + // FIXME: and here + } + } + if (!m_database.tableExists("Databases")) { + if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) { + // FIXME: and here + } + } +} + +bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize) +{ + ASSERT(currentThread() == m_thread); + + // Populate the origins before we establish a database; this guarantees that quotaForOrigin + // can run on the database thread later. + populateOrigins(); + + SecurityOrigin* origin = document->securityOrigin(); + + // Since we're imminently opening a database within this Document's origin, make sure this origin is being tracked by the QuotaTracker + // by fetching it's current usage now + unsigned long long usage = usageForOrigin(origin); + + // If a database already exists, ignore the passed-in estimated size and say it's OK. + if (hasEntryForDatabase(origin, name)) + return true; + + // If the database will fit, allow its creation. + unsigned long long requirement = usage + max(1UL, estimatedSize); + if (requirement < usage) + return false; // If the estimated size is so big it causes an overflow, don't allow creation. + if (requirement <= quotaForOrigin(origin)) + return true; + + // Give the chrome client a chance to increase the quota. + // Temporarily make the details of the proposed database available, so the client can get at them. + Page* page = document->page(); + if (!page) + return false; + pair<SecurityOrigin*, DatabaseDetails> details(origin, DatabaseDetails(name, displayName, estimatedSize, 0)); + m_proposedDatabase = &details; + page->chrome()->client()->exceededDatabaseQuota(document->frame(), name); + m_proposedDatabase = 0; + + // If the database will fit now, allow its creation. + return requirement <= quotaForOrigin(origin); +} + +bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread); + populateOrigins(); + MutexLocker lockQuotaMap(m_quotaMapGuard); + return m_quotaMap->contains(origin); +} + +bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier) +{ + ASSERT(currentThread() == m_thread); + openTrackerDatabase(false); + if (!m_database.isOpen()) + return false; + SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;"); + + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + statement.bindText(2, databaseIdentifier); + + return statement.step() == SQLResultRow; +} + +String DatabaseTracker::originPath(SecurityOrigin* origin) const +{ + ASSERT(currentThread() == m_thread); + if (m_databaseDirectoryPath.isEmpty()) + return String(); + return pathByAppendingComponent(m_databaseDirectoryPath, origin->databaseIdentifier()); +} + +String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists) +{ + ASSERT(currentThread() == m_thread); + + if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name) + return String(); + + String originIdentifier = origin->databaseIdentifier(); + String originPath = this->originPath(origin); + + // Make sure the path for this SecurityOrigin exists + if (createIfNotExists && !makeAllDirectories(originPath)) + return String(); + + // See if we have a path for this database yet + openTrackerDatabase(false); + if (!m_database.isOpen()) + return String(); + SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;"); + + if (statement.prepare() != SQLResultOk) + return String(); + + statement.bindText(1, originIdentifier); + statement.bindText(2, name); + + int result = statement.step(); + + if (result == SQLResultRow) + return pathByAppendingComponent(originPath, statement.getColumnText(0)); + if (!createIfNotExists) + return String(); + + if (result != SQLResultDone) { + LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin->databaseIdentifier().ascii().data(), name.ascii().data()); + return String(); + } + statement.finalize(); + + SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';"); + + // FIXME: More informative error handling here, even though these steps should never fail + if (sequenceStatement.prepare() != SQLResultOk) + return String(); + result = sequenceStatement.step(); + + // This has a range of 2^63 and starts at 0 for every time a user resets Safari - + // I can't imagine it'd over overflow + int64_t seq = 0; + if (result == SQLResultRow) { + seq = sequenceStatement.getColumnInt64(0); + } else if (result != SQLResultDone) + return String(); + sequenceStatement.finalize(); + + String filename; + do { + ++seq; + filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq)); + } while (fileExists(filename)); + + if (!addDatabase(origin, name, String::format("%016llx.db", seq))) + return String(); + + // If this origin's quota is being tracked (open handle to a database in this origin), add this new database + // to the quota manager now + { + Locker<OriginQuotaManager> locker(originQuotaManager()); + if (originQuotaManager().tracksOrigin(origin)) + originQuotaManager().addDatabase(origin, name, filename); + } + + return filename; +} + +void DatabaseTracker::populateOrigins() +{ + if (m_quotaMap) + return; + + ASSERT(currentThread() == m_thread); + + m_quotaMap.set(new QuotaMap); + m_quotaManager.set(new OriginQuotaManager); + + openTrackerDatabase(false); + if (!m_database.isOpen()) + return; + + SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins"); + + if (statement.prepare() != SQLResultOk) + return; + + int result; + while ((result = statement.step()) == SQLResultRow) { + RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0)); + m_quotaMap->set(origin.get(), statement.getColumnInt64(1)); + } + + if (result != SQLResultDone) + LOG_ERROR("Failed to read in all origins from the database"); +} + +void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result) +{ + ASSERT(currentThread() == m_thread); + populateOrigins(); + MutexLocker lockQuotaMap(m_quotaMapGuard); + copyKeysToVector(*m_quotaMap, result); +} + +bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector) +{ + ASSERT(currentThread() == m_thread); + openTrackerDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;"); + + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + + int result; + while ((result = statement.step()) == SQLResultRow) + resultVector.append(statement.getColumnText(0)); + + if (result != SQLResultDone) { + LOG_ERROR("Failed to retrieve all database names for origin %s", origin->databaseIdentifier().ascii().data()); + return false; + } + + return true; +} + +DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread); + + if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name) + return m_proposedDatabase->second; + + String originIdentifier = origin->databaseIdentifier(); + + openTrackerDatabase(false); + if (!m_database.isOpen()) + return DatabaseDetails(); + SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?"); + if (statement.prepare() != SQLResultOk) + return DatabaseDetails(); + + statement.bindText(1, originIdentifier); + statement.bindText(2, name); + + int result = statement.step(); + if (result == SQLResultDone) + return DatabaseDetails(); + + if (result != SQLResultRow) { + LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data()); + return DatabaseDetails(); + } + + return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin)); +} + +void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize) +{ + ASSERT(currentThread() == m_thread); + + String originIdentifier = origin->databaseIdentifier(); + int64_t guid = 0; + + openTrackerDatabase(true); + if (!m_database.isOpen()) + return; + SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindText(1, originIdentifier); + statement.bindText(2, name); + + int result = statement.step(); + if (result == SQLResultRow) + guid = statement.getColumnInt64(0); + statement.finalize(); + + if (guid == 0) { + if (result != SQLResultDone) + LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data()); + else { + // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker + // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case + // So we'll print an error instead + LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker", + name.ascii().data(), originIdentifier.ascii().data()); + } + return; + } + + SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?"); + if (updateStatement.prepare() != SQLResultOk) + return; + + updateStatement.bindText(1, displayName); + updateStatement.bindInt64(2, estimatedSize); + updateStatement.bindInt64(3, guid); + + if (updateStatement.step() != SQLResultDone) { + LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data()); + return; + } + + if (m_client) + m_client->dispatchDidModifyDatabase(origin, name); +} + +unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread); + String path = fullPathForDatabase(origin, name, false); + if (path.isEmpty()) + return 0; + + long long size; + return getFileSize(path, size) ? size : 0; +} + +void DatabaseTracker::addOpenDatabase(Database* database) +{ + if (!database) + return; + + MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard); + + if (!m_openDatabaseMap) + m_openDatabaseMap.set(new DatabaseOriginMap); + + RefPtr<SecurityOrigin> origin(database->securityOriginCopy()); + String name(database->stringIdentifier()); + + DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin); + if (!nameMap) { + nameMap = new DatabaseNameMap; + m_openDatabaseMap->set(origin, nameMap); + } + + DatabaseSet* databaseSet = nameMap->get(name); + if (!databaseSet) { + databaseSet = new DatabaseSet; + nameMap->set(name, databaseSet); + } + + databaseSet->add(database); + + LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database); +} + +void DatabaseTracker::removeOpenDatabase(Database* database) +{ + if (!database) + return; + + MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard); + + if (!m_openDatabaseMap) { + ASSERT_NOT_REACHED(); + return; + } + + RefPtr<SecurityOrigin> origin(database->securityOriginCopy()); + String name(database->stringIdentifier()); + + DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin); + if (!nameMap) { + ASSERT_NOT_REACHED(); + return; + } + + DatabaseSet* databaseSet = nameMap->get(name); + if (!databaseSet) { + ASSERT_NOT_REACHED(); + return; + } + + databaseSet->remove(database); + + LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database); + + if (!databaseSet->isEmpty()) + return; + + nameMap->remove(name); + delete databaseSet; + + if (!nameMap->isEmpty()) + return; + + m_openDatabaseMap->remove(origin); + delete nameMap; +} + +unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread); + Locker<OriginQuotaManager> locker(originQuotaManager()); + + // Use the OriginQuotaManager mechanism to calculate the usage + if (originQuotaManager().tracksOrigin(origin)) + return originQuotaManager().diskUsage(origin); + + // If the OriginQuotaManager doesn't track this origin already, prime it to do so + originQuotaManager().trackOrigin(origin); + + Vector<String> names; + databaseNamesForOrigin(origin, names); + + for (unsigned i = 0; i < names.size(); ++i) + originQuotaManager().addDatabase(origin, names[i], fullPathForDatabase(origin, names[i], false)); + + if (!originQuotaManager().tracksOrigin(origin)) + return 0; + return originQuotaManager().diskUsage(origin); +} + +unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread || m_quotaMap); + populateOrigins(); + MutexLocker lockQuotaMap(m_quotaMapGuard); + return m_quotaMap->get(origin); +} + +void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota) +{ + ASSERT(currentThread() == m_thread); + if (quotaForOrigin(origin) == quota) + return; + + openTrackerDatabase(true); + if (!m_database.isOpen()) + return; + + { + MutexLocker lockQuotaMap(m_quotaMapGuard); + + if (!m_quotaMap->contains(origin)) { + SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data()); + } else { + statement.bindText(1, origin->databaseIdentifier()); + statement.bindInt64(2, quota); + + if (statement.step() != SQLResultDone) + LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data()); + } + } else { + SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?"); + bool error = statement.prepare() != SQLResultOk; + if (!error) { + statement.bindInt64(1, quota); + statement.bindText(2, origin->databaseIdentifier()); + + error = !statement.executeCommand(); + } + + if (error) + LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->databaseIdentifier().ascii().data()); + } + + // FIXME: Is it really OK to update the quota in memory if we failed to update it on disk? + m_quotaMap->set(origin, quota); + } + + if (m_client) + m_client->dispatchDidModifyOrigin(origin); +} + +bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path) +{ + ASSERT(currentThread() == m_thread); + openTrackerDatabase(true); + if (!m_database.isOpen()) + return false; + + // New database should never be added until the origin has been established + ASSERT(hasEntryForOrigin(origin)); + + SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);"); + + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + statement.bindText(2, name); + statement.bindText(3, path); + + if (!statement.executeCommand()) { + LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->databaseIdentifier().ascii().data(), m_database.lastErrorMsg()); + return false; + } + + if (m_client) + m_client->dispatchDidModifyOrigin(origin); + + return true; +} + +void DatabaseTracker::deleteAllDatabases() +{ + ASSERT(currentThread() == m_thread); + + Vector<RefPtr<SecurityOrigin> > originsCopy; + origins(originsCopy); + + for (unsigned i = 0; i < originsCopy.size(); ++i) + deleteOrigin(originsCopy[i].get()); +} + +void DatabaseTracker::deleteOrigin(SecurityOrigin* origin) +{ + ASSERT(currentThread() == m_thread); + openTrackerDatabase(false); + if (!m_database.isOpen()) + return; + + Vector<String> databaseNames; + if (!databaseNamesForOrigin(origin, databaseNames)) { + LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->databaseIdentifier().ascii().data()); + return; + } + + for (unsigned i = 0; i < databaseNames.size(); ++i) { + if (!deleteDatabaseFile(origin, databaseNames[i])) { + LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->databaseIdentifier().ascii().data()); + return; + } + } + + SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data()); + return; + } + + statement.bindText(1, origin->databaseIdentifier()); + + if (!statement.executeCommand()) { + LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data()); + return; + } + + SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?"); + if (originStatement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->databaseIdentifier().ascii().data()); + return; + } + + originStatement.bindText(1, origin->databaseIdentifier()); + + if (!originStatement.executeCommand()) { + LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data()); + return; + } + + deleteEmptyDirectory(originPath(origin)); + + RefPtr<SecurityOrigin> originPossiblyLastReference = origin; + { + MutexLocker lockQuotaMap(m_quotaMapGuard); + m_quotaMap->remove(origin); + + Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager()); + originQuotaManager().removeOrigin(origin); + + // If we removed the last origin, do some additional deletion. + if (m_quotaMap->isEmpty()) { + if (m_database.isOpen()) + m_database.close(); + deleteFile(trackerDatabasePath()); + deleteEmptyDirectory(m_databaseDirectoryPath); + } + } + + if (m_client) { + m_client->dispatchDidModifyOrigin(origin); + for (unsigned i = 0; i < databaseNames.size(); ++i) + m_client->dispatchDidModifyDatabase(origin, databaseNames[i]); + } +} + +void DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name) +{ + ASSERT(currentThread() == m_thread); + openTrackerDatabase(false); + if (!m_database.isOpen()) + return; + + if (!deleteDatabaseFile(origin, name)) { + LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->databaseIdentifier().ascii().data()); + return; + } + + SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data()); + return; + } + + statement.bindText(1, origin->databaseIdentifier()); + statement.bindText(2, name); + + if (!statement.executeCommand()) { + LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data()); + return; + } + + { + Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager()); + originQuotaManager().removeDatabase(origin, name); + } + + if (m_client) { + m_client->dispatchDidModifyOrigin(origin); + m_client->dispatchDidModifyDatabase(origin, name); + } +} + +bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name) +{ + ASSERT(currentThread() == m_thread); + String fullPath = fullPathForDatabase(origin, name, false); + if (fullPath.isEmpty()) + return true; + + Vector<RefPtr<Database> > deletedDatabases; + + // Make sure not to hold the m_openDatabaseMapGuard mutex when calling + // Database::markAsDeletedAndClose(), since that can cause a deadlock + // during the synchronous DatabaseThread call it triggers. + + { + MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard); + if (m_openDatabaseMap) { + // There are some open databases, lets check if they are for this origin. + DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin); + if (nameMap && nameMap->size()) { + // There are some open databases for this origin, lets check + // if they are this database by name. + DatabaseSet* databaseSet = nameMap->get(name); + if (databaseSet && databaseSet->size()) { + // We have some database open with this name. Mark them as deleted. + DatabaseSet::const_iterator end = databaseSet->end(); + for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it) + deletedDatabases.append(*it); + } + } + } + } + + for (unsigned i = 0; i < deletedDatabases.size(); ++i) + deletedDatabases[i]->markAsDeletedAndClose(); + + return deleteFile(fullPath); +} + +void DatabaseTracker::setClient(DatabaseTrackerClient* client) +{ + ASSERT(currentThread() == m_thread); + m_client = client; +} + +static Mutex& notificationMutex() +{ + static Mutex mutex; + return mutex; +} + +static Vector<pair<SecurityOrigin*, String> >& notificationQueue() +{ + static Vector<pair<SecurityOrigin*, String> > queue; + return queue; +} + +void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name) +{ + MutexLocker locker(notificationMutex()); + + notificationQueue().append(pair<SecurityOrigin*, String>(origin, name.copy())); + scheduleForNotification(); +} + +static bool notificationScheduled = false; + +void DatabaseTracker::scheduleForNotification() +{ + ASSERT(!notificationMutex().tryLock()); + + if (!notificationScheduled) { + callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0); + notificationScheduled = true; + } +} + +void DatabaseTracker::notifyDatabasesChanged(void*) +{ + // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification + // mechanism to include which tracker the notification goes out on as well. + DatabaseTracker& theTracker(tracker()); + + Vector<pair<SecurityOrigin*, String> > notifications; + { + MutexLocker locker(notificationMutex()); + + notifications.swap(notificationQueue()); + + notificationScheduled = false; + } + + if (!theTracker.m_client) + return; + + for (unsigned i = 0; i < notifications.size(); ++i) + theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first, notifications[i].second); +} + + +} // namespace WebCore diff --git a/WebCore/storage/DatabaseTracker.h b/WebCore/storage/DatabaseTracker.h new file mode 100644 index 0000000..3760822 --- /dev/null +++ b/WebCore/storage/DatabaseTracker.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef DatabaseTracker_h +#define DatabaseTracker_h + +#include "DatabaseDetails.h" +#include "PlatformString.h" +#include "SQLiteDatabase.h" +#include "StringHash.h" +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class Database; +class DatabaseTrackerClient; +class Document; +class OriginQuotaManager; +class SecurityOrigin; + +struct SecurityOriginHash; +struct SecurityOriginTraits; + +class DatabaseTracker { +public: + void setDatabaseDirectoryPath(const String&); + const String& databaseDirectoryPath() const; + + bool canEstablishDatabase(Document*, const String& name, const String& displayName, unsigned long estimatedSize); + void setDatabaseDetails(SecurityOrigin*, const String& name, const String& displayName, unsigned long estimatedSize); + String fullPathForDatabase(SecurityOrigin*, const String& name, bool createIfDoesNotExist = true); + + void origins(Vector<RefPtr<SecurityOrigin> >& result); + bool databaseNamesForOrigin(SecurityOrigin*, Vector<String>& result); + + DatabaseDetails detailsForNameAndOrigin(const String&, SecurityOrigin*); + + void addOpenDatabase(Database*); + void removeOpenDatabase(Database*); + + unsigned long long usageForDatabase(const String&, SecurityOrigin*); + unsigned long long usageForOrigin(SecurityOrigin*); + unsigned long long quotaForOrigin(SecurityOrigin*); + void setQuota(SecurityOrigin*, unsigned long long); + + void deleteAllDatabases(); + void deleteOrigin(SecurityOrigin*); + void deleteDatabase(SecurityOrigin*, const String& name); + + void setClient(DatabaseTrackerClient*); + + // From a secondary thread, must be thread safe with its data + void scheduleNotifyDatabaseChanged(SecurityOrigin*, const String& name); + + OriginQuotaManager& originQuotaManager(); + + static DatabaseTracker& tracker(); + +private: + DatabaseTracker(); + + String trackerDatabasePath() const; + void openTrackerDatabase(bool createIfDoesNotExist); + + String originPath(SecurityOrigin*) const; + + bool hasEntryForOrigin(SecurityOrigin*); + bool hasEntryForDatabase(SecurityOrigin*, const String& databaseIdentifier); + + bool addDatabase(SecurityOrigin*, const String& name, const String& path); + void populateOrigins(); + + bool deleteDatabaseFile(SecurityOrigin*, const String& name); + + SQLiteDatabase m_database; + + typedef HashMap<RefPtr<SecurityOrigin>, unsigned long long, SecurityOriginHash> QuotaMap; + Mutex m_quotaMapGuard; + mutable OwnPtr<QuotaMap> m_quotaMap; + + typedef HashSet<Database*> DatabaseSet; + typedef HashMap<String, DatabaseSet*> DatabaseNameMap; + typedef HashMap<RefPtr<SecurityOrigin>, DatabaseNameMap*, SecurityOriginHash> DatabaseOriginMap; + + Mutex m_openDatabaseMapGuard; + mutable OwnPtr<DatabaseOriginMap> m_openDatabaseMap; + + OwnPtr<OriginQuotaManager> m_quotaManager; + + String m_databaseDirectoryPath; + + DatabaseTrackerClient* m_client; + + std::pair<SecurityOrigin*, DatabaseDetails>* m_proposedDatabase; + +#ifndef NDEBUG + ThreadIdentifier m_thread; +#endif + + static void scheduleForNotification(); + static void notifyDatabasesChanged(void*); +}; + +} // namespace WebCore + +#endif // DatabaseTracker_h diff --git a/WebCore/storage/DatabaseTrackerClient.h b/WebCore/storage/DatabaseTrackerClient.h new file mode 100644 index 0000000..f9b0f70 --- /dev/null +++ b/WebCore/storage/DatabaseTrackerClient.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef DatabaseTrackerClient_h +#define DatabaseTrackerClient_h + +namespace WebCore { + +class SecurityOrigin; +class String; + +class DatabaseTrackerClient { +public: + virtual ~DatabaseTrackerClient() { } + virtual void dispatchDidModifyOrigin(SecurityOrigin*) = 0; + virtual void dispatchDidModifyDatabase(SecurityOrigin*, const String& databaseName) = 0; +}; + +} // namespace WebCore + +#endif // DatabaseTrackerClient_h diff --git a/WebCore/storage/LocalStorage.cpp b/WebCore/storage/LocalStorage.cpp new file mode 100644 index 0000000..0177593 --- /dev/null +++ b/WebCore/storage/LocalStorage.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "LocalStorage.h" + +#include "CString.h" +#include "EventNames.h" +#include "FileSystem.h" +#include "Frame.h" +#include "FrameTree.h" +#include "LocalStorageArea.h" +#include "Page.h" +#include "PageGroup.h" +#include "StorageArea.h" + +namespace WebCore { + +typedef HashMap<String, LocalStorage*> LocalStorageMap; + +static LocalStorageMap& localStorageMap() +{ + static LocalStorageMap localStorageMap; + return localStorageMap; +} + +PassRefPtr<LocalStorage> LocalStorage::localStorage(const String& path) +{ + const String lookupPath = path.isNull() ? String("") : path; + LocalStorageMap::iterator it = localStorageMap().find(lookupPath); + if (it == localStorageMap().end()) { + RefPtr<LocalStorage> localStorage = adoptRef(new LocalStorage(lookupPath)); + localStorageMap().set(lookupPath, localStorage.get()); + return localStorage.release(); + } + + return it->second; +} + +LocalStorage::LocalStorage(const String& path) + : m_path(path.copy()) +{ + // If the path is empty, we know we're never going to be using the thread for anything, so don't start it. + // In the future, we might also want to consider removing it from the DOM in that case - <rdar://problem/5960470> + if (path.isEmpty()) + return; + + m_thread = LocalStorageThread::create(); + m_thread->start(); + m_thread->scheduleImport(this); +} + +LocalStorage::~LocalStorage() +{ + ASSERT(localStorageMap().get(m_path) == this); + localStorageMap().remove(m_path); +} + +PassRefPtr<StorageArea> LocalStorage::storageArea(Frame* sourceFrame, SecurityOrigin* origin) +{ + ASSERT(isMainThread()); + + // FIXME: If the security origin in question has never had a storage area established, + // we need to ask a client call if establishing it is okay. If the client denies the request, + // this method will return null. + // The sourceFrame argument exists for the purpose of asking a client. + // To know if an area has previously been established, we need to wait until this LocalStorage + // object has finished it's AreaImport task. + + + // FIXME: If the storage area is being established for the first time here, we need to + // sync its existance and quota out to disk via an task of type AreaSync + + RefPtr<LocalStorageArea> storageArea; + if (storageArea = m_storageAreaMap.get(origin)) + return storageArea.release(); + + storageArea = LocalStorageArea::create(origin, this); + m_storageAreaMap.set(origin, storageArea); + return storageArea.release(); +} + +String LocalStorage::fullDatabaseFilename(SecurityOrigin* origin) +{ + // FIXME: Once we actually track origin/quota entries to see which origins have local storage established, + // we will return an empty path name if the origin isn't allowed to have LocalStorage. + // We'll need to wait here until the AreaImport task to complete before making that decision. + + if (m_path.isEmpty()) + return String(); + + ASSERT(origin); + if (!origin) + return String(); + + if (!makeAllDirectories(m_path)) { + LOG_ERROR("Unabled to create LocalStorage database path %s", m_path.utf8().data()); + return String(); + } + + return pathByAppendingComponent(m_path, origin->databaseIdentifier() + ".localstorage"); +} + +void LocalStorage::performImport() +{ + ASSERT(!isMainThread()); + + // FIXME: Import all known local storage origins here along with their quotas +} + +void LocalStorage::performSync() +{ + ASSERT(!isMainThread()); + + // FIXME: Write out new origins and quotas here +} + +void LocalStorage::close() +{ + ASSERT(isMainThread()); + + LocalStorageAreaMap::iterator end = m_storageAreaMap.end(); + for (LocalStorageAreaMap::iterator it = m_storageAreaMap.begin(); it != end; ++it) + it->second->scheduleFinalSync(); + + if (m_thread) { + m_thread->terminate(); + m_thread = 0; + } +} + +bool LocalStorage::scheduleImport(PassRefPtr<LocalStorageArea> area) +{ + ASSERT(isMainThread()); + + if (m_thread) + m_thread->scheduleImport(area); + + return m_thread; +} + +void LocalStorage::scheduleSync(PassRefPtr<LocalStorageArea> area) +{ + ASSERT(isMainThread()); + if (m_thread) + m_thread->scheduleSync(area); +} + +} // namespace WebCore diff --git a/WebCore/storage/LocalStorage.h b/WebCore/storage/LocalStorage.h new file mode 100644 index 0000000..4816de7 --- /dev/null +++ b/WebCore/storage/LocalStorage.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef LocalStorage_h +#define LocalStorage_h + +#include "LocalStorageArea.h" +#include "LocalStorageTask.h" +#include "LocalStorageThread.h" +#include "SecurityOriginHash.h" + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class PageGroup; + class StorageArea; + + class LocalStorage : public ThreadSafeShared<LocalStorage> { + public: + ~LocalStorage(); + + static PassRefPtr<LocalStorage> localStorage(const String& path); + + PassRefPtr<StorageArea> storageArea(Frame* sourceFrame, SecurityOrigin*); + + bool scheduleImport(PassRefPtr<LocalStorageArea>); + void scheduleSync(PassRefPtr<LocalStorageArea>); + + void close(); + + private: + LocalStorage(const String& path); + + typedef HashMap<RefPtr<SecurityOrigin>, RefPtr<LocalStorageArea>, SecurityOriginHash> LocalStorageAreaMap; + LocalStorageAreaMap m_storageAreaMap; + + RefPtr<LocalStorageThread> m_thread; + + // The following members are subject to thread synchronization issues + public: + // To be called from the background thread: + String fullDatabaseFilename(SecurityOrigin*); + + void performImport(); + void performSync(); + + private: + String m_path; + + typedef HashMap<RefPtr<SecurityOrigin>, unsigned long long, SecurityOriginHash> SecurityOriginQuoteMap; + SecurityOriginQuoteMap m_securityOriginQuoteMap; + }; + +} // namespace WebCore + +#endif // LocalStorage_h diff --git a/WebCore/storage/LocalStorageArea.cpp b/WebCore/storage/LocalStorageArea.cpp new file mode 100644 index 0000000..56dc246 --- /dev/null +++ b/WebCore/storage/LocalStorageArea.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "LocalStorageArea.h" + +#include "CString.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameTree.h" +#include "LocalStorage.h" +#include "LocalStorageTask.h" +#include "LocalStorageThread.h" +#include "Page.h" +#include "PageGroup.h" +#include "PlatformString.h" +#include "SecurityOrigin.h" +#include "SQLiteStatement.h" + +namespace WebCore { + +// If the LocalStorageArea undergoes rapid changes, don't sync each change to disk. +// Instead, queue up a batch of items to sync and actually do the sync at the following interval. +static const double LocalStorageSyncInterval = 1.0; + +LocalStorageArea::LocalStorageArea(SecurityOrigin* origin, LocalStorage* localStorage) + : StorageArea(origin) + , m_syncTimer(this, &LocalStorageArea::syncTimerFired) + , m_itemsCleared(false) + , m_finalSyncScheduled(false) + , m_localStorage(localStorage) + , m_clearItemsWhileSyncing(false) + , m_syncScheduled(false) + , m_importComplete(false) +{ + ASSERT(m_localStorage); + + if (!m_localStorage->scheduleImport(this)) + m_importComplete = true; +} + +LocalStorageArea::~LocalStorageArea() +{ + ASSERT(!m_syncTimer.isActive()); +} + +void LocalStorageArea::scheduleFinalSync() +{ + m_syncTimer.stop(); + syncTimerFired(&m_syncTimer); + m_finalSyncScheduled = true; +} + +unsigned LocalStorageArea::length() const +{ + ASSERT(isMainThread()); + + if (m_importComplete) + return internalLength(); + + MutexLocker locker(m_importLock); + if (m_importComplete) + return internalLength(); + + while (!m_importComplete) + m_importCondition.wait(m_importLock); + ASSERT(m_importComplete); + + return internalLength(); +} + +String LocalStorageArea::key(unsigned index, ExceptionCode& ec) const +{ + ASSERT(isMainThread()); + + if (m_importComplete) + return internalKey(index, ec); + + MutexLocker locker(m_importLock); + if (m_importComplete) + return internalKey(index, ec); + + while (!m_importComplete) + m_importCondition.wait(m_importLock); + ASSERT(m_importComplete); + + return internalKey(index, ec); +} + +String LocalStorageArea::getItem(const String& key) const +{ + ASSERT(isMainThread()); + + if (m_importComplete) + return internalGetItem(key); + + MutexLocker locker(m_importLock); + if (m_importComplete) + return internalGetItem(key); + + String item = internalGetItem(key); + if (!item.isNull()) + return item; + + while (!m_importComplete) + m_importCondition.wait(m_importLock); + ASSERT(m_importComplete); + + return internalGetItem(key); +} + +void LocalStorageArea::setItem(const String& key, const String& value, ExceptionCode& ec, Frame* frame) +{ + ASSERT(isMainThread()); + + if (m_importComplete) { + internalSetItem(key, value, ec, frame); + return; + } + + MutexLocker locker(m_importLock); + internalSetItem(key, value, ec, frame); +} + +void LocalStorageArea::removeItem(const String& key, Frame* frame) +{ + ASSERT(isMainThread()); + + if (m_importComplete) { + internalRemoveItem(key, frame); + return; + } + + MutexLocker locker(m_importLock); + internalRemoveItem(key, frame); +} + +bool LocalStorageArea::contains(const String& key) const +{ + ASSERT(isMainThread()); + + if (m_importComplete) + return internalContains(key); + + MutexLocker locker(m_importLock); + if (m_importComplete) + return internalContains(key); + + bool contained = internalContains(key); + if (contained) + return true; + + while (!m_importComplete) + m_importCondition.wait(m_importLock); + ASSERT(m_importComplete); + + return internalContains(key); +} + +void LocalStorageArea::itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame) +{ + ASSERT(isMainThread()); + + scheduleItemForSync(key, newValue); + dispatchStorageEvent(key, oldValue, newValue, sourceFrame); +} + +void LocalStorageArea::itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame) +{ + ASSERT(isMainThread()); + + scheduleItemForSync(key, String()); + dispatchStorageEvent(key, oldValue, String(), sourceFrame); +} + +void LocalStorageArea::areaCleared(Frame* sourceFrame) +{ + ASSERT(isMainThread()); + + scheduleClear(); + dispatchStorageEvent(String(), String(), String(), sourceFrame); +} + +void LocalStorageArea::dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame) +{ + ASSERT(isMainThread()); + + Page* page = sourceFrame->page(); + if (!page) + return; + + // Need to copy all relevant frames from every page to a vector, since sending the event to one frame might mutate the frame tree + // of any given page in the group, or mutate the page group itself + Vector<RefPtr<Frame> > frames; + const HashSet<Page*>& pages = page->group().pages(); + + HashSet<Page*>::const_iterator end = pages.end(); + for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) { + for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) { + if (Document* document = frame->document()) + if (document->securityOrigin()->equal(securityOrigin())) + frames.append(frame); + } + } + + for (unsigned i = 0; i < frames.size(); ++i) { + if (HTMLElement* body = frames[i]->document()->body()) + body->dispatchStorageEvent(eventNames().storageEvent, key, oldValue, newValue, sourceFrame); + } +} + +void LocalStorageArea::scheduleItemForSync(const String& key, const String& value) +{ + ASSERT(isMainThread()); + ASSERT(!m_finalSyncScheduled); + + m_changedItems.set(key, value); + if (!m_syncTimer.isActive()) + m_syncTimer.startOneShot(LocalStorageSyncInterval); +} + +void LocalStorageArea::scheduleClear() +{ + ASSERT(isMainThread()); + ASSERT(!m_finalSyncScheduled); + + m_changedItems.clear(); + m_itemsCleared = true; + if (!m_syncTimer.isActive()) + m_syncTimer.startOneShot(LocalStorageSyncInterval); +} + +void LocalStorageArea::syncTimerFired(Timer<LocalStorageArea>*) +{ + ASSERT(isMainThread()); + + HashMap<String, String>::iterator it = m_changedItems.begin(); + HashMap<String, String>::iterator end = m_changedItems.end(); + + { + MutexLocker locker(m_syncLock); + + if (m_itemsCleared) { + m_itemsPendingSync.clear(); + m_clearItemsWhileSyncing = true; + m_itemsCleared = false; + } + + for (; it != end; ++it) + m_itemsPendingSync.set(it->first.copy(), it->second.copy()); + + if (!m_syncScheduled) { + m_syncScheduled = true; + m_localStorage->scheduleSync(this); + } + } + + m_changedItems.clear(); +} + +void LocalStorageArea::performImport() +{ + ASSERT(!isMainThread()); + ASSERT(!m_database.isOpen()); + + String databaseFilename = m_localStorage->fullDatabaseFilename(securityOrigin()); + + if (databaseFilename.isEmpty()) { + LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage"); + markImported(); + return; + } + + if (!m_database.open(databaseFilename)) { + LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data()); + markImported(); + return; + } + + if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL)")) { + LOG_ERROR("Failed to create table ItemTable for local storage"); + markImported(); + return; + } + + SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable"); + if (query.prepare() != SQLResultOk) { + LOG_ERROR("Unable to select items from ItemTable for local storage"); + markImported(); + return; + } + + HashMap<String, String> itemMap; + + int result = query.step(); + while (result == SQLResultRow) { + itemMap.set(query.getColumnText(0), query.getColumnText(1)); + result = query.step(); + } + + if (result != SQLResultDone) { + LOG_ERROR("Error reading items from ItemTable for local storage"); + markImported(); + return; + } + + MutexLocker locker(m_importLock); + + HashMap<String, String>::iterator it = itemMap.begin(); + HashMap<String, String>::iterator end = itemMap.end(); + + for (; it != end; ++it) + importItem(it->first, it->second); + + m_importComplete = true; + m_importCondition.signal(); +} + +void LocalStorageArea::markImported() +{ + ASSERT(!isMainThread()); + + MutexLocker locker(m_importLock); + m_importComplete = true; + m_importCondition.signal(); +} + +void LocalStorageArea::performSync() +{ + ASSERT(!isMainThread()); + + if (!m_database.isOpen()) + return; + + HashMap<String, String> items; + bool clearFirst = false; + { + MutexLocker locker(m_syncLock); + m_itemsPendingSync.swap(items); + clearFirst = m_clearItemsWhileSyncing; + m_clearItemsWhileSyncing = false; + m_syncScheduled = false; + } + + // If the clear flag is marked, then we clear all items out before we write any new ones in + if (clearFirst) { + SQLiteStatement clear(m_database, "DELETE FROM ItemTable"); + if (clear.prepare() != SQLResultOk) { + LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database"); + return; + } + + int result = clear.step(); + if (result != SQLResultDone) { + LOG_ERROR("Failed to clear all items in the local storage database - %i", result); + return; + } + } + + SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)"); + if (insert.prepare() != SQLResultOk) { + LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database"); + return; + } + + SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?"); + if (remove.prepare() != SQLResultOk) { + LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database"); + return; + } + + HashMap<String, String>::iterator end = items.end(); + + for (HashMap<String, String>::iterator it = items.begin(); it != end; ++it) { + // Based on the null-ness of the second argument, decide whether this is an insert or a delete + SQLiteStatement& query = it->second.isNull() ? remove : insert; + + query.bindText(1, it->first); + + // If the second argument is non-null, we're doing an insert, so bind it as the value. + if (!it->second.isNull()) + query.bindText(2, it->second); + + int result = query.step(); + if (result != SQLResultDone) { + LOG_ERROR("Failed to update item in the local storage database - %i", result); + break; + } + + query.reset(); + } +} + +} // namespace WebCore diff --git a/WebCore/storage/LocalStorageArea.h b/WebCore/storage/LocalStorageArea.h new file mode 100644 index 0000000..c6e7015 --- /dev/null +++ b/WebCore/storage/LocalStorageArea.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef LocalStorageArea_h +#define LocalStorageArea_h + +#include "LocalStorageTask.h" +#include "LocalStorageThread.h" + +#include "SQLiteDatabase.h" +#include "StorageArea.h" +#include "StringHash.h" +#include "Timer.h" + +#include <wtf/HashMap.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class LocalStorage; + + class LocalStorageArea : public StorageArea { + public: + virtual ~LocalStorageArea(); + + static PassRefPtr<LocalStorageArea> create(SecurityOrigin* origin, LocalStorage* localStorage) { return adoptRef(new LocalStorageArea(origin, localStorage)); } + + void scheduleFinalSync(); + + private: + LocalStorageArea(SecurityOrigin*, LocalStorage*); + + virtual void itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame); + virtual void itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame); + virtual void areaCleared(Frame* sourceFrame); + + void scheduleItemForSync(const String& key, const String& value); + void scheduleClear(); + void dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame); + + Timer<LocalStorageArea> m_syncTimer; + HashMap<String, String> m_changedItems; + bool m_itemsCleared; + + bool m_finalSyncScheduled; + + LocalStorage* m_localStorage; + + // The database handle will only ever be opened and used on the background thread. + SQLiteDatabase m_database; + + // The following members are subject to thread synchronization issues. + public: + // Called on the main thread + virtual unsigned length() const; + virtual String key(unsigned index, ExceptionCode&) const; + virtual String getItem(const String&) const; + virtual void setItem(const String& key, const String& value, ExceptionCode&, Frame* sourceFrame); + virtual void removeItem(const String&, Frame* sourceFrame); + virtual bool contains(const String& key) const; + + // Called from the background thread + virtual void performImport(); + virtual void performSync(); + + private: + void syncTimerFired(Timer<LocalStorageArea>*); + + Mutex m_syncLock; + HashMap<String, String> m_itemsPendingSync; + bool m_clearItemsWhileSyncing; + bool m_syncScheduled; + + mutable Mutex m_importLock; + mutable ThreadCondition m_importCondition; + mutable bool m_importComplete; + void markImported(); + }; + +} // namespace WebCore + +#endif // LocalStorageArea_h diff --git a/WebCore/storage/LocalStorageTask.cpp b/WebCore/storage/LocalStorageTask.cpp new file mode 100644 index 0000000..3d5b987 --- /dev/null +++ b/WebCore/storage/LocalStorageTask.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "LocalStorageTask.h" + +#include "LocalStorage.h" +#include "LocalStorageArea.h" +#include "LocalStorageThread.h" + +namespace WebCore { + +LocalStorageTask::LocalStorageTask(Type type, PassRefPtr<LocalStorageArea> area) + : m_type(type) + , m_area(area) +{ + ASSERT(m_area); + ASSERT(m_type == AreaImport || m_type == AreaSync); +} + +LocalStorageTask::LocalStorageTask(Type type, PassRefPtr<LocalStorage> storage) + : m_type(type) + , m_storage(storage) +{ + ASSERT(m_storage); + ASSERT(m_type == StorageImport || m_type == StorageSync); +} + +LocalStorageTask::LocalStorageTask(Type type, PassRefPtr<LocalStorageThread> thread) + : m_type(type) + , m_thread(thread) +{ + ASSERT(m_thread); + ASSERT(m_type == TerminateThread); +} + +void LocalStorageTask::performTask() +{ + switch (m_type) { + case StorageImport: + ASSERT(m_storage); + m_storage->performImport(); + break; + case StorageSync: + ASSERT(m_storage); + m_storage->performSync(); + break; + case AreaImport: + ASSERT(m_area); + m_area->performImport(); + break; + case AreaSync: + ASSERT(m_area); + m_area->performSync(); + break; + case TerminateThread: + m_thread->performTerminate(); + break; + } +} + +} diff --git a/WebCore/storage/LocalStorageTask.h b/WebCore/storage/LocalStorageTask.h new file mode 100644 index 0000000..79063d0 --- /dev/null +++ b/WebCore/storage/LocalStorageTask.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef LocalStorageTask_h +#define LocalStorageTask_h + +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class LocalStorage; + class LocalStorageArea; + class LocalStorageThread; + + class LocalStorageTask : public ThreadSafeShared<LocalStorageTask> { + public: + enum Type { StorageImport, StorageSync, AreaImport, AreaSync, TerminateThread }; + + static PassRefPtr<LocalStorageTask> createImport(PassRefPtr<LocalStorage> storage) { return adoptRef(new LocalStorageTask(StorageImport, storage)); } + static PassRefPtr<LocalStorageTask> createImport(PassRefPtr<LocalStorageArea> area) { return adoptRef(new LocalStorageTask(AreaImport, area)); } + static PassRefPtr<LocalStorageTask> createSync(PassRefPtr<LocalStorage> storage) { return adoptRef(new LocalStorageTask(StorageSync, storage)); } + static PassRefPtr<LocalStorageTask> createSync(PassRefPtr<LocalStorageArea> area) { return adoptRef(new LocalStorageTask(AreaSync, area)); } + static PassRefPtr<LocalStorageTask> createTerminate(PassRefPtr<LocalStorageThread> thread) { return adoptRef(new LocalStorageTask(TerminateThread, thread)); } + + void performTask(); + + private: + LocalStorageTask(Type, PassRefPtr<LocalStorageArea>); + LocalStorageTask(Type, PassRefPtr<LocalStorage>); + LocalStorageTask(Type, PassRefPtr<LocalStorageThread>); + + Type m_type; + RefPtr<LocalStorageArea> m_area; + RefPtr<LocalStorage> m_storage; + RefPtr<LocalStorageThread> m_thread; + }; + +} // namespace WebCore + +#endif // LocalStorageTask_h diff --git a/WebCore/storage/LocalStorageThread.cpp b/WebCore/storage/LocalStorageThread.cpp new file mode 100644 index 0000000..8f1829c --- /dev/null +++ b/WebCore/storage/LocalStorageThread.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "LocalStorageThread.h" + +#include "LocalStorage.h" +#include "LocalStorageArea.h" +#include "LocalStorageTask.h" + +namespace WebCore { + +PassRefPtr<LocalStorageThread> LocalStorageThread::create() +{ + return adoptRef(new LocalStorageThread); +} + +LocalStorageThread::LocalStorageThread() + : m_threadID(0) +{ + m_selfRef = this; +} + +bool LocalStorageThread::start() +{ + if (m_threadID) + return true; + + m_threadID = createThread(LocalStorageThread::localStorageThreadStart, this, "WebCore::LocalStorage"); + + return m_threadID; +} + +void* LocalStorageThread::localStorageThreadStart(void* thread) +{ + return static_cast<LocalStorageThread*>(thread)->localStorageThread(); +} + +void* LocalStorageThread::localStorageThread() +{ + while (true) { + RefPtr<LocalStorageTask> task; + if (!m_queue.waitForMessage(task)) + break; + + task->performTask(); + } + + // Detach the thread so its resources are no longer of any concern to anyone else + detachThread(m_threadID); + m_threadID = 0; + + // Clear the self refptr, possibly resulting in deletion + m_selfRef = 0; + + return 0; +} + +void LocalStorageThread::scheduleImport(PassRefPtr<LocalStorage> storage) +{ + ASSERT(!m_queue.killed() && m_threadID); + m_queue.append(LocalStorageTask::createImport(storage)); +} + +void LocalStorageThread::scheduleSync(PassRefPtr<LocalStorage> storage) +{ + ASSERT(!m_queue.killed() && m_threadID); + m_queue.append(LocalStorageTask::createSync(storage)); +} + +void LocalStorageThread::scheduleImport(PassRefPtr<LocalStorageArea> area) +{ + ASSERT(!m_queue.killed() && m_threadID); + m_queue.append(LocalStorageTask::createImport(area)); +} + +void LocalStorageThread::scheduleSync(PassRefPtr<LocalStorageArea> area) +{ + ASSERT(!m_queue.killed() && m_threadID); + m_queue.append(LocalStorageTask::createSync(area)); +} + +void LocalStorageThread::terminate() +{ + ASSERT(isMainThread()); + + // Ideally we'd never be killing a thread that wasn't live, so ASSERT it. + // But if we do in a release build, make sure to not wait on a condition that will never get signalled + ASSERT(!m_queue.killed() && m_threadID); + if (!m_threadID) + return; + + MutexLocker locker(m_terminateLock); + + m_queue.append(LocalStorageTask::createTerminate(this)); + + m_terminateCondition.wait(m_terminateLock); +} + +void LocalStorageThread::performTerminate() +{ + ASSERT(!isMainThread()); + + m_queue.kill(); + + MutexLocker locker(m_terminateLock); + m_terminateCondition.signal(); +} + +} diff --git a/WebCore/storage/LocalStorageThread.h b/WebCore/storage/LocalStorageThread.h new file mode 100644 index 0000000..fcca712 --- /dev/null +++ b/WebCore/storage/LocalStorageThread.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef LocalStorageThread_h +#define LocalStorageThread_h + +#include <wtf/HashSet.h> +#include <wtf/MessageQueue.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class LocalStorage; + class LocalStorageArea; + class LocalStorageTask; + + class LocalStorageThread : public ThreadSafeShared<LocalStorageThread> { + public: + static PassRefPtr<LocalStorageThread> create(); + + bool start(); + + void scheduleImport(PassRefPtr<LocalStorage>); + void scheduleSync(PassRefPtr<LocalStorage>); + void scheduleImport(PassRefPtr<LocalStorageArea>); + void scheduleSync(PassRefPtr<LocalStorageArea>); + + // Called from the main thread to synchronously shut down this thread + void terminate(); + // Background thread part of the terminate procedure + void performTerminate(); + + private: + LocalStorageThread(); + + static void* localStorageThreadStart(void*); + void* localStorageThread(); + + ThreadIdentifier m_threadID; + RefPtr<LocalStorageThread> m_selfRef; + + MessageQueue<RefPtr<LocalStorageTask> > m_queue; + + Mutex m_terminateLock; + ThreadCondition m_terminateCondition; + }; + +} // namespace WebCore + +#endif // LocalStorageThread_h diff --git a/WebCore/storage/OriginQuotaManager.cpp b/WebCore/storage/OriginQuotaManager.cpp new file mode 100644 index 0000000..fa2a580 --- /dev/null +++ b/WebCore/storage/OriginQuotaManager.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "OriginQuotaManager.h" + +#include "Database.h" +#include "OriginUsageRecord.h" + +namespace WebCore { + +OriginQuotaManager::OriginQuotaManager() +#ifndef NDEBUG + : m_usageRecordGuardLocked(false) +#endif +{ +} + +void OriginQuotaManager::lock() +{ + m_usageRecordGuard.lock(); +#ifndef NDEBUG + m_usageRecordGuardLocked = true; +#endif +} + +void OriginQuotaManager::unlock() +{ +#ifndef NDEBUG + m_usageRecordGuardLocked = false; +#endif + m_usageRecordGuard.unlock(); +} + +void OriginQuotaManager::trackOrigin(PassRefPtr<SecurityOrigin> origin) +{ + ASSERT(m_usageRecordGuardLocked); + ASSERT(!m_usageMap.contains(origin.get())); + + m_usageMap.set(origin, new OriginUsageRecord); +} + +bool OriginQuotaManager::tracksOrigin(SecurityOrigin* origin) const +{ + ASSERT(m_usageRecordGuardLocked); + return m_usageMap.contains(origin); +} + +void OriginQuotaManager::addDatabase(SecurityOrigin* origin, const String& databaseIdentifier, const String& fullPath) +{ + ASSERT(m_usageRecordGuardLocked); + + OriginUsageRecord* usageRecord = m_usageMap.get(origin); + ASSERT(usageRecord); + + usageRecord->addDatabase(databaseIdentifier.copy(), fullPath.copy()); +} + +void OriginQuotaManager::removeDatabase(SecurityOrigin* origin, const String& databaseIdentifier) +{ + ASSERT(m_usageRecordGuardLocked); + + if (OriginUsageRecord* usageRecord = m_usageMap.get(origin)) + usageRecord->removeDatabase(databaseIdentifier); +} + +void OriginQuotaManager::removeOrigin(SecurityOrigin* origin) +{ + ASSERT(m_usageRecordGuardLocked); + + if (OriginUsageRecord* usageRecord = m_usageMap.get(origin)) { + m_usageMap.remove(origin); + delete usageRecord; + } +} + +void OriginQuotaManager::markDatabase(Database* database) +{ + ASSERT(database); + ASSERT(m_usageRecordGuardLocked); + RefPtr<SecurityOrigin> origin = database->securityOriginCopy(); + OriginUsageRecord* usageRecord = m_usageMap.get(origin); + ASSERT(usageRecord); + + usageRecord->markDatabase(database->stringIdentifier()); +} + +unsigned long long OriginQuotaManager::diskUsage(SecurityOrigin* origin) const +{ + ASSERT(m_usageRecordGuardLocked); + + OriginUsageRecord* usageRecord = m_usageMap.get(origin); + ASSERT(usageRecord); + + return usageRecord->diskUsage(); +} + + +} diff --git a/WebCore/storage/OriginQuotaManager.h b/WebCore/storage/OriginQuotaManager.h new file mode 100644 index 0000000..81a29e5 --- /dev/null +++ b/WebCore/storage/OriginQuotaManager.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef OriginQuotaManager_h +#define OriginQuotaManager_h + +#include "StringHash.h" +#include "SecurityOriginHash.h" +#include <wtf/HashMap.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class Database; +class OriginUsageRecord; + +class OriginQuotaManager : public Noncopyable { +public: + OriginQuotaManager(); + + void lock(); + void unlock(); + + void trackOrigin(PassRefPtr<SecurityOrigin>); + bool tracksOrigin(SecurityOrigin*) const; + void addDatabase(SecurityOrigin*, const String& databaseIdentifier, const String& fullPath); + void removeDatabase(SecurityOrigin*, const String& databaseIdentifier); + void removeOrigin(SecurityOrigin*); + + void markDatabase(Database*); // Mark dirtiness of a specific database. + unsigned long long diskUsage(SecurityOrigin*) const; + +private: + mutable Mutex m_usageRecordGuard; +#ifndef NDEBUG + bool m_usageRecordGuardLocked; +#endif + + typedef HashMap<RefPtr<SecurityOrigin>, OriginUsageRecord*, SecurityOriginHash> OriginUsageMap; + OriginUsageMap m_usageMap; +}; + +} // namespace WebCore + +#endif // OriginQuotaManager_h diff --git a/WebCore/storage/OriginUsageRecord.cpp b/WebCore/storage/OriginUsageRecord.cpp new file mode 100644 index 0000000..d7a7b11 --- /dev/null +++ b/WebCore/storage/OriginUsageRecord.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "OriginUsageRecord.h" + +#include "FileSystem.h" + +namespace WebCore { + +OriginUsageRecord::OriginUsageRecord() + : m_cachedDiskUsageIsValid(false) +{ +} + +void OriginUsageRecord::addDatabase(const String& identifier, const String& fullPath) +{ + ASSERT(!m_databaseMap.contains(identifier)); + ASSERT_ARG(identifier, identifier.impl()->refCount() == 1); + ASSERT_ARG(fullPath, fullPath.impl()->refCount() == 1); + + m_databaseMap.set(identifier, DatabaseEntry(fullPath)); + m_unknownSet.add(identifier); + + m_cachedDiskUsageIsValid = false; +} + +void OriginUsageRecord::removeDatabase(const String& identifier) +{ + ASSERT(m_databaseMap.contains(identifier)); + + m_databaseMap.remove(identifier); + m_unknownSet.remove(identifier); + m_cachedDiskUsageIsValid = false; +} + +void OriginUsageRecord::markDatabase(const String& identifier) +{ + ASSERT(m_databaseMap.contains(identifier)); + ASSERT_ARG(identifier, identifier.impl()->refCount() == 1); + + m_unknownSet.add(identifier); + m_cachedDiskUsageIsValid = false; +} + +unsigned long long OriginUsageRecord::diskUsage() +{ + // Use the last cached usage value if we have it. + if (m_cachedDiskUsageIsValid) + return m_cachedDiskUsage; + + // stat() for the sizes known to be dirty. + HashSet<String>::iterator iUnknown = m_unknownSet.begin(); + HashSet<String>::iterator endUnknown = m_unknownSet.end(); + for (; iUnknown != endUnknown; ++iUnknown) { + const String& path = m_databaseMap.get(*iUnknown).filename; + ASSERT(!path.isEmpty()); + + long long size; + if (getFileSize(path, size)) + m_databaseMap.set(*iUnknown, DatabaseEntry(path, size)); + else { + // When we can't determine the file size, we'll just have to assume the file is missing/inaccessible. + m_databaseMap.set(*iUnknown, DatabaseEntry(path, 0)); + } + } + m_unknownSet.clear(); + + // Recalculate the cached usage value. + m_cachedDiskUsage = 0; + HashMap<String, DatabaseEntry>::iterator iDatabase = m_databaseMap.begin(); + HashMap<String, DatabaseEntry>::iterator endDatabase = m_databaseMap.end(); + for (; iDatabase != endDatabase; ++iDatabase) + m_cachedDiskUsage += iDatabase->second.size; + + m_cachedDiskUsageIsValid = true; + return m_cachedDiskUsage; +} + +} diff --git a/WebCore/storage/OriginUsageRecord.h b/WebCore/storage/OriginUsageRecord.h new file mode 100644 index 0000000..094ce3d --- /dev/null +++ b/WebCore/storage/OriginUsageRecord.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef OriginUsageRecord_h +#define OriginUsageRecord_h + +#include "PlatformString.h" +#include "StringHash.h" + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +namespace WebCore { + +// Objects of this class can be used from multiple threads with external synchronization. +// String arguments are also supposed to be deeply copied by the caller when necessary. +class OriginUsageRecord { +public: + OriginUsageRecord(); + + void addDatabase(const String& identifier, const String& fullPath); + void removeDatabase(const String& identifier); + void markDatabase(const String& identifier); // Size may have changed, and will need to be recalculated. + unsigned long long diskUsage(); + +private: + struct DatabaseEntry { + DatabaseEntry() { } + DatabaseEntry(const String& filename) : filename(filename) { } + DatabaseEntry(const String& filename, unsigned long long size) : filename(filename), size(size) { } + String filename; + unsigned long long size; // This member remains uninitialized until disk usage is calculated. + }; + HashMap<String, DatabaseEntry> m_databaseMap; + HashSet<String> m_unknownSet; + + unsigned long long m_cachedDiskUsage; + bool m_cachedDiskUsageIsValid; +}; + +} // namespace WebCore + +#endif diff --git a/WebCore/storage/SQLError.h b/WebCore/storage/SQLError.h new file mode 100644 index 0000000..69c1228 --- /dev/null +++ b/WebCore/storage/SQLError.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLError_h +#define SQLError_h + +#include "PlatformString.h" +#include <wtf/Threading.h> + +namespace WebCore { + +class SQLError : public ThreadSafeShared<SQLError> { +public: + static PassRefPtr<SQLError> create(unsigned code, const String& message) { return adoptRef(new SQLError(code, message)); } + + unsigned code() const { return m_code; } + String message() const { return m_message.copy(); } + +private: + SQLError(unsigned code, const String& message) : m_code(code), m_message(message.copy()) { } + unsigned m_code; + String m_message; +}; + +} + +#endif // SQLError_h diff --git a/WebCore/storage/SQLError.idl b/WebCore/storage/SQLError.idl new file mode 100644 index 0000000..188b9db --- /dev/null +++ b/WebCore/storage/SQLError.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLError { + readonly attribute unsigned int code; + readonly attribute DOMString message; + }; +} diff --git a/WebCore/storage/SQLResultSet.cpp b/WebCore/storage/SQLResultSet.cpp new file mode 100644 index 0000000..37024a1 --- /dev/null +++ b/WebCore/storage/SQLResultSet.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "SQLResultSet.h" + +#include "ExceptionCode.h" +#include "SQLValue.h" + +namespace WebCore { + +static unsigned const MaxErrorCode = 2; + +SQLResultSet::SQLResultSet() + : m_rows(SQLResultSetRowList::create()) + , m_insertId(0) + , m_insertIdSet(false) + , m_rowsAffected(0) +{ +} + +int64_t SQLResultSet::insertId(ExceptionCode& e) const +{ + // 4.11.4 - Return the id of the last row inserted as a result of the query + // If the query didn't result in any rows being added, raise an INVALID_ACCESS_ERR exception + if (m_insertIdSet) + return m_insertId; + + e = INVALID_ACCESS_ERR; + return -1; +} + +int SQLResultSet::rowsAffected() const +{ + return m_rowsAffected; +} + +SQLResultSetRowList* SQLResultSet::rows() const +{ + return m_rows.get(); +} + +void SQLResultSet::setInsertId(int64_t id) +{ + ASSERT(!m_insertIdSet); + + m_insertId = id; + m_insertIdSet = true; +} + +void SQLResultSet::setRowsAffected(int count) +{ + m_rowsAffected = count; +} + +} diff --git a/WebCore/storage/SQLResultSet.h b/WebCore/storage/SQLResultSet.h new file mode 100644 index 0000000..58f13b7 --- /dev/null +++ b/WebCore/storage/SQLResultSet.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLResultSet_h +#define SQLResultSet_h + +#include "SQLResultSetRowList.h" +#include <wtf/Threading.h> + +namespace WebCore { + +typedef int ExceptionCode; + +class SQLResultSet : public ThreadSafeShared<SQLResultSet> { +public: + static PassRefPtr<SQLResultSet> create() { return adoptRef(new SQLResultSet); } + + SQLResultSetRowList* rows() const; + + int64_t insertId(ExceptionCode&) const; + int rowsAffected() const; + +// For internal (non-JS) use + void setInsertId(int64_t); + void setRowsAffected(int); + +private: + SQLResultSet(); + + RefPtr<SQLResultSetRowList> m_rows; + int64_t m_insertId; + bool m_insertIdSet; + int m_rowsAffected; +}; + +} // namespace WebCore + +#endif // SQLResultSet_h diff --git a/WebCore/storage/SQLResultSet.idl b/WebCore/storage/SQLResultSet.idl new file mode 100644 index 0000000..fbad936 --- /dev/null +++ b/WebCore/storage/SQLResultSet.idl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLResultSet { + readonly attribute SQLResultSetRowList rows; + + readonly attribute int insertId + getter raises(DOMException); + readonly attribute int rowsAffected; + }; +} diff --git a/WebCore/storage/SQLResultSetRowList.cpp b/WebCore/storage/SQLResultSetRowList.cpp new file mode 100644 index 0000000..5b5b943 --- /dev/null +++ b/WebCore/storage/SQLResultSetRowList.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "SQLResultSetRowList.h" + +namespace WebCore { + +unsigned SQLResultSetRowList::length() const +{ + if (m_result.size() == 0) + return 0; + + ASSERT(m_result.size() % m_columns.size() == 0); + + return m_result.size() / m_columns.size(); +} + +} diff --git a/WebCore/storage/SQLResultSetRowList.h b/WebCore/storage/SQLResultSetRowList.h new file mode 100644 index 0000000..1b8a8b4 --- /dev/null +++ b/WebCore/storage/SQLResultSetRowList.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLResultSetRowList_h +#define SQLResultSetRowList_h + +#include <wtf/PassRefPtr.h> +#include "SQLValue.h" + +namespace WebCore { + +class SQLResultSetRowList : public RefCounted<SQLResultSetRowList> { +public: + static PassRefPtr<SQLResultSetRowList> create() { return adoptRef(new SQLResultSetRowList); } + + const Vector<String>& columnNames() const { return m_columns; } + const Vector<SQLValue>& values() const { return m_result; } + + void addColumn(const String& name) { m_columns.append(name); } + void addResult(const SQLValue& result) { m_result.append(result); } + + unsigned length() const; + +private: + SQLResultSetRowList() { } + + Vector<String> m_columns; + Vector<SQLValue> m_result; +}; + +} + +#endif diff --git a/WebCore/storage/SQLResultSetRowList.idl b/WebCore/storage/SQLResultSetRowList.idl new file mode 100644 index 0000000..f853e90 --- /dev/null +++ b/WebCore/storage/SQLResultSetRowList.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLResultSetRowList { + readonly attribute unsigned long length; + [Custom] DOMObject item(in unsigned long index); + }; +} diff --git a/WebCore/storage/SQLStatement.cpp b/WebCore/storage/SQLStatement.cpp new file mode 100644 index 0000000..9bd62dd --- /dev/null +++ b/WebCore/storage/SQLStatement.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "SQLStatement.h" + +#include "Database.h" +#include "DatabaseAuthorizer.h" +#include "Logging.h" +#include "SQLError.h" +#include "SQLiteDatabase.h" +#include "SQLiteStatement.h" +#include "SQLResultSet.h" +#include "SQLStatementCallback.h" +#include "SQLStatementErrorCallback.h" +#include "SQLTransaction.h" +#include "SQLValue.h" + +namespace WebCore { + +PassRefPtr<SQLStatement> SQLStatement::create(const String& statement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> errorCallback) +{ + return adoptRef(new SQLStatement(statement, arguments, callback, errorCallback)); +} + +SQLStatement::SQLStatement(const String& statement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> errorCallback) + : m_statement(statement.copy()) + , m_arguments(arguments) + , m_statementCallback(callback) + , m_statementErrorCallback(errorCallback) +{ +} + +bool SQLStatement::execute(Database* db) +{ + ASSERT(!m_resultSet); + + // If we're re-running this statement after a quota violation, we need to clear that error now + clearFailureDueToQuota(); + + // This transaction might have been marked bad while it was being set up on the main thread, + // so if there is still an error, return false. + if (m_error) + return false; + + SQLiteDatabase* database = &db->m_sqliteDatabase; + + SQLiteStatement statement(*database, m_statement); + int result = statement.prepare(); + + if (result != SQLResultOk) { + LOG(StorageAPI, "Unable to verify correctness of statement %s - error %i (%s)", m_statement.ascii().data(), result, database->lastErrorMsg()); + m_error = SQLError::create(1, database->lastErrorMsg()); + return false; + } + + // FIXME: If the statement uses the ?### syntax supported by sqlite, the bind parameter count is very likely off from the number of question marks. + // If this is the case, they might be trying to do something fishy or malicious + if (statement.bindParameterCount() != m_arguments.size()) { + LOG(StorageAPI, "Bind parameter count doesn't match number of question marks"); + m_error = SQLError::create(1, "number of '?'s in statement string does not match argument count"); + return false; + } + + for (unsigned i = 0; i < m_arguments.size(); ++i) { + result = statement.bindValue(i + 1, m_arguments[i]); + if (result == SQLResultFull) { + setFailureDueToQuota(); + return false; + } + + if (result != SQLResultOk) { + LOG(StorageAPI, "Failed to bind value index %i to statement for query '%s'", i + 1, m_statement.ascii().data()); + m_error = SQLError::create(1, database->lastErrorMsg()); + return false; + } + } + + RefPtr<SQLResultSet> resultSet = SQLResultSet::create(); + + // Step so we can fetch the column names. + result = statement.step(); + if (result == SQLResultRow) { + int columnCount = statement.columnCount(); + SQLResultSetRowList* rows = resultSet->rows(); + + for (int i = 0; i < columnCount; i++) + rows->addColumn(statement.getColumnName(i)); + + do { + for (int i = 0; i < columnCount; i++) + rows->addResult(statement.getColumnValue(i)); + + result = statement.step(); + } while (result == SQLResultRow); + + if (result != SQLResultDone) { + m_error = SQLError::create(1, database->lastErrorMsg()); + return false; + } + } else if (result == SQLResultDone) { + // Didn't find anything, or was an insert + if (db->m_databaseAuthorizer->lastActionWasInsert()) + resultSet->setInsertId(database->lastInsertRowID()); + } else if (result == SQLResultFull) { + // Return the Quota error - the delegate will be asked for more space and this statement might be re-run + setFailureDueToQuota(); + return false; + } else { + m_error = SQLError::create(1, database->lastErrorMsg()); + return false; + } + + // FIXME: If the spec allows triggers, and we want to be "accurate" in a different way, we'd use + // sqlite3_total_changes() here instead of sqlite3_changed, because that includes rows modified from within a trigger + // For now, this seems sufficient + resultSet->setRowsAffected(database->lastChanges()); + + m_resultSet = resultSet; + return true; +} + +void SQLStatement::setDatabaseDeletedError() +{ + ASSERT(!m_error && !m_resultSet); + m_error = SQLError::create(0, "unable to execute statement, because the user deleted the database"); +} + +void SQLStatement::setVersionMismatchedError() +{ + ASSERT(!m_error && !m_resultSet); + m_error = SQLError::create(2, "current version of the database and `oldVersion` argument do not match"); +} + +bool SQLStatement::performCallback(SQLTransaction* transaction) +{ + ASSERT(transaction); + + bool callbackError = false; + + // Call the appropriate statement callback and track if it resulted in an error, + // because then we need to jump to the transaction error callback. + if (m_error) { + ASSERT(m_statementErrorCallback); + callbackError = m_statementErrorCallback->handleEvent(transaction, m_error.get()); + } else if (m_statementCallback) + m_statementCallback->handleEvent(transaction, m_resultSet.get(), callbackError); + + // Now release our callbacks, to break reference cycles. + m_statementCallback = 0; + m_statementErrorCallback = 0; + + return callbackError; +} + +void SQLStatement::setFailureDueToQuota() +{ + ASSERT(!m_error && !m_resultSet); + m_error = SQLError::create(4, "there was not enough remaining storage space, or the storage quota was reached and the user declined to allow more space"); +} + +void SQLStatement::clearFailureDueToQuota() +{ + if (lastExecutionFailedDueToQuota()) + m_error = 0; +} + +bool SQLStatement::lastExecutionFailedDueToQuota() const +{ + return m_error && m_error->code() == 4; +} + +} // namespace WebCore diff --git a/WebCore/storage/SQLStatement.h b/WebCore/storage/SQLStatement.h new file mode 100644 index 0000000..e72fcf7 --- /dev/null +++ b/WebCore/storage/SQLStatement.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef SQLStatement_h +#define SQLStatement_h + +#include "PlatformString.h" + +#include "SQLError.h" +#include "SQLResultSet.h" +#include "SQLStatementCallback.h" +#include "SQLStatementErrorCallback.h" +#include "SQLValue.h" + +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Database; +class SQLTransaction; +class String; + +class SQLStatement : public ThreadSafeShared<SQLStatement> { +public: + static PassRefPtr<SQLStatement> create(const String&, const Vector<SQLValue>&, PassRefPtr<SQLStatementCallback>, PassRefPtr<SQLStatementErrorCallback>); + + bool execute(Database*); + bool lastExecutionFailedDueToQuota() const; + + bool hasStatementCallback() const { return m_statementCallback; } + bool hasStatementErrorCallback() const { return m_statementErrorCallback; } + + void setDatabaseDeletedError(); + void setVersionMismatchedError(); + + bool performCallback(SQLTransaction*); + + SQLError* sqlError() const { return m_error.get(); } +private: + SQLStatement(const String& statement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> errorCallback); + + void setFailureDueToQuota(); + void clearFailureDueToQuota(); + + String m_statement; + Vector<SQLValue> m_arguments; + RefPtr<SQLStatementCallback> m_statementCallback; + RefPtr<SQLStatementErrorCallback> m_statementErrorCallback; + + RefPtr<SQLError> m_error; + RefPtr<SQLResultSet> m_resultSet; +}; + +} // namespace WebCore + +#endif // SQLStatement_h diff --git a/WebCore/storage/SQLStatementCallback.h b/WebCore/storage/SQLStatementCallback.h new file mode 100644 index 0000000..58a9436 --- /dev/null +++ b/WebCore/storage/SQLStatementCallback.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef SQLStatementCallback_h +#define SQLStatementCallback_h + +#include <wtf/Threading.h> + +namespace WebCore { + +class SQLTransaction; +class SQLResultSet; + +class SQLStatementCallback : public ThreadSafeShared<SQLStatementCallback> { +public: + virtual ~SQLStatementCallback() { } + virtual void handleEvent(SQLTransaction*, SQLResultSet*, bool& raisedException) = 0; +}; + +} + +#endif // SQLStatementErrorCallback_h + + diff --git a/WebCore/storage/SQLStatementCallback.idl b/WebCore/storage/SQLStatementCallback.idl new file mode 100644 index 0000000..783e8c4 --- /dev/null +++ b/WebCore/storage/SQLStatementCallback.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLStatementCallback { + void handleEvent(in SQLTransaction transaction, in SQLResultSet resultSet); + }; + +} diff --git a/WebCore/storage/SQLStatementErrorCallback.h b/WebCore/storage/SQLStatementErrorCallback.h new file mode 100644 index 0000000..5dc3f5a --- /dev/null +++ b/WebCore/storage/SQLStatementErrorCallback.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLStatementErrorCallback_h +#define SQLStatementErrorCallback_h + +#include <wtf/Threading.h> + +namespace WebCore { + +class SQLTransaction; +class SQLError; + +class SQLStatementErrorCallback : public ThreadSafeShared<SQLStatementErrorCallback> { +public: + virtual ~SQLStatementErrorCallback() { } + virtual bool handleEvent(SQLTransaction*, SQLError*) = 0; +}; + +} + +#endif // SQLStatementErrorCallback_h + + diff --git a/WebCore/storage/SQLStatementErrorCallback.idl b/WebCore/storage/SQLStatementErrorCallback.idl new file mode 100644 index 0000000..9fae8bf --- /dev/null +++ b/WebCore/storage/SQLStatementErrorCallback.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLStatementErrorCallback { + boolean handleEvent(in SQLTransaction transaction, in SQLError error); + }; + +} diff --git a/WebCore/storage/SQLTransaction.cpp b/WebCore/storage/SQLTransaction.cpp new file mode 100644 index 0000000..5820d7f --- /dev/null +++ b/WebCore/storage/SQLTransaction.cpp @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2007, 2008 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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" +#include "SQLTransaction.h" + +#include "ChromeClient.h" +#include "Database.h" +#include "DatabaseAuthorizer.h" +#include "DatabaseDetails.h" +#include "DatabaseTracker.h" +#include "Document.h" +#include "ExceptionCode.h" +#include "Logging.h" +#include "OriginQuotaManager.h" +#include "Page.h" +#include "PlatformString.h" +#include "SecurityOrigin.h" +#include "SQLError.h" +#include "SQLiteTransaction.h" +#include "SQLResultSet.h" +#include "SQLStatement.h" +#include "SQLStatementCallback.h" +#include "SQLStatementErrorCallback.h" +#include "SQLValue.h" + +// There's no way of knowing exactly how much more space will be required when a statement hits the quota limit. +// For now, we'll arbitrarily choose currentQuota + 1mb. +// In the future we decide to track if a size increase wasn't enough, and ask for larger-and-larger increases until its enough. +static const int DefaultQuotaSizeIncrease = 1048576; + +namespace WebCore { + +PassRefPtr<SQLTransaction> SQLTransaction::create(Database* db, PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, + PassRefPtr<VoidCallback> successCallback, PassRefPtr<SQLTransactionWrapper> wrapper) +{ + return adoptRef(new SQLTransaction(db, callback, errorCallback, successCallback, wrapper)); +} + +SQLTransaction::SQLTransaction(Database* db, PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, PassRefPtr<VoidCallback> successCallback, + PassRefPtr<SQLTransactionWrapper> wrapper) + : m_nextStep(&SQLTransaction::openTransactionAndPreflight) + , m_executeSqlAllowed(false) + , m_database(db) + , m_wrapper(wrapper) + , m_callback(callback) + , m_successCallback(successCallback) + , m_errorCallback(errorCallback) + , m_shouldRetryCurrentStatement(false) + , m_shouldCommitAfterErrorCallback(true) + , m_modifiedDatabase(false) +{ + ASSERT(m_database); +} + +SQLTransaction::~SQLTransaction() +{ +} + +void SQLTransaction::executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> callbackError, ExceptionCode& e) +{ + if (!m_executeSqlAllowed || m_database->stopped()) { + e = INVALID_STATE_ERR; + return; + } + + RefPtr<SQLStatement> statement = SQLStatement::create(sqlStatement.copy(), arguments, callback, callbackError); + + if (m_database->deleted()) + statement->setDatabaseDeletedError(); + + if (!m_database->versionMatchesExpected()) + statement->setVersionMismatchedError(); + + enqueueStatement(statement); +} + +void SQLTransaction::enqueueStatement(PassRefPtr<SQLStatement> statement) +{ + MutexLocker locker(m_statementMutex); + m_statementQueue.append(statement); +} + +#ifndef NDEBUG +const char* SQLTransaction::debugStepName(SQLTransaction::TransactionStepMethod step) +{ + if (step == &SQLTransaction::openTransactionAndPreflight) + return "openTransactionAndPreflight"; + else if (step == &SQLTransaction::runStatements) + return "runStatements"; + else if (step == &SQLTransaction::postflightAndCommit) + return "postflightAndCommit"; + else if (step == &SQLTransaction::cleanupAfterTransactionErrorCallback) + return "cleanupAfterTransactionErrorCallback"; + else if (step == &SQLTransaction::deliverTransactionCallback) + return "deliverTransactionCallback"; + else if (step == &SQLTransaction::deliverTransactionErrorCallback) + return "deliverTransactionErrorCallback"; + else if (step == &SQLTransaction::deliverStatementCallback) + return "deliverStatementCallback"; + else if (step == &SQLTransaction::deliverQuotaIncreaseCallback) + return "deliverQuotaIncreaseCallback"; + else if (step == &SQLTransaction::deliverSuccessCallback) + return "deliverSuccessCallback"; + else if (step == &SQLTransaction::cleanupAfterSuccessCallback) + return "cleanupAfterSuccessCallback"; + else + return "UNKNOWN"; +} +#endif + +void SQLTransaction::checkAndHandleClosedDatabase() +{ + if (!m_database->stopped()) + return; + + // If the database was stopped, don't do anything and cancel queued work + LOG(StorageAPI, "Database was stopped - cancelling work for this transaction"); + MutexLocker locker(m_statementMutex); + m_statementQueue.clear(); + m_nextStep = 0; + + // The current SQLite transaction should be stopped, as well + if (m_sqliteTransaction) { + m_sqliteTransaction->stop(); + m_sqliteTransaction.clear(); + } +} + + +bool SQLTransaction::performNextStep() +{ + LOG(StorageAPI, "Step %s\n", debugStepName(m_nextStep)); + + ASSERT(m_nextStep == &SQLTransaction::openTransactionAndPreflight || + m_nextStep == &SQLTransaction::runStatements || + m_nextStep == &SQLTransaction::postflightAndCommit || + m_nextStep == &SQLTransaction::cleanupAfterSuccessCallback || + m_nextStep == &SQLTransaction::cleanupAfterTransactionErrorCallback); + + checkAndHandleClosedDatabase(); + + if (m_nextStep) + (this->*m_nextStep)(); + + // If there is no nextStep after performing the above step, the transaction is complete + return !m_nextStep; +} + +void SQLTransaction::performPendingCallback() +{ + LOG(StorageAPI, "Callback %s\n", debugStepName(m_nextStep)); + + ASSERT(m_nextStep == &SQLTransaction::deliverTransactionCallback || + m_nextStep == &SQLTransaction::deliverTransactionErrorCallback || + m_nextStep == &SQLTransaction::deliverStatementCallback || + m_nextStep == &SQLTransaction::deliverQuotaIncreaseCallback || + m_nextStep == &SQLTransaction::deliverSuccessCallback); + + checkAndHandleClosedDatabase(); + + if (m_nextStep) + (this->*m_nextStep)(); +} + +void SQLTransaction::openTransactionAndPreflight() +{ + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + + LOG(StorageAPI, "Opening and preflighting transaction %p", this); + + // If the database was deleted, jump to the error callback + if (m_database->deleted()) { + m_transactionError = SQLError::create(0, "unable to open a transaction, because the user deleted the database"); + handleTransactionError(false); + return; + } + + // Set the maximum usage for this transaction + m_database->m_sqliteDatabase.setMaximumSize(m_database->maximumSize()); + + ASSERT(!m_sqliteTransaction); + m_sqliteTransaction.set(new SQLiteTransaction(m_database->m_sqliteDatabase)); + + m_database->m_databaseAuthorizer->disable(); + m_sqliteTransaction->begin(); + m_database->m_databaseAuthorizer->enable(); + + // Transaction Steps 1+2 - Open a transaction to the database, jumping to the error callback if that fails + if (!m_sqliteTransaction->inProgress()) { + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + m_sqliteTransaction.clear(); + m_transactionError = SQLError::create(0, "unable to open a transaction to the database"); + handleTransactionError(false); + return; + } + + // Transaction Steps 3 - Peform preflight steps, jumping to the error callback if they fail + if (m_wrapper && !m_wrapper->performPreflight(this)) { + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + m_sqliteTransaction.clear(); + m_transactionError = m_wrapper->sqlError(); + if (!m_transactionError) + m_transactionError = SQLError::create(0, "unknown error occured setting up transaction"); + + handleTransactionError(false); + return; + } + + // Transaction Step 4 - Invoke the transaction callback with the new SQLTransaction object + m_nextStep = &SQLTransaction::deliverTransactionCallback; + LOG(StorageAPI, "Scheduling deliverTransactionCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); +} + +void SQLTransaction::deliverTransactionCallback() +{ + bool shouldDeliverErrorCallback = false; + + if (m_callback) { + m_executeSqlAllowed = true; + m_callback->handleEvent(this, shouldDeliverErrorCallback); + m_executeSqlAllowed = false; + } else + shouldDeliverErrorCallback = true; + + // Transaction Step 5 - If the transaction callback was null or raised an exception, jump to the error callback + if (shouldDeliverErrorCallback) { + m_transactionError = SQLError::create(0, "the SQLTransactionCallback was null or threw an exception"); + deliverTransactionErrorCallback(); + } else + scheduleToRunStatements(); +} + +void SQLTransaction::scheduleToRunStatements() +{ + m_nextStep = &SQLTransaction::runStatements; + LOG(StorageAPI, "Scheduling runStatements for transaction %p\n", this); + m_database->scheduleTransactionStep(this); +} + +void SQLTransaction::runStatements() +{ + // If there is a series of statements queued up that are all successful and have no associated + // SQLStatementCallback objects, then we can burn through the queue + do { + if (m_shouldRetryCurrentStatement) { + m_shouldRetryCurrentStatement = false; + // FIXME - Another place that needs fixing up after <rdar://problem/5628468> is addressed. + // See ::openTransactionAndPreflight() for discussion + + // Reset the maximum size here, as it was increased to allow us to retry this statement + m_database->m_sqliteDatabase.setMaximumSize(m_database->maximumSize()); + } else { + // If the current statement has already been run, failed due to quota constraints, and we're not retrying it, + // that means it ended in an error. Handle it now + if (m_currentStatement && m_currentStatement->lastExecutionFailedDueToQuota()) { + handleCurrentStatementError(); + break; + } + + // Otherwise, advance to the next statement + getNextStatement(); + } + } while (runCurrentStatement()); + + // If runCurrentStatement() returned false, that means either there was no current statement to run, + // or the current statement requires a callback to complete. In the later case, it also scheduled + // the callback or performed any other additional work so we can return + if (!m_currentStatement) + postflightAndCommit(); +} + +void SQLTransaction::getNextStatement() +{ + m_currentStatement = 0; + + MutexLocker locker(m_statementMutex); + if (!m_statementQueue.isEmpty()) { + m_currentStatement = m_statementQueue.first(); + m_statementQueue.removeFirst(); + } +} + +bool SQLTransaction::runCurrentStatement() +{ + if (!m_currentStatement) + return false; + + m_database->m_databaseAuthorizer->reset(); + + if (m_currentStatement->execute(m_database.get())) { + if (m_database->m_databaseAuthorizer->lastActionChangedDatabase()) { + // Flag this transaction as having changed the database for later delegate notification + m_modifiedDatabase = true; + // Also dirty the size of this database file for calculating quota usage + OriginQuotaManager& manager(DatabaseTracker::tracker().originQuotaManager()); + Locker<OriginQuotaManager> locker(manager); + + manager.markDatabase(m_database.get()); + } + + if (m_currentStatement->hasStatementCallback()) { + m_nextStep = &SQLTransaction::deliverStatementCallback; + LOG(StorageAPI, "Scheduling deliverStatementCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); + return false; + } + return true; + } + + if (m_currentStatement->lastExecutionFailedDueToQuota()) { + m_nextStep = &SQLTransaction::deliverQuotaIncreaseCallback; + LOG(StorageAPI, "Scheduling deliverQuotaIncreaseCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); + return false; + } + + handleCurrentStatementError(); + + return false; +} + +void SQLTransaction::handleCurrentStatementError() +{ + // Transaction Steps 6.error - Call the statement's error callback, but if there was no error callback, + // jump to the transaction error callback + if (m_currentStatement->hasStatementErrorCallback()) { + m_nextStep = &SQLTransaction::deliverStatementCallback; + LOG(StorageAPI, "Scheduling deliverStatementCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); + } else { + m_transactionError = m_currentStatement->sqlError(); + if (!m_transactionError) + m_transactionError = SQLError::create(1, "the statement failed to execute"); + handleTransactionError(false); + } +} + +void SQLTransaction::deliverStatementCallback() +{ + ASSERT(m_currentStatement); + + // Transaction Step 6.6 and 6.3(error) - If the statement callback went wrong, jump to the transaction error callback + // Otherwise, continue to loop through the statement queue + m_executeSqlAllowed = true; + bool result = m_currentStatement->performCallback(this); + m_executeSqlAllowed = false; + + if (result) { + m_transactionError = SQLError::create(0, "the statement callback raised an exception or statement error callback did not return false"); + handleTransactionError(true); + } else + scheduleToRunStatements(); +} + +void SQLTransaction::deliverQuotaIncreaseCallback() +{ + ASSERT(m_currentStatement); + ASSERT(!m_shouldRetryCurrentStatement); + + Page* page = m_database->document()->page(); + ASSERT(page); + + RefPtr<SecurityOrigin> origin = m_database->securityOriginCopy(); + + unsigned long long currentQuota = DatabaseTracker::tracker().quotaForOrigin(origin.get()); + page->chrome()->client()->exceededDatabaseQuota(m_database->document()->frame(), m_database->stringIdentifier()); + unsigned long long newQuota = DatabaseTracker::tracker().quotaForOrigin(origin.get()); + + // If the new quota ended up being larger than the old quota, we will retry the statement. + if (newQuota > currentQuota) + m_shouldRetryCurrentStatement = true; + + m_nextStep = &SQLTransaction::runStatements; + LOG(StorageAPI, "Scheduling runStatements for transaction %p\n", this); + m_database->scheduleTransactionStep(this); +} + +void SQLTransaction::postflightAndCommit() +{ + // Transaction Step 7 - Peform postflight steps, jumping to the error callback if they fail + if (m_wrapper && !m_wrapper->performPostflight(this)) { + m_transactionError = m_wrapper->sqlError(); + if (!m_transactionError) + m_transactionError = SQLError::create(0, "unknown error occured setting up transaction"); + handleTransactionError(false); + return; + } + + // Transacton Step 8+9 - Commit the transaction, jumping to the error callback if that fails + ASSERT(m_sqliteTransaction); + + m_database->m_databaseAuthorizer->disable(); + m_sqliteTransaction->commit(); + m_database->m_databaseAuthorizer->enable(); + + // If the commit failed, the transaction will still be marked as "in progress" + if (m_sqliteTransaction->inProgress()) { + m_shouldCommitAfterErrorCallback = false; + m_transactionError = SQLError::create(0, "failed to commit the transaction"); + handleTransactionError(false); + return; + } + + // The commit was successful, notify the delegates if the transaction modified this database + if (m_modifiedDatabase) + DatabaseTracker::tracker().scheduleNotifyDatabaseChanged(m_database->m_securityOrigin.get(), m_database->m_name); + + // Now release our unneeded callbacks, to break reference cycles. + m_callback = 0; + m_errorCallback = 0; + + // Transaction Step 10 - Deliver success callback, if there is one + if (m_successCallback) { + m_nextStep = &SQLTransaction::deliverSuccessCallback; + LOG(StorageAPI, "Scheduling deliverSuccessCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); + } else + cleanupAfterSuccessCallback(); +} + +void SQLTransaction::deliverSuccessCallback() +{ + // Transaction Step 10 - Deliver success callback + ASSERT(m_successCallback); + m_successCallback->handleEvent(); + + // Release the last callback to break reference cycle + m_successCallback = 0; + + // Schedule a "post-success callback" step to return control to the database thread in case there + // are further transactions queued up for this Database + m_nextStep = &SQLTransaction::cleanupAfterSuccessCallback; + LOG(StorageAPI, "Scheduling cleanupAfterSuccessCallback for transaction %p\n", this); + m_database->scheduleTransactionStep(this); +} + +void SQLTransaction::cleanupAfterSuccessCallback() +{ + // Transaction Step 11 - End transaction steps + // There is no next step + LOG(StorageAPI, "Transaction %p is complete\n", this); + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + m_nextStep = 0; +} + +void SQLTransaction::handleTransactionError(bool inCallback) +{ + if (m_errorCallback) { + if (inCallback) + deliverTransactionErrorCallback(); + else { + m_nextStep = &SQLTransaction::deliverTransactionErrorCallback; + LOG(StorageAPI, "Scheduling deliverTransactionErrorCallback for transaction %p\n", this); + m_database->scheduleTransactionCallback(this); + } + return; + } + + // Transaction Step 12 - If the callback couldn't be called, then rollback the transaction. + m_shouldCommitAfterErrorCallback = false; + if (inCallback) { + m_nextStep = &SQLTransaction::cleanupAfterTransactionErrorCallback; + LOG(StorageAPI, "Scheduling cleanupAfterTransactionErrorCallback for transaction %p\n", this); + m_database->scheduleTransactionStep(this); + } else { + cleanupAfterTransactionErrorCallback(); + } +} + +void SQLTransaction::deliverTransactionErrorCallback() +{ + ASSERT(m_transactionError); + + // Transaction Step 12 - If the callback didn't return false, then rollback the transaction. + // This includes the callback not existing, returning true, or throwing an exception + if (!m_errorCallback || m_errorCallback->handleEvent(m_transactionError.get())) + m_shouldCommitAfterErrorCallback = false; + + m_nextStep = &SQLTransaction::cleanupAfterTransactionErrorCallback; + LOG(StorageAPI, "Scheduling cleanupAfterTransactionErrorCallback for transaction %p\n", this); + m_database->scheduleTransactionStep(this); +} + +void SQLTransaction::cleanupAfterTransactionErrorCallback() +{ + m_database->m_databaseAuthorizer->disable(); + if (m_sqliteTransaction) { + // Transaction Step 12 -If the error callback returned false, and the last error wasn't itself a + // failure when committing the transaction, then try to commit the transaction + if (m_shouldCommitAfterErrorCallback) + m_sqliteTransaction->commit(); + + if (m_sqliteTransaction->inProgress()) { + // Transaction Step 12 - If that fails, or if the callback couldn't be called + // or if it didn't return false, then rollback the transaction. + m_sqliteTransaction->rollback(); + } else if (m_modifiedDatabase) { + // But if the commit was successful, notify the delegates if the transaction modified this database + DatabaseTracker::tracker().scheduleNotifyDatabaseChanged(m_database->m_securityOrigin.get(), m_database->m_name); + } + + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + m_sqliteTransaction.clear(); + } + m_database->m_databaseAuthorizer->enable(); + + // Transaction Step 12 - Any still-pending statements in the transaction are discarded. + { + MutexLocker locker(m_statementMutex); + m_statementQueue.clear(); + } + + // Transaction is complete! There is no next step + LOG(StorageAPI, "Transaction %p is complete with an error\n", this); + ASSERT(!m_database->m_sqliteDatabase.transactionInProgress()); + m_nextStep = 0; + + // Now release our callbacks, to break reference cycles. + m_callback = 0; + m_errorCallback = 0; +} + +} // namespace WebCore diff --git a/WebCore/storage/SQLTransaction.h b/WebCore/storage/SQLTransaction.h new file mode 100644 index 0000000..87d438e --- /dev/null +++ b/WebCore/storage/SQLTransaction.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ +#ifndef SQLTransaction_h +#define SQLTransaction_h + +#include <wtf/Threading.h> + +#include "SQLiteTransaction.h" +#include "SQLStatement.h" +#include "SQLTransactionCallback.h" +#include "SQLTransactionErrorCallback.h" +#include <wtf/Deque.h> +#include <wtf/Forward.h> +#include <wtf/OwnPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +typedef int ExceptionCode; + +class Database; +class SQLError; +class SQLStatementCallback; +class SQLStatementErrorCallback; +class SQLTransaction; +class SQLValue; +class String; +class VoidCallback; + +class SQLTransactionWrapper : public ThreadSafeShared<SQLTransactionWrapper> { +public: + virtual ~SQLTransactionWrapper() { } + virtual bool performPreflight(SQLTransaction*) = 0; + virtual bool performPostflight(SQLTransaction*) = 0; + + virtual SQLError* sqlError() const = 0; +}; + +class SQLTransaction : public ThreadSafeShared<SQLTransaction> { +public: + static PassRefPtr<SQLTransaction> create(Database*, PassRefPtr<SQLTransactionCallback>, PassRefPtr<SQLTransactionErrorCallback>, PassRefPtr<VoidCallback>, PassRefPtr<SQLTransactionWrapper>); + + ~SQLTransaction(); + + void executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, + PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> callbackError, ExceptionCode& e); + + bool performNextStep(); + void performPendingCallback(); + + Database* database() { return m_database.get(); } + +private: + SQLTransaction(Database*, PassRefPtr<SQLTransactionCallback>, PassRefPtr<SQLTransactionErrorCallback>, PassRefPtr<VoidCallback>, PassRefPtr<SQLTransactionWrapper>); + + typedef void (SQLTransaction::*TransactionStepMethod)(); + TransactionStepMethod m_nextStep; + + void enqueueStatement(PassRefPtr<SQLStatement>); + + void checkAndHandleClosedDatabase(); + + void openTransactionAndPreflight(); + void deliverTransactionCallback(); + void scheduleToRunStatements(); + void runStatements(); + void getNextStatement(); + bool runCurrentStatement(); + void handleCurrentStatementError(); + void deliverStatementCallback(); + void deliverQuotaIncreaseCallback(); + void postflightAndCommit(); + void deliverSuccessCallback(); + void cleanupAfterSuccessCallback(); + void handleTransactionError(bool inCallback); + void deliverTransactionErrorCallback(); + void cleanupAfterTransactionErrorCallback(); + +#ifndef NDEBUG + static const char* debugStepName(TransactionStepMethod); +#endif + + RefPtr<SQLStatement> m_currentStatement; + + bool m_executeSqlAllowed; + + RefPtr<Database> m_database; + RefPtr<SQLTransactionWrapper> m_wrapper; + RefPtr<SQLTransactionCallback> m_callback; + RefPtr<VoidCallback> m_successCallback; + RefPtr<SQLTransactionErrorCallback> m_errorCallback; + RefPtr<SQLError> m_transactionError; + bool m_shouldRetryCurrentStatement; + bool m_shouldCommitAfterErrorCallback; + bool m_modifiedDatabase; + + Mutex m_statementMutex; + Deque<RefPtr<SQLStatement> > m_statementQueue; + + OwnPtr<SQLiteTransaction> m_sqliteTransaction; +}; + +} // namespace WebCore + +#endif // SQLTransaction_h diff --git a/WebCore/storage/SQLTransaction.idl b/WebCore/storage/SQLTransaction.idl new file mode 100644 index 0000000..41436f1 --- /dev/null +++ b/WebCore/storage/SQLTransaction.idl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLTransaction { + [Custom] void executeSql(in DOMString sqlStatement, in ObjectArray arguments, in SQLStatementCallback callback, in SQLStatementErrorCallback errorCallback); + }; +} diff --git a/WebCore/storage/SQLTransactionCallback.h b/WebCore/storage/SQLTransactionCallback.h new file mode 100644 index 0000000..bbe62a5 --- /dev/null +++ b/WebCore/storage/SQLTransactionCallback.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 Apple 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: + *Transaction + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLTransactionCallback_h +#define SQLTransactionCallback_h + +#include <wtf/Threading.h> + +namespace WebCore { + +class SQLTransaction; +class SQLError; + +class SQLTransactionCallback : public ThreadSafeShared<SQLTransactionCallback> { +public: + virtual ~SQLTransactionCallback() { } + virtual void handleEvent(SQLTransaction*, bool& raisedException) = 0; +}; + +} + +#endif // SQLTransactionCallback_h diff --git a/WebCore/storage/SQLTransactionCallback.idl b/WebCore/storage/SQLTransactionCallback.idl new file mode 100644 index 0000000..cee726f --- /dev/null +++ b/WebCore/storage/SQLTransactionCallback.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLTransactionCallback { + void handleEvent(in SQLTransaction transaction); + }; + +} diff --git a/WebCore/storage/SQLTransactionErrorCallback.h b/WebCore/storage/SQLTransactionErrorCallback.h new file mode 100644 index 0000000..2890556 --- /dev/null +++ b/WebCore/storage/SQLTransactionErrorCallback.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef SQLTransactionErrorCallback_h +#define SQLTransactionErrorCallback_h + +#include <wtf/Threading.h> + +namespace WebCore { + + class SQLError; + + class SQLTransactionErrorCallback : public ThreadSafeShared<SQLTransactionErrorCallback> { + public: + virtual ~SQLTransactionErrorCallback() { } + virtual bool handleEvent(SQLError*) = 0; + }; + +} + +#endif // SQLTransactionErrorCallback_h + + diff --git a/WebCore/storage/SQLTransactionErrorCallback.idl b/WebCore/storage/SQLTransactionErrorCallback.idl new file mode 100644 index 0000000..9355d9a --- /dev/null +++ b/WebCore/storage/SQLTransactionErrorCallback.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +module storage { + + interface SQLTransactionErrorCallback { + void handleEvent(in SQLError error); + }; + +} diff --git a/WebCore/storage/SessionStorage.cpp b/WebCore/storage/SessionStorage.cpp new file mode 100644 index 0000000..e70227a --- /dev/null +++ b/WebCore/storage/SessionStorage.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "SessionStorage.h" + +#include "EventNames.h" +#include "Frame.h" +#include "FrameTree.h" +#include "Page.h" +#include "SecurityOrigin.h" +#include "StorageArea.h" +#include "StorageMap.h" + +namespace WebCore { + +PassRefPtr<SessionStorage> SessionStorage::create(Page* page) +{ + return adoptRef(new SessionStorage(page)); +} + +SessionStorage::SessionStorage(Page* page) + : m_page(page) +{ + ASSERT(m_page); +} + +PassRefPtr<SessionStorage> SessionStorage::copy(Page* newPage) +{ + ASSERT(newPage); + RefPtr<SessionStorage> newSession = SessionStorage::create(newPage); + + SessionStorageAreaMap::iterator end = m_storageAreaMap.end(); + for (SessionStorageAreaMap::iterator i = m_storageAreaMap.begin(); i != end; ++i) { + RefPtr<SessionStorageArea> areaCopy = i->second->copy(i->first.get(), newPage); + newSession->m_storageAreaMap.set(i->first, areaCopy.release()); + } + + return newSession.release(); +} + +PassRefPtr<StorageArea> SessionStorage::storageArea(SecurityOrigin* origin) +{ + RefPtr<SessionStorageArea> storageArea; + if (storageArea = m_storageAreaMap.get(origin)) + return storageArea.release(); + + storageArea = SessionStorageArea::create(origin, m_page); + m_storageAreaMap.set(origin, storageArea); + return storageArea.release(); +} + +} diff --git a/WebCore/storage/SessionStorage.h b/WebCore/storage/SessionStorage.h new file mode 100644 index 0000000..7b59aeb --- /dev/null +++ b/WebCore/storage/SessionStorage.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef SessionStorage_h +#define SessionStorage_h + +#include "SecurityOriginHash.h" +#include "SessionStorageArea.h" + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + + class Page; + + class SessionStorage : public RefCounted<SessionStorage> { + public: + static PassRefPtr<SessionStorage> create(Page*); + PassRefPtr<SessionStorage> copy(Page*); + + PassRefPtr<StorageArea> storageArea(SecurityOrigin*); + +#ifndef NDEBUG + Page* page() { return m_page; } +#endif + + private: + SessionStorage(Page*); + + void dispatchStorageEvent(StorageArea*, const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame); + + Page* m_page; + + typedef HashMap<RefPtr<SecurityOrigin>, RefPtr<SessionStorageArea>, SecurityOriginHash> SessionStorageAreaMap; + SessionStorageAreaMap m_storageAreaMap; + }; + +} // namespace WebCore + +#endif // SessionStorage_h diff --git a/WebCore/storage/SessionStorageArea.cpp b/WebCore/storage/SessionStorageArea.cpp new file mode 100644 index 0000000..048c488 --- /dev/null +++ b/WebCore/storage/SessionStorageArea.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "SessionStorageArea.h" + +#include "EventNames.h" +#include "Frame.h" +#include "FrameTree.h" +#include "Page.h" +#include "PlatformString.h" +#include "SecurityOrigin.h" +#include "StorageMap.h" + +namespace WebCore { + +PassRefPtr<SessionStorageArea> SessionStorageArea::copy(SecurityOrigin* origin, Page* page) +{ + return adoptRef(new SessionStorageArea(origin, page, this)); +} + +SessionStorageArea::SessionStorageArea(SecurityOrigin* origin, Page* page) + : StorageArea(origin) + , m_page(page) +{ + ASSERT(page); +} + +SessionStorageArea::SessionStorageArea(SecurityOrigin* origin, Page* page, SessionStorageArea* area) + : StorageArea(origin, area) + , m_page(page) +{ + ASSERT(page); +} + +void SessionStorageArea::itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame) +{ + dispatchStorageEvent(key, oldValue, newValue, sourceFrame); +} + +void SessionStorageArea::itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame) +{ + dispatchStorageEvent(key, oldValue, String(), sourceFrame); +} + +void SessionStorageArea::areaCleared(Frame* sourceFrame) +{ + dispatchStorageEvent(String(), String(), String(), sourceFrame); +} + +void SessionStorageArea::dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame) +{ + // For SessionStorage events, each frame in the page's frametree with the same origin as this StorageArea needs to be notified of the change + Vector<RefPtr<Frame> > frames; + for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree()->traverseNext()) { + if (Document* document = frame->document()) + if (document->securityOrigin()->equal(securityOrigin())) + frames.append(frame); + } + + for (unsigned i = 0; i < frames.size(); ++i) { + if (HTMLElement* body = frames[i]->document()->body()) + body->dispatchStorageEvent(eventNames().storageEvent, key, oldValue, newValue, sourceFrame); + } +} + +} // namespace WebCore diff --git a/WebCore/storage/SessionStorageArea.h b/WebCore/storage/SessionStorageArea.h new file mode 100644 index 0000000..3dad1af --- /dev/null +++ b/WebCore/storage/SessionStorageArea.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef SessionStorageArea_h +#define SessionStorageArea_h + +#include "StorageArea.h" + +namespace WebCore { + + class Page; + + class SessionStorageArea : public StorageArea { + public: + static PassRefPtr<SessionStorageArea> create(SecurityOrigin* origin, Page* page) { return adoptRef(new SessionStorageArea(origin, page)); } + PassRefPtr<SessionStorageArea> copy(SecurityOrigin*, Page*); + + Page* page() { return m_page; } + + private: + SessionStorageArea(SecurityOrigin*, Page*); + SessionStorageArea(SecurityOrigin*, Page*, SessionStorageArea*); + + virtual void itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame); + virtual void itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame); + virtual void areaCleared(Frame* sourceFrame); + + void dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame); + + Page* m_page; + }; + +} // namespace WebCore + +#endif // SessionStorageArea_h diff --git a/WebCore/storage/Storage.cpp b/WebCore/storage/Storage.cpp new file mode 100644 index 0000000..cf4413a --- /dev/null +++ b/WebCore/storage/Storage.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "Storage.h" + +#include "StorageArea.h" +#include "PlatformString.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +PassRefPtr<Storage> Storage::create(Frame* frame, PassRefPtr<StorageArea> storageArea) +{ + return adoptRef(new Storage(frame, storageArea)); +} + +Storage::Storage(Frame* frame, PassRefPtr<StorageArea> storageArea) + : m_frame(frame) + , m_storageArea(storageArea) +{ + ASSERT(m_frame); + ASSERT(m_storageArea); +} + +unsigned Storage::length() const +{ + if (!m_frame) + return 0; + + return m_storageArea->length(); +} + +String Storage::key(unsigned index, ExceptionCode& ec) const +{ + ec = 0; + if (!m_frame) + return String(); + + return m_storageArea->key(index, ec); +} + +String Storage::getItem(const String& key) const +{ + if (!m_frame) + return String(); + + return m_storageArea->getItem(key); +} + +void Storage::setItem(const String& key, const String& value, ExceptionCode& ec) +{ + ec = 0; + if (!m_frame) + return; + + m_storageArea->setItem(key, value, ec, m_frame); +} + +void Storage::removeItem(const String& key) +{ + if (!m_frame) + return; + + m_storageArea->removeItem(key, m_frame); +} + +void Storage::clear() +{ + if (!m_frame) + return; + + m_storageArea->clear(m_frame); +} + +bool Storage::contains(const String& key) const +{ + if (!m_frame) + return false; + + return m_storageArea->contains(key); +} + +} diff --git a/WebCore/storage/Storage.h b/WebCore/storage/Storage.h new file mode 100644 index 0000000..061f7c4 --- /dev/null +++ b/WebCore/storage/Storage.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef Storage_h +#define Storage_h + +#include "StorageArea.h" + +#include <wtf/Forward.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class Frame; + class String; + typedef int ExceptionCode; + + class Storage : public RefCounted<Storage> { + public: + static PassRefPtr<Storage> create(Frame*, PassRefPtr<StorageArea>); + + unsigned length() const; + String key(unsigned index, ExceptionCode&) const; + String getItem(const String&) const; + void setItem(const String& key, const String& value, ExceptionCode&); + void removeItem(const String&); + void clear(); + + bool contains(const String& key) const; + + void disconnectFrame() { m_frame = 0; } + + private: + Storage(Frame*, PassRefPtr<StorageArea>); + + Frame* m_frame; + RefPtr<StorageArea> m_storageArea; + }; + +} // namespace WebCore + +#endif // Storage_h diff --git a/WebCore/storage/Storage.idl b/WebCore/storage/Storage.idl new file mode 100644 index 0000000..8ff9cd9 --- /dev/null +++ b/WebCore/storage/Storage.idl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +module storage { + + interface [ + GenerateConstructor, + HasNameGetter, + CustomDeleteProperty, + CustomGetPropertyNames, + CustomPutFunction + ] Storage { + readonly attribute [DontEnum] unsigned long length; + [DontEnum] DOMString key(in unsigned long index) + raises(DOMException); + [DontEnum, ConvertNullStringTo=Null] DOMString getItem(in DOMString key); + [DontEnum] void setItem(in DOMString key, in DOMString data) + raises(DOMException); + [DontEnum] void removeItem(in DOMString key); + [DontEnum] void clear(); + }; + +} diff --git a/WebCore/storage/StorageArea.cpp b/WebCore/storage/StorageArea.cpp new file mode 100644 index 0000000..6a1b7aa --- /dev/null +++ b/WebCore/storage/StorageArea.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "StorageArea.h" + +#include "CString.h" +#include "ExceptionCode.h" +#include "SecurityOrigin.h" +#include "StorageMap.h" + +namespace WebCore { + +StorageArea::StorageArea(SecurityOrigin* origin) + : m_securityOrigin(origin) + , m_storageMap(StorageMap::create()) +{ +} + +StorageArea::StorageArea(SecurityOrigin* origin, StorageArea* area) + : m_securityOrigin(origin) + , m_storageMap(area->m_storageMap) +{ +} + +StorageArea::~StorageArea() +{ +} + +unsigned StorageArea::internalLength() const +{ + return m_storageMap->length(); +} + +String StorageArea::internalKey(unsigned index, ExceptionCode& ec) const +{ + String key; + + if (!m_storageMap->key(index, key)) { + ec = INDEX_SIZE_ERR; + return String(); + } + + return key; +} + +String StorageArea::internalGetItem(const String& key) const +{ + return m_storageMap->getItem(key); +} + +void StorageArea::internalSetItem(const String& key, const String& value, ExceptionCode& ec, Frame* frame) +{ + ASSERT(!value.isNull()); + + // FIXME: For LocalStorage where a disk quota will be enforced, here is where we need to do quota checking. + // If we decide to enforce a memory quota for SessionStorage, this is where we'd do that, also. + // if (<over quota>) { + // ec = INVALID_ACCESS_ERR; + // return; + // } + + String oldValue; + RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue); + + if (newMap) + m_storageMap = newMap.release(); + + // Only notify the client if an item was actually changed + if (oldValue != value) + itemChanged(key, oldValue, value, frame); +} + +void StorageArea::internalRemoveItem(const String& key, Frame* frame) +{ + String oldValue; + RefPtr<StorageMap> newMap = m_storageMap->removeItem(key, oldValue); + if (newMap) + m_storageMap = newMap.release(); + + // Only notify the client if an item was actually removed + if (!oldValue.isNull()) + itemRemoved(key, oldValue, frame); +} + +void StorageArea::internalClear(Frame* frame) +{ + m_storageMap = StorageMap::create(); + + areaCleared(frame); +} + +bool StorageArea::internalContains(const String& key) const +{ + return m_storageMap->contains(key); +} + +void StorageArea::importItem(const String& key, const String& value) +{ + m_storageMap->importItem(key, value); +} + +} diff --git a/WebCore/storage/StorageArea.h b/WebCore/storage/StorageArea.h new file mode 100644 index 0000000..2163287 --- /dev/null +++ b/WebCore/storage/StorageArea.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef StorageArea_h +#define StorageArea_h + +#include "PlatformString.h" + +#include <wtf/Forward.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class Frame; + class SecurityOrigin; + class StorageMap; + typedef int ExceptionCode; + + class StorageArea : public RefCounted<StorageArea> { + public: + virtual ~StorageArea(); + + virtual unsigned length() const { return internalLength(); } + virtual String key(unsigned index, ExceptionCode& ec) const { return internalKey(index, ec); } + virtual String getItem(const String& key) const { return internalGetItem(key); } + virtual void setItem(const String& key, const String& value, ExceptionCode& ec, Frame* sourceFrame) { internalSetItem(key, value, ec, sourceFrame); } + virtual void removeItem(const String& key, Frame* sourceFrame) { internalRemoveItem(key, sourceFrame); } + virtual void clear(Frame* sourceFrame) { internalClear(sourceFrame); } + virtual bool contains(const String& key) const { return internalContains(key); } + + SecurityOrigin* securityOrigin() { return m_securityOrigin.get(); } + + protected: + StorageArea(SecurityOrigin*); + StorageArea(SecurityOrigin*, StorageArea*); + + unsigned internalLength() const; + String internalKey(unsigned index, ExceptionCode&) const; + String internalGetItem(const String&) const; + void internalSetItem(const String& key, const String& value, ExceptionCode&, Frame* sourceFrame); + void internalRemoveItem(const String&, Frame* sourceFrame); + void internalClear(Frame* sourceFrame); + bool internalContains(const String& key) const; + + // This is meant to be called from a background thread for LocalStorageArea's background thread import procedure. + void importItem(const String& key, const String& value); + + private: + virtual void itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame) = 0; + virtual void itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame) = 0; + virtual void areaCleared(Frame* sourceFrame) = 0; + + RefPtr<SecurityOrigin> m_securityOrigin; + RefPtr<StorageMap> m_storageMap; + }; + +} // namespace WebCore + +#endif // StorageArea_h diff --git a/WebCore/storage/StorageEvent.cpp b/WebCore/storage/StorageEvent.cpp new file mode 100644 index 0000000..54fd9ad --- /dev/null +++ b/WebCore/storage/StorageEvent.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "StorageEvent.h" + +#include "DOMWindow.h" + +namespace WebCore { + +StorageEvent::StorageEvent(const AtomicString& type, const String& key, const String& oldValue, const String& newValue, const String& uri, PassRefPtr<DOMWindow> source) + : Event(type, false, true) + , m_key(key) + , m_oldValue(oldValue) + , m_newValue(newValue) + , m_uri(uri) + , m_source(source) +{ +} + +void StorageEvent::initStorageEvent(const AtomicString& type, bool canBubble, bool cancelable, const String& key, const String& oldValue, const String& newValue, const String& uri, PassRefPtr<DOMWindow> source) +{ + if (dispatched()) + return; + + initEvent(type, canBubble, cancelable); + + m_key = key; + m_oldValue = oldValue; + m_newValue = newValue; + m_uri = uri; + m_source = source; +} + +} diff --git a/WebCore/storage/StorageEvent.h b/WebCore/storage/StorageEvent.h new file mode 100644 index 0000000..1cd87c6 --- /dev/null +++ b/WebCore/storage/StorageEvent.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef StorageEvent_h +#define StorageEvent_h + +#include "Event.h" + +namespace WebCore { + + class DOMWindow; + + class StorageEvent : public Event { + public: + static PassRefPtr<StorageEvent> create() + { + return adoptRef(new StorageEvent); + } + static PassRefPtr<StorageEvent> create(const AtomicString& type, const String& key, const String& oldValue, const String& newValue, const String& uri, PassRefPtr<DOMWindow> source) + { + return adoptRef(new StorageEvent(type, key, oldValue, newValue, uri, source)); + } + + const String& key() const { return m_key; } + const String& oldValue() const { return m_oldValue; } + const String& newValue() const { return m_newValue; } + const String& uri() const { return m_uri; } + DOMWindow* source() const { return m_source.get(); } + + void initStorageEvent(const AtomicString& type, bool canBubble, bool cancelable, const String& key, const String& oldValue, const String& newValue, const String& uri, PassRefPtr<DOMWindow> source); + + // Needed once we support init<blank>EventNS + // void initStorageEventNS(in DOMString namespaceURI, in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in DOMString oldValueArg, in DOMString newValueArg, in DOMString uriArg, in Window sourceArg); + + virtual bool isStorageEvent() const { return true; } + + private: + StorageEvent() { } + StorageEvent(const AtomicString& type, const String& key, const String& oldValue, const String& newValue, const String& uri, PassRefPtr<DOMWindow> source); + + String m_key; + String m_oldValue; + String m_newValue; + String m_uri; + RefPtr<DOMWindow> m_source; + }; + +} // namespace WebCore + +#endif // StorageEvent_h diff --git a/WebCore/storage/StorageEvent.idl b/WebCore/storage/StorageEvent.idl new file mode 100644 index 0000000..057770c --- /dev/null +++ b/WebCore/storage/StorageEvent.idl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +module storage { + + interface [ + GenerateConstructor + ] StorageEvent : Event { + readonly attribute DOMString key; + readonly attribute [ConvertNullStringTo=Null] DOMString oldValue; + readonly attribute [ConvertNullStringTo=Null] DOMString newValue; + readonly attribute DOMString uri; + readonly attribute DOMWindow source; + void initStorageEvent(in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in DOMString oldValueArg, in DOMString newValueArg, in DOMString uriArg, in DOMWindow sourceArg); + + // Needed once we support init<blank>EventNS + // void initStorageEventNS(in DOMString namespaceURI, in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in DOMString oldValueArg, in DOMString newValueArg, in DOMString uriArg, in Window sourceArg); + }; + +} diff --git a/WebCore/storage/StorageMap.cpp b/WebCore/storage/StorageMap.cpp new file mode 100644 index 0000000..880fc93 --- /dev/null +++ b/WebCore/storage/StorageMap.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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" +#include "StorageMap.h" + +namespace WebCore { + +PassRefPtr<StorageMap> StorageMap::create() +{ + return adoptRef(new StorageMap); +} + +StorageMap::StorageMap() + : m_iterator(m_map.end()) + , m_iteratorIndex(UINT_MAX) +{ +} + +PassRefPtr<StorageMap> StorageMap::copy() +{ + RefPtr<StorageMap> newMap = create(); + newMap->m_map = m_map; + return newMap.release(); +} + +void StorageMap::invalidateIterator() +{ + m_iterator = m_map.end(); + m_iteratorIndex = UINT_MAX; +} + +void StorageMap::setIteratorToIndex(unsigned index) const +{ + // FIXME: Once we have bidirectional iterators for HashMap we can be more intelligent about this. + // The requested index will be closest to begin(), our current iterator, or end(), and we + // can take the shortest route. + // Until that mechanism is available, we'll always increment our iterator from begin() or current. + + if (m_iteratorIndex == index) + return; + + if (index < m_iteratorIndex) { + m_iteratorIndex = 0; + m_iterator = m_map.begin(); + ASSERT(m_iterator != m_map.end()); + } + + while (m_iteratorIndex < index) { + ++m_iteratorIndex; + ++m_iterator; + ASSERT(m_iterator != m_map.end()); + } +} + +unsigned StorageMap::length() const +{ + return m_map.size(); +} + +bool StorageMap::key(unsigned index, String& key) const +{ + if (index >= length()) + return false; + + setIteratorToIndex(index); + + key = m_iterator->first; + return true; +} + +String StorageMap::getItem(const String& key) const +{ + return m_map.get(key); +} + +PassRefPtr<StorageMap> StorageMap::setItem(const String& key, const String& value, String& oldValue) +{ + ASSERT(!value.isNull()); + + // Implement copy-on-write semantics here. We're guaranteed that the only refs of StorageMaps belong to Storage objects + // so if more than one Storage object refs this map, copy it before mutating it. + if (refCount() > 1) { + RefPtr<StorageMap> newStorageMap = copy(); + newStorageMap->setItem(key, value, oldValue); + return newStorageMap.release(); + } + + pair<HashMap<String, String>::iterator, bool> addResult = m_map.add(key, value); + + if (addResult.second) { + // There was no "oldValue" so null it out. + oldValue = String(); + } else { + oldValue = addResult.first->second; + addResult.first->second = value; + } + + invalidateIterator(); + + return 0; +} + +PassRefPtr<StorageMap> StorageMap::removeItem(const String& key, String& oldValue) +{ + // Implement copy-on-write semantics here. We're guaranteed that the only refs of StorageMaps belong to Storage objects + // so if more than one Storage object refs this map, copy it before mutating it. + if (refCount() > 1) { + RefPtr<StorageMap> newStorage = copy(); + newStorage->removeItem(key, oldValue); + return newStorage.release(); + } + + oldValue = m_map.take(key); + if (!oldValue.isNull()) + invalidateIterator(); + + return 0; +} + +bool StorageMap::contains(const String& key) const +{ + return m_map.contains(key); +} + +void StorageMap::importItem(const String& key, const String& value) const +{ + // Be sure to copy the keys/values as items imported on a background thread are destined + // to cross a thread boundary + pair<HashMap<String, String>::iterator, bool> result = m_map.add(key.copy(), String()); + + if (result.second) + result.first->second = value.copy(); +} + +} diff --git a/WebCore/storage/StorageMap.h b/WebCore/storage/StorageMap.h new file mode 100644 index 0000000..d5d00a9 --- /dev/null +++ b/WebCore/storage/StorageMap.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple 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 INC. ``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 INC. OR + * 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. + */ + +#ifndef StorageMap_h +#define StorageMap_h + +#include "PlatformString.h" +#include "StringHash.h" + +#include <wtf/HashMap.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + + class StorageMap : public RefCounted<StorageMap> { + public: + static PassRefPtr<StorageMap> create(); + + unsigned length() const; + bool key(unsigned index, String& key) const; + String getItem(const String&) const; + PassRefPtr<StorageMap> setItem(const String& key, const String& value, String& oldValue); + PassRefPtr<StorageMap> removeItem(const String&, String& oldValue); + + bool contains(const String& key) const; + + void importItem(const String& key, const String& value) const; + + private: + StorageMap(); + PassRefPtr<StorageMap> copy(); + void invalidateIterator(); + void setIteratorToIndex(unsigned) const; + + mutable HashMap<String, String> m_map; + mutable HashMap<String, String>::iterator m_iterator; + mutable unsigned m_iteratorIndex; + }; + +} // namespace WebCore + +#endif // StorageMap_h |