Bug 1350637 - Part 2: Core changes for LocalStorage on PBackground; r=asuth
authorJan Varga <jan.varga@gmail.com>
Wed, 26 Jul 2017 12:19:13 +0200
changeset 373541 d5f2490d5138
parent 373540 f8241dee5ef3
child 373542 d91109b3ea08
push id32304
push usercbook@mozilla.com
push dateWed, 09 Aug 2017 09:37:21 +0000
treeherdermozilla-central@4c5fbf493763 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1350637
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1350637 - Part 2: Core changes for LocalStorage on PBackground; r=asuth - stop inheriting StorageDBBridge in StorageDBThread and StorageDBChild - move StorageDBThread and StorageDBChild initialization out of LocalStorageCache - use IPC even for the intra-process communication in main process - rationalize a bit storage observer code - make StorageDBParent to always be created and destroyed on the background thread
dom/storage/LocalStorageCache.cpp
dom/storage/LocalStorageCache.h
dom/storage/LocalStorageManager.cpp
dom/storage/LocalStorageManager.h
dom/storage/StorageDBThread.cpp
dom/storage/StorageDBThread.h
dom/storage/StorageIPC.cpp
dom/storage/StorageIPC.h
dom/storage/StorageObserver.cpp
dom/storage/StorageObserver.h
--- a/dom/storage/LocalStorageCache.cpp
+++ b/dom/storage/LocalStorageCache.cpp
@@ -19,20 +19,16 @@
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
 
