Bug 1286798 - Part 6: Fix a dead lock in the single process case; r=asuth,janv
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:47:30 +0100
changeset 505224 0b0a03cf7b64cc9cc6eff36c3ae7f56f010e944c
parent 505223 25c5ff3cec8f573b05d2435859ba68e4ad53a4a0
child 505225 d5f293542508df0e6c56623f30b2d13b218c617f
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, janv
bugs1286798
milestone65.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 1286798 - Part 6: Fix a dead lock in the single process case; r=asuth,janv Expose the nested main event target, so it can be used in the single process case by the parent side to process runnables which need to run on the main thread. After this change we don't have use hacks like getting profile directory path on the child side and sending it to the parent. The parent side can now do it freely even in the single process case. This patch was enhanced by asuth to not tunnel the nested main event target through IPC.
dom/localstorage/ActorsParent.cpp
dom/localstorage/LSObject.cpp
dom/localstorage/LSObject.h
dom/localstorage/PBackgroundLSSharedTypes.ipdlh
dom/quota/ActorsParent.cpp
dom/quota/QuotaManager.h
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ActorsParent.h"
 
 #include "LocalStorageCommon.h"
+#include "LSObject.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
@@ -701,20 +702,17 @@ class PrepareDatastoreOp
 {
   enum class State
   {
     // Just created on the PBackground thread. Next step is OpeningOnMainThread
     // or OpeningOnOwningThread.
     Initial,
 
     // Waiting to open/opening on the main thread. Next step is FinishOpen.
-    OpeningOnMainThread,
-
-    // Waiting to open/opening on the owning thread. Next step is FinishOpen.
-    OpeningOnOwningThread,
+    Opening,
 
     // Checking if a prepare datastore operation is already running for given
     // origin on the PBackground thread. Next step is PreparationPending.
     FinishOpen,
 
     // Opening directory or initializing quota manager on the PBackground
     // thread. Next step is either DirectoryOpenPending if quota manager is
     // already initialized or QuotaManagerPending if quota manager needs to be
@@ -748,30 +746,32 @@ class PrepareDatastoreOp
     // Waiting to send/sending results on the PBackground thread. Next step is
     // Completed.
     SendingResults,
 
     // All done.
     Completed
   };
 
+  nsCOMPtr<nsIEventTarget> mMainEventTarget;
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Datastore> mDatastore;
   const LSRequestPrepareDatastoreParams mParams;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
   State mState;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 public:
-  explicit PrepareDatastoreOp(const LSRequestParams& aParams);
+  PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
+                     const LSRequestParams& aParams);
 
   bool
   OriginIsKnown() const
   {
     AssertIsOnOwningThread();
 
     return !mOrigin.IsEmpty();
   }
@@ -803,20 +803,17 @@ public:
 
   void
   Dispatch() override;
 
 private:
   ~PrepareDatastoreOp() override;
 
   nsresult
-  OpenOnMainThread();
-
-  nsresult
-  OpenOnOwningThread();
+  Open();
 
   nsresult
   FinishOpen();
 
   nsresult
   PreparationOpen();
 
   nsresult
