forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1478 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1478 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "IndexedDatabaseManager.h"
 | 
						|
 | 
						|
#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
 | 
						|
#include "nsIConsoleService.h"
 | 
						|
#include "nsIDiskSpaceWatcher.h"
 | 
						|
#include "nsIDOMWindow.h"
 | 
						|
#include "nsIEventTarget.h"
 | 
						|
#include "nsIFile.h"
 | 
						|
#include "nsIObserverService.h"
 | 
						|
#include "nsIScriptError.h"
 | 
						|
#include "nsIScriptGlobalObject.h"
 | 
						|
 | 
						|
#include "jsapi.h"
 | 
						|
#include "mozilla/ClearOnShutdown.h"
 | 
						|
#include "mozilla/CondVar.h"
 | 
						|
#include "mozilla/ContentEvents.h"
 | 
						|
#include "mozilla/EventDispatcher.h"
 | 
						|
#include "mozilla/Preferences.h"
 | 
						|
#include "mozilla/Services.h"
 | 
						|
#include "mozilla/dom/DOMException.h"
 | 
						|
#include "mozilla/dom/ErrorEvent.h"
 | 
						|
#include "mozilla/dom/ErrorEventBinding.h"
 | 
						|
#include "mozilla/dom/quota/QuotaManager.h"
 | 
						|
#include "mozilla/ipc/BackgroundChild.h"
 | 
						|
#include "mozilla/ipc/BackgroundParent.h"
 | 
						|
#include "mozilla/ipc/PBackgroundChild.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "nsGlobalWindow.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
#include "mozilla/Logging.h"
 | 
						|
 | 
						|
#include "FileInfo.h"
 | 
						|
#include "FileManager.h"
 | 
						|
#include "IDBEvents.h"
 | 
						|
#include "IDBFactory.h"
 | 
						|
#include "IDBKeyRange.h"
 | 
						|
#include "IDBRequest.h"
 | 
						|
#include "ProfilerHelpers.h"
 | 
						|
#include "ScriptErrorHelper.h"
 | 
						|
#include "WorkerScope.h"
 | 
						|
#include "WorkerPrivate.h"
 | 
						|
#include "nsCharSeparatedTokenizer.h"
 | 
						|
#include "unicode/locid.h"
 | 
						|
 | 
						|
// Bindings for ResolveConstructors
 | 
						|
#include "mozilla/dom/IDBCursorBinding.h"
 | 
						|
#include "mozilla/dom/IDBDatabaseBinding.h"
 | 
						|
#include "mozilla/dom/IDBFactoryBinding.h"
 | 
						|
#include "mozilla/dom/IDBIndexBinding.h"
 | 
						|
#include "mozilla/dom/IDBKeyRangeBinding.h"
 | 
						|
#include "mozilla/dom/IDBMutableFileBinding.h"
 | 
						|
#include "mozilla/dom/IDBObjectStoreBinding.h"
 | 
						|
#include "mozilla/dom/IDBOpenDBRequestBinding.h"
 | 
						|
#include "mozilla/dom/IDBRequestBinding.h"
 | 
						|
#include "mozilla/dom/IDBTransactionBinding.h"
 | 
						|
#include "mozilla/dom/IDBVersionChangeEventBinding.h"
 | 
						|
 | 
						|
#define IDB_STR "indexedDB"
 | 
						|
 | 
						|
// The two possible values for the data argument when receiving the disk space
 | 
						|
// observer notification.
 | 
						|
#define LOW_DISK_SPACE_DATA_FULL "full"
 | 
						|