-// static
-StorageDBBridge* LocalStorageCache::sDatabase = nullptr;
-bool LocalStorageCache::sDatabaseDown = false;
-
 namespace {
 
 const uint32_t kDefaultSet = 0;
 const uint32_t kPrivateSet = 1;
 const uint32_t kSessionSet = 2;
 
 inline uint32_t
 GetDataSetIndex(bool aPrivate, bool aSessionOnly)
@@ -238,23 +234,24 @@ LocalStorageCache::ProcessUsageDelta(uin
 
 void
 LocalStorageCache::Preload()
 {
   if (mLoaded || !mPersistent) {
     return;
   }
 
-  if (!StartDatabase()) {
+  StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+  if (!storageChild) {
     mLoaded = true;
     mLoadResult = NS_ERROR_FAILURE;
     return;
   }
 
-  sDatabase->AsyncPreload(this);
+  storageChild->AsyncPreload(this);
 }
 
 namespace {
 
 // The AutoTimer provided by telemetry headers is only using static,
 // i.e. compile time known ID, but here we know the ID only at run time.
 // Hence a new class.
 class TelemetryAutoTimer
@@ -304,17 +301,17 @@ LocalStorageCache::WaitForPreload(Teleme
   // SyncPreload will just wait for it to finish rather then synchronously
   // read from the database.  It seems to me more optimal.
 
   // TODO place for A/B testing (force main thread load vs. let preload finish)
 
   // No need to check sDatabase for being non-null since preload is either
   // done before we've shut the DB down or when the DB could not start,
   // preload has not even be started.
-  sDatabase->SyncPreload(this);
+  StorageDBChild::Get()->SyncPreload(this);
 }
 
 nsresult
 LocalStorageCache::GetLength(const LocalStorage* aStorage, uint32_t* aRetval)
 {
   if (Persist(aStorage)) {
     WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
     if (NS_FAILED(mLoadResult)) {
@@ -424,27 +421,28 @@ LocalStorageCache::SetItem(const LocalSt
 
   if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
     return NS_SUCCESS_DOM_NO_OPERATION;
   }
 
   data.mKeys.Put(aKey, aValue);
 
   if (aSource == ContentMutation && Persist(aStorage)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     if (DOMStringIsNull(aOld)) {
-      return sDatabase->AsyncAddItem(this, aKey, aValue);
+      return storageChild->AsyncAddItem(this, aKey, aValue);
     }
 
-    return sDatabase->AsyncUpdateItem(this, aKey, aValue);
+    return storageChild->AsyncUpdateItem(this, aKey, aValue);
   }
 
   return NS_OK;
 }
 
 nsresult
 LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
                               const nsAString& aKey,
@@ -465,23 +463,24 @@ LocalStorageCache::RemoveItem(const Loca
 
   // Recalculate the cached data size
   const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
                           static_cast<int64_t>(aKey.Length()));
   Unused << ProcessUsageDelta(aStorage, delta, aSource);
   data.mKeys.Remove(aKey);
 
   if (aSource == ContentMutation && Persist(aStorage)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
-    return sDatabase->AsyncRemoveItem(this, aKey);
+    return storageChild->AsyncRemoveItem(this, aKey);
   }
 
   return NS_OK;
 }
 
 nsresult
 LocalStorageCache::Clear(const LocalStorage* aStorage,
                          const MutationSource aSource)
@@ -506,23 +505,24 @@ LocalStorageCache::Clear(const LocalStor
   bool hadData = !!data.mKeys.Count();
 
   if (hadData) {
     Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
     data.mKeys.Clear();
   }
 
   if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) {
-    if (!sDatabase) {
+    StorageDBChild* storageChild = StorageDBChild::Get();
+    if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
-    return sDatabase->AsyncClear(this);
+    return storageChild->AsyncClear(this);
   }
 
   return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
 }
 
 int64_t
 LocalStorageCache::GetOriginQuotaUsage(const LocalStorage* aStorage) const
 {
@@ -667,72 +667,10 @@ StorageUsage::CheckAndSetETLD1UsageDelta
       aDelta > 0 && newUsage > LocalStorageManager::GetQuota()) {
     return false;
   }
 
   mUsage[aDataSetIndex] = newUsage;
   return true;
 }
 
-
-// static
-StorageDBBridge*
-LocalStorageCache::StartDatabase()
-{
-  if (sDatabase || sDatabaseDown) {
-    // When sDatabaseDown is at true, sDatabase is null.
-    // Checking sDatabaseDown flag here prevents reinitialization of
-    // the database after shutdown.
-    return sDatabase;
-  }
-
-  if (XRE_IsParentProcess()) {
-    // XXX Fix me!
-    return nullptr;
-  } else {
-    // Use LocalStorageManager::Ensure in case we're called from
-    // DOMSessionStorageManager's initializer and we haven't yet initialized the
-    // local storage manager.
-    RefPtr<StorageDBChild> db = new StorageDBChild(
-        LocalStorageManager::Ensure());
-
-    nsresult rv = db->Init();
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    db.forget(&sDatabase);
-  }
-
-  return sDatabase;
-}
-
-// static
-StorageDBBridge*
-LocalStorageCache::GetDatabase()
-{
-  return sDatabase;
-}
-
-// static
-nsresult
-LocalStorageCache::StopDatabase()
-{
-  if (!sDatabase) {
-    return NS_OK;
-  }
-
-  sDatabaseDown = true;
-
-  nsresult rv = sDatabase->Shutdown();
-  if (XRE_IsParentProcess()) {
-    delete sDatabase;
-  } else {
-    StorageDBChild* child = static_cast<StorageDBChild*>(sDatabase);
-    NS_RELEASE(child);
-  }
-
-  sDatabase = nullptr;
-  return rv;
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/LocalStorageCache.h
+++ b/dom/storage/LocalStorageCache.h
@@ -126,23 +126,16 @@ public:
   nsresult RemoveItem(const LocalStorage* aStorage, const nsAString& aKey,
                       nsString& aOld,
                       const MutationSource aSource=ContentMutation);
   nsresult Clear(const LocalStorage* aStorage,
                  const MutationSource aSource=ContentMutation);
 
   void GetKeys(const LocalStorage* aStorage, nsTArray<nsString>& aKeys);
 
-  // Starts the database engine thread or the IPC bridge
-  static StorageDBBridge* StartDatabase();
-  static StorageDBBridge* GetDatabase();
-
-  // Stops the thread and flushes all uncommited data
-  static nsresult StopDatabase();
-
   // LocalStorageCacheBridge
 
   virtual const nsCString Origin() const;
   virtual const nsCString& OriginNoSuffix() const { return mOriginNoSuffix; }
   virtual const nsCString& OriginSuffix() const { return mOriginSuffix; }
   virtual bool Loaded() { return mLoaded; }
   virtual uint32_t LoadedCount();
   virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
@@ -255,31 +248,24 @@ private:
   // - True after access to session-only data has been made for the first time.
   // We also fill session-only data set with the default one at that moment.
   // Drops back to false when session-only data are cleared from chrome.
   bool mSessionOnlyDataSetActive : 1;
 
   // Whether we have already captured state of the cache preload on our first
   // access.
   bool mPreloadTelemetryRecorded : 1;
-
-  // StorageDBThread on the parent or single process,
-  // StorageDBChild on the child process.
-  static StorageDBBridge* sDatabase;
-
-  // False until we shut the database down.
-  static bool sDatabaseDown;
 };
 
 // StorageUsage
 // Infrastructure to manage and check eTLD+1 quota
 class StorageUsageBridge
 {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageUsageBridge)
+  NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(StorageUsageBridge)
 
   virtual const nsCString& OriginScope() = 0;
   virtual void LoadUsage(const int64_t aUsage) = 0;
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~StorageUsageBridge() {}
 };
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -71,17 +71,17 @@ LocalStorageManager::LocalStorageManager
 
   NS_ASSERTION(!sSelf, "Somebody is trying to do_CreateInstance(\"@mozilla/dom/localStorage-manager;1\"");
   sSelf = this;
 
   if (!XRE_IsParentProcess()) {
     // Do this only on the child process.  The thread IPC bridge
     // is also used to communicate chrome observer notifications.
     // Note: must be called after we set sSelf
-    LocalStorageCache::StartDatabase();
+    StorageDBChild::GetOrCreate();
   }
 }
 
 LocalStorageManager::~LocalStorageManager()
 {
   StorageObserver* observer = StorageObserver::Self();
   if (observer) {
     observer->RemoveSink(this);
@@ -163,19 +163,19 @@ LocalStorageManager::GetOriginUsage(cons
 {
   RefPtr<StorageUsage> usage;
   if (mUsages.Get(aOriginNoSuffix, &usage)) {
     return usage.forget();
   }
 
   usage = new StorageUsage(aOriginNoSuffix);
 
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (db) {
-    db->AsyncGetUsage(usage);
+  StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+  if (storageChild) {
+    storageChild->AsyncGetUsage(usage);
   }
 
   mUsages.Put(aOriginNoSuffix, usage);
 
   return usage.forget();
 }
 
 already_AddRefed<LocalStorageCache>
@@ -229,17 +229,17 @@ LocalStorageManager::GetStorageInternal(
     if (aCreateMode == CreateMode::UseIfExistsNeverCreate) {
       *aRetval = nullptr;
       return NS_OK;
     }
 
     if (aCreateMode == CreateMode::CreateIfShouldPreload) {
       // This is a demand to just preload the cache, if the scope has
       // no data stored, bypass creation and preload of the cache.
-      StorageDBBridge* db = LocalStorageCache::GetDatabase();
+      StorageDBChild* db = StorageDBChild::Get();
       if (db) {
         if (!db->ShouldPreloadOrigin(LocalStorageManager::CreateOrigin(originAttrSuffix, originKey))) {
           return NS_OK;
         }
       } else {
         if (originKey.EqualsLiteral("knalb.:about")) {
           return NS_OK;
         }
--- a/dom/storage/LocalStorageManager.h
+++ b/dom/storage/LocalStorageManager.h
@@ -126,16 +126,17 @@ private:
   // Like Self, but creates an instance if we're not yet initialized.
   static LocalStorageManager* Ensure();
 
 private:
   // Keeps usage cache objects for eTLD+1 scopes we have touched.
   nsDataHashtable<nsCStringHashKey, RefPtr<StorageUsage> > mUsages;
 
   friend class LocalStorageCache;
+  friend class StorageDBChild;
   // Releases cache since it is no longer referrered by any Storage object.
   virtual void DropCache(LocalStorageCache* aCache);
 
   static LocalStorageManager* sSelf;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/StorageDBThread.cpp
+++ b/dom/storage/StorageDBThread.cpp
@@ -18,16 +18,17 @@
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozIStorageService.h"
 #include "mozIStorageBindingParamsArray.h"
 #include "mozIStorageBindingParams.h"
 #include "mozIStorageValueArray.h"
 #include "mozIStorageFunction.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "nsIObserverService.h"
 #include "nsVariant.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Services.h"
 #include "mozilla/Tokenizer.h"
 #include "GeckoProfiler.h"
 
 // How long we collect write oprerations
@@ -43,16 +44,21 @@
 
 namespace mozilla {
 namespace dom {
 
 using namespace StorageUtils;
 
 namespace { // anon
 
+StorageDBThread* sStorageThread = nullptr;
+
+// False until we shut the storage thread down.
+bool sStorageThreadDown = false;
+
 // This is only a compatibility code for schema version 0.  Returns the 'scope'
 // key in the schema version 0 format for the scope column.
 nsCString
 Scheme0Scope(LocalStorageCacheBridge* aCache)
 {
   nsCString result;
 
   nsCString suffix = aCache->OriginSuffix();
@@ -101,74 +107,175 @@ Scheme0Scope(LocalStorageCacheBridge* aC
 
   result.Append(aCache->OriginNoSuffix());
 
   return result;
 }
 
 } // anon
 
-
+// XXX Fix me!
+#if 0
 StorageDBBridge::StorageDBBridge()
 {
 }
+#endif
 
+class StorageDBThread::InitHelper final
+  : public Runnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+  nsString mProfilePath;
+  nsresult mMainThreadResultCode;
+  bool mWaiting;
+
+public:
+  InitHelper()
+    : Runnable("dom::StorageDBThread::InitHelper")
+    , mOwningThread(GetCurrentThreadEventTarget())
+    , mMutex("InitHelper::mMutex")
+    , mCondVar(mMutex, "InitHelper::mCondVar")
+    , mMainThreadResultCode(NS_OK)
+    , mWaiting(true)
+  { }
+
+  // Because of the `sync Preload` IPC, we need to be able to synchronously
+  // initialize, which includes consulting and initializing
+  // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
+  nsresult
+  SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
+
+private:
+  ~InitHelper() override = default;
+
+  nsresult
+  RunOnMainThread();
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class StorageDBThread::NoteBackgroundThreadRunnable final
+  : public Runnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+
+public:
+  NoteBackgroundThreadRunnable()
+    : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable")
+    , mOwningThread(GetCurrentThreadEventTarget())
+  { }
+
+private:
+  ~NoteBackgroundThreadRunnable() override = default;
+
+  NS_DECL_NSIRUNNABLE
+};
 
 StorageDBThread::StorageDBThread()
   : mThread(nullptr)
   , mThreadObserver(new ThreadObserver())
   , mStopIOThread(false)
   , mWALModeEnabled(false)
   , mDBReady(false)
   , mStatus(NS_OK)
   , mWorkerStatements(mWorkerConnection)
   , mReaderStatements(mReaderConnection)
   , mDirtyEpoch(0)
   , mFlushImmediately(false)
   , mPriorityCounter(0)
 {
 }
 
+// static
+StorageDBThread*
+StorageDBThread::Get()
+{
+  AssertIsOnBackgroundThread();
+
+  return sStorageThread;
+}
+
+// static
+StorageDBThread*
+StorageDBThread::GetOrCreate()
+{
+  AssertIsOnBackgroundThread();
+
+  if (sStorageThread || sStorageThreadDown) {
+    // When sStorageThreadDown is at true, sStorageThread is null.
+    // Checking sStorageThreadDown flag here prevents reinitialization of
+    // the storage thread after shutdown.
+    return sStorageThread;
+  }
+
+  nsAutoPtr<StorageDBThread> storageThread(new StorageDBThread());
+
+  nsresult rv = storageThread->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  sStorageThread = storageThread.forget();
+
+  return sStorageThread;
+}
+
 nsresult
 StorageDBThread::Init()
 {
-  nsresult rv;
+  AssertIsOnBackgroundThread();
+
+  RefPtr<InitHelper> helper = new InitHelper();
+
+  nsString profilePath;
+  nsresult rv = helper->SyncDispatchAndReturnProfilePath(profilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  // Need to determine location on the main thread, since
-  // NS_GetSpecialDirectory access the atom table that can
-  // be accessed only on the main thread.
-  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                              getter_AddRefs(mDatabaseFile));
-  NS_ENSURE_SUCCESS(rv, rv);
+  mDatabaseFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mDatabaseFile->InitWithPath(profilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Ensure mozIStorageService init on the main thread first.
-  nsCOMPtr<mozIStorageService> service =
-    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // Need to keep the lock to avoid setting mThread later then
   // the thread body executes.
   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
                             PR_JOINABLE_THREAD, 262144);
   if (!mThread) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  RefPtr<NoteBackgroundThreadRunnable> runnable =
+    new NoteBackgroundThreadRunnable();
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
   return NS_OK;
 }
 
 nsresult
 StorageDBThread::Shutdown()
 {
+  AssertIsOnBackgroundThread();
+
+  sStorageThreadDown = true;
+
   if (!mThread) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
 
   {
     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
@@ -899,17 +1006,17 @@ StorageDBThread::DBOperation::Perform(St
   case opPreloadUrgent:
   {
     // Already loaded?
     if (mCache->Loaded()) {
       break;
     }
 
     StatementCache* statements;
-    if (MOZ_UNLIKELY(NS_IsMainThread())) {
+    if (MOZ_UNLIKELY(IsOnBackgroundThread())) {
       statements = &aThread->mReaderStatements;
     } else {
       statements = &aThread->mWorkerStatements;
     }
 
     // OFFSET is an optimization when we have to do a sync load
     // and cache has already loaded some parts asynchronously.
     // It skips keys we have already loaded.
@@ -1489,10 +1596,120 @@ StorageDBThread::PendingOperations::IsOr
     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
       return true;
     }
   }
 
   return false;
 }
 
+nsresult
+StorageDBThread::
+InitHelper::SyncDispatchAndReturnProfilePath(nsAString& aProfilePath)
+{
+  AssertIsOnBackgroundThread();
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  mozilla::MutexAutoLock autolock(mMutex);
+  while (mWaiting) {
+    mCondVar.Wait();
+  }
+
+  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+    return mMainThreadResultCode;
+  }
+
+  aProfilePath = mProfilePath;
+  return NS_OK;
+}
+
+nsresult
+StorageDBThread::
+InitHelper::RunOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Need to determine location on the main thread, since
+  // NS_GetSpecialDirectory accesses the atom table that can
+  // only be accessed on the main thread.
+  nsCOMPtr<nsIFile> profileDir;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                                       getter_AddRefs(profileDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = profileDir->GetPath(mProfilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // This service has to be started on the main thread currently.
+  nsCOMPtr<mozIStorageService> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+InitHelper::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = RunOnMainThread();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mMainThreadResultCode = rv;
+  }
+
+  mozilla::MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mWaiting);
+
+  mWaiting = false;
+  mCondVar.Notify();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+NoteBackgroundThreadRunnable::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StorageObserver* observer = StorageObserver::Self();
+  MOZ_ASSERT(observer);
+
+  observer->NoteBackgroundThread(mOwningThread);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::
+ShutdownRunnable::Run()
+{
+  if (NS_IsMainThread()) {
+    mDone = true;
+
+    return NS_OK;
+  }
+
+  AssertIsOnBackgroundThread();
+
+  if (sStorageThread) {
+    sStorageThread->Shutdown();
+
+    delete sStorageThread;
+    sStorageThread = nullptr;
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/storage/StorageDBThread.h
+++ b/dom/storage/StorageDBThread.h
@@ -27,16 +27,25 @@ namespace mozilla {
 namespace dom {
 
 class LocalStorageCacheBridge;
 class StorageUsageBridge;
 class StorageUsage;
 
 typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
 
+// XXX Fix me!
+//     1. Move comments to StorageDBThread/StorageDBChild.
+//     2. Devirtualize relevant methods in StorageDBThread/StorageDBChild.
+//     3. Remove relevant methods in StorageDBThread/StorageDBChild that are
+//        unused.
+//     4. Remove this class completely.
+//
+//     See bug 1387636 for more details.
+#if 0
 // Interface used by the cache to post operations to the asynchronous
 // database thread or process.
 class StorageDBBridge
 {
 public:
   StorageDBBridge();
   virtual ~StorageDBBridge() {}
 
@@ -95,27 +104,25 @@ public:
   virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern) = 0;
 
   // Forces scheduled DB operations to be early flushed to the disk
   virtual void AsyncFlush() = 0;
 
   // Check whether the scope has any data stored on disk and is thus allowed to
   // preload
   virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix) = 0;
-
-  // Get the complete list of scopes having data
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins) = 0;
 };
+#endif
 
 // The implementation of the the database engine, this directly works
 // with the sqlite or any other db API we are based on
 // This class is resposible for collecting and processing asynchronous
 // DB operations over caches (LocalStorageCache) communicating though
 // LocalStorageCacheBridge interface class
-class StorageDBThread final : public StorageDBBridge
+class StorageDBThread final
 {
 public:
   class PendingOperations;
 
   // Representation of a singe database task, like adding and removing keys,
   // (pre)loading the whole origin data, cleaning.
   class DBOperation
   {
@@ -282,21 +289,53 @@ public:
 
   private:
     virtual ~ThreadObserver() {}
     bool mHasPendingEvents;
     // The monitor we drive the thread with
     Monitor mMonitor;
   };
 
+  class InitHelper;
+
+  class NoteBackgroundThreadRunnable;
+
+  class ShutdownRunnable : public Runnable
+  {
+    // Only touched on the main thread.
+    bool& mDone;
+
+  public:
+    explicit ShutdownRunnable(bool& aDone)
+      : Runnable("dom::StorageDBThread::ShutdownRunnable")
+      , mDone(aDone)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+
+  private:
+    ~ShutdownRunnable()
+    { }
+
+    NS_DECL_NSIRUNNABLE
+  };
+
 public:
   StorageDBThread();
   virtual ~StorageDBThread() {}
 
+  static StorageDBThread*
+  Get();
+
+  static StorageDBThread*
+  GetOrCreate();
+
   virtual nsresult Init();
+
+  // Flushes all uncommited data and stops the I/O thread.
   virtual nsresult Shutdown();
 
   virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
                             bool aPriority = false)
   {
     InsertDBOp(new DBOperation(aPriority
                                  ? DBOperation::opPreloadUrgent
                                  : DBOperation::opPreload,
@@ -353,17 +392,19 @@ public:
   {
     InsertDBOp(new DBOperation(DBOperation::opClearMatchingOriginAttributes,
                                aPattern));
   }
 
   virtual void AsyncFlush();
 
   virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
+
+  // Get the complete list of scopes having data.
+  void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
 
 private:
   nsCOMPtr<nsIFile> mDatabaseFile;
   PRThread* mThread;
 
   // Used to observe runnables dispatched to our thread and to monitor it.
   RefPtr<ThreadObserver> mThreadObserver;
 
--- a/dom/storage/StorageIPC.cpp
+++ b/dom/storage/StorageIPC.cpp
@@ -15,20 +15,48 @@
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/Unused.h"
 #include "nsIDiskSpaceWatcher.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+StorageDBChild* sStorageChild = nullptr;
+
+// False until we shut the storage child down.
+bool sStorageChildDown = false;
+
+}
+
 // ----------------------------------------------------------------------------
 // Child
 // ----------------------------------------------------------------------------
 
+class StorageDBChild::ShutdownObserver final
+  : public nsIObserver
+{
+public:
+  ShutdownObserver()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+private:
+  ~ShutdownObserver()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+};
+
 NS_IMPL_ADDREF(StorageDBChild)
 
 NS_IMETHODIMP_(MozExternalRefCountType) StorageDBChild::Release(void)
 {
   NS_PRECONDITION(0 != mRefCnt, "dup release");
   nsrefcnt count = --mRefCnt;
   NS_LOG_RELEASE(this, count, "StorageDBChild");
   if (count == 1 && mIPCOpen) {
@@ -65,38 +93,88 @@ StorageDBChild::StorageDBChild(LocalStor
   , mIPCOpen(false)
 {
 }
 
 StorageDBChild::~StorageDBChild()
 {
 }
 
+// static
+StorageDBChild*
+StorageDBChild::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return sStorageChild;
+}
+
+// static
+StorageDBChild*
+StorageDBChild::GetOrCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sStorageChild || sStorageChildDown) {
+    // When sStorageChildDown is at true, sStorageChild is null.
+    // Checking sStorageChildDown flag here prevents reinitialization of
+    // the storage child after shutdown.
+    return sStorageChild;
+  }
+
+  // Use LocalStorageManager::Ensure in case we're called from
+  // DOMSessionStorageManager's initializer and we haven't yet initialized the
+  // local storage manager.
+  RefPtr<StorageDBChild> storageChild =
+    new StorageDBChild(LocalStorageManager::Ensure());
+
+  nsresult rv = storageChild->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  storageChild.forget(&sStorageChild);
+
+  return sStorageChild;
+}
+
 nsTHashtable<nsCStringHashKey>&
 StorageDBChild::OriginsHavingData()
 {
   if (!mOriginsHavingData) {
     mOriginsHavingData = new nsTHashtable<nsCStringHashKey>;
   }
 
   return *mOriginsHavingData;
 }
 
 nsresult
 StorageDBChild::Init()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!actor)) {
     return NS_ERROR_FAILURE;
   }
 
   AddIPDLReference();
 
   actor->SendPBackgroundStorageConstructor(this);
 
+  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+  MOZ_ASSERT(observerService);
+
+  nsCOMPtr<nsIObserver> observer = new ShutdownObserver();
+
+  MOZ_ALWAYS_SUCCEEDS(
+    observerService->AddObserver(observer,
+                                 "xpcom-shutdown",
+                                 false));
+
   return NS_OK;
 }
 
 nsresult
 StorageDBChild::Shutdown()
 {
   // There is nothing to do here, IPC will release automatically and
   // the actual thread running on the parent process will also stop
@@ -290,16 +368,45 @@ StorageDBChild::RecvLoadUsage(const nsCS
 
 mozilla::ipc::IPCResult
 StorageDBChild::RecvError(const nsresult& aRv)
 {
   mStatus = aRv;
   return IPC_OK();
 }
 
+NS_IMPL_ISUPPORTS(StorageDBChild::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+StorageDBChild::
+ShutdownObserver::Observe(nsISupports* aSubject,
+                          const char* aTopic,
+                          const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (NS_WARN_IF(!observerService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  Unused << observerService->RemoveObserver(this, "xpcom-shutdown");
+
+  if (sStorageChild) {
+    sStorageChildDown = true;
+
+    NS_RELEASE(sStorageChild);
+    sStorageChild = nullptr;
+  }
+
+  return NS_OK;
+}
+
 // ----------------------------------------------------------------------------
 // Parent
 // ----------------------------------------------------------------------------
 
 NS_IMPL_ADDREF(StorageDBParent)
 NS_IMPL_RELEASE(StorageDBParent)
 
 void
@@ -330,20 +437,20 @@ public:
 
 private:
   NS_IMETHOD Run() override
   {
     if (!mParent->IPCOpen()) {
       return NS_OK;
     }
 
-    StorageDBBridge* db = LocalStorageCache::GetDatabase();
-    if (db) {
+    StorageDBThread* storageThread = StorageDBThread::Get();
+    if (storageThread) {
       InfallibleTArray<nsCString> scopes;
-      db->GetOriginsHavingData(&scopes);
+      storageThread->GetOriginsHavingData(&scopes);
       mozilla::Unused << mParent->SendOriginsHavingData(scopes);
     }
 
     // XXX Fix me!
 #if 0
     // We need to check if the device is in a low disk space situation, so
     // we can forbid in that case any write in localStorage.
     nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcher =
@@ -408,37 +515,41 @@ StorageDBParent::ActorDestroy(ActorDestr
   // Implement me! Bug 1005169
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncPreload(const nsCString& aOriginSuffix,
                                   const nsCString& aOriginNoSuffix,
                                   const bool& aPriority)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  db->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix), aPriority);
+  storageThread->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix),
+                              aPriority);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncGetUsage(const nsCString& aOriginNoSuffix)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   // The object releases it self in LoadUsage method
   RefPtr<UsageParentBridge> usage =
     new UsageParentBridge(this, aOriginNoSuffix);
-  db->AsyncGetUsage(usage);
+
+  storageThread->AsyncGetUsage(usage);
+
   return IPC_OK();
 }
 
 namespace {
 
 // We need another implementation of LocalStorageCacheBridge to do
 // synchronous IPC preload.  This class just receives Load* notifications
 // and fills the returning arguments of RecvPreload with the database
@@ -519,114 +630,122 @@ private:
 mozilla::ipc::IPCResult
 StorageDBParent::RecvPreload(const nsCString& aOriginSuffix,
                              const nsCString& aOriginNoSuffix,
                              const uint32_t& aAlreadyLoadedCount,
                              InfallibleTArray<nsString>* aKeys,
                              InfallibleTArray<nsString>* aValues,
                              nsresult* aRv)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   RefPtr<SyncLoadCacheHelper> cache(
     new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount,
                             aKeys, aValues, aRv));
 
-  db->SyncPreload(cache, true);
+  storageThread->SyncPreload(cache, true);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncAddItem(const nsCString& aOriginSuffix,
                                   const nsCString& aOriginNoSuffix,
                                   const nsString& aKey,
                                   const nsString& aValue)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey,
-                                 aValue);
+  nsresult rv =
+    storageThread->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                aKey,
+                                aValue);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncUpdateItem(const nsCString& aOriginSuffix,
                                      const nsCString& aOriginNoSuffix,
                                      const nsString& aKey,
                                      const nsString& aValue)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix),
-                                    aKey, aValue);
+  nsresult rv =
+    storageThread->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                   aKey,
+                                   aValue);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncRemoveItem(const nsCString& aOriginSuffix,
                                      const nsCString& aOriginNoSuffix,
                                      const nsString& aKey)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix),
-                                    aKey);
+  nsresult rv =
+    storageThread->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix),
+                                   aKey);
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncClear(const nsCString& aOriginSuffix,
                                 const nsCString& aOriginNoSuffix)
 {
-  StorageDBBridge* db = LocalStorageCache::StartDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::GetOrCreate();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  nsresult rv = db->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
+  nsresult rv =
+    storageThread->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
   if (NS_FAILED(rv) && mIPCOpen) {
     mozilla::Unused << SendError(rv);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 StorageDBParent::RecvAsyncFlush()
 {
-  StorageDBBridge* db = LocalStorageCache::GetDatabase();
-  if (!db) {
+  StorageDBThread* storageThread = StorageDBThread::Get();
+  if (!storageThread) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  db->AsyncFlush();
+  storageThread->AsyncFlush();
+
   return IPC_OK();
 }
 
 // StorageObserverSink
 
 nsresult
 StorageDBParent::Observe(const char* aTopic,
                          const nsAString& aOriginAttributesPattern,
@@ -699,16 +818,18 @@ private:
     case loadItem:
       mozilla::Unused << mParent->SendLoadItem(mSuffix, mOrigin, mKey, mValue);
       break;
     case loadDone:
       mozilla::Unused << mParent->SendLoadDone(mSuffix, mOrigin, mRv);
       break;
     }
 
+    mParent = nullptr;
+
     return NS_OK;
   }
 };
 
 } // namespace
 
 // StorageDBParent::CacheParentBridge
 
@@ -726,43 +847,84 @@ StorageDBParent::CacheParentBridge::Load
     return false;
   }
 
   ++mLoadedCount;
 
   RefPtr<LoadRunnable> r =
     new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix,
                      mOriginNoSuffix, aKey, aValue);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+
   return true;
 }
 
 void
 StorageDBParent::CacheParentBridge::LoadDone(nsresult aRv)
 {
   // Prevent send of duplicate LoadDone.
   if (mLoaded) {
     return;
   }
 
   mLoaded = true;
 
   RefPtr<LoadRunnable> r =
     new LoadRunnable(mParent, LoadRunnable::loadDone, mOriginSuffix,
                      mOriginNoSuffix, aRv);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(
+    mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
 }
 
 void
 StorageDBParent::CacheParentBridge::LoadWait()
 {
   // Should never be called on this implementation
   MOZ_ASSERT(false);
 }
 
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::CacheParentBridge, Destroy)
+// But due to different strings used for refcount logging and different return
+// types, this is done manually for now.
+NS_IMETHODIMP_(void)
+StorageDBParent::CacheParentBridge::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
+  if (0 == count) {
+    mRefCnt = 1; /* stabilize */
+    /* enable this to find non-threadsafe destructors: */
+    /* NS_ASSERT_OWNINGTHREAD(_class); */
+    Destroy();
+  }
+}
+
+void
+StorageDBParent::CacheParentBridge::Destroy()
+{
+  if (mOwningEventTarget->IsOnCurrentThread()) {
+    delete this;
+    return;
+  }
+
+  RefPtr<Runnable> destroyRunnable =
+    NewNonOwningRunnableMethod("CacheParentBridge::Destroy",
+                               this,
+                               &CacheParentBridge::Destroy);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(destroyRunnable,
+                                                   NS_DISPATCH_NORMAL));
+}
+
 // StorageDBParent::UsageParentBridge
 
 namespace {
 
 class UsageRunnable : public Runnable
 {
 public:
   UsageRunnable(StorageDBParent* aParent,
@@ -777,31 +939,70 @@ public:
 private:
   NS_IMETHOD Run() override
   {
     if (!mParent->IPCOpen()) {
       return NS_OK;
     }
 
     mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage);
+
+    mParent = nullptr;
+
     return NS_OK;
   }
 
   RefPtr<StorageDBParent> mParent;
   nsCString mOriginScope;
   int64_t mUsage;
 };
 
 } // namespace
 
 void
 StorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage)
 {
   RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mOriginScope, aUsage);
