2010-09-10 23:12:11 +04:00
|
|
|
/* -*- 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"
|
|
|
|
#include "nsISimpleEnumerator.h"
|
2010-10-19 21:58:33 +04:00
|
|
|
#include "nsITimer.h"
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
#include "mozilla/Services.h"
|
|
|
|
#include "nsContentUtils.h"
|
|
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "nsXPCOM.h"
|
2010-10-19 21:58:33 +04:00
|
|
|
#include "nsXPCOMPrivate.h"
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
#include "IDBDatabase.h"
|
|
|
|
#include "IDBFactory.h"
|
2010-10-19 21:58:33 +04:00
|
|
|
#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
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
USING_INDEXEDDB_NAMESPACE
|
|
|
|
using namespace mozilla::services;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2010-10-19 21:58:42 +04:00
|
|
|
PRInt32 gShutdown = 0;
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// Does not hold a reference.
|
2010-09-10 23:12:11 +04:00
|
|
|
IndexedDatabaseManager* gInstance = nsnull;
|
|
|
|
|
|
|
|
// 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!");
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
nsTArray<IDBDatabase*>* array =
|
|
|
|
static_cast<nsTArray<IDBDatabase*>*>(aUserArg);
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
if (!array->AppendElements(*aValue)) {
|
|
|
|
NS_WARNING("Out of memory!");
|
|
|
|
return PL_DHASH_STOP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PL_DHASH_NEXT;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // 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;
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// static
|
|
|
|
already_AddRefed<IndexedDatabaseManager>
|
|
|
|
IndexedDatabaseManager::GetOrCreate()
|
2010-09-10 23:12:11 +04:00
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
2010-10-19 21:58:42 +04:00
|
|
|
if (IsShuttingDown()) {
|
2010-09-10 23:12:11 +04:00
|
|
|
NS_ERROR("Calling GetOrCreateInstance() after shutdown!");
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
nsRefPtr<IndexedDatabaseManager> instance(gInstance);
|
|
|
|
|
|
|
|
if (!instance) {
|
|
|
|
instance = new IndexedDatabaseManager();
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
if (!instance->mLiveDatabases.Init()) {
|
|
|
|
NS_WARNING("Out of memory!");
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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);
|
|
|
|
|
2010-09-10 23:12:11 +04:00
|
|
|
nsCOMPtr<nsIObserverService> obs = GetObserverService();
|
2010-10-19 21:58:33 +04:00
|
|
|
NS_ENSURE_TRUE(obs, nsnull);
|
|
|
|
|
|
|
|
// We need this callback to know when to shut down all our threads.
|
2010-09-10 23:12:11 +04:00
|
|
|
nsresult rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
|
|
|
PR_FALSE);
|
|
|
|
NS_ENSURE_SUCCESS(rv, nsnull);
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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,
|
|
|
|
PR_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).
|
2010-10-19 21:58:42 +04:00
|
|
|
instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
|
|
|
|
LazyIdleThread::ManualShutdown);
|
2010-10-19 21:58:33 +04:00
|
|
|
|
|
|
|
// The observer service will hold our last reference, don't AddRef here.
|
|
|
|
gInstance = instance;
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
return instance.forget();
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// static
|
2010-09-10 23:12:11 +04:00
|
|
|
IndexedDatabaseManager*
|
2010-10-19 21:58:33 +04:00
|
|
|
IndexedDatabaseManager::Get()
|
2010-09-10 23:12:11 +04:00
|
|
|
{
|
2010-10-19 21:58:33 +04:00
|
|
|
// Does not return an owning reference.
|
2010-09-10 23:12:11 +04:00
|
|
|
return gInstance;
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// static
|
|
|
|
IndexedDatabaseManager*
|
|
|
|
IndexedDatabaseManager::FactoryCreate()
|
|
|
|
{
|
|
|
|
// Returns a raw pointer that carries an owning reference! Lame, but the
|
|
|
|
// singleton factory macros force this.
|
|
|
|
return GetOrCreate().get();
|
|
|
|
}
|
|
|
|
|
2010-09-10 23:12:11 +04:00
|
|
|
// Called when an IDBDatabase is constructed.
|
|
|
|
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.
|
2010-10-19 21:58:42 +04:00
|
|
|
if (IsShuttingDown()) {
|
2010-09-10 23:12:11 +04:00
|
|
|
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;
|
|
|
|
}
|
2010-10-19 21:58:42 +04:00
|
|
|
|
|
|
|
aDatabase->mRegistered = true;
|
2010-09-10 23:12:11 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when an IDBDatabase is destroyed.
|
|
|
|
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!");
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// Called when OriginClearRunnable has finished its Run() method.
|
|
|
|
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->mDatabasesWaiting.IsEmpty(), "Databases waiting?!");
|
|
|
|
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!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when AsyncUsageRunnable has finished its Run() method.
|
|
|
|
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!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Waits until it is safe for a new database to be created with the given origin
|
|
|
|
// before dispatching the given runnable.
|
2010-09-10 23:12:11 +04:00
|
|
|
nsresult
|
|
|
|
IndexedDatabaseManager::WaitForClearAndDispatch(const nsACString& aOrigin,
|
|
|
|
nsIRunnable* aRunnable)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
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.
|
2010-10-19 21:58:33 +04:00
|
|
|
PRUint32 count = mOriginClearRunnables.Length();
|
2010-09-10 23:12:11 +04:00
|
|
|
for (PRUint32 index = 0; index < count; index++) {
|
2010-10-19 21:58:33 +04:00
|
|
|
nsRefPtr<OriginClearRunnable>& data = mOriginClearRunnables[index];
|
|
|
|
if (data->mOrigin == aOrigin) {
|
2010-09-10 23:12:11 +04:00
|
|
|
nsCOMPtr<nsIRunnable>* newPtr =
|
2010-10-19 21:58:33 +04:00
|
|
|
data->mDelayedRunnables.AppendElement(aRunnable);
|
2010-09-10 23:12:11 +04:00
|
|
|
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We aren't currently clearing databases for this origin, so dispatch the
|
|
|
|
// runnable immediately.
|
|
|
|
return NS_DispatchToCurrentThread(aRunnable);
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:42 +04:00
|
|
|
// static
|
|
|
|
bool
|
|
|
|
IndexedDatabaseManager::IsShuttingDown()
|
|
|
|
{
|
|
|
|
return !!gShutdown;
|
|
|
|
}
|
|
|
|
|
2010-09-10 23:12:11 +04:00
|
|
|
NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
|
|
|
|
nsIObserver)
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2010-10-19 21:58:33 +04:00
|
|
|
IndexedDatabaseManager::GetUsageForURI(
|
|
|
|
nsIURI* aURI,
|
|
|
|
nsIIndexedDatabaseUsageCallback* aCallback)
|
2010-09-10 23:12:11 +04:00
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
|
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
2010-10-19 21:58:33 +04:00
|
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
// Figure out which origin we're dealing with.
|
|
|
|
nsCString origin;
|
|
|
|
nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
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.
|
2010-09-10 23:12:11 +04:00
|
|
|
if (origin.EqualsLiteral("null")) {
|
2010-10-19 21:58:33 +04:00
|
|
|
rv = NS_DispatchToCurrentThread(runnable);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2010-09-10 23:12:11 +04:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// Otherwise dispatch to the IO thread to actually compute the usage.
|
|
|
|
rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
2010-09-10 23:12:11 +04:00
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
NS_IMETHODIMP
|
|
|
|
IndexedDatabaseManager::CancelGetUsageForURI(
|
|
|
|
nsIURI* aURI,
|
|
|
|
nsIIndexedDatabaseUsageCallback* aCallback)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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];
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
PRBool equals;
|
|
|
|
nsresult rv = runnable->mURI->Equals(aURI, &equals);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
if (equals && SameCOMIdentity(aCallback, runnable->mCallback)) {
|
|
|
|
runnable->Cancel();
|
|
|
|
break;
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
2010-10-19 21:58:33 +04:00
|
|
|
PRUint32 clearDataCount = mOriginClearRunnables.Length();
|
2010-09-10 23:12:11 +04:00
|
|
|
for (PRUint32 index = 0; index < clearDataCount; index++) {
|
2010-10-19 21:58:33 +04:00
|
|
|
if (mOriginClearRunnables[index]->mOrigin == origin) {
|
2010-09-10 23:12:11 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
nsRefPtr<OriginClearRunnable> runnable =
|
|
|
|
new OriginClearRunnable(origin, mIOThread, liveDatabases);
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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 (!runnable->mDatabasesWaiting.IsEmpty()) {
|
|
|
|
PRUint32 count = runnable->mDatabasesWaiting.Length();
|
2010-09-10 23:12:11 +04:00
|
|
|
|
|
|
|
// Invalidate all the live databases first.
|
|
|
|
for (PRUint32 index = 0; index < count; index++) {
|
2010-10-19 21:58:33 +04:00
|
|
|
runnable->mDatabasesWaiting[index]->Invalidate();
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// Now set up our callbacks so that we know when they have finished.
|
|
|
|
TransactionThreadPool* pool = TransactionThreadPool::Get();
|
2010-09-10 23:12:11 +04:00
|
|
|
for (PRUint32 index = 0; index < count; index++) {
|
2010-10-19 21:58:33 +04:00
|
|
|
if (!pool->WaitForAllTransactionsToComplete(
|
|
|
|
runnable->mDatabasesWaiting[index],
|
|
|
|
OriginClearRunnable::DatabaseCompleteCallback,
|
|
|
|
runnable)) {
|
|
|
|
NS_WARNING("Out of memory!");
|
|
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
IndexedDatabaseManager::Observe(nsISupports* aSubject,
|
|
|
|
const char* aTopic,
|
|
|
|
const PRUnichar* aData)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
|
2010-10-19 21:58:42 +04:00
|
|
|
// Setting this flag prevents the service from being recreated and prevents
|
2010-09-10 23:12:11 +04:00
|
|
|
// further databases from being created.
|
2010-10-19 21:58:42 +04:00
|
|
|
if (PR_AtomicSet(&gShutdown, 1)) {
|
|
|
|
NS_ERROR("Shutdown more than once?!");
|
|
|
|
}
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// 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;
|
2010-09-10 23:12:11 +04:00
|
|
|
mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
// Invalidate them all.
|
2010-09-10 23:12:11 +04:00
|
|
|
if (!liveDatabases.IsEmpty()) {
|
|
|
|
PRUint32 count = liveDatabases.Length();
|
|
|
|
for (PRUint32 index = 0; index < count; index++) {
|
2010-10-19 21:58:33 +04:00
|
|
|
liveDatabases[index]->Invalidate();
|
2010-09-10 23:12:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2010-09-10 23:12:11 +04:00
|
|
|
|
2010-10-19 21:58:33 +04:00
|
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
|
|
// We're dying now.
|
2010-09-10 23:12:11 +04:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_NOTREACHED("Unknown topic!");
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
2010-10-19 21:58:33 +04:00
|
|
|
|
|
|
|
// Called by the TransactionThreadPool when the given database has completed all
|
|
|
|
// of its transactions.
|
|
|
|
void
|
|
|
|
IndexedDatabaseManager::OriginClearRunnable::OnDatabaseComplete(
|
|
|
|
IDBDatabase* aDatabase)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
NS_ASSERTION(aDatabase, "Null pointer!");
|
|
|
|
NS_ASSERTION(mThread, "This shouldn't be cleared yet!");
|
|
|
|
|
|
|
|
// Remove the database from the list of databases that we're waiting on.
|
|
|
|
if (!mDatabasesWaiting.RemoveElement(aDatabase)) {
|
|
|
|
NS_ERROR("Don't know anything about this database!");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now dispatch this runnable to the IO thread if the list is empty.
|
|
|
|
if (mDatabasesWaiting.IsEmpty()) {
|
|
|
|
if (NS_FAILED(mThread->Dispatch(this, NS_DISPATCH_NORMAL))) {
|
|
|
|
NS_WARNING("Can't dispatch to IO thread!");
|
|
|
|
}
|
|
|
|
|
|
|
|
// We no longer need to keep the thread alive.
|
|
|
|
mThread = nsnull;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
|
|
|
|
nsIRunnable)
|
|
|
|
|
|
|
|
// Runs twice, first on the IO thread, then again on the main thread. While on
|
|
|
|
// the IO thread the runnable will actually remove the origin's database files
|
|
|
|
// and the directory that contains them before dispatching itself back to the
|
|
|
|
// main thread. When on the main thread the runnable will dispatch any queued
|
|
|
|
// runnables and then notify the IndexedDatabaseManager that the job has been
|
|
|
|
// completed.
|
|
|
|
NS_IMETHODIMP
|
|
|
|
IndexedDatabaseManager::OriginClearRunnable::Run()
|
|
|
|
{
|
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the directory that contains all our databases.
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
|
|
nsresult rv = IDBFactory::GetDirectoryForOrigin(mOrigin,
|
|
|
|
getter_AddRefs(directory));
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
PRBool exists;
|
|
|
|
rv = directory->Exists(&exists);
|
|
|
|
if (NS_SUCCEEDED(rv) && exists) {
|
|
|
|
rv = directory->Remove(PR_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!");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets the canceled flag so that the callback is never called.
|
|
|
|
void
|
|
|
|
IndexedDatabaseManager::AsyncUsageRunnable::Cancel()
|
|
|
|
{
|
|
|
|
if (PR_AtomicSet(&mCanceled, 1)) {
|
|
|
|
NS_ERROR("Canceled more than once?!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs twice, first on the IO thread, then again on the main thread. While on
|
|
|
|
// the IO thread the runnable will calculate the size of all files in the
|
|
|
|
// origin's directory before dispatching itself back to the main thread. When on
|
|
|
|
// the main thread the runnable will call the callback and then notify the
|
|
|
|
// IndexedDatabaseManager that the job has been completed.
|
|
|
|
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);
|
|
|
|
|
|
|
|
PRBool 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) {
|
|
|
|
PRBool 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)
|
|
|
|
|
|
|
|
// Calls the RunInternal method and makes sure that we always dispatch to the
|
|
|
|
// main thread in case of an error.
|
|
|
|
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;
|
|
|
|
}
|