Bug 1350637 - Part 2: Core changes for LocalStorage on PBackground. r=asuth, a=lizzard
authorJan Varga <jan.varga@gmail.com>
Wed, 26 Jul 2017 12:19:13 +0200
changeset 421295 5b5c8f31b0a7
parent 421294 37a05ddf9427
child 421296 b0bed209e7f7
push id7647
push userryanvm@gmail.com
push dateMon, 21 Aug 2017 19:15:39 +0000
treeherdermozilla-beta@6a2167880016 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, lizzard
bugs1350637
milestone56.0
Bug 1350637 - Part 2: Core changes for LocalStorage on PBackground. r=asuth, a=lizzard - 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
@@ -64,18 +64,19 @@ StorageObserver::Init()
   obs->AddObserver(sSelf, "cookie-changed", true);
   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);
 
   // 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
@@ -139,16 +140,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.
@@ -171,33 +178,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
@@ -270,20 +283,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")) {
@@ -295,37 +311,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");
@@ -333,20 +361,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