647 lines
19 KiB
C++
647 lines
19 KiB
C++
/*
|
|
* Copyright (C) 2011 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. 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 INC. 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 "StorageTracker.h"
|
|
|
|
#include "StorageThread.h"
|
|
#include "StorageTrackerClient.h"
|
|
#include "WebStorageNamespaceProvider.h"
|
|
#include <WebCore/SQLiteDatabaseTracker.h>
|
|
#include <WebCore/SQLiteStatement.h>
|
|
#include <WebCore/SecurityOrigin.h>
|
|
#include <WebCore/SecurityOriginData.h>
|
|
#include <WebCore/TextEncoding.h>
|
|
#include <wtf/FileSystem.h>
|
|
#include <wtf/MainThread.h>
|
|
#include <wtf/StdLibExtras.h>
|
|
#include <wtf/Vector.h>
|
|
#include <wtf/text/CString.h>
|
|
|
|
#if PLATFORM(IOS_FAMILY)
|
|
#include <pal/spi/ios/SQLite3SPI.h>
|
|
#endif
|
|
|
|
using namespace WebCore;
|
|
|
|
namespace WebKit {
|
|
|
|
static StorageTracker* storageTracker = nullptr;
|
|
|
|
// If there is no document referencing a storage database, close the underlying database
|
|
// after it has been idle for m_StorageDatabaseIdleInterval seconds.
|
|
static const Seconds defaultStorageDatabaseIdleInterval { 300_s };
|
|
|
|
void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
|
|
{
|
|
ASSERT(isMainThread());
|
|
ASSERT(!storageTracker || !storageTracker->m_client);
|
|
|
|
if (!storageTracker)
|
|
storageTracker = new StorageTracker(storagePath);
|
|
|
|
storageTracker->m_client = client;
|
|
storageTracker->m_needsInitialization = true;
|
|
}
|
|
|
|
void StorageTracker::internalInitialize()
|
|
{
|
|
m_needsInitialization = false;
|
|
|
|
ASSERT(isMainThread());
|
|
|
|
// Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
|
|
// FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
|
|
UTF8Encoding();
|
|
|
|
storageTracker->setIsActive(true);
|
|
storageTracker->m_thread->start();
|
|
storageTracker->importOriginIdentifiers();
|
|
|
|
m_thread->dispatch([this] {
|
|
FileSystem::deleteFile(FileSystem::pathByAppendingComponent(m_storageDirectoryPath, "StorageTracker.db"));
|
|
});
|
|
}
|
|
|
|
StorageTracker& StorageTracker::tracker()
|
|
{
|
|
if (!storageTracker)
|
|
storageTracker = new StorageTracker("");
|
|
if (storageTracker->m_needsInitialization)
|
|
storageTracker->internalInitialize();
|
|
|
|
return *storageTracker;
|
|
}
|
|
|
|
StorageTracker::StorageTracker(const String& storagePath)
|
|
: m_storageDirectoryPath(storagePath.isolatedCopy())
|
|
, m_client(0)
|
|
, m_thread(makeUnique<StorageThread>())
|
|
, m_isActive(false)
|
|
, m_needsInitialization(false)
|
|
, m_StorageDatabaseIdleInterval(defaultStorageDatabaseIdleInterval)
|
|
{
|
|
}
|
|
|
|
String StorageTracker::trackerDatabasePath()
|
|
{
|
|
ASSERT(!m_databaseMutex.tryLock());
|
|
return FileSystem::pathByAppendingComponent(m_storageDirectoryPath, "LegacyStorageTracker.db");
|
|
}
|
|
|
|
static bool ensureDatabaseFileExists(const String& fileName, bool createIfDoesNotExist)
|
|
{
|
|
if (createIfDoesNotExist)
|
|
return FileSystem::makeAllDirectories(FileSystem::parentPath(fileName));
|
|
|
|
return FileSystem::fileExists(fileName);
|
|
}
|
|
|
|
void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
|
|
{
|
|
ASSERT(m_isActive);
|
|
ASSERT(!isMainThread());
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
ASSERT(!m_databaseMutex.tryLock());
|
|
|
|
if (m_database.isOpen())
|
|
return;
|
|
|
|
String databasePath = trackerDatabasePath();
|
|
|
|
if (!ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
|
|
if (createIfDoesNotExist)
|
|
LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
|
|
return;
|
|
}
|
|
|
|
if (!m_database.open(databasePath)) {
|
|
LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
|
|
return;
|
|
}
|
|
|
|
m_database.disableThreadingChecks();
|
|
|
|
if (!m_database.tableExists("Origins")) {
|
|
if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"_s))
|
|
LOG_ERROR("Failed to create Origins table.");
|
|
}
|
|
}
|
|
|
|
void StorageTracker::importOriginIdentifiers()
|
|
{
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
ASSERT(isMainThread());
|
|
ASSERT(m_thread);
|
|
|
|
m_thread->dispatch([this] {
|
|
syncImportOriginIdentifiers();
|
|
});
|
|
}
|
|
|
|
void StorageTracker::finishedImportingOriginIdentifiers()
|
|
{
|
|
Locker locker { m_databaseMutex };
|
|
if (m_client)
|
|
m_client->didFinishLoadingOrigins();
|
|
}
|
|
|
|
void StorageTracker::syncImportOriginIdentifiers()
|
|
{
|
|
ASSERT(m_isActive);
|
|
|
|
ASSERT(!isMainThread());
|
|
|
|
{
|
|
Locker locker { m_databaseMutex };
|
|
|
|
// Don't force creation of StorageTracker's db just because a tracker
|
|
// was initialized. It will be created if local storage dbs are found
|
|
// by syncFileSystemAndTrackerDatabse() or the next time a local storage
|
|
// db is created by StorageAreaSync.
|
|
openTrackerDatabase(false);
|
|
|
|
if (m_database.isOpen()) {
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
auto statement = m_database.prepareStatement("SELECT origin FROM Origins"_s);
|
|
if (!statement) {
|
|
LOG_ERROR("Failed to prepare statement.");
|
|
return;
|
|
}
|
|
|
|
int result;
|
|
|
|
{
|
|
Locker lockOrigins { m_originSetMutex };
|
|
while ((result = statement->step()) == SQLITE_ROW)
|
|
m_originSet.add(statement->columnText(0).isolatedCopy());
|
|
}
|
|
|
|
if (result != SQLITE_DONE) {
|
|
LOG_ERROR("Failed to read in all origins from the database.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
syncFileSystemAndTrackerDatabase();
|
|
|
|
{
|
|
Locker locker { m_clientMutex };
|
|
|
|
if (m_client) {
|
|
Locker locker { m_originSetMutex };
|
|
OriginSet::const_iterator end = m_originSet.end();
|
|
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
|
|
m_client->dispatchDidModifyOrigin(*it);
|
|
}
|
|
}
|
|
|
|
callOnMainThread([this] {
|
|
finishedImportingOriginIdentifiers();
|
|
});
|
|
}
|
|
|
|
void StorageTracker::syncFileSystemAndTrackerDatabase()
|
|
{
|
|
ASSERT(!isMainThread());
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
ASSERT(m_isActive);
|
|
|
|
Vector<String> fileNames;
|
|
{
|
|
Locker locker { m_databaseMutex };
|
|
fileNames = FileSystem::listDirectory(m_storageDirectoryPath);
|
|
}
|
|
|
|
// Use a copy of m_originSet to find expired entries and to schedule their
|
|
// deletions from disk and from m_originSet.
|
|
OriginSet originSetCopy;
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
|
|
originSetCopy.add((*it).isolatedCopy());
|
|
}
|
|
|
|
// Add missing StorageTracker records.
|
|
OriginSet foundOrigins;
|
|
String fileExtension = ".localstorage"_s;
|
|
|
|
for (auto& fileName : fileNames) {
|
|
if (fileName.length() <= fileExtension.length() || !fileName.endsWith(fileExtension))
|
|
continue;
|
|
|
|
auto filePath = FileSystem::pathByAppendingComponent(m_storageDirectoryPath, fileName);
|
|
String originIdentifier = fileName.substring(0, fileName.length() - fileExtension.length());
|
|
if (!originSetCopy.contains(originIdentifier))
|
|
syncSetOriginDetails(originIdentifier, filePath);
|
|
|
|
foundOrigins.add(originIdentifier);
|
|
}
|
|
|
|
// Delete stale StorageTracker records.
|
|
for (OriginSet::const_iterator it = originSetCopy.begin(), end = originSetCopy.end(); it != end; ++it) {
|
|
const String& originIdentifier = *it;
|
|
if (foundOrigins.contains(originIdentifier))
|
|
continue;
|
|
|
|
callOnMainThread([this, originIdentifier = originIdentifier.isolatedCopy()] {
|
|
deleteOriginWithIdentifier(originIdentifier);
|
|
});
|
|
}
|
|
}
|
|
|
|
void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
|
|
{
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
|
|
if (m_originSet.contains(originIdentifier))
|
|
return;
|
|
|
|
m_originSet.add(originIdentifier);
|
|
}
|
|
|
|
auto function = [this, originIdentifier = originIdentifier.isolatedCopy(), databaseFile = databaseFile.isolatedCopy()] {
|
|
syncSetOriginDetails(originIdentifier, databaseFile);
|
|
};
|
|
|
|
// FIXME: This weird ping-ponging was done to fix a deadlock. We should figure out a cleaner way to avoid it instead.
|
|
ensureOnMainThread([this, function = WTFMove(function)]() mutable {
|
|
m_thread->dispatch(WTFMove(function));
|
|
});
|
|
}
|
|
|
|
void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
|
|
{
|
|
ASSERT(!isMainThread());
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
Locker locker { m_databaseMutex };
|
|
|
|
openTrackerDatabase(true);
|
|
|
|
if (!m_database.isOpen())
|
|
return;
|
|
|
|
auto statement = m_database.prepareStatement("INSERT INTO Origins VALUES (?, ?)"_s);
|
|
if (!statement) {
|
|
LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
|
|
return;
|
|
}
|
|
|
|
statement->bindText(1, originIdentifier);
|
|
statement->bindText(2, databaseFile);
|
|
|
|
if (statement->step() != SQLITE_DONE)
|
|
LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
|
|
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
if (!m_originSet.contains(originIdentifier))
|
|
m_originSet.add(originIdentifier);
|
|
}
|
|
|
|
{
|
|
Locker locker { m_clientMutex };
|
|
if (m_client)
|
|
m_client->dispatchDidModifyOrigin(originIdentifier);
|
|
}
|
|
}
|
|
|
|
Vector<SecurityOriginData> StorageTracker::origins()
|
|
{
|
|
ASSERT(m_isActive);
|
|
|
|
if (!m_isActive)
|
|
return { };
|
|
|
|
Locker locker { m_originSetMutex };
|
|
|
|
Vector<SecurityOriginData> result;
|
|
result.reserveInitialCapacity(m_originSet.size());
|
|
for (auto& identifier : m_originSet) {
|
|
auto origin = SecurityOriginData::fromDatabaseIdentifier(identifier);
|
|
if (!origin) {
|
|
ASSERT_NOT_REACHED();
|
|
continue;
|
|
}
|
|
result.uncheckedAppend(origin.value());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void StorageTracker::deleteAllOrigins()
|
|
{
|
|
ASSERT(m_isActive);
|
|
ASSERT(isMainThread());
|
|
ASSERT(m_thread);
|
|
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
willDeleteAllOrigins();
|
|
m_originSet.clear();
|
|
}
|
|
|
|
WebStorageNamespaceProvider::clearLocalStorageForAllOrigins();
|
|
|
|
m_thread->dispatch([this] {
|
|
syncDeleteAllOrigins();
|
|
});
|
|
}
|
|
|
|
#if PLATFORM(IOS_FAMILY)
|
|
static void truncateDatabaseFile(SQLiteDatabase& database)
|
|
{
|
|
sqlite3_file_control(database.sqlite3Handle(), 0, SQLITE_TRUNCATE_DATABASE, 0);
|
|
}
|
|
#endif
|
|
|
|
void StorageTracker::syncDeleteAllOrigins()
|
|
{
|
|
ASSERT(!isMainThread());
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
Locker locker { m_databaseMutex };
|
|
|
|
openTrackerDatabase(false);
|
|
if (!m_database.isOpen())
|
|
return;
|
|
|
|
auto statement = m_database.prepareStatement("SELECT origin, path FROM Origins"_s);
|
|
if (!statement) {
|
|
LOG_ERROR("Failed to prepare statement.");
|
|
return;
|
|
}
|
|
|
|
int result;
|
|
while ((result = statement->step()) == SQLITE_ROW) {
|
|
if (!canDeleteOrigin(statement->columnText(0)))
|
|
continue;
|
|
|
|
FileSystem::deleteFile(statement->columnText(1));
|
|
|
|
{
|
|
Locker locker { m_clientMutex };
|
|
if (m_client)
|
|
m_client->dispatchDidModifyOrigin(statement->columnText(0));
|
|
}
|
|
}
|
|
|
|
if (result != SQLITE_DONE)
|
|
LOG_ERROR("Failed to read in all origins from the database.");
|
|
|
|
if (m_database.isOpen()) {
|
|
#if PLATFORM(IOS_FAMILY)
|
|
truncateDatabaseFile(m_database);
|
|
#endif
|
|
m_database.close();
|
|
}
|
|
|
|
#if !PLATFORM(IOS_FAMILY)
|
|
if (!FileSystem::deleteFile(trackerDatabasePath())) {
|
|
// In the case where it is not possible to delete the database file (e.g some other program
|
|
// like a virus scanner is accessing it), make sure to remove all entries.
|
|
openTrackerDatabase(false);
|
|
if (!m_database.isOpen())
|
|
return;
|
|
auto deleteStatement = m_database.prepareStatement("DELETE FROM Origins"_s);
|
|
if (!deleteStatement) {
|
|
LOG_ERROR("Unable to prepare deletion of all origins");
|
|
return;
|
|
}
|
|
if (!deleteStatement->executeCommand()) {
|
|
LOG_ERROR("Unable to execute deletion of all origins");
|
|
return;
|
|
}
|
|
}
|
|
FileSystem::deleteEmptyDirectory(m_storageDirectoryPath);
|
|
#endif
|
|
}
|
|
|
|
void StorageTracker::deleteOriginWithIdentifier(const String& originIdentifier)
|
|
{
|
|
auto origin = SecurityOriginData::fromDatabaseIdentifier(originIdentifier);
|
|
if (!origin) {
|
|
ASSERT_NOT_REACHED();
|
|
return;
|
|
}
|
|
deleteOrigin(origin.value());
|
|
}
|
|
|
|
void StorageTracker::deleteOrigin(const SecurityOriginData& origin)
|
|
{
|
|
ASSERT(m_isActive);
|
|
ASSERT(isMainThread());
|
|
ASSERT(m_thread);
|
|
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
// Before deleting database, we need to clear in-memory local storage data
|
|
// in StorageArea, and to close the StorageArea db. It's possible for an
|
|
// item to be added immediately after closing the db and cause StorageAreaSync
|
|
// to reopen the db before the db is deleted by a StorageTracker thread.
|
|
// In this case, reopening the db in StorageAreaSync will cancel a pending
|
|
// StorageTracker db deletion.
|
|
WebStorageNamespaceProvider::clearLocalStorageForOrigin(origin);
|
|
|
|
String originId = origin.databaseIdentifier();
|
|
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
willDeleteOrigin(originId);
|
|
m_originSet.remove(originId);
|
|
}
|
|
|
|
m_thread->dispatch([this, originId = originId.isolatedCopy()] {
|
|
syncDeleteOrigin(originId);
|
|
});
|
|
}
|
|
|
|
void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
|
|
{
|
|
ASSERT(!isMainThread());
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
Locker locker { m_databaseMutex };
|
|
|
|
if (!canDeleteOrigin(originIdentifier)) {
|
|
LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
|
|
return;
|
|
}
|
|
|
|
openTrackerDatabase(false);
|
|
if (!m_database.isOpen())
|
|
return;
|
|
|
|
String path = databasePathForOrigin(originIdentifier);
|
|
if (path.isEmpty()) {
|
|
// It is possible to get a request from the API to delete the storage for an origin that
|
|
// has no such storage.
|
|
return;
|
|
}
|
|
|
|
{
|
|
auto deleteStatement = m_database.prepareStatement("DELETE FROM Origins where origin=?"_s);
|
|
if (!deleteStatement) {
|
|
LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
|
|
return;
|
|
}
|
|
deleteStatement->bindText(1, originIdentifier);
|
|
if (!deleteStatement->executeCommand()) {
|
|
LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
|
|
return;
|
|
}
|
|
}
|
|
|
|
FileSystem::deleteFile(path);
|
|
|
|
bool shouldDeleteTrackerFiles = false;
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
m_originSet.remove(originIdentifier);
|
|
shouldDeleteTrackerFiles = m_originSet.isEmpty();
|
|
}
|
|
|
|
if (shouldDeleteTrackerFiles) {
|
|
#if PLATFORM(IOS_FAMILY)
|
|
truncateDatabaseFile(m_database);
|
|
#endif
|
|
m_database.close();
|
|
#if !PLATFORM(IOS_FAMILY)
|
|
FileSystem::deleteFile(trackerDatabasePath());
|
|
FileSystem::deleteEmptyDirectory(m_storageDirectoryPath);
|
|
#endif
|
|
}
|
|
|
|
{
|
|
Locker locker { m_clientMutex };
|
|
if (m_client)
|
|
m_client->dispatchDidModifyOrigin(originIdentifier);
|
|
}
|
|
}
|
|
|
|
void StorageTracker::willDeleteAllOrigins()
|
|
{
|
|
ASSERT(!m_originSetMutex.tryLock());
|
|
|
|
OriginSet::const_iterator end = m_originSet.end();
|
|
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
|
|
m_originsBeingDeleted.add((*it).isolatedCopy());
|
|
}
|
|
|
|
void StorageTracker::willDeleteOrigin(const String& originIdentifier)
|
|
{
|
|
ASSERT(isMainThread());
|
|
ASSERT(!m_originSetMutex.tryLock());
|
|
|
|
m_originsBeingDeleted.add(originIdentifier);
|
|
}
|
|
|
|
bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
|
|
{
|
|
ASSERT(!m_databaseMutex.tryLock());
|
|
Locker locker { m_originSetMutex };
|
|
return m_originsBeingDeleted.contains(originIdentifier);
|
|
}
|
|
|
|
void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
|
|
{
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
Locker locker { m_databaseMutex };
|
|
{
|
|
Locker locker { m_originSetMutex };
|
|
if (!m_originsBeingDeleted.isEmpty())
|
|
m_originsBeingDeleted.remove(originIdentifier);
|
|
}
|
|
}
|
|
|
|
bool StorageTracker::isActive()
|
|
{
|
|
return m_isActive;
|
|
}
|
|
|
|
void StorageTracker::setIsActive(bool flag)
|
|
{
|
|
m_isActive = flag;
|
|
}
|
|
|
|
String StorageTracker::databasePathForOrigin(const String& originIdentifier)
|
|
{
|
|
ASSERT(!m_databaseMutex.tryLock());
|
|
ASSERT(m_isActive);
|
|
|
|
if (!m_database.isOpen())
|
|
return String();
|
|
|
|
SQLiteTransactionInProgressAutoCounter transactionCounter;
|
|
|
|
auto pathStatement = m_database.prepareStatement("SELECT path FROM Origins WHERE origin=?"_s);
|
|
if (!pathStatement) {
|
|
LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
|
|
return String();
|
|
}
|
|
pathStatement->bindText(1, originIdentifier);
|
|
int result = pathStatement->step();
|
|
if (result != SQLITE_ROW)
|
|
return String();
|
|
|
|
return pathStatement->columnText(0);
|
|
}
|
|
|
|
uint64_t StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
|
|
{
|
|
if (!m_isActive)
|
|
return 0;
|
|
|
|
Locker locker { m_databaseMutex };
|
|
|
|
String path = databasePathForOrigin(origin->data().databaseIdentifier());
|
|
if (path.isEmpty())
|
|
return 0;
|
|
|
|
return FileSystem::fileSize(path).value_or(0);
|
|
}
|
|
|
|
} // namespace WebCore
|