@@ -1061,41 +1058,29 @@ AllocPBackgroundLSRequestParent(PBackgro
                                 const LSRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
+  // If we're in the same process as the actor, we need to get the target event
+  // queue from the current RequestHelper.
+  nsCOMPtr<nsIEventTarget> mainEventTarget;
+  if (!BackgroundParent::IsOtherProcessActor(aBackgroundActor)) {
+    mainEventTarget = LSObject::GetSyncLoopEventTarget();
+  }
+
   RefPtr<LSRequestBase> actor;
 
   switch (aParams.type()) {
     case LSRequestParams::TLSRequestPrepareDatastoreParams: {
-      bool isOtherProcess =
-        BackgroundParent::IsOtherProcessActor(aBackgroundActor);
-
-      const LSRequestPrepareDatastoreParams& params =
-        aParams.get_LSRequestPrepareDatastoreParams();
-
-      const PrincipalOrQuotaInfo& info = params.info();
-
-      PrincipalOrQuotaInfo::Type infoType = info.type();
-
-      bool paramsOk =
-        (isOtherProcess && infoType == PrincipalOrQuotaInfo::TPrincipalInfo) ||
-        (!isOtherProcess && infoType == PrincipalOrQuotaInfo::TQuotaInfo);
-
-      if (NS_WARN_IF(!paramsOk)) {
-        ASSERT_UNLESS_FUZZING();
-        return nullptr;
-      }
-
       RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
-        new PrepareDatastoreOp(aParams);
+        new PrepareDatastoreOp(mainEventTarget, aParams);
 
       if (!gPrepareDatastoreOps) {
         gPrepareDatastoreOps = new PrepareDatastoreOpArray();
       }
       gPrepareDatastoreOps->AppendElement(prepareDatastoreOp);
 
       actor = std::move(prepareDatastoreOp);
 
@@ -1567,18 +1552,20 @@ LSRequestBase::RecvCancel()
 
   return IPC_OK();
 }
 
 /*******************************************************************************
  * PrepareDatastoreOp
  ******************************************************************************/
 
-PrepareDatastoreOp::PrepareDatastoreOp(const LSRequestParams& aParams)
-  : mParams(aParams.get_LSRequestPrepareDatastoreParams())
+PrepareDatastoreOp::PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
+                                       const LSRequestParams& aParams)
+  : mMainEventTarget(aMainEventTarget)
+  , mParams(aParams.get_LSRequestPrepareDatastoreParams())
   , mState(State::Initial)
   , mRequestedDirectoryLock(false)
   , mInvalidated(false)
 {
   MOZ_ASSERT(aParams.type() ==
                LSRequestParams::TLSRequestPrepareDatastoreParams);
 }
 
@@ -1589,45 +1576,37 @@ PrepareDatastoreOp::~PrepareDatastoreOp(
                 mState == State::Initial || mState == State::Completed);
 }
 
 void
 PrepareDatastoreOp::Dispatch()
 {
   AssertIsOnOwningThread();
 
-  const PrincipalOrQuotaInfo& info = mParams.info();
-
-  if (info.type() == PrincipalOrQuotaInfo::TPrincipalInfo) {
-    mState = State::OpeningOnMainThread;
+  mState = State::Opening;
+
+  if (mMainEventTarget) {
+    MOZ_ALWAYS_SUCCEEDS(mMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+  } else {
     MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-  } else {
-    MOZ_ASSERT(info.type() == PrincipalOrQuotaInfo::TQuotaInfo);
-
-    mState = State::OpeningOnOwningThread;
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
   }
 }
 
 nsresult
-PrepareDatastoreOp::OpenOnMainThread()
+PrepareDatastoreOp::Open()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::OpeningOnMainThread);
+  MOZ_ASSERT(mState == State::Opening);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
-  const PrincipalOrQuotaInfo& info = mParams.info();
-
-  MOZ_ASSERT(info.type() == PrincipalOrQuotaInfo::TPrincipalInfo);
-
-  const PrincipalInfo& principalInfo = info.get_PrincipalInfo();
+  const PrincipalInfo& principalInfo = mParams.principalInfo();
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin);
   } else {
     MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
 
     nsresult rv;
     nsCOMPtr<nsIPrincipal> principal =
@@ -1653,43 +1632,16 @@ PrepareDatastoreOp::OpenOnMainThread()
 
   mState = State::FinishOpen;
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
 nsresult
-PrepareDatastoreOp::OpenOnOwningThread()
-{
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::OpeningOnOwningThread);
-
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
-      !MayProceed()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  const PrincipalOrQuotaInfo& info = mParams.info();
-
-  MOZ_ASSERT(info.type() == PrincipalOrQuotaInfo::TQuotaInfo);
-
-  const QuotaInfo& quotaInfo = info.get_QuotaInfo();
-
-  mSuffix = quotaInfo.suffix();
-  mGroup = quotaInfo.group();
-  mOrigin = quotaInfo.origin();
-
-  mState = State::FinishOpen;
-  MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
-
-  return NS_OK;
-}
-
-nsresult
 PrepareDatastoreOp::FinishOpen()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::FinishOpen);
   MOZ_ASSERT(gPrepareDatastoreOps);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