#define LOW_DISK_SPACE_DATA_FREE "free"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
namespace dom {
 | 
						|
namespace indexedDB {
 | 
						|
 | 
						|
using namespace mozilla::dom::quota;
 | 
						|
using namespace mozilla::dom::workers;
 | 
						|
using namespace mozilla::ipc;
 | 
						|
 | 
						|
class FileManagerInfo
 | 
						|
{
 | 
						|
public:
 | 
						|
  already_AddRefed<FileManager>
 | 
						|
  GetFileManager(PersistenceType aPersistenceType,
 | 
						|
                 const nsAString& aName) const;
 | 
						|
 | 
						|
  void
 | 
						|
  AddFileManager(FileManager* aFileManager);
 | 
						|
 | 
						|
  bool
 | 
						|
  HasFileManagers() const
 | 
						|
  {
 | 
						|
    AssertIsOnIOThread();
 | 
						|
 | 
						|
    return !mPersistentStorageFileManagers.IsEmpty() ||
 | 
						|
           !mTemporaryStorageFileManagers.IsEmpty() ||
 | 
						|
           !mDefaultStorageFileManagers.IsEmpty();
 | 
						|
  }
 | 
						|
 | 
						|
  void
 | 
						|
  InvalidateAllFileManagers() const;
 | 
						|
 | 
						|
  void
 | 
						|
  InvalidateAndRemoveFileManagers(PersistenceType aPersistenceType);
 | 
						|
 | 
						|
  void
 | 
						|
  InvalidateAndRemoveFileManager(PersistenceType aPersistenceType,
 | 
						|
                                 const nsAString& aName);
 | 
						|
 | 
						|
private:
 | 
						|
  nsTArray<RefPtr<FileManager> >&
 | 
						|
  GetArray(PersistenceType aPersistenceType);
 | 
						|
 | 
						|
  const nsTArray<RefPtr<FileManager> >&
 | 
						|
  GetImmutableArray(PersistenceType aPersistenceType) const
 | 
						|
  {
 | 
						|
    return const_cast<FileManagerInfo*>(this)->GetArray(aPersistenceType);
 | 
						|
  }
 | 
						|
 | 
						|
  nsTArray<RefPtr<FileManager> > mPersistentStorageFileManagers;
 | 
						|
  nsTArray<RefPtr<FileManager> > mTemporaryStorageFileManagers;
 | 
						|
  nsTArray<RefPtr<FileManager> > mDefaultStorageFileManagers;
 | 
						|
};
 | 
						|
 | 
						|
} // namespace indexedDB
 | 
						|
 | 
						|
using namespace mozilla::dom::indexedDB;
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
NS_DEFINE_IID(kIDBRequestIID, PRIVATE_IDBREQUEST_IID);
 | 
						|
 | 
						|
const uint32_t kDeleteTimeoutMs = 1000;
 | 
						|
 | 
						|
// The threshold we use for structured clone data storing.
 | 
						|
// Anything smaller than the threshold is compressed and stored in the database.
 | 
						|
// Anything larger is compressed and stored outside the database.
 | 
						|
const int32_t kDefaultDataThresholdBytes = 1024 * 1024; // 1MB
 | 
						|
 | 
						|
// The maximal size of a serialized object to be transfered through IPC.
 | 
						|
const int32_t kDefaultMaxSerializedMsgSize = IPC::Channel::kMaximumMessageSize;
 | 
						|
 | 
						|
#define IDB_PREF_BRANCH_ROOT "dom.indexedDB."
 | 
						|
 | 
						|
const char kTestingPref[] = IDB_PREF_BRANCH_ROOT "testing";
 | 
						|
const char kPrefExperimental[] = IDB_PREF_BRANCH_ROOT "experimental";
 | 
						|
const char kPrefFileHandle[] = "dom.fileHandle.enabled";
 | 
						|
const char kDataThresholdPref[] = IDB_PREF_BRANCH_ROOT "dataThreshold";
 | 
						|
const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSize";
 | 
						|
const char kPrefErrorEventToSelfError[] = IDB_PREF_BRANCH_ROOT "errorEventToSelfError";
 | 
						|
 | 
						|
#define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging."
 | 
						|
 | 
						|
const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled";
 | 
						|
const char kPrefLoggingDetails[] = IDB_PREF_LOGGING_BRANCH_ROOT "details";
 | 
						|
 | 
						|
#if defined(DEBUG) || defined(MOZ_GECKO_PROFILER)
 | 
						|
const char kPrefLoggingProfiler[] =
 | 
						|
  IDB_PREF_LOGGING_BRANCH_ROOT "profiler-marks";
 | 
						|
#endif
 | 
						|
 | 
						|
#undef IDB_PREF_LOGGING_BRANCH_ROOT
 | 
						|
#undef IDB_PREF_BRANCH_ROOT
 | 
						|
 | 
						|
StaticRefPtr<IndexedDatabaseManager> gDBManager;
 | 
						|
 | 
						|
Atomic<bool> gInitialized(false);
 | 
						|
Atomic<bool> gClosed(false);
 | 
						|
Atomic<bool> gTestingMode(false);
 | 
						|
Atomic<bool> gExperimentalFeaturesEnabled(false);
 | 
						|
Atomic<bool> gFileHandleEnabled(false);
 | 
						|
Atomic<bool> gPrefErrorEventToSelfError(false);
 | 
						|
Atomic<int32_t> gDataThresholdBytes(0);
 | 
						|
Atomic<int32_t> gMaxSerializedMsgSize(0);
 | 
						|
 | 
						|
class DeleteFilesRunnable final
 | 
						|
  : public nsIRunnable
 | 
						|
  , public OpenDirectoryListener
 | 
						|
{
 | 
						|
  typedef mozilla::dom::quota::DirectoryLock DirectoryLock;
 | 
						|
 | 
						|
  enum State
 | 
						|
  {
 | 
						|
    // Just created on the main thread. Next step is State_DirectoryOpenPending.
 | 
						|
    State_Initial,
 | 
						|
 | 
						|
    // Waiting for directory open allowed on the main thread. The next step is
 | 
						|
    // State_DatabaseWorkOpen.
 | 
						|
    State_DirectoryOpenPending,
 | 
						|
 | 
						|
    // Waiting to do/doing work on the QuotaManager IO thread. The next step is
 | 
						|
    // State_UnblockingOpen.
 | 
						|
    State_DatabaseWorkOpen,
 | 
						|
 | 
						|
    // Notifying the QuotaManager that it can proceed to the next operation on
 | 
						|
    // the main thread. Next step is State_Completed.
 | 
						|
    State_UnblockingOpen,
 | 
						|
 | 
						|
    // All done.
 | 
						|
    State_Completed
 | 
						|
  };
 | 
						|
 | 
						|
  nsCOMPtr<nsIEventTarget> mBackgroundThread;
 | 
						|
 | 
						|
  RefPtr<FileManager> mFileManager;
 | 
						|
  nsTArray<int64_t> mFileIds;
 | 
						|
 | 
						|
  RefPtr<DirectoryLock> mDirectoryLock;
 | 
						|
 | 
						|
  nsCOMPtr<nsIFile> mDirectory;
 | 
						|
  nsCOMPtr<nsIFile> mJournalDirectory;
 | 
						|
 | 
						|
  State mState;
 | 
						|
 | 
						|
public:
 | 
						|
  DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
 | 
						|
                      FileManager* aFileManager,
 | 
						|
                      nsTArray<int64_t>& aFileIds);
 | 
						|
 | 
						|
  void
 | 
						|
  Dispatch();
 | 
						|
 | 
						|
  NS_DECL_THREADSAFE_ISUPPORTS
 | 
						|
  NS_DECL_NSIRUNNABLE
 | 
						|
 | 
						|
  virtual void
 | 
						|
  DirectoryLockAcquired(DirectoryLock* aLock) override;
 | 
						|
 | 
						|
  virtual void
 | 
						|
  DirectoryLockFailed() override;
 | 
						|
 | 
						|
private:
 | 
						|
  ~DeleteFilesRunnable() {}
 | 
						|
 | 
						|
  nsresult
 | 
						|
  Open();
 | 
						|
 | 
						|
  nsresult
 | 
						|
  DeleteFile(int64_t aFileId);
 | 
						|
 | 
						|
  nsresult
 | 
						|
  DoDatabaseWork();
 | 
						|
 | 
						|
  void
 | 
						|
  Finish();
 | 
						|
 | 
						|
  void
 | 
						|
  UnblockOpen();
 | 
						|
};
 | 
						|
 | 
						|
void
 | 
						|
AtomicBoolPrefChangedCallback(const char* aPrefName, void* aClosure)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(aClosure);
 | 
						|
 | 
						|
  *static_cast<Atomic<bool>*>(aClosure) = Preferences::GetBool(aPrefName);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DataThresholdPrefChangedCallback(const char* aPrefName, void* aClosure)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(!strcmp(aPrefName, kDataThresholdPref));
 | 
						|
  MOZ_ASSERT(!aClosure);
 | 
						|
 | 
						|
  int32_t dataThresholdBytes =
 | 
						|
    Preferences::GetInt(aPrefName, kDefaultDataThresholdBytes);
 | 
						|
 | 
						|
  // The magic -1 is for use only by tests that depend on stable blob file id's.
 | 
						|
  if (dataThresholdBytes == -1) {
 | 
						|
    dataThresholdBytes = INT32_MAX;
 | 
						|
  }
 | 
						|
 | 
						|
  gDataThresholdBytes = dataThresholdBytes;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