-  NS_DispatchToMainThread(r);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+}
+
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::UsageParentBridge, Destroy)
+// But due to different strings used for refcount logging, this is done manually
+// for now.
+NS_IMETHODIMP_(MozExternalRefCountType)
+StorageDBParent::UsageParentBridge::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "StorageUsageBridge");
+  if (count == 0) {
+    Destroy();
+    return 0;
+  }
+  return count;
+}
+
+void
+StorageDBParent::UsageParentBridge::Destroy()
+{
+  if (mOwningEventTarget->IsOnCurrentThread()) {
+    delete this;
+    return;
+  }
+
+  RefPtr<Runnable> destroyRunnable =
+    NewNonOwningRunnableMethod("UsageParentBridge::Destroy",
+                               this,
+                               &UsageParentBridge::Destroy);
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(destroyRunnable,
+                                                   NS_DISPATCH_NORMAL));
 }
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
 PBackgroundStorageParent*
 AllocPBackgroundStorageParent()
--- a/dom/storage/StorageIPC.h
+++ b/dom/storage/StorageIPC.h
@@ -23,24 +23,32 @@ namespace dom {
 
 class LocalStorageManager;
 class PBackgroundStorageParent;
 
 // Child side of the IPC protocol, exposes as DB interface but
 // is responsible to send all requests to the parent process
 // and expects asynchronous answers. Those are then transparently
 // forwarded back to consumers on the child process.
-class StorageDBChild final : public StorageDBBridge
-                           , public PBackgroundStorageChild
+class StorageDBChild final
+  : public PBackgroundStorageChild
 {
+  class ShutdownObserver;
+
   virtual ~StorageDBChild();
 
 public:
   explicit StorageDBChild(LocalStorageManager* aManager);
 
+  static StorageDBChild*
+  Get();
+
+  static StorageDBChild*
+  GetOrCreate();
+
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
   NS_IMETHOD_(MozExternalRefCountType) Release(void);
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
   virtual nsresult Init();
   virtual nsresult Shutdown();
@@ -64,27 +72,31 @@ public:
   virtual void AsyncClearAll()
   {
     if (mOriginsHavingData) {
       mOriginsHavingData->Clear(); /* NO-OP on the child process otherwise */
     }
   }
 
   virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
-    { /* NO-OP on the child process */ }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
-    { /* NO-OP on the child process */ }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual void AsyncFlush()
-    { SendAsyncFlush(); }
+  {
+    MOZ_CRASH("Shouldn't be called!");
+  }
 
   virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix);
-  virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
-    { NS_NOTREACHED("Not implemented for child process"); }
 
 private:
   mozilla::ipc::IPCResult RecvObserve(const nsCString& aTopic,
                                       const nsString& aOriginAttributesPattern,
                                       const nsCString& aOriginScope);
   mozilla::ipc::IPCResult RecvLoadItem(const nsCString& aOriginSuffix,
                                        const nsCString& aOriginNoSuffix,
                                        const nsString& aKey,
@@ -143,17 +155,18 @@ public:
 public:
   // Fake cache class receiving async callbacks from DB thread, sending
   // them back to appropriate cache object on the child process.
   class CacheParentBridge : public LocalStorageCacheBridge {
   public:
     CacheParentBridge(StorageDBParent* aParentDB,
                       const nsACString& aOriginSuffix,
                       const nsACString& aOriginNoSuffix)
-      : mParent(aParentDB)
+      : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
+      , mParent(aParentDB)
       , mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix)
       , mLoaded(false), mLoadedCount(0) {}
     virtual ~CacheParentBridge() {}
 
     // LocalStorageCacheBridge
     virtual const nsCString Origin() const;
     virtual const nsCString& OriginNoSuffix() const
       { return mOriginNoSuffix; }
@@ -163,37 +176,53 @@ public:
       { return mLoaded; }
     virtual uint32_t LoadedCount()
       { return mLoadedCount; }
 
     virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
     virtual void LoadDone(nsresult aRv);
     virtual void LoadWait();
 
+    NS_IMETHOD_(void)
+    Release(void);
+
   private:
+    void
+    Destroy();
+
+    nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
     RefPtr<StorageDBParent> mParent;
     nsCString mOriginSuffix, mOriginNoSuffix;
     bool mLoaded;
     uint32_t mLoadedCount;
   };
 
   // Fake usage class receiving async callbacks from DB thread
   class UsageParentBridge : public StorageUsageBridge
   {
   public:
     UsageParentBridge(StorageDBParent* aParentDB,
                       const nsACString& aOriginScope)
-      : mParent(aParentDB), mOriginScope(aOriginScope) {}
+      : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
+      , mParent(aParentDB)
+      , mOriginScope(aOriginScope) {}
     virtual ~UsageParentBridge() {}
 
     // StorageUsageBridge
     virtual const nsCString& OriginScope() { return mOriginScope; }
     virtual void LoadUsage(const int64_t usage);
 
+    NS_IMETHOD_(MozExternalRefCountType)
+    Release(void);
+
   private:
+    void
+    Destroy();
+
+    nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
     RefPtr<StorageDBParent> mParent;
     nsCString mOriginScope;
   };
 
 private:
   // IPC
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   mozilla::ipc::IPCResult RecvAsyncPreload(const nsCString& aOriginSuffix,
--- a/dom/storage/StorageObserver.cpp
+++ b/dom/storage/StorageObserver.cpp
@@ -65,18 +65,19 @@ StorageObserver::Init()
   obs->AddObserver(sSelf, "perm-changed", true);
   obs->AddObserver(sSelf, "browser:purge-domain-data", true);
   obs->AddObserver(sSelf, "last-pb-context-exited", true);
   obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
   obs->AddObserver(sSelf, "extension:purge-localStorage", true);
 
   // Shutdown
   obs->AddObserver(sSelf, "profile-after-change", true);
-  obs->AddObserver(sSelf, "profile-before-change", true);
-  obs->AddObserver(sSelf, "xpcom-shutdown", true);
+  if (XRE_IsParentProcess()) {
+    obs->AddObserver(sSelf, "profile-before-change", true);
+  }
 
   // Observe low device storage notifications.
   obs->AddObserver(sSelf, "disk-space-watcher", true);
 
   // Testing
 #ifdef DOM_STORAGE_TESTS
   Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
 #endif
@@ -140,16 +141,22 @@ StorageObserver::Notify(const char* aTop
                         const nsACString& aOriginScope)
 {
   for (uint32_t i = 0; i < mSinks.Length(); ++i) {
     StorageObserverSink* sink = mSinks[i];
     sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
   }
 }
 
+void
+StorageObserver::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
+{
+  mBackgroundThread = aBackgroundThread;
+}
+
 NS_IMETHODIMP
 StorageObserver::Observe(nsISupports* aSubject,
                          const char* aTopic,
                          const char16_t* aData)
 {
   nsresult rv;
 
   // Start the thread that opens the database.
@@ -172,33 +179,39 @@ StorageObserver::Observe(nsISupports* aS
     nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
     if (!timer) {
       return NS_ERROR_UNEXPECTED;
     }
 
     if (timer == mDBThreadStartDelayTimer) {
       mDBThreadStartDelayTimer = nullptr;
 
+    // XXX Fix me!
+#if 0
       StorageDBBridge* db = LocalStorageCache::StartDatabase();
       NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+#endif
     }
 
     return NS_OK;
   }
 
   // Clear everything, caches + database
   if (!strcmp(aTopic, "cookie-changed")) {
     if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
       return NS_OK;
     }
 
+    // XXX Fix me!
+#if 0
     StorageDBBridge* db = LocalStorageCache::StartDatabase();
     NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
 
     db->AsyncClearAll();
+#endif
 
     Notify("cookie-cleared");
 
     return NS_OK;
   }
 
   // Clear from caches everything that has been stored
   // while in session-only mode
@@ -249,20 +262,23 @@ StorageObserver::Observe(nsISupports* aS
 
     Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
            originScope);
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "extension:purge-localStorage")) {
+    // XXX Fix me!
+#if 0
     StorageDBBridge* db = LocalStorageCache::StartDatabase();
     NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
 
     db->AsyncClearAll();
+#endif
 
     Notify("extension:purge-localStorage-caches");
 
     return NS_OK;
   }
 
   // Clear everything (including so and pb data) from caches and database
   // for the gived domain and subdomains.