@@ -1697,19 +1649,17 @@ PrepareDatastoreOp::FinishOpen()
   }
 
   // Normally it's safe to access member variables without a mutex because even
   // though we hop between threads, the variables are never accessed by multiple
   // threads at the same time.
   // However, the methods OriginIsKnown and Origin can be called at any time.
   // So we have to make sure the member variable is set on the same thread as
   // those methods are called.
-  if (mParams.info().type() == PrincipalOrQuotaInfo::TPrincipalInfo) {
-    mOrigin = mMainThreadOrigin;
-  }
+  mOrigin = mMainThreadOrigin;
 
   MOZ_ASSERT(!mOrigin.IsEmpty());
 
   mState = State::PreparationPending;
 
   // See if this PrepareDatastoreOp needs to wait.
   bool foundThis = false;
   for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) {
@@ -1755,17 +1705,17 @@ PrepareDatastoreOp::BeginDatastorePrepar
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     return NS_OK;
   }
 
   mState = State::QuotaManagerPending;
-  QuotaManager::GetOrCreate(this);
+  QuotaManager::GetOrCreate(this, mMainEventTarget);
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::QuotaManagerOpen()
 {
   AssertIsOnOwningThread();
@@ -2072,22 +2022,18 @@ PrepareDatastoreOp::Cleanup()
 NS_IMPL_ISUPPORTS_INHERITED0(PrepareDatastoreOp, LSRequestBase)
 
 NS_IMETHODIMP
 PrepareDatastoreOp::Run()
 {
   nsresult rv;
 
   switch (mState) {
-    case State::OpeningOnMainThread:
-      rv = OpenOnMainThread();
-      break;
-
-    case State::OpeningOnOwningThread:
-      rv = OpenOnOwningThread();
+    case State::Opening:
+      rv = Open();
       break;
 
     case State::FinishOpen:
       rv = FinishOpen();
       break;
 
     case State::PreparationPending:
       rv = BeginDatastorePreparation();
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -78,16 +78,27 @@ public:
 
   void
   AssertIsOnOwningThread() const
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(IsOnOwningThread());
   }
 
+  // Used for requests from the parent process to the parent process; in that
+  // case we want ActorsParent to know our event-target and this is better than
+  // trying to tunnel the pointer through IPC.
+  const nsCOMPtr<nsIEventTarget>&
+  GetSyncLoopEventTarget() const
+  {
+    MOZ_ASSERT(XRE_IsParentProcess());
+
+    return mNestedEventTarget;
+  }
+
   nsresult
   StartAndReturnResponse(LSRequestResponse& aResponse);
 
   nsresult
   CancelOnAnyThread();
 
 private:
   ~RequestHelper()
@@ -141,63 +152,51 @@ LSObject::Create(nsPIDOMWindowInner* aWi
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
   MOZ_ASSERT(sop);
 
   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
   if (NS_WARN_IF(!principal)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv;
-
-  nsAutoPtr<PrincipalOrQuotaInfo> info(new PrincipalOrQuotaInfo());
-  if (XRE_IsParentProcess()) {
-    nsCString suffix;
-    nsCString group;
-    nsCString origin;
-    rv = QuotaManager::GetInfoFromPrincipal(principal,
-                                            &suffix,
-                                            &group,
-                                            &origin);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    QuotaInfo quotaInfo;
-    quotaInfo.suffix() = suffix;
-    quotaInfo.group() = group;
-    quotaInfo.origin() = origin;
-
-    *info = quotaInfo;
-
-    // This service has to be started on the main thread currently.
-    nsCOMPtr<mozIStorageService> ss;
-    if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
-      return NS_ERROR_FAILURE;
-    }
-  } else {
-    PrincipalInfo principalInfo;
-    rv = PrincipalToPrincipalInfo(principal, &principalInfo);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
-
-    *info = principalInfo;
+  nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+  nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
+  MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo);
+
   RefPtr<LSObject> object = new LSObject(aWindow, principal);
-  object->mInfo = std::move(info);
+  object->mPrincipalInfo = std::move(principalInfo);
 
   object.forget(aStorage);
   return NS_OK;
 }
 
 // static
+already_AddRefed<nsIEventTarget>
+LSObject::GetSyncLoopEventTarget()
+{
+  RefPtr<RequestHelper> helper;
+
+  {
+    StaticMutexAutoLock lock(gRequestHelperMutex);
+    helper = gRequestHelper;
+  }
+
+  nsCOMPtr<nsIEventTarget> target;
+  if (helper) {
+    target = helper->GetSyncLoopEventTarget();
+  }
+
+  return target.forget();
+}
+
+// static
 void
 LSObject::CancelSyncLoop()
 {
   RefPtr<RequestHelper> helper;
 
   {
     StaticMutexAutoLock lock(gRequestHelperMutex);
     helper = gRequestHelper;
@@ -431,17 +430,17 @@ LSObject::EnsureDatabase()
   // it can fail and parent would keep an extra strong ref to the datastore.
   PBackgroundChild* backgroundActor =
     BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!backgroundActor)) {
     return NS_ERROR_FAILURE;
   }
 
   LSRequestPrepareDatastoreParams params;
-  params.info() = *mInfo;
+  params.principalInfo() = *mPrincipalInfo;
 
   RefPtr<RequestHelper> helper = new RequestHelper(this, params);
 
   LSRequestResponse response;
 
   // This will start and finish the request on the DOM File thread.
   // The owning thread is synchronously blocked while the request is
   // asynchronously processed on the DOM File thread.
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -11,36 +11,51 @@
 
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 
 class ErrorResult;
 
+namespace ipc {
+
+class PrincipalInfo;
+
+} // namespace ipc
+
 namespace dom {
 
 class LSDatabase;
 class LSRequestChild;
 class LSRequestChildCallback;
 class LSRequestParams;
-class PrincipalOrQuotaInfo;
 
 class LSObject final
   : public Storage
 {
-  nsAutoPtr<PrincipalOrQuotaInfo> mInfo;
+  typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+  nsAutoPtr<PrincipalInfo> mPrincipalInfo;
 
   RefPtr<LSDatabase> mDatabase;
 
 public:
   static nsresult
   Create(nsPIDOMWindowInner* aWindow,
          Storage** aStorage);
 
+  /**
+   * Used for requests from the parent process to the parent process; in that
+   * case we want ActorsParent to know our event-target and this is better than
+   * trying to tunnel the pointer through IPC.
+   */
+  static already_AddRefed<nsIEventTarget>
+  GetSyncLoopEventTarget();
+
   static void
   CancelSyncLoop();
 
   void
   AssertIsOnOwningThread() const
   {
     NS_ASSERT_OWNINGTHREAD(LSObject);
   }
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -2,30 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include PBackgroundSharedTypes;
 
 namespace mozilla {
 namespace dom {
 
-struct QuotaInfo {
-  nsCString suffix;
-  nsCString group;
-  nsCString origin;
-};
-
-union PrincipalOrQuotaInfo {
-  PrincipalInfo;
-  QuotaInfo;
-};
-
 struct LSRequestPrepareDatastoreParams
 {
-  PrincipalOrQuotaInfo info;
+  PrincipalInfo principalInfo;
 };
 
 union LSRequestParams
 {
   LSRequestPrepareDatastoreParams;
 };
 
 } // namespace dom
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -400,16 +400,17 @@ public:
 private:
   ~DirectoryLockImpl();
 };
 
 class QuotaManager::CreateRunnable final
   : public BackgroundThreadObject
   , public Runnable
 {
+  nsCOMPtr<nsIEventTarget> mMainEventTarget;
   nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
   nsString mBaseDirPath;
   RefPtr<QuotaManager> mManager;
   nsresult mResultCode;
 
   enum class State
   {
     Initial,
@@ -417,18 +418,19 @@ class QuotaManager::CreateRunnable final
     RegisteringObserver,
     CallingCallbacks,
     Completed
   };
 
   State mState;
 
 public:
-  CreateRunnable()
+  explicit CreateRunnable(nsIEventTarget* aMainEventTarget)
     : Runnable("dom::quota::QuotaManager::CreateRunnable")
+    , mMainEventTarget(aMainEventTarget)
     , mResultCode(NS_OK)
     , mState(State::Initial)
   {
     AssertIsOnBackgroundThread();
   }
 
   void
   AddCallback(nsIRunnable* aCallback)
@@ -2775,17 +2777,21 @@ auto
 QuotaManager::
 CreateRunnable::GetNextState(nsCOMPtr<nsIEventTarget>& aThread) -> State
 {
   switch (mState) {
     case State::Initial:
       aThread = mOwningThread;
       return State::CreatingManager;
     case State::CreatingManager:
-      aThread = GetMainThreadEventTarget();
+      if (mMainEventTarget) {
+        aThread = mMainEventTarget;
+      } else {
+        aThread = GetMainThreadEventTarget();
+      }
       return State::RegisteringObserver;
     case State::RegisteringObserver:
       aThread = mOwningThread;
       return State::CallingCallbacks;
     case State::CallingCallbacks:
       aThread = nullptr;
       return State::Completed;
     default:
@@ -3240,34 +3246,41 @@ QuotaManager::QuotaManager()
 
 QuotaManager::~QuotaManager()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!gInstance || gInstance == this);
 }
 
 void
-QuotaManager::GetOrCreate(nsIRunnable* aCallback)
+QuotaManager::GetOrCreate(nsIRunnable* aCallback,
+                          nsIEventTarget* aMainEventTarget)
 {
   AssertIsOnBackgroundThread();
 
   if (IsShuttingDown()) {
     MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
     return;
   }
 
   if (gInstance || gCreateFailed) {
     MOZ_ASSERT(!gCreateRunnable);
     MOZ_ASSERT_IF(gCreateFailed, !gInstance);
 
     MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
   } else {
     if (!gCreateRunnable) {
-      gCreateRunnable = new CreateRunnable();
-      MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable));
+      gCreateRunnable = new CreateRunnable(aMainEventTarget);
+      if (aMainEventTarget) {
+        MOZ_ALWAYS_SUCCEEDS(aMainEventTarget->Dispatch(gCreateRunnable,
+                                                       NS_DISPATCH_NORMAL));
+      } else {
+        MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable));
+      }
+
     }
 
     gCreateRunnable->AddCallback(aCallback);
   }
 }
 
 // static
 QuotaManager*
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -112,17 +112,18 @@ public:
   {
     static bool kRunningXPCShellTests = !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
     return kRunningXPCShellTests;
   }
 
   static const char kReplaceChars[];
 
   static void
-  GetOrCreate(nsIRunnable* aCallback);
+  GetOrCreate(nsIRunnable* aCallback,
+              nsIEventTarget* aMainEventTarget = nullptr);
 
   // Returns a non-owning reference.
   static QuotaManager*
   Get();
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -55,17 +55,17 @@ fail-if = appname == "thunderbird"
 [test_ext_extension.js]
 [test_ext_extensionPreferencesManager.js]
 [test_ext_extensionSettingsStore.js]
 [test_ext_extension_content_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_extension_startup_telemetry.js]
 [test_ext_geturl.js]
 [test_ext_idle.js]
-#[test_ext_localStorage.js]
+[test_ext_localStorage.js]
 [test_ext_management.js]
 skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
 [test_ext_management_uninstall_self.js]
 [test_ext_messaging_startup.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_permission_xhr.js]