blob: 5ccb094207ed07f1111098302fad3bfdfe7dff5f [file] [log] [blame]
/*
* Copyright (C) 2012 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 "DatabaseManager.h"
#include "Database.h"
#include "DatabaseCallback.h"
#include "DatabaseContext.h"
#include "DatabaseTask.h"
#include "DatabaseTracker.h"
#include "Document.h"
#include "InspectorInstrumentation.h"
#include "Logging.h"
#include "PlatformStrategies.h"
#include "ScriptController.h"
#include "SecurityOrigin.h"
#include "SecurityOriginData.h"
#include "WindowEventLoop.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
class DatabaseManager::ProposedDatabase {
public:
ProposedDatabase(DatabaseManager&, SecurityOrigin&, const String& name, const String& displayName, unsigned long estimatedSize);
~ProposedDatabase();
SecurityOrigin& origin() { return m_origin; }
DatabaseDetails& details() { return m_details; }
private:
DatabaseManager& m_manager;
Ref<SecurityOrigin> m_origin;
DatabaseDetails m_details;
};
DatabaseManager::ProposedDatabase::ProposedDatabase(DatabaseManager& manager, SecurityOrigin& origin, const String& name, const String& displayName, unsigned long estimatedSize)
: m_manager(manager)
, m_origin(origin.isolatedCopy())
, m_details(name.isolatedCopy(), displayName.isolatedCopy(), estimatedSize, 0, std::nullopt, std::nullopt)
{
m_manager.addProposedDatabase(*this);
}
inline DatabaseManager::ProposedDatabase::~ProposedDatabase()
{
m_manager.removeProposedDatabase(*this);
}
DatabaseManager& DatabaseManager::singleton()
{
static NeverDestroyed<DatabaseManager> instance;
return instance;
}
void DatabaseManager::initialize(const String& databasePath)
{
platformInitialize(databasePath);
DatabaseTracker::initializeTracker(databasePath);
}
void DatabaseManager::setClient(DatabaseManagerClient* client)
{
m_client = client;
DatabaseTracker::singleton().setClient(client);
}
bool DatabaseManager::isAvailable()
{
return m_databaseIsAvailable;
}
void DatabaseManager::setIsAvailable(bool available)
{
m_databaseIsAvailable = available;
}
Ref<DatabaseContext> DatabaseManager::databaseContext(Document& document)
{
if (auto databaseContext = document.databaseContext())
return *databaseContext;
auto context = adoptRef(*new DatabaseContext(document));
context->suspendIfNeeded();
return context;
}
#if LOG_DISABLED
static inline void logOpenDatabaseError(Document&, const String&)
{
}
#else
static void logOpenDatabaseError(Document& document, const String& name)
{
LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.utf8().data(), document.securityOrigin().toString().utf8().data());
}
#endif
ExceptionOr<Ref<Database>> DatabaseManager::openDatabaseBackend(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase)
{
auto backend = tryToOpenDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, FirstTryToOpenDatabase);
if (backend.hasException()) {
if (backend.exception().code() == QuotaExceededError) {
// Notify the client that we've exceeded the database quota.
// The client may want to increase the quota, and we'll give it
// one more try after if that is the case.
{
ProposedDatabase proposedDatabase { *this, document.securityOrigin(), name, displayName, estimatedSize };
this->databaseContext(document)->databaseExceededQuota(name, proposedDatabase.details());
}
backend = tryToOpenDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, RetryOpenDatabase);
}
}
if (backend.hasException()) {
if (backend.exception().code() == InvalidStateError)
logErrorMessage(document, backend.exception().message());
else
logOpenDatabaseError(document, name);
}
return backend;
}
ExceptionOr<Ref<Database>> DatabaseManager::tryToOpenDatabaseBackend(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase,
OpenAttempt attempt)
{
auto* page = document.page();
if (!page || page->usesEphemeralSession())
return Exception { SecurityError };
auto backendContext = this->databaseContext(document);
ExceptionOr<void> preflightResult;
switch (attempt) {
case FirstTryToOpenDatabase:
preflightResult = DatabaseTracker::singleton().canEstablishDatabase(backendContext, name, estimatedSize);
break;
case RetryOpenDatabase:
preflightResult = DatabaseTracker::singleton().retryCanEstablishDatabase(backendContext, name, estimatedSize);
break;
}
if (preflightResult.hasException())
return preflightResult.releaseException();
auto database = adoptRef(*new Database(backendContext, name, expectedVersion, displayName, estimatedSize));
auto openResult = database->openAndVerifyVersion(setVersionInNewDatabase);
if (openResult.hasException())
return openResult.releaseException();
// FIXME: What guarantees backendContext.securityOrigin() is non-null?
DatabaseTracker::singleton().setDatabaseDetails(backendContext->securityOrigin(), name, displayName, estimatedSize);
return database;
}
void DatabaseManager::addProposedDatabase(ProposedDatabase& database)
{
Locker locker { m_proposedDatabasesLock };
m_proposedDatabases.add(&database);
}
void DatabaseManager::removeProposedDatabase(ProposedDatabase& database)
{
Locker locker { m_proposedDatabasesLock };
m_proposedDatabases.remove(&database);
}
ExceptionOr<Ref<Database>> DatabaseManager::openDatabase(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, RefPtr<DatabaseCallback>&& creationCallback)
{
ASSERT(isMainThread());
// FIXME: Remove this call to ScriptController::initializeMainThread(). The
// main thread should have been initialized by a WebKit entrypoint already.
// Also, initializeMainThread() does nothing on iOS.
ScriptController::initializeMainThread();
bool setVersionInNewDatabase = !creationCallback;
auto openResult = openDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase);
if (openResult.hasException())
return openResult.releaseException();
RefPtr<Database> database = openResult.releaseReturnValue();
auto databaseContext = this->databaseContext(document);
databaseContext->setHasOpenDatabases();
InspectorInstrumentation::didOpenDatabase(*database);
if (database->isNew() && creationCallback.get()) {
LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get());
database->setHasPendingCreationEvent(true);
database->m_document->eventLoop().queueTask(TaskSource::Networking, [creationCallback, database]() {
creationCallback->handleEvent(*database);
database->setHasPendingCreationEvent(false);
});
}
return database.releaseNonNull();
}
bool DatabaseManager::hasOpenDatabases(Document& document)
{
auto databaseContext = document.databaseContext();
return databaseContext && databaseContext->hasOpenDatabases();
}
void DatabaseManager::stopDatabases(Document& document, DatabaseTaskSynchronizer* synchronizer)
{
auto databaseContext = document.databaseContext();
if (!databaseContext || !databaseContext->stopDatabases(synchronizer)) {
if (synchronizer)
synchronizer->taskCompleted();
}
}
String DatabaseManager::fullPathForDatabase(SecurityOrigin& origin, const String& name, bool createIfDoesNotExist)
{
{
Locker locker { m_proposedDatabasesLock };
for (auto* proposedDatabase : m_proposedDatabases) {
if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin))
return String();
}
}
return DatabaseTracker::singleton().fullPathForDatabase(origin.data(), name, createIfDoesNotExist);
}
DatabaseDetails DatabaseManager::detailsForNameAndOrigin(const String& name, SecurityOrigin& origin)
{
{
Locker locker { m_proposedDatabasesLock };
for (auto* proposedDatabase : m_proposedDatabases) {
if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) {
ASSERT(&proposedDatabase->details().thread() == &Thread::current() || isMainThread());
return proposedDatabase->details();
}
}
}
return DatabaseTracker::singleton().detailsForNameAndOrigin(name, origin.data());
}
void DatabaseManager::logErrorMessage(Document& document, const String& message)
{
document.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message);
}
#if !PLATFORM(COCOA)
void DatabaseManager::platformInitialize(const String& databasePath)
{
UNUSED_PARAM(databasePath);
}
#endif
} // namespace WebCore