@@ -282,20 +298,23 @@ StorageObserver::Observe(nsISupports* aS
                         fallible);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsAutoCString originScope;
     rv = CreateReversedDomain(aceDomain, originScope);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    // XXX Fix me!
+#if 0
     StorageDBBridge* db = LocalStorageCache::StartDatabase();
     NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
 
     db->AsyncClearMatchingOrigin(originScope);
+#endif
 
     Notify("domain-data-cleared", EmptyString(), originScope);
 
     return NS_OK;
   }
 
   // Clear all private-browsing caches
   if (!strcmp(aTopic, "last-pb-context-exited")) {
@@ -307,37 +326,49 @@ StorageObserver::Observe(nsISupports* aS
   // Clear data of the origins whose prefixes will match the suffix.
   if (!strcmp(aTopic, "clear-origin-attributes-data")) {
     OriginAttributesPattern pattern;
     if (!pattern.Init(nsDependentString(aData))) {
       NS_ERROR("Cannot parse origin attributes pattern");
       return NS_ERROR_FAILURE;
     }
 
+    // XXX Fix me!
+#if 0
     StorageDBBridge* db = LocalStorageCache::StartDatabase();
     NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
 
     db->AsyncClearMatchingOriginAttributes(pattern);
+#endif
 
     Notify("origin-attr-pattern-cleared", nsDependentString(aData));
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "profile-after-change")) {
     Notify("profile-change");
 
     return NS_OK;
   }
 
-  if (!strcmp(aTopic, "profile-before-change") ||
-      !strcmp(aTopic, "xpcom-shutdown")) {
-    rv = LocalStorageCache::StopDatabase();
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Error while stopping Storage DB background thread");
+  if (!strcmp(aTopic, "profile-before-change")) {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
+    if (mBackgroundThread) {
+      bool done = false;
+
+      RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
+        new StorageDBThread::ShutdownRunnable(done);
+      MOZ_ALWAYS_SUCCEEDS(
+        mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
+
+      MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
+
+      mBackgroundThread = nullptr;
     }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "disk-space-watcher")) {
     if (NS_LITERAL_STRING("full").Equals(aData)) {
       Notify("low-disk-space");
@@ -345,20 +376,23 @@ StorageObserver::Observe(nsISupports* aS
       Notify("no-low-disk-space");
     }
 
     return NS_OK;
   }
 
 #ifdef DOM_STORAGE_TESTS
   if (!strcmp(aTopic, "domstorage-test-flush-force")) {
+    // XXX Fix me!
+#if 0
     StorageDBBridge* db = LocalStorageCache::GetDatabase();
     if (db) {
       db->AsyncFlush();
     }
+#endif
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "domstorage-test-flushed")) {
     // Only used to propagate to IPC children
     Notify("test-flushed");
 
--- a/dom/storage/StorageObserver.h
+++ b/dom/storage/StorageObserver.h
@@ -46,23 +46,28 @@ public:
   static StorageObserver* Self() { return sSelf; }
 
   void AddSink(StorageObserverSink* aObs);
   void RemoveSink(StorageObserverSink* aObs);
   void Notify(const char* aTopic,
               const nsAString& aOriginAttributesPattern = EmptyString(),
               const nsACString& aOriginScope = EmptyCString());
 
+  void
+  NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
+
 private:
   virtual ~StorageObserver() {}
 
   static void TestingPrefChanged(const char* aPrefName, void* aClosure);
 
   static StorageObserver* sSelf;
 
+  nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
   // Weak references
   nsTArray<StorageObserverSink*> mSinks;
   nsCOMPtr<nsITimer> mDBThreadStartDelayTimer;
 };
 
 } // namespace dom
 } // namespace mozilla