MaxSerializedMsgSizePrefChangeCallback(const char* aPrefName, void* aClosure)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(!strcmp(aPrefName, kPrefMaxSerilizedMsgSize));
 | 
						|
  MOZ_ASSERT(!aClosure);
 | 
						|
 | 
						|
  gMaxSerializedMsgSize =
 | 
						|
    Preferences::GetInt(aPrefName, kDefaultMaxSerializedMsgSize);
 | 
						|
  MOZ_ASSERT(gMaxSerializedMsgSize > 0);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
IndexedDatabaseManager::IndexedDatabaseManager()
 | 
						|
  : mFileMutex("IndexedDatabaseManager.mFileMutex")
 | 
						|
  , mBackgroundActor(nullptr)
 | 
						|
{
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | 
						|
}
 | 
						|
 | 
						|
IndexedDatabaseManager::~IndexedDatabaseManager()
 | 
						|
{
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | 
						|
 | 
						|
  if (mBackgroundActor) {
 | 
						|
    mBackgroundActor->SendDeleteMeInternal();
 | 
						|
    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool IndexedDatabaseManager::sIsMainProcess = false;
 | 
						|
bool IndexedDatabaseManager::sFullSynchronousMode = false;
 | 
						|
 | 
						|
mozilla::LazyLogModule IndexedDatabaseManager::sLoggingModule("IndexedDB");
 | 
						|
 | 
						|
Atomic<IndexedDatabaseManager::LoggingMode>
 | 
						|
  IndexedDatabaseManager::sLoggingMode(
 | 
						|
    IndexedDatabaseManager::Logging_Disabled);
 | 
						|
 | 
						|
mozilla::Atomic<bool> IndexedDatabaseManager::sLowDiskSpaceMode(false);
 | 
						|
 | 
						|
// static
 | 
						|
IndexedDatabaseManager*
 | 
						|
IndexedDatabaseManager::GetOrCreate()
 | 
						|
{
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | 
						|
 | 
						|
  if (IsClosed()) {
 | 
						|
    NS_ERROR("Calling GetOrCreate() after shutdown!");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!gDBManager) {
 | 
						|
    sIsMainProcess = XRE_IsParentProcess();
 | 
						|
 | 
						|
    if (sIsMainProcess && Preferences::GetBool("disk_space_watcher.enabled", false)) {
 | 
						|
      // See if we're starting up in low disk space conditions.
 | 
						|
      nsCOMPtr<nsIDiskSpaceWatcher> watcher =
 | 
						|
        do_GetService(DISKSPACEWATCHER_CONTRACTID);
 | 
						|
      if (watcher) {
 | 
						|
        bool isDiskFull;
 | 
						|
        if (NS_SUCCEEDED(watcher->GetIsDiskFull(&isDiskFull))) {
 | 
						|
          sLowDiskSpaceMode = isDiskFull;
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          NS_WARNING("GetIsDiskFull failed!");
 | 
						|
        }
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        NS_WARNING("No disk space watcher component available!");
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    RefPtr<IndexedDatabaseManager> instance(new IndexedDatabaseManager());
 | 
						|
 | 
						|
    nsresult rv = instance->Init();
 | 
						|
    NS_ENSURE_SUCCESS(rv, nullptr);
 | 
						|
 | 
						|
    if (gInitialized.exchange(true)) {
 | 
						|
      NS_ERROR("Initialized more than once?!");
 | 
						|
    }
 | 
						|
 | 
						|
    gDBManager = instance;
 | 
						|
 | 
						|
    ClearOnShutdown(&gDBManager);
 | 
						|
  }
 | 
						|
 | 
						|
  return gDBManager;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
IndexedDatabaseManager*
 | 
						|
IndexedDatabaseManager::Get()
 | 
						|
{
 | 
						|
  // Does not return an owning reference.
 | 
						|
  return gDBManager;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
IndexedDatabaseManager::Init()
 | 
						|
{
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | 
						|
 | 
						|
  // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess
 | 
						|
  // directly.
 | 
						|
  if (sIsMainProcess) {
 | 
						|
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | 
						|
    NS_ENSURE_STATE(obs);
 | 
						|
 | 
						|
    nsresult rv =
 | 
						|
      obs->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC, false);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    mDeleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
 | 
						|
    NS_ENSURE_STATE(mDeleteTimer);
 | 
						|
 | 
						|
    if (QuotaManager* quotaManager = QuotaManager::Get()) {
 | 
						|
      NoteLiveQuotaManager(quotaManager);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
 | 
						|
                                       kTestingPref,
 | 
						|
                                       &gTestingMode);
 | 
						|
  Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
 | 
						|
                                       kPrefExperimental,
 | 
						|
                                       &gExperimentalFeaturesEnabled);
 | 
						|
  Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
 | 
						|
                                       kPrefFileHandle,
 | 
						|
                                       &gFileHandleEnabled);
 | 
						|
  Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
 | 
						|
                                       kPrefErrorEventToSelfError,
 | 
						|
                                       &gPrefErrorEventToSelfError);
 | 
						|
 | 
						|
  // By default IndexedDB uses SQLite with PRAGMA synchronous = NORMAL. This
 | 
						|
  // guarantees (unlike synchronous = OFF) atomicity and consistency, but not
 | 
						|
  // necessarily durability in situations such as power loss. This preference
 | 
						|
  // allows enabling PRAGMA synchronous = FULL on SQLite, which does guarantee
 | 
						|
  // durability, but with an extra fsync() and the corresponding performance
 | 
						|
  // hit.
 | 
						|
  sFullSynchronousMode = Preferences::GetBool("dom.indexedDB.fullSynchronous");
 | 
						|
 | 
						|
  Preferences::RegisterCallback(LoggingModePrefChangedCallback,
 | 
						|
                                kPrefLoggingDetails);
 | 
						|
#ifdef MOZ_GECKO_PROFILER
 | 
						|
  Preferences::RegisterCallback(LoggingModePrefChangedCallback,
 | 
						|
                                kPrefLoggingProfiler);
 | 
						|
#endif
 | 
						|
  Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback,
 | 
						|
                                       kPrefLoggingEnabled);
 | 
						|
 | 
						|
  Preferences::RegisterCallbackAndCall(DataThresholdPrefChangedCallback,
 | 
						|
                                       kDataThresholdPref);
 | 
						|
 | 
						|
  Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback,
 | 
						|
                                       kPrefMaxSerilizedMsgSize);
 | 
						|
 | 
						|
  nsAutoCString acceptLang;
 | 
						|
  Preferences::GetLocalizedCString("intl.accept_languages", acceptLang);
 | 
						|
 | 
						|
  // Split values on commas.
 | 
						|
  nsCCharSeparatedTokenizer langTokenizer(acceptLang, ',');
 | 
						|
  while (langTokenizer.hasMoreTokens()) {
 | 
						|
    nsAutoCString lang(langTokenizer.nextToken());
 | 
						|
    icu::Locale locale = icu::Locale::createCanonical(lang.get());
 | 
						|
    if (!locale.isBogus()) {
 | 
						|
      // icu::Locale::getBaseName is always ASCII as per BCP 47
 | 
						|
      mLocale.AssignASCII(locale.getBaseName());
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (mLocale.IsEmpty()) {
 | 
						|
    mLocale.AssignLiteral("en_US");
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::Destroy()
 | 
						|
{
 | 
						|
  // Setting the closed flag prevents the service from being recreated.
 | 
						|
  // Don't set it though if there's no real instance created.
 | 
						|
  if (gInitialized && gClosed.exchange(true)) {
 | 
						|
    NS_ERROR("Shutdown more than once?!");
 | 
						|
  }
 | 
						|
 | 
						|
  if (sIsMainProcess && mDeleteTimer) {
 | 
						|
    if (NS_FAILED(mDeleteTimer->Cancel())) {
 | 
						|
      NS_WARNING("Failed to cancel timer!");
 | 
						|
    }
 | 
						|
 | 
						|
    mDeleteTimer = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
 | 
						|
                                  kTestingPref,
 | 
						|
                                  &gTestingMode);
 | 
						|
  Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
 | 
						|
                                  kPrefExperimental,
 | 
						|
                                  &gExperimentalFeaturesEnabled);
 | 
						|
  Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
 | 
						|
                                  kPrefFileHandle,
 | 
						|
                                  &gFileHandleEnabled);
 | 
						|
  Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
 | 
						|
                                  kPrefErrorEventToSelfError,
 | 
						|
                                  &gPrefErrorEventToSelfError);
 | 
						|
 | 
						|
  Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
 | 
						|
                                  kPrefLoggingDetails);
 | 
						|
#ifdef MOZ_GECKO_PROFILER
 | 
						|
  Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
 | 
						|
                                  kPrefLoggingProfiler);
 | 
						|
#endif
 | 
						|
  Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
 | 
						|
                                  kPrefLoggingEnabled);
 | 
						|
 | 
						|
  Preferences::UnregisterCallback(DataThresholdPrefChangedCallback,
 | 
						|
                                  kDataThresholdPref);
 | 
						|
 | 
						|
  Preferences::UnregisterCallback(MaxSerializedMsgSizePrefChangeCallback,
 | 
						|
                                  kPrefMaxSerilizedMsgSize);
 | 
						|
 | 
						|
  delete this;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
