gecko/dom/indexedDB/IndexedDatabaseManager.cpp

1286 lines
40 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Indexed Database.
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ben Turner <bent.mozilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "IndexedDatabaseManager.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
2011-08-26 01:21:35 -07:00
#include "nsISHEntry.h"
#include "nsISimpleEnumerator.h"
#include "nsITimer.h"
2011-08-26 01:21:35 -07:00
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
2011-08-26 01:21:35 -07:00
#include "mozilla/storage.h"
#include "nsContentUtils.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXPCOMPrivate.h"
#include "AsyncConnectionHelper.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "LazyIdleThread.h"
#include "TransactionThreadPool.h"
// The amount of time, in milliseconds, that our IO thread will stay alive
// after the last event it processes.
#define DEFAULT_THREAD_TIMEOUT_MS 30000
// The amount of time, in milliseconds, that we will wait for active database
// transactions on shutdown before aborting them.
#define DEFAULT_SHUTDOWN_TIMER_MS 30000
2011-08-26 01:21:35 -07:00
// Amount of space that IndexedDB databases may use by default in megabytes.
#define DEFAULT_QUOTA_MB 50
// Preference that users can set to override DEFAULT_QUOTA_MB
#define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
// A bad TLS index number.
#define BAD_TLS_INDEX (PRUintn)-1
USING_INDEXEDDB_NAMESPACE
using namespace mozilla::services;
2011-08-26 01:21:35 -07:00
using mozilla::Preferences;
namespace {
PRInt32 gShutdown = 0;
// Does not hold a reference.
IndexedDatabaseManager* gInstance = nsnull;
2011-08-26 01:21:35 -07:00
PRUintn gCurrentDatabaseIndex = BAD_TLS_INDEX;
PRInt32 gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
class QuotaCallback : public mozIStorageQuotaCallback
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD
QuotaExceeded(const nsACString& aFilename,
PRInt64 aCurrentSizeLimit,
PRInt64 aCurrentTotalSize,
nsISupports* aUserData,
PRInt64* _retval)
{
NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
"This should be impossible!");
IDBDatabase* database =
static_cast<IDBDatabase*>(PR_GetThreadPrivate(gCurrentDatabaseIndex));
if (database && database->IsQuotaDisabled()) {
*_retval = 0;
return NS_OK;
}
return NS_ERROR_FAILURE;
}
};
NS_IMPL_THREADSAFE_ISUPPORTS1(QuotaCallback, mozIStorageQuotaCallback)
// Adds all databases in the hash to the given array.
PLDHashOperator
EnumerateToTArray(const nsACString& aKey,
nsTArray<IDBDatabase*>* aValue,
void* aUserArg)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
NS_ASSERTION(aValue, "Null pointer!");
NS_ASSERTION(aUserArg, "Null pointer!");
nsTArray<IDBDatabase*>* array =
static_cast<nsTArray<IDBDatabase*>*>(aUserArg);
if (!array->AppendElements(*aValue)) {
NS_WARNING("Out of memory!");
return PL_DHASH_STOP;
}
return PL_DHASH_NEXT;
}
// Responsible for calling IDBDatabase.setVersion after a pending version change
// transaction has completed.
class DelayedSetVersion : public nsRunnable
{
public:
DelayedSetVersion(IDBDatabase* aDatabase,
IDBOpenDBRequest* aRequest,
PRInt64 aOldVersion,
PRInt64 aNewVersion,
AsyncConnectionHelper* aHelper)
: mDatabase(aDatabase),
mRequest(aRequest),
mOldVersion(aOldVersion),
mNewVersion(aNewVersion),
mHelper(aHelper)
{
NS_ASSERTION(aDatabase, "Null database!");
NS_ASSERTION(aRequest, "Null request!");
NS_ASSERTION(aHelper, "Null helper!");
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
nsresult rv = mgr->SetDatabaseVersion(mDatabase, mRequest,
mOldVersion, mNewVersion,
mHelper);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
private:
nsRefPtr<IDBDatabase> mDatabase;
nsRefPtr<IDBOpenDBRequest> mRequest;
PRInt64 mOldVersion;
PRInt64 mNewVersion;
nsRefPtr<AsyncConnectionHelper> mHelper;
};
// Responsible for firing "versionchange" events at all live and non-closed
// databases, and for firing a "blocked" event at the requesting database if any
// databases fail to close.
class VersionChangeEventsRunnable : public nsRunnable
{
public:
VersionChangeEventsRunnable(
IDBDatabase* aRequestingDatabase,
IDBOpenDBRequest* aRequest,
nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
PRInt64 aOldVersion,
PRInt64 aNewVersion)
: mRequestingDatabase(aRequestingDatabase),
mRequest(aRequest),
mOldVersion(aOldVersion),
mNewVersion(aNewVersion)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRequestingDatabase, "Null pointer!");
NS_ASSERTION(aRequest, "Null pointer!");
if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
NS_ERROR("This should never fail!");
}
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Fire version change events at all of the databases that are not already
// closed. Also kick bfcached documents out of bfcache.
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
if (database->IsClosed()) {
continue;
}
// First check if the document the IDBDatabase is part of is bfcached
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
nsIBFCacheEntry* bfCacheEntry;
if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
bfCacheEntry->RemoveFromBFCacheSync();
NS_ASSERTION(database->IsClosed(),
"Kicking doc out of bfcache should have closed database");
continue;
2010-11-23 21:16:23 -08:00
}
// Otherwise fire a versionchange event.
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
database->DispatchEvent(event, &dummy);
}
// Now check to see if any didn't close. If there are some running still
// then fire the blocked event.
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
if (!mWaitingDatabases[index]->IsClosed()) {
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
mRequest->DispatchEvent(event, &dummy);
break;
}
}
return NS_OK;
}
private:
nsRefPtr<IDBDatabase> mRequestingDatabase;
nsRefPtr<IDBOpenDBRequest> mRequest;
nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
PRInt64 mOldVersion;
PRInt64 mNewVersion;
};
} // anonymous namespace
IndexedDatabaseManager::IndexedDatabaseManager()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!gInstance, "More than one instance!");
}
IndexedDatabaseManager::~IndexedDatabaseManager()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(gInstance == this, "Different instances!");
gInstance = nsnull;
}
// static
already_AddRefed<IndexedDatabaseManager>
IndexedDatabaseManager::GetOrCreate()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (IsShuttingDown()) {
NS_ERROR("Calling GetOrCreateInstance() after shutdown!");
return nsnull;
}
nsRefPtr<IndexedDatabaseManager> instance(gInstance);
if (!instance) {
2011-08-26 01:21:35 -07:00
// We need a thread-local to hold our current database.
if (gCurrentDatabaseIndex == BAD_TLS_INDEX) {
if (PR_NewThreadPrivateIndex(&gCurrentDatabaseIndex, nsnull) !=
PR_SUCCESS) {
NS_ERROR("PR_NewThreadPrivateIndex failed!");
gCurrentDatabaseIndex = BAD_TLS_INDEX;
return nsnull;
}
if (NS_FAILED(Preferences::AddIntVarCache(&gIndexedDBQuotaMB,
PREF_INDEXEDDB_QUOTA,
DEFAULT_QUOTA_MB))) {
NS_WARNING("Unable to respond to quota pref changes!");
gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
}
}
instance = new IndexedDatabaseManager();
if (!instance->mLiveDatabases.Init()) {
NS_WARNING("Out of memory!");
return nsnull;
}
// Make a timer here to avoid potential failures later. We don't actually
// initialize the timer until shutdown.
instance->mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
NS_ENSURE_TRUE(instance->mShutdownTimer, nsnull);
nsCOMPtr<nsIObserverService> obs = GetObserverService();
NS_ENSURE_TRUE(obs, nsnull);
// We need this callback to know when to shut down all our threads.
nsresult rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
false);
NS_ENSURE_SUCCESS(rv, nsnull);
// We don't really need this callback but we want the observer service to
// hold us alive until XPCOM shutdown. That way other consumers can continue
// to use this service until shutdown.
rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
false);
NS_ENSURE_SUCCESS(rv, nsnull);
// Make a lazy thread for any IO we need (like clearing or enumerating the
// contents of indexedDB database directories).
instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
LazyIdleThread::ManualShutdown);
2011-08-26 01:21:35 -07:00
// We need one quota callback object to hand to SQLite.
instance->mQuotaCallbackSingleton = new QuotaCallback();
// The observer service will hold our last reference, don't AddRef here.
gInstance = instance;
}
return instance.forget();
}
// static
IndexedDatabaseManager*
IndexedDatabaseManager::Get()
{
// Does not return an owning reference.
return gInstance;
}
// static
IndexedDatabaseManager*
IndexedDatabaseManager::FactoryCreate()
{
// Returns a raw pointer that carries an owning reference! Lame, but the
// singleton factory macros force this.
return GetOrCreate().get();
}
bool
IndexedDatabaseManager::RegisterDatabase(IDBDatabase* aDatabase)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Don't allow any new databases to be created after shutdown.
if (IsShuttingDown()) {
return false;
}
// Add this database to its origin array if it exists, create it otherwise.
nsTArray<IDBDatabase*>* array;
if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
nsAutoPtr<nsTArray<IDBDatabase*> > newArray(new nsTArray<IDBDatabase*>());
if (!mLiveDatabases.Put(aDatabase->Origin(), newArray)) {
NS_WARNING("Out of memory?");
return false;
}
array = newArray.forget();
}
if (!array->AppendElement(aDatabase)) {
NS_WARNING("Out of memory?");
return false;
}
aDatabase->mRegistered = true;
return true;
}
void
IndexedDatabaseManager::UnregisterDatabase(IDBDatabase* aDatabase)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Remove this database from its origin array, maybe remove the array if it
// is then empty.
nsTArray<IDBDatabase*>* array;
if (mLiveDatabases.Get(aDatabase->Origin(), &array) &&
array->RemoveElement(aDatabase)) {
if (array->IsEmpty()) {
mLiveDatabases.Remove(aDatabase->Origin());
}
return;
}
NS_ERROR("Didn't know anything about this database!");
}
void
IndexedDatabaseManager::OnOriginClearComplete(OriginClearRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(!aRunnable->mThread, "Thread should be null!");
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
"Delayed runnables should have been dispatched already!");
if (!mOriginClearRunnables.RemoveElement(aRunnable)) {
NS_ERROR("Don't know anything about this runnable!");
}
}
void
IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(!aRunnable->mURI, "Should have been cleared!");
NS_ASSERTION(!aRunnable->mCallback, "Should have been cleared!");
if (!mUsageRunnables.RemoveElement(aRunnable)) {
NS_ERROR("Don't know anything about this runnable!");
}
}
void
IndexedDatabaseManager::OnSetVersionRunnableComplete(
SetVersionRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
"Delayed runnables should have been dispatched already!");
// Remove this runnable from the list. This will allow other databases to
// begin to request version changes.
if (!mSetVersionRunnables.RemoveElement(aRunnable)) {
NS_ERROR("Don't know anything about this runnable!");
}
}
nsresult
IndexedDatabaseManager::WaitForOpenAllowed(const nsAString& aName,
const nsACString& aOrigin,
nsIRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
NS_ASSERTION(aRunnable, "Null pointer!");
// See if we're currently clearing database files for this origin. If so then
// queue the runnable for later dispatch after we're done clearing.
PRUint32 count = mOriginClearRunnables.Length();
for (PRUint32 index = 0; index < count; index++) {
nsRefPtr<OriginClearRunnable>& data = mOriginClearRunnables[index];
if (data->mOrigin == aOrigin) {
nsCOMPtr<nsIRunnable>* newPtr =
data->mDelayedRunnables.AppendElement(aRunnable);
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
}
// Check to see if we're currently doing a SetVersion transaction for this
// database. If so then we delay this runnable for later.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Name() == aName &&
runnable->mRequestingDatabase->Origin() == aOrigin) {
nsCOMPtr<nsIRunnable>* newPtr =
runnable->mDelayedRunnables.AppendElement(aRunnable);
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
}
// We aren't currently clearing databases for this origin and we're not
// running a SetVersion transaction for this database so dispatch the runnable
// immediately.
return NS_DispatchToCurrentThread(aRunnable);
}
// static
bool
IndexedDatabaseManager::IsShuttingDown()
{
return !!gShutdown;
}
nsresult
IndexedDatabaseManager::SetDatabaseVersion(IDBDatabase* aDatabase,
IDBOpenDBRequest* aRequest,
PRInt64 aOldVersion,
PRInt64 aNewVersion,
AsyncConnectionHelper* aHelper)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
NS_ASSERTION(aHelper, "Null pointer!");
nsresult rv;
// See if another database has already asked to change the version.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
if (runnable->mRequestingDatabase == aDatabase) {
// Same database, just queue this call to run after the current
// SetVersion transaction completes.
nsRefPtr<DelayedSetVersion> delayed =
new DelayedSetVersion(aDatabase, aRequest, aOldVersion, aNewVersion,
aHelper);
if (!runnable->mDelayedRunnables.AppendElement(delayed)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
// Different database, we can't let this one succeed.
aHelper->SetError(NS_ERROR_DOM_INDEXEDDB_DEADLOCK_ERR);
rv = NS_DispatchToCurrentThread(aHelper);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
}
// Grab all live databases for the same origin.
nsTArray<IDBDatabase*>* array;
if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
NS_ERROR("Must have some alive if we've got a live argument!");
}
// Hold on to all database objects that represent the same database file
// (except the one that is requesting this version change).
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
for (PRUint32 index = 0; index < array->Length(); index++) {
IDBDatabase*& database = array->ElementAt(index);
if (database != aDatabase &&
database->Id() == aDatabase->Id() &&
!database->IsClosed() &&
!liveDatabases.AppendElement(database)) {
NS_WARNING("Out of memory?");
return NS_ERROR_OUT_OF_MEMORY;
}
}
// Adding an element to this array here will keep other databases from
// requesting a version change.
nsRefPtr<SetVersionRunnable> runnable =
new SetVersionRunnable(aDatabase, liveDatabases);
if (!mSetVersionRunnables.AppendElement(runnable)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
// When all databases are closed we want to dispatch the SetVersion
// transaction to the transaction pool.
runnable->mHelper = aHelper;
if (runnable->mDatabases.IsEmpty()) {
// There are no other databases that need to be closed. Go ahead and run
// the transaction now.
RunSetVersionTransaction(aDatabase);
}
else {
// Otherwise we need to wait for all the other databases to complete.
// Schedule a version change events runnable .
nsTArray<nsRefPtr<IDBDatabase> > waitingDatabases;
if (!waitingDatabases.AppendElements(runnable->mDatabases)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
new VersionChangeEventsRunnable(aDatabase, aRequest, waitingDatabases,
aOldVersion, aNewVersion);
rv = NS_DispatchToCurrentThread(eventsRunnable);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
void
IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aWindow, "Null pointer!");
nsAutoTArray<IDBDatabase*, 50> liveDatabases;
mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
TransactionThreadPool* pool = TransactionThreadPool::Get();
for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
IDBDatabase*& database = liveDatabases[index];
if (database->Owner() == aWindow) {
if (NS_FAILED(database->Close())) {
NS_WARNING("Failed to close database for dying window!");
}
if (pool) {
pool->AbortTransactionsForDatabase(database);
}
}
}
}
bool
IndexedDatabaseManager::HasOpenTransactions(nsPIDOMWindow* aWindow)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aWindow, "Null pointer!");
nsAutoTArray<IDBDatabase*, 50> liveDatabases;
mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
TransactionThreadPool* pool = TransactionThreadPool::Get();
if (!pool) {
return false;
}
for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
IDBDatabase*& database = liveDatabases[index];
if (database->Owner() == aWindow &&
pool->HasTransactionsForDatabase(database)) {
return true;
}
}
return false;
}
void
IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Check through the list of SetVersionRunnables we have amassed to see if
// this database is part of a SetVersion callback.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
// This is the SetVersionRunnable for the given database file. Remove the
// database from the list of databases that need to be closed. Since we
// use this hook for SetVersion requests that don't actually need to wait
// for other databases the mDatabases array may be empty.
if (!runnable->mDatabases.IsEmpty() &&
!runnable->mDatabases.RemoveElement(aDatabase)) {
NS_ERROR("Didn't have this database in our list!");
}
// Now run the helper if there are no more live databases.
if (runnable->mHelper && runnable->mDatabases.IsEmpty()) {
// Don't hold the callback alive longer than necessary.
nsRefPtr<AsyncConnectionHelper> helper;
helper.swap(runnable->mHelper);
if (NS_FAILED(helper->DispatchToTransactionPool())) {
NS_WARNING("Failed to dispatch to thread pool!");
}
// Now wait for the transaction to complete. Completing the transaction
// will be our cue to remove the SetVersionRunnable from our list and
// therefore allow other SetVersion requests to begin.
TransactionThreadPool* pool = TransactionThreadPool::Get();
NS_ASSERTION(pool, "This should never be null!");
// All other databases should be closed, so we only need to wait on this
// one.
nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
if (!array.AppendElement(aDatabase)) {
NS_ERROR("This should never fail!");
}
// Use the SetVersionRunnable as the callback.
if (!pool->WaitForAllDatabasesToComplete(array, runnable)) {
NS_WARNING("Failed to wait for transaction to complete!");
}
}
break;
}
}
}
2011-08-26 01:21:35 -07:00
// static
bool
IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
{
NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
"This should have been set already!");
#ifdef DEBUG
if (aDatabase) {
NS_ASSERTION(!PR_GetThreadPrivate(gCurrentDatabaseIndex),
"Someone forgot to unset gCurrentDatabaseIndex!");
}
else {
NS_ASSERTION(PR_GetThreadPrivate(gCurrentDatabaseIndex),
"Someone forgot to set gCurrentDatabaseIndex!");
}
#endif
if (PR_SetThreadPrivate(gCurrentDatabaseIndex, aDatabase) != PR_SUCCESS) {
NS_WARNING("Failed to set gCurrentDatabaseIndex!");
return false;
}
return true;
}
// static
PRUint32
IndexedDatabaseManager::GetIndexedDBQuotaMB()
{
return PRUint32(NS_MAX(gIndexedDBQuotaMB, 0));
}
nsresult
IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory)
{
#ifdef DEBUG
{
bool correctThread;
2011-08-26 01:21:35 -07:00
NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) &&
correctThread,
"Running on the wrong thread!");
}
#endif
NS_ASSERTION(aDirectory, "Null pointer!");
nsCString path;
nsresult rv = aDirectory->GetNativePath(path);
NS_ENSURE_SUCCESS(rv, rv);
if (mTrackedQuotaPaths.Contains(path)) {
return true;
}
// First figure out the filename pattern we'll use.
nsCOMPtr<nsIFile> patternFile;
rv = aDirectory->Clone(getter_AddRefs(patternFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = patternFile->Append(NS_LITERAL_STRING("*"));
NS_ENSURE_SUCCESS(rv, rv);
nsCString pattern;
rv = patternFile->GetNativePath(pattern);
NS_ENSURE_SUCCESS(rv, rv);
// Now tell SQLite to start tracking this pattern.
nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
rv = ss->SetQuotaForFilenamePattern(pattern,
GetIndexedDBQuotaMB() * 1024 * 1024,
mQuotaCallbackSingleton, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
// If the directory exists then we need to see if there are any files in it
// already. We need to tell SQLite about all of them.
bool exists;
2011-08-26 01:21:35 -07:00
rv = aDirectory->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
// Make sure this really is a directory.
bool isDirectory;
2011-08-26 01:21:35 -07:00
rv = aDirectory->IsDirectory(&isDirectory);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsISimpleEnumerator> entries;
rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore;
2011-08-26 01:21:35 -07:00
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
NS_ENSURE_TRUE(file, NS_NOINTERFACE);
rv = ss->UpdateQutoaInformationForFile(file);
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ASSERTION(!mTrackedQuotaPaths.Contains(path), "What?!");
mTrackedQuotaPaths.AppendElement(path);
return rv;
}
NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
nsIObserver)
NS_IMETHODIMP
IndexedDatabaseManager::GetUsageForURI(
nsIURI* aURI,
nsIIndexedDatabaseUsageCallback* aCallback)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_ARG_POINTER(aURI);
NS_ENSURE_ARG_POINTER(aCallback);
// Figure out which origin we're dealing with.
nsCString origin;
nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<AsyncUsageRunnable> runnable =
new AsyncUsageRunnable(aURI, origin, aCallback);
nsRefPtr<AsyncUsageRunnable>* newRunnable =
mUsageRunnables.AppendElement(runnable);
NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
// Non-standard URIs can't create databases anyway so fire the callback
// immediately.
if (origin.EqualsLiteral("null")) {
rv = NS_DispatchToCurrentThread(runnable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// See if we're currently clearing the databases for this origin. If so then
// we pretend that we've already deleted everything.
for (PRUint32 index = 0; index < mOriginClearRunnables.Length(); index++) {
if (mOriginClearRunnables[index]->mOrigin == origin) {
rv = NS_DispatchToCurrentThread(runnable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
}
// Otherwise dispatch to the IO thread to actually compute the usage.
rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
IndexedDatabaseManager::CancelGetUsageForURI(
nsIURI* aURI,
nsIIndexedDatabaseUsageCallback* aCallback)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_ARG_POINTER(aURI);
NS_ENSURE_ARG_POINTER(aCallback);
// See if one of our pending callbacks matches both the URI and the callback
// given. Cancel an remove it if so.
for (PRUint32 index = 0; index < mUsageRunnables.Length(); index++) {
nsRefPtr<AsyncUsageRunnable>& runnable = mUsageRunnables[index];
bool equals;
nsresult rv = runnable->mURI->Equals(aURI, &equals);
NS_ENSURE_SUCCESS(rv, rv);
if (equals && SameCOMIdentity(aCallback, runnable->mCallback)) {
runnable->Cancel();
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_ARG_POINTER(aURI);
// Figure out which origin we're dealing with.
nsCString origin;
nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
NS_ENSURE_SUCCESS(rv, rv);
// Non-standard URIs can't create databases anyway, so return immediately.
if (origin.EqualsLiteral("null")) {
return NS_OK;
}
// If we're already clearing out files for this origin then return
// immediately.
PRUint32 clearDataCount = mOriginClearRunnables.Length();
for (PRUint32 index = 0; index < clearDataCount; index++) {
if (mOriginClearRunnables[index]->mOrigin == origin) {
return NS_OK;
}
}
// We need to grab references to any live databases here to prevent them from
// dying while we invalidate them.
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
// Grab all live databases for this origin.
nsTArray<IDBDatabase*>* array;
if (mLiveDatabases.Get(origin, &array)) {
if (!liveDatabases.AppendElements(*array)) {
NS_WARNING("Out of memory?");
return NS_ERROR_OUT_OF_MEMORY;
}
}
nsRefPtr<OriginClearRunnable> runnable =
new OriginClearRunnable(origin, mIOThread);
// Make a new entry for this origin in mOriginClearRunnables.
nsRefPtr<OriginClearRunnable>* newRunnable =
mOriginClearRunnables.AppendElement(runnable);
NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
if (liveDatabases.IsEmpty()) {
rv = runnable->Run();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// Invalidate all the live databases first.
for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
liveDatabases[index]->Invalidate();
}
// Now set up our callback so that we know when they have finished.
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
if (!pool->WaitForAllDatabasesToComplete(liveDatabases, runnable)) {
NS_WARNING("Can't wait on databases!");
return NS_ERROR_FAILURE;
}
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
return NS_OK;
}
NS_IMETHODIMP
IndexedDatabaseManager::Observe(nsISupports* aSubject,
const char* aTopic,
const PRUnichar* aData)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
// Setting this flag prevents the service from being recreated and prevents
// further databases from being created.
if (PR_ATOMIC_SET(&gShutdown, 1)) {
NS_ERROR("Shutdown more than once?!");
}
// Make sure to join with our IO thread.
if (NS_FAILED(mIOThread->Shutdown())) {
NS_WARNING("Failed to shutdown IO thread!");
}
// Kick off the shutdown timer.
if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
nsITimer::TYPE_ONE_SHOT))) {
NS_WARNING("Failed to initialize shutdown timer!");
}
// This will spin the event loop while we wait on all the database threads
// to close. Our timer may fire during that loop.
TransactionThreadPool::Shutdown();
// Cancel the timer regardless of whether it actually fired.
if (NS_FAILED(mShutdownTimer->Cancel())) {
NS_WARNING("Failed to cancel shutdown timer!");
}
return NS_OK;
}
if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
NS_WARNING("Some database operations are taking longer than expected "
"during shutdown and will be aborted!");
// Grab all live databases, for all origins.
nsAutoTArray<IDBDatabase*, 50> liveDatabases;
mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
// Invalidate them all.
if (!liveDatabases.IsEmpty()) {
PRUint32 count = liveDatabases.Length();
for (PRUint32 index = 0; index < count; index++) {
liveDatabases[index]->Invalidate();
}
}
return NS_OK;
}
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
// We're dying now.
return NS_OK;
}
NS_NOTREACHED("Unknown topic!");
return NS_ERROR_UNEXPECTED;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::OriginClearRunnable::Run()
{
if (NS_IsMainThread()) {
// On the first time on the main thread we simply dispatch to the IO thread.
if (mFirstCallback) {
NS_ASSERTION(mThread, "Should have a thread here!");
mFirstCallback = false;
nsCOMPtr<nsIThread> thread;
mThread.swap(thread);
// Dispatch to the IO thread.
if (NS_FAILED(thread->Dispatch(this, NS_DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch to IO thread!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_ASSERTION(!mThread, "Should have been cleared already!");
// Dispatch any queued runnables that we collected while we were waiting.
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
NS_WARNING("Failed to dispatch delayed runnable!");
}
}
mDelayedRunnables.Clear();
// Tell the IndexedDatabaseManager that we're done.
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
if (mgr) {
mgr->OnOriginClearComplete(this);
}
return NS_OK;
}
NS_ASSERTION(!mThread, "Should have been cleared already!");
// Remove the directory that contains all our databases.
nsCOMPtr<nsIFile> directory;
nsresult rv = IDBFactory::GetDirectoryForOrigin(mOrigin,
getter_AddRefs(directory));
if (NS_SUCCEEDED(rv)) {
bool exists;
rv = directory->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists) {
rv = directory->Remove(true);
}
}
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to remove directory!");
// Switch back to the main thread to complete the sequence.
rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable(
nsIURI* aURI,
const nsACString& aOrigin,
nsIIndexedDatabaseUsageCallback* aCallback)
: mURI(aURI),
mOrigin(aOrigin),
mCallback(aCallback),
mUsage(0),
mCanceled(0)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aURI, "Null pointer!");
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
NS_ASSERTION(aCallback, "Null pointer!");
}
void
IndexedDatabaseManager::AsyncUsageRunnable::Cancel()
{
if (PR_ATOMIC_SET(&mCanceled, 1)) {
NS_ERROR("Canceled more than once?!");
}
}
nsresult
IndexedDatabaseManager::AsyncUsageRunnable::RunInternal()
{
if (NS_IsMainThread()) {
// Call the callback unless we were canceled.
if (!mCanceled) {
mCallback->OnUsageResult(mURI, mUsage);
}
// Clean up.
mURI = nsnull;
mCallback = nsnull;
// And tell the IndexedDatabaseManager that we're done.
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
if (mgr) {
mgr->OnUsageCheckComplete(this);
}
return NS_OK;
}
if (mCanceled) {
return NS_OK;
}
// Get the directory that contains all the database files we care about.
nsCOMPtr<nsIFile> directory;
nsresult rv = IDBFactory::GetDirectoryForOrigin(mOrigin,
getter_AddRefs(directory));
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = directory->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
// If the directory exists then enumerate all the files inside, adding up the
// sizes to get the final usage statistic.
if (exists && !mCanceled) {
nsCOMPtr<nsISimpleEnumerator> entries;
rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv);
if (entries) {
bool hasMore;
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
hasMore && !mCanceled) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> file(do_QueryInterface(entry));
NS_ASSERTION(file, "Don't know what this is!");
PRInt64 fileSize;
rv = file->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(fileSize > 0, "Negative size?!");
// Watch for overflow!
if (NS_UNLIKELY((LL_MAXINT - mUsage) <= PRUint64(fileSize))) {
NS_WARNING("Database sizes exceed max we can report!");
mUsage = LL_MAXINT;
}
else {
mUsage += fileSize;
}
}
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncUsageRunnable,
nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::AsyncUsageRunnable::Run()
{
nsresult rv = RunInternal();
if (!NS_IsMainThread()) {
if (NS_FAILED(rv)) {
mUsage = 0;
}
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch to main thread!");
}
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
IndexedDatabaseManager::SetVersionRunnable::SetVersionRunnable(
IDBDatabase* aDatabase,
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases)
: mRequestingDatabase(aDatabase)
{
NS_ASSERTION(aDatabase, "Null database!");
if (!mDatabases.SwapElements(aDatabases)) {
NS_ERROR("This should never fail!");
}
}
IndexedDatabaseManager::SetVersionRunnable::~SetVersionRunnable()
{
}
NS_IMPL_ISUPPORTS1(IndexedDatabaseManager::SetVersionRunnable, nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::SetVersionRunnable::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mHelper, "Should have been cleared already!");
// Dispatch any queued runnables that we picked up while waiting for the
// SetVersion transaction to complete.
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
NS_WARNING("Failed to dispatch delayed runnable!");
}
}
// No need to hold these alive any longer.
mDelayedRunnables.Clear();
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
// Let the IndexedDatabaseManager know that the SetVersion transaction has
// completed.
mgr->OnSetVersionRunnableComplete(this);
return NS_OK;
}