nsresult
 | 
						|
IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor,
 | 
						|
                                              IDBFactory* aFactory)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(aVisitor.mDOMEvent);
 | 
						|
  MOZ_ASSERT(aFactory);
 | 
						|
 | 
						|
  if (!gPrefErrorEventToSelfError) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  Event* internalEvent = aVisitor.mDOMEvent->InternalDOMEvent();
 | 
						|
  MOZ_ASSERT(internalEvent);
 | 
						|
 | 
						|
  if (!internalEvent->IsTrusted()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  nsString type;
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(internalEvent->GetType(type));
 | 
						|
 | 
						|
  MOZ_ASSERT(nsDependentString(kErrorEventType).EqualsLiteral("error"));
 | 
						|
  if (!type.EqualsLiteral("error")) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<EventTarget> eventTarget = internalEvent->GetTarget();
 | 
						|
  MOZ_ASSERT(eventTarget);
 | 
						|
 | 
						|
  // Only mess with events that were originally targeted to an IDBRequest.
 | 
						|
  RefPtr<IDBRequest> request;
 | 
						|
  if (NS_FAILED(eventTarget->QueryInterface(kIDBRequestIID,
 | 
						|
                                            getter_AddRefs(request))) ||
 | 
						|
      !request) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<DOMException> error = request->GetErrorAfterResult();
 | 
						|
 | 
						|
  nsString errorName;
 | 
						|
  if (error) {
 | 
						|
    error->GetName(errorName);
 | 
						|
  }
 | 
						|
 | 
						|
  RootedDictionary<ErrorEventInit> init(RootingCx());
 | 
						|
  request->GetCallerLocation(init.mFilename, &init.mLineno, &init.mColno);
 | 
						|
 | 
						|
  init.mMessage = errorName;
 | 
						|
  init.mCancelable = true;
 | 
						|
  init.mBubbles = true;
 | 
						|
 | 
						|
  nsEventStatus status = nsEventStatus_eIgnore;
 | 
						|
 | 
						|
  if (NS_IsMainThread()) {
 | 
						|
    nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(eventTarget->GetOwnerGlobal());
 | 
						|
    if (window) {
 | 
						|
      nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
 | 
						|
      MOZ_ASSERT(sgo);
 | 
						|
 | 
						|
      if (NS_WARN_IF(NS_FAILED(sgo->HandleScriptError(init, &status)))) {
 | 
						|
        status = nsEventStatus_eIgnore;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // We don't fire error events at any global for non-window JS on the main
 | 
						|
      // thread.
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // Not on the main thread, must be in a worker.
 | 
						|
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
 | 
						|
    MOZ_ASSERT(workerPrivate);
 | 
						|
 | 
						|
    RefPtr<WorkerGlobalScope> globalScope = workerPrivate->GlobalScope();
 | 
						|
    MOZ_ASSERT(globalScope);
 | 
						|
 | 
						|
    RefPtr<ErrorEvent> errorEvent =
 | 
						|
      ErrorEvent::Constructor(globalScope,
 | 
						|
                              nsDependentString(kErrorEventType),
 | 
						|
                              init);
 | 
						|
    MOZ_ASSERT(errorEvent);
 | 
						|
 | 
						|
    errorEvent->SetTrusted(true);
 | 
						|
 | 
						|
    auto* target = static_cast<EventTarget*>(globalScope.get());
 | 
						|
 | 
						|
    if (NS_WARN_IF(NS_FAILED(
 | 
						|
      EventDispatcher::DispatchDOMEvent(target,
 | 
						|
                                        /* aWidgetEvent */ nullptr,
 | 
						|
                                        errorEvent,
 | 
						|
                                        /* aPresContext */ nullptr,
 | 
						|
                                        &status)))) {
 | 
						|
      status = nsEventStatus_eIgnore;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (status == nsEventStatus_eConsumeNoDefault) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // Log the error to the error console.
 | 
						|
  ScriptErrorHelper::Dump(errorName,
 | 
						|
                          init.mFilename,
 | 
						|
                          init.mLineno,
 | 
						|
                          init.mColno,
 | 
						|
                          nsIScriptError::errorFlag,
 | 
						|
                          aFactory->IsChrome(),
 | 
						|
                          aFactory->InnerWindowID());
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::ResolveSandboxBinding(JSContext* aCx)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(js::GetObjectClass(JS::CurrentGlobalOrNull(aCx))->flags &
 | 
						|
             JSCLASS_DOM_GLOBAL,
 | 
						|
             "Passed object is not a global object!");
 | 
						|
 | 
						|
  // We need to ensure that the manager has been created already here so that we
 | 
						|
  // load preferences that may control which properties are exposed.
 | 
						|
  if (NS_WARN_IF(!GetOrCreate())) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!IDBCursorBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBCursorWithValueBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBDatabaseBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBFactoryBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBIndexBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBKeyRangeBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBLocaleAwareKeyRangeBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBMutableFileBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBObjectStoreBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBOpenDBRequestBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBRequestBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBTransactionBinding::GetConstructorObject(aCx) ||
 | 
						|
      !IDBVersionChangeEventBinding::GetConstructorObject(aCx))
 | 
						|
  {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx,
 | 
						|
                                        JS::Handle<JSObject*> aGlobal)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
 | 
						|
             "Passed object is not a global object!");
 | 
						|
 | 
						|
  RefPtr<IDBFactory> factory;
 | 
						|
  if (NS_FAILED(IDBFactory::CreateForMainThreadJS(aCx,
 | 
						|
                                                  aGlobal,
 | 
						|
                                                  getter_AddRefs(factory)))) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(factory, "This should never fail for chrome!");
 | 
						|
 | 
						|
  JS::Rooted<JS::Value> indexedDB(aCx);
 | 
						|
  js::AssertSameCompartment(aCx, aGlobal);
 | 
						|
  if (!GetOrCreateDOMReflector(aCx, factory, &indexedDB)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return JS_DefineProperty(aCx, aGlobal, IDB_STR, indexedDB, JSPROP_ENUMERATE);
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::IsClosed()
 | 
						|
{
 | 
						|
  return gClosed;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::IsMainProcess()
 | 
						|
{
 | 
						|
  NS_ASSERTION(gDBManager,
 | 
						|
               "IsMainProcess() called before indexedDB has been initialized!");
 | 
						|
  NS_ASSERTION((XRE_IsParentProcess()) ==
 | 
						|
               sIsMainProcess, "XRE_GetProcessType changed its tune!");
 | 
						|
  return sIsMainProcess;
 | 
						|
}
 | 
						|
 | 
						|
//static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::InLowDiskSpaceMode()
 | 
						|
{
 | 
						|
  NS_ASSERTION(gDBManager,
 | 
						|
               "InLowDiskSpaceMode() called before indexedDB has been "
 | 
						|
               "initialized!");
 | 
						|
  return sLowDiskSpaceMode;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
IndexedDatabaseManager::LoggingMode
 | 
						|
IndexedDatabaseManager::GetLoggingMode()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "GetLoggingMode called before IndexedDatabaseManager has been "
 | 
						|
             "initialized!");
 | 
						|
 | 
						|
  return sLoggingMode;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
mozilla::LogModule*
 | 
						|
IndexedDatabaseManager::GetLoggingModule()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "GetLoggingModule called before IndexedDatabaseManager has been "
 | 
						|
             "initialized!");
 | 
						|
 | 
						|
  return sLoggingModule;
 | 
						|
}
 | 
						|
 | 
						|
#endif // DEBUG
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::InTestingMode()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "InTestingMode() called before indexedDB has been initialized!");
 | 
						|
 | 
						|
  return gTestingMode;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::FullSynchronous()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "FullSynchronous() called before indexedDB has been initialized!");
 | 
						|
 | 
						|
  return sFullSynchronousMode;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::ExperimentalFeaturesEnabled()
 | 
						|
{
 | 
						|
  if (NS_IsMainThread()) {
 | 
						|
    if (NS_WARN_IF(!GetOrCreate())) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    MOZ_ASSERT(Get(),
 | 
						|
               "ExperimentalFeaturesEnabled() called off the main thread "
 | 
						|
               "before indexedDB has been initialized!");
 | 
						|
  }
 | 
						|
 | 
						|
  return gExperimentalFeaturesEnabled;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::ExperimentalFeaturesEnabled(JSContext* aCx, JSObject* aGlobal)
 | 
						|
{
 | 
						|
  // If, in the child process, properties of the global object are enumerated
 | 
						|
  // before the chrome registry (and thus the value of |intl.accept_languages|)
 | 
						|
  // is ready, calling IndexedDatabaseManager::Init will permanently break
 | 
						|
  // that preference. We can retrieve gExperimentalFeaturesEnabled without
 | 
						|
  // actually going through IndexedDatabaseManager.
 | 
						|
  // See Bug 1198093 comment 14 for detailed explanation.
 | 
						|
  if (IsNonExposedGlobal(aCx, js::GetGlobalForObjectCrossCompartment(aGlobal),
 | 
						|
                         GlobalNames::BackstagePass)) {
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    static bool featureRetrieved = false;
 | 
						|
    if (!featureRetrieved) {
 | 
						|
      gExperimentalFeaturesEnabled = Preferences::GetBool(kPrefExperimental);
 | 
						|
      featureRetrieved = true;
 | 
						|
    }
 | 
						|
    return gExperimentalFeaturesEnabled;
 | 
						|
  }
 | 
						|
 | 
						|
  return ExperimentalFeaturesEnabled();
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool
 | 
						|
IndexedDatabaseManager::IsFileHandleEnabled()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "IsFileHandleEnabled() called before indexedDB has been "
 | 
						|
             "initialized!");
 | 
						|
 | 
						|
  return gFileHandleEnabled;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
uint32_t
 | 
						|
IndexedDatabaseManager::DataThreshold()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "DataThreshold() called before indexedDB has been initialized!");
 | 
						|
 | 
						|
  return gDataThresholdBytes;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
uint32_t
 | 
						|
IndexedDatabaseManager::MaxSerializedMsgSize()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(gDBManager,
 | 
						|
             "MaxSerializedMsgSize() called before indexedDB has been initialized!");
 | 
						|
  MOZ_ASSERT(gMaxSerializedMsgSize > 0);
 | 
						|
 | 
						|
  return gMaxSerializedMsgSize;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::ClearBackgroundActor()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  mBackgroundActor = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::NoteLiveQuotaManager(QuotaManager* aQuotaManager)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(IsMainProcess());
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(aQuotaManager);
 | 
						|
 | 
						|
  mBackgroundThread = aQuotaManager->OwningThread();
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::NoteShuttingDownQuotaManager()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(IsMainProcess());
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
 | 
						|
 | 
						|
  mBackgroundThread = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
already_AddRefed<FileManager>
 | 
						|
IndexedDatabaseManager::GetFileManager(PersistenceType aPersistenceType,
 | 
						|
                                       const nsACString& aOrigin,
 | 
						|
                                       const nsAString& aDatabaseName)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  FileManagerInfo* info;
 | 
						|
  if (!mFileManagerInfos.Get(aOrigin, &info)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<FileManager> fileManager =
 | 
						|
    info->GetFileManager(aPersistenceType, aDatabaseName);
 | 
						|
 | 
						|
  return fileManager.forget();
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::AddFileManager(FileManager* aFileManager)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
  NS_ASSERTION(aFileManager, "Null file manager!");
 | 
						|
 | 
						|
  FileManagerInfo* info;
 | 
						|
  if (!mFileManagerInfos.Get(aFileManager->Origin(), &info)) {
 | 
						|
    info = new FileManagerInfo();
 | 
						|
    mFileManagerInfos.Put(aFileManager->Origin(), info);
 | 
						|
  }
 | 
						|
 | 
						|
  info->AddFileManager(aFileManager);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::InvalidateAllFileManagers()
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  for (auto iter = mFileManagerInfos.ConstIter(); !iter.Done(); iter.Next()) {
 | 
						|
    auto value = iter.Data();
 | 
						|
    MOZ_ASSERT(value);
 | 
						|
 | 
						|
    value->InvalidateAllFileManagers();
 | 
						|
  }
 | 
						|
 | 
						|
  mFileManagerInfos.Clear();
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::InvalidateFileManagers(PersistenceType aPersistenceType,
 | 
						|
                                               const nsACString& aOrigin)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
  MOZ_ASSERT(!aOrigin.IsEmpty());
 | 
						|
 | 
						|
  FileManagerInfo* info;
 | 
						|
  if (!mFileManagerInfos.Get(aOrigin, &info)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  info->InvalidateAndRemoveFileManagers(aPersistenceType);
 | 
						|
 | 
						|
  if (!info->HasFileManagers()) {
 | 
						|
    mFileManagerInfos.Remove(aOrigin);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
IndexedDatabaseManager::InvalidateFileManager(PersistenceType aPersistenceType,
 | 
						|
                                              const nsACString& aOrigin,
 | 
						|
                                              const nsAString& aDatabaseName)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  FileManagerInfo* info;
 | 
						|
  if (!mFileManagerInfos.Get(aOrigin, &info)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  info->InvalidateAndRemoveFileManager(aPersistenceType, aDatabaseName);
 | 
						|
 | 
						|
  if (!info->HasFileManagers()) {
 | 
						|
    mFileManagerInfos.Remove(aOrigin);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
 | 
						|
                                        int64_t aFileId)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(IsMainProcess());
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(aFileManager);
 | 
						|
  MOZ_ASSERT(aFileId > 0);
 | 
						|
  MOZ_ASSERT(mDeleteTimer);
 | 
						|
 | 
						|
  if (!mBackgroundThread) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = mDeleteTimer->Cancel();
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  rv = mDeleteTimer->InitWithCallback(this, kDeleteTimeoutMs,
 | 
						|
                                      nsITimer::TYPE_ONE_SHOT);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  nsTArray<int64_t>* array;
 | 
						|
  if (!mPendingDeleteInfos.Get(aFileManager, &array)) {
 | 
						|
    array = new nsTArray<int64_t>();
 | 
						|
    mPendingDeleteInfos.Put(aFileManager, array);
 | 
						|
  }
 | 
						|
 | 
						|
  array->AppendElement(aFileId);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
IndexedDatabaseManager::BlockAndGetFileReferences(
 | 
						|
                                               PersistenceType aPersistenceType,
 | 
						|
                                               const nsACString& aOrigin,
 | 
						|
                                               const nsAString& aDatabaseName,
 | 
						|
                                               int64_t aFileId,
 | 
						|
                                               int32_t* aRefCnt,
 | 
						|
                                               int32_t* aDBRefCnt,
 | 
						|
                                               int32_t* aSliceRefCnt,
 | 
						|
                                               bool* aResult)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!InTestingMode())) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mBackgroundActor) {
 | 
						|
    PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread();
 | 
						|
    if (NS_WARN_IF(!bgActor)) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    BackgroundUtilsChild* actor = new BackgroundUtilsChild(this);
 | 
						|
 | 
						|
    // We don't set event target for BackgroundUtilsChild because:
 | 
						|
    // 1. BackgroundUtilsChild is a singleton.
 | 
						|
    // 2. SendGetFileReferences is a sync operation to be returned asap if unlabeled.
 | 
						|
    // 3. The rest operations like DeleteMe/__delete__ only happens at shutdown.
 | 
						|
    // Hence, we should keep it unlabeled.
 | 
						|
    mBackgroundActor =
 | 
						|
      static_cast<BackgroundUtilsChild*>(
 | 
						|
        bgActor->SendPBackgroundIndexedDBUtilsConstructor(actor));
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!mBackgroundActor)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mBackgroundActor->SendGetFileReferences(aPersistenceType,
 | 
						|
                                               nsCString(aOrigin),
 | 
						|
                                               nsString(aDatabaseName),
 | 
						|
                                               aFileId,
 | 
						|
                                               aRefCnt,
 | 
						|
                                               aDBRefCnt,
 | 
						|
                                               aSliceRefCnt,
 | 
						|
                                               aResult)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
IndexedDatabaseManager::FlushPendingFileDeletions()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!InTestingMode())) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsMainProcess()) {
 | 
						|
    nsresult rv = mDeleteTimer->Cancel();
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    rv = Notify(mDeleteTimer);
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread();
 | 
						|
    if (NS_WARN_IF(!bgActor)) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!bgActor->SendFlushPendingFileDeletions()) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void
 | 
						|
IndexedDatabaseManager::LoggingModePrefChangedCallback(
 | 
						|
                                                    const char* /* aPrefName */,
 | 
						|
                                                    void* /* aClosure */)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (!Preferences::GetBool(kPrefLoggingEnabled)) {
 | 
						|
    sLoggingMode = Logging_Disabled;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  bool useProfiler =
 | 
						|
#if defined(DEBUG) || defined(MOZ_GECKO_PROFILER)
 | 
						|
    Preferences::GetBool(kPrefLoggingProfiler);
 | 
						|
#if !defined(MOZ_GECKO_PROFILER)
 | 
						|
  if (useProfiler) {
 | 
						|
    NS_WARNING("IndexedDB cannot create profiler marks because this build does "
 | 
						|
               "not have profiler extensions enabled!");
 | 
						|
    useProfiler = false;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
#else
 | 
						|
    false;
 | 
						|
#endif
 | 
						|
 | 
						|
  const bool logDetails = Preferences::GetBool(kPrefLoggingDetails);
 | 
						|
 | 
						|
  if (useProfiler) {
 | 
						|
    sLoggingMode = logDetails ?
 | 
						|
                   Logging_DetailedProfilerMarks :
 | 
						|
                   Logging_ConciseProfilerMarks;
 | 
						|
  } else {
 | 
						|
    sLoggingMode = logDetails ? Logging_Detailed : Logging_Concise;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
const nsCString&
 | 
						|
IndexedDatabaseManager::GetLocale()
 | 
						|
{
 | 
						|
  IndexedDatabaseManager* idbManager = Get();
 | 
						|
  MOZ_ASSERT(idbManager, "IDBManager is not ready!");
 | 
						|
 | 
						|
  return idbManager->mLocale;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_ADDREF(IndexedDatabaseManager)
 | 
						|
NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy())
 | 
						|
NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsIObserver, nsITimerCallback,
 | 
						|
                        nsINamed)
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
IndexedDatabaseManager::Observe(nsISupports* aSubject, const char* aTopic,
 | 
						|
                                const char16_t* aData)
 | 
						|
{
 | 
						|
  NS_ASSERTION(IsMainProcess(), "Wrong process!");
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | 
						|
 | 
						|
  if (!strcmp(aTopic, DISKSPACEWATCHER_OBSERVER_TOPIC)) {
 | 
						|
    NS_ASSERTION(aData, "No data?!");
 | 
						|
 | 
						|
    const nsDependentString data(aData);
 | 
						|
 | 
						|
    if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FULL)) {
 | 
						|
      sLowDiskSpaceMode = true;
 | 
						|
    }
 | 
						|
    else if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FREE)) {
 | 
						|
      sLowDiskSpaceMode = false;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      NS_NOTREACHED("Unknown data value!");
 | 
						|
    }
 | 
						|
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
   NS_NOTREACHED("Unknown topic!");
 | 
						|
   return NS_ERROR_UNEXPECTED;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
IndexedDatabaseManager::Notify(nsITimer* aTimer)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(IsMainProcess());
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(mBackgroundThread);
 | 
						|
 | 
						|
  for (auto iter = mPendingDeleteInfos.ConstIter(); !iter.Done(); iter.Next()) {
 | 
						|
    auto key = iter.Key();
 | 
						|
    auto value = iter.Data();
 | 
						|
    MOZ_ASSERT(!value->IsEmpty());
 | 
						|
 | 
						|
    RefPtr<DeleteFilesRunnable> runnable =
 | 
						|
      new DeleteFilesRunnable(mBackgroundThread, key, *value);
 | 
						|
 | 
						|
    MOZ_ASSERT(value->IsEmpty());
 | 
						|
 | 
						|
    runnable->Dispatch();
 | 
						|
  }
 | 
						|
 | 
						|
  mPendingDeleteInfos.Clear();
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
IndexedDatabaseManager::GetName(nsACString& aName)
 | 
						|
{
 | 
						|
  aName.AssignLiteral("IndexedDatabaseManager");
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
already_AddRefed<FileManager>
 | 
						|
FileManagerInfo::GetFileManager(PersistenceType aPersistenceType,
 | 
						|
                                const nsAString& aName) const
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  const nsTArray<RefPtr<FileManager> >& managers =
 | 
						|
    GetImmutableArray(aPersistenceType);
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < managers.Length(); i++) {
 | 
						|
    const RefPtr<FileManager>& fileManager = managers[i];
 | 
						|
 | 
						|
    if (fileManager->DatabaseName() == aName) {
 | 
						|
      RefPtr<FileManager> result = fileManager;
 | 
						|
      return result.forget();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
FileManagerInfo::AddFileManager(FileManager* aFileManager)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  nsTArray<RefPtr<FileManager> >& managers = GetArray(aFileManager->Type());
 | 
						|
 | 
						|
  NS_ASSERTION(!managers.Contains(aFileManager), "Adding more than once?!");
 | 
						|
 | 
						|
  managers.AppendElement(aFileManager);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
FileManagerInfo::InvalidateAllFileManagers() const
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  uint32_t i;
 | 
						|
 | 
						|
  for (i = 0; i < mPersistentStorageFileManagers.Length(); i++) {
 | 
						|
    mPersistentStorageFileManagers[i]->Invalidate();
 | 
						|
  }
 | 
						|
 | 
						|
  for (i = 0; i < mTemporaryStorageFileManagers.Length(); i++) {
 | 
						|
    mTemporaryStorageFileManagers[i]->Invalidate();
 | 
						|
  }
 | 
						|
 | 
						|
  for (i = 0; i < mDefaultStorageFileManagers.Length(); i++) {
 | 
						|
    mDefaultStorageFileManagers[i]->Invalidate();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
FileManagerInfo::InvalidateAndRemoveFileManagers(
 | 
						|
                                               PersistenceType aPersistenceType)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  nsTArray<RefPtr<FileManager > >& managers = GetArray(aPersistenceType);
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < managers.Length(); i++) {
 | 
						|
    managers[i]->Invalidate();
 | 
						|
  }
 | 
						|
 | 
						|
  managers.Clear();
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
FileManagerInfo::InvalidateAndRemoveFileManager(
 | 
						|
                                               PersistenceType aPersistenceType,
 | 
						|
                                               const nsAString& aName)
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
 | 
						|
  nsTArray<RefPtr<FileManager > >& managers = GetArray(aPersistenceType);
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < managers.Length(); i++) {
 | 
						|
    RefPtr<FileManager>& fileManager = managers[i];
 | 
						|
    if (fileManager->DatabaseName() == aName) {
 | 
						|
      fileManager->Invalidate();
 | 
						|
      managers.RemoveElementAt(i);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsTArray<RefPtr<FileManager> >&
 | 
						|
FileManagerInfo::GetArray(PersistenceType aPersistenceType)
 | 
						|
{
 | 
						|
  switch (aPersistenceType) {
 | 
						|
    case PERSISTENCE_TYPE_PERSISTENT:
 | 
						|
      return mPersistentStorageFileManagers;
 | 
						|
    case PERSISTENCE_TYPE_TEMPORARY:
 | 
						|
      return mTemporaryStorageFileManagers;
 | 
						|
    case PERSISTENCE_TYPE_DEFAULT:
 | 
						|
      return mDefaultStorageFileManagers;
 | 
						|
 | 
						|
    case PERSISTENCE_TYPE_INVALID:
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("Bad storage type value!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
DeleteFilesRunnable::DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
 | 
						|
                                         FileManager* aFileManager,
 | 
						|
                                         nsTArray<int64_t>& aFileIds)
 | 
						|
  : mBackgroundThread(aBackgroundThread)
 | 
						|
  , mFileManager(aFileManager)
 | 
						|
  , mState(State_Initial)
 | 
						|
{
 | 
						|
  mFileIds.SwapElements(aFileIds);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DeleteFilesRunnable::Dispatch()
 | 
						|
{
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  MOZ_ASSERT(mState == State_Initial);
 | 
						|
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(DeleteFilesRunnable, nsIRunnable)
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
DeleteFilesRunnable::Run()
 | 
						|
{
 | 
						|
  nsresult rv;
 | 
						|
 | 
						|
  switch (mState) {
 | 
						|
    case State_Initial:
 | 
						|
      rv = Open();
 | 
						|
      break;
 | 
						|
 | 
						|
    case State_DatabaseWorkOpen:
 | 
						|
      rv = DoDatabaseWork();
 | 
						|
      break;
 | 
						|
 | 
						|
    case State_UnblockingOpen:
 | 
						|
      UnblockOpen();
 | 
						|
      return NS_OK;
 | 
						|
 | 
						|
    case State_DirectoryOpenPending:
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("Should never get here!");
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
 | 
						|
    Finish();
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
 | 
						|
{
 | 
						|
  AssertIsOnBackgroundThread();
 | 
						|
  MOZ_ASSERT(mState == State_DirectoryOpenPending);
 | 
						|
  MOZ_ASSERT(!mDirectoryLock);
 | 
						|
 | 
						|
  mDirectoryLock = aLock;
 | 
						|
 | 
						|
  QuotaManager* quotaManager = QuotaManager::Get();
 | 
						|
  MOZ_ASSERT(quotaManager);
 | 
						|
 | 
						|
  // Must set this before dispatching otherwise we will race with the IO thread
 | 
						|
  mState = State_DatabaseWorkOpen;
 | 
						|
 | 
						|
  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    Finish();
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DeleteFilesRunnable::DirectoryLockFailed()
 | 
						|
{
 | 
						|
  AssertIsOnBackgroundThread();
 | 
						|
  MOZ_ASSERT(mState == State_DirectoryOpenPending);
 | 
						|
  MOZ_ASSERT(!mDirectoryLock);
 | 
						|
 | 
						|
  Finish();
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
DeleteFilesRunnable::Open()
 | 
						|
{
 | 
						|
  AssertIsOnBackgroundThread();
 | 
						|
  MOZ_ASSERT(mState == State_Initial);
 | 
						|
 | 
						|
  QuotaManager* quotaManager = QuotaManager::Get();
 | 
						|
  if (NS_WARN_IF(!quotaManager)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  mState = State_DirectoryOpenPending;
 | 
						|
 | 
						|
  quotaManager->OpenDirectory(mFileManager->Type(),
 | 
						|
                              mFileManager->Group(),
 | 
						|
                              mFileManager->Origin(),
 | 
						|
                              Client::IDB,
 | 
						|
                              /* aExclusive */ false,
 | 
						|
                              this);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
DeleteFilesRunnable::DeleteFile(int64_t aFileId)
 | 
						|
{
 | 
						|
  MOZ_ASSERT(mDirectory);
 | 
						|
  MOZ_ASSERT(mJournalDirectory);
 | 
						|
 | 
						|
  nsCOMPtr<nsIFile> file = mFileManager->GetFileForId(mDirectory, aFileId);
 | 
						|
  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
 | 
						|
 | 
						|
  nsresult rv;
 | 
						|
  int64_t fileSize;
 | 
						|
 | 
						|
  if (mFileManager->EnforcingQuota()) {
 | 
						|
    rv = file->GetFileSize(&fileSize);
 | 
						|
    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  rv = file->Remove(false);
 | 
						|
  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 | 
						|
 | 
						|
  if (mFileManager->EnforcingQuota()) {
 | 
						|
    QuotaManager* quotaManager = QuotaManager::Get();
 | 
						|
    NS_ASSERTION(quotaManager, "Shouldn't be null!");
 | 
						|
 | 
						|
    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
 | 
						|
                                         mFileManager->Group(),
 | 
						|
                                         mFileManager->Origin(), fileSize);
 | 
						|
  }
 | 
						|
 | 
						|
  file = mFileManager->GetFileForId(mJournalDirectory, aFileId);
 | 
						|
  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
 | 
						|
 | 
						|
  rv = file->Remove(false);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
DeleteFilesRunnable::DoDatabaseWork()
 | 
						|
{
 | 
						|
  AssertIsOnIOThread();
 | 
						|
  MOZ_ASSERT(mState == State_DatabaseWorkOpen);
 | 
						|
 | 
						|
  if (!mFileManager->Invalidated()) {
 | 
						|
    mDirectory = mFileManager->GetDirectory();
 | 
						|
    if (NS_WARN_IF(!mDirectory)) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    mJournalDirectory = mFileManager->GetJournalDirectory();
 | 
						|
    if (NS_WARN_IF(!mJournalDirectory)) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    for (int64_t fileId : mFileIds) {
 | 
						|
      if (NS_FAILED(DeleteFile(fileId))) {
 | 
						|
        NS_WARNING("Failed to delete file!");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Finish();
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DeleteFilesRunnable::Finish()
 | 
						|
{
 | 
						|
  // Must set mState before dispatching otherwise we will race with the main
 | 
						|
  // thread.
 | 
						|
  mState = State_UnblockingOpen;
 | 
						|
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
DeleteFilesRunnable::UnblockOpen()
 | 
						|
{
 | 
						|
  AssertIsOnBackgroundThread();
 | 
						|
  MOZ_ASSERT(mState == State_UnblockingOpen);
 | 
						|
 | 
						|
  mDirectoryLock = nullptr;
 | 
						|
 | 
						|
  mState = State_Completed;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace dom
 | 
						|
} // namespace mozilla
 |