Bug 1286798 - Part 18: Verify that data is persisted on disk; r=asuth,mrbkap
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:48:11 +0100
changeset 448800 61ce2d795e8e3db93cb8a191bad0a80b67eee92a
parent 448799 03b477c00c4f5ca4321efdc32bffa43bcae510d3
child 448801 5714205a7bf9ea1083a7c24f18b188d8304679cd
push id35128
push userrmaries@mozilla.com
push dateFri, 30 Nov 2018 03:06:13 +0000
treeherdermozilla-central@e18555e129e7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, mrbkap
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 18: Verify that data is persisted on disk; r=asuth,mrbkap New methods open() and close() are added to the Storage WebIDL interface. They are only available when a pref is set and are only intended for testing. There's also a new method resetStoragesForPrincipal() which is used as a callback for close() since datastores don't release directory locks immediately. resetStoragesForPrincipal() requests an exclusive lock for given origin, so it must wait for any exising shared locks to be released.
dom/localstorage/LSObject.cpp
dom/localstorage/LSObject.h
dom/quota/ActorsChild.cpp
dom/quota/ActorsParent.cpp
dom/quota/PQuota.ipdl
dom/quota/PQuotaRequest.ipdl
dom/quota/QuotaManagerService.cpp
dom/quota/nsIQuotaManagerService.idl
dom/storage/Storage.h
dom/tests/mochitest/localstorage/localStorageCommon.js
dom/tests/mochitest/localstorage/mochitest.ini
dom/tests/mochitest/localstorage/test_bug600307-DBOps.html
dom/webidl/Storage.webidl
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -552,16 +552,48 @@ LSObject::Clear(nsIPrincipal& aSubjectPr
     return;
   }
 
   if (info.changed()) {
     OnChange(VoidString(), VoidString(), VoidString());
   }
 }
 
+void
+LSObject::Open(nsIPrincipal& aSubjectPrincipal,
+               ErrorResult& aError)
+{
+  AssertIsOnOwningThread();
+
+  if (!CanUseStorage(aSubjectPrincipal)) {
+    aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  nsresult rv = EnsureDatabase();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aError.Throw(rv);
+    return;
+  }
+}
+
+void
+LSObject::Close(nsIPrincipal& aSubjectPrincipal,
+                ErrorResult& aError)
+{
+  AssertIsOnOwningThread();
+
+  if (!CanUseStorage(aSubjectPrincipal)) {
+    aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  DropDatabase();
+}
+
 NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
 NS_IMPL_RELEASE_INHERITED(LSObject, Storage)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LSObject)
 NS_INTERFACE_MAP_END_INHERITING(Storage)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(LSObject)
 
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -135,16 +135,24 @@ public:
   RemoveItem(const nsAString& aKey,
              nsIPrincipal& aSubjectPrincipal,
              ErrorResult& aError) override;
 
   void
   Clear(nsIPrincipal& aSubjectPrincipal,
         ErrorResult& aError) override;
 
+  void
+  Open(nsIPrincipal& aSubjectPrincipal,
+       ErrorResult& aError) override;
+
+  void
+  Close(nsIPrincipal& aSubjectPrincipal,
+        ErrorResult& aError) override;
+
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(LSObject, Storage)
 
 private:
   LSObject(nsPIDOMWindowInner* aWindow,
            nsIPrincipal* aPrincipal);
 
   ~LSObject();
--- a/dom/quota/ActorsChild.cpp
+++ b/dom/quota/ActorsChild.cpp
@@ -313,16 +313,17 @@ QuotaRequestChild::Recv__delete__(const 
   switch (aResponse.type()) {
     case RequestResponse::Tnsresult:
       HandleResponse(aResponse.get_nsresult());
       break;
 
     case RequestResponse::TInitResponse:
     case RequestResponse::TInitTemporaryStorageResponse:
     case RequestResponse::TClearOriginResponse:
+    case RequestResponse::TResetOriginResponse:
     case RequestResponse::TClearDataResponse:
     case RequestResponse::TClearAllResponse:
     case RequestResponse::TResetAllResponse:
     case RequestResponse::TPersistResponse:
       HandleResponse();
       break;
 
     case RequestResponse::TInitOriginResponse:
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -1309,34 +1309,39 @@ private:
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class ClearRequestBase
   : public QuotaRequestBase
 {
 protected:
-  explicit ClearRequestBase(bool aExclusive)
+  const bool mClear;
+
+protected:
+  ClearRequestBase(bool aExclusive,
+                   bool aClear)
     : QuotaRequestBase(aExclusive)
+    , mClear(aClear)
   {
     AssertIsOnOwningThread();
   }
 
   void
   DeleteFiles(QuotaManager* aQuotaManager,
               PersistenceType aPersistenceType);
 
   nsresult
   DoDirectoryWork(QuotaManager* aQuotaManager) override;
 };
 
 class ClearOriginOp final
   : public ClearRequestBase
 {
-  const ClearOriginParams mParams;
+  const ClearResetOriginParams mParams;
 
 public:
   explicit ClearOriginOp(const RequestParams& aParams);
 
   bool
   Init(Quota* aQuota) override;
 
 private:
@@ -6810,16 +6815,17 @@ Quota::AllocPQuotaRequestParent(const Re
       actor = new InitTemporaryStorageOp();
       break;
 
     case RequestParams::TInitOriginParams:
       actor = new InitOriginOp(aParams);
       break;
 
     case RequestParams::TClearOriginParams:
+    case RequestParams::TResetOriginParams:
       actor = new ClearOriginOp(aParams);
       break;
 
     case RequestParams::TClearDataParams:
       actor = new ClearDataOp(aParams);
       break;
 
     case RequestParams::TClearAllParams:
@@ -7646,17 +7652,16 @@ ResetOrClearOp::GetResponse(RequestRespo
 
 void
 ClearRequestBase::DeleteFiles(QuotaManager* aQuotaManager,
                               PersistenceType aPersistenceType)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aQuotaManager);
 
-
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aQuotaManager->GetStoragePath(aPersistenceType),
                                 false, getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   nsCOMPtr<nsIDirectoryEnumerator> entries;
@@ -7810,32 +7815,39 @@ ClearRequestBase::DeleteFiles(QuotaManag
 
 nsresult
 ClearRequestBase::DoDirectoryWork(QuotaManager* aQuotaManager)
 {
   AssertIsOnIOThread();
 
   AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
 
-  if (mPersistenceType.IsNull()) {
-    for (const PersistenceType type : kAllPersistenceTypes) {
-      DeleteFiles(aQuotaManager, type);
-    }
-  } else {
-    DeleteFiles(aQuotaManager, mPersistenceType.Value());
+  if (mClear) {
+    if (mPersistenceType.IsNull()) {
+      for (const PersistenceType type : kAllPersistenceTypes) {
+        DeleteFiles(aQuotaManager, type);
+      }
+    } else {
+      DeleteFiles(aQuotaManager, mPersistenceType.Value());
+    }
   }
 
   return NS_OK;
 }
 
 ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
-  : ClearRequestBase(/* aExclusive */ true)
-  , mParams(aParams)
-{
-  MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams);
+  : ClearRequestBase(/* aExclusive */ true,
+                     aParams.type() == RequestParams::TClearOriginParams)
+  , mParams(aParams.type() == RequestParams::TClearOriginParams ?
+              aParams.get_ClearOriginParams().commonParams() :
+              aParams.get_ResetOriginParams().commonParams())
+
+{
+  MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams ||
+             aParams.type() == RequestParams::TResetOriginParams);
 }
 
 bool
 ClearOriginOp::Init(Quota* aQuota)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aQuota);
 
@@ -7879,35 +7891,40 @@ ClearOriginOp::DoInitOnMainThread()
   // Figure out which origin we're dealing with.
   nsCString origin;
   rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr,
                                           &origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mParams.clearAll()) {
+  if (mParams.matchAll()) {
     mOriginScope.SetFromPrefix(origin);
   } else {
     mOriginScope.SetFromOrigin(origin);
   }
 
   return NS_OK;
 }
 
 void
 ClearOriginOp::GetResponse(RequestResponse& aResponse)
 {
   AssertIsOnOwningThread();
 
-  aResponse = ClearOriginResponse();
+  if (mClear) {
+    aResponse = ClearOriginResponse();
+  } else {
+    aResponse = ResetOriginResponse();
+  }
 }
 
 ClearDataOp::ClearDataOp(const RequestParams& aParams)
-  : ClearRequestBase(/* aExclusive */ true)
+  : ClearRequestBase(/* aExclusive */ true,
+                     /* aClear */ true)
   , mParams(aParams)
 {
   MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
 }
 
 bool
 ClearDataOp::Init(Quota* aQuota)
 {
--- a/dom/quota/PQuota.ipdl
+++ b/dom/quota/PQuota.ipdl
@@ -46,24 +46,34 @@ struct OriginUsageParams
 };
 
 union UsageRequestParams
 {
   AllUsageParams;
   OriginUsageParams;
 };
 
-struct ClearOriginParams
+struct ClearResetOriginParams
 {
   PrincipalInfo principalInfo;
   PersistenceType persistenceType;
   bool persistenceTypeIsExplicit;
   Type clientType;
   bool clientTypeIsExplicit;
-  bool clearAll;
+  bool matchAll;
+};
+
+struct ClearOriginParams
+{
+  ClearResetOriginParams commonParams;
+};
+
+struct ResetOriginParams
+{
+  ClearResetOriginParams commonParams;
 };
 
 struct ClearDataParams
 {
   nsString pattern;
 };
 
 struct ClearAllParams
@@ -85,16 +95,17 @@ struct PersistParams
 };
 
 union RequestParams
 {
   InitParams;
   InitTemporaryStorageParams;
   InitOriginParams;
   ClearOriginParams;
+  ResetOriginParams;
   ClearDataParams;
   ClearAllParams;
   ResetAllParams;
   PersistedParams;
   PersistParams;
 };
 
 protocol PQuota
--- a/dom/quota/PQuotaRequest.ipdl
+++ b/dom/quota/PQuotaRequest.ipdl
@@ -20,16 +20,20 @@ struct InitOriginResponse
 {
   bool created;
 };
 
 struct ClearOriginResponse
 {
 };
 
+struct ResetOriginResponse
+{
+};
+
 struct ClearDataResponse
 {
 };
 
 struct ClearAllResponse
 {
 };
 
@@ -48,16 +52,17 @@ struct PersistResponse
 
 union RequestResponse
 {
   nsresult;
   InitResponse;
   InitTemporaryStorageResponse;
   InitOriginResponse;
   ClearOriginResponse;
+  ResetOriginResponse;
   ClearDataResponse;
   ClearAllResponse;
   ResetAllResponse;
   PersistedResponse;
   PersistResponse;
 };
 
 protocol PQuotaRequest
--- a/dom/quota/QuotaManagerService.cpp
+++ b/dom/quota/QuotaManagerService.cpp
@@ -73,16 +73,63 @@ CheckedPrincipalToPrincipalInfo(nsIPrinc
   if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
       aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
+nsresult
+GetClearResetOriginParams(nsIPrincipal* aPrincipal,
+                          const nsACString& aPersistenceType,
+                          const nsAString& aClientType,
+                          bool aMatchAll,
+                          ClearResetOriginParams& aParams)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                aParams.principalInfo());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  Nullable<PersistenceType> persistenceType;
+  rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (persistenceType.IsNull()) {
+    aParams.persistenceTypeIsExplicit() = false;
+  } else {
+    aParams.persistenceType() = persistenceType.Value();
+    aParams.persistenceTypeIsExplicit() = true;
+  }
+
+  Nullable<Client::Type> clientType;
+  rv = Client::NullableTypeFromText(aClientType, &clientType);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (clientType.IsNull()) {
+    aParams.clientTypeIsExplicit() = false;
+  } else {
+    aParams.clientType() = clientType.Value();
+    aParams.clientTypeIsExplicit() = true;
+  }
+
+  aParams.matchAll() = aMatchAll;
+
+  return NS_OK;
+}
+
 class AbortOperationsRunnable final
   : public Runnable
 {
   ContentParentId mContentParentId;
 
 public:
   explicit AbortOperationsRunnable(ContentParentId aContentParentId)
     : Runnable("dom::quota::AbortOperationsRunnable")
@@ -623,51 +670,29 @@ QuotaManagerService::ClearStoragesForPri
   if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) {
     // The originAttributes should be default originAttributes when the
     // aClearAll flag is set.
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<Request> request = new Request(aPrincipal);
 
-  ClearOriginParams params;
+  ClearResetOriginParams commonParams;
 
-  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
-                                                params.principalInfo());
+  nsresult rv = GetClearResetOriginParams(aPrincipal,
+                                          aPersistenceType,
+                                          aClientType,
+                                          aClearAll,
+                                          commonParams);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  Nullable<PersistenceType> persistenceType;
-  rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (persistenceType.IsNull()) {
-    params.persistenceTypeIsExplicit() = false;
-  } else {
-    params.persistenceType() = persistenceType.Value();
-    params.persistenceTypeIsExplicit() = true;
-  }
-
-  Nullable<Client::Type> clientType;
-  rv = Client::NullableTypeFromText(aClientType, &clientType);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (clientType.IsNull()) {
-    params.clientTypeIsExplicit() = false;
-  } else {
-    params.clientType() = clientType.Value();
-    params.clientTypeIsExplicit() = true;
-  }
-
-  params.clearAll() = aClearAll;
+  RequestParams params;
+  params = ClearOriginParams(commonParams);
 
   nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
 
   rv = InitiateRequest(info);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -695,16 +720,66 @@ QuotaManagerService::Reset(nsIQuotaReque
     return rv;
   }
 
   request.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+QuotaManagerService::ResetStoragesForPrincipal(nsIPrincipal* aPrincipal,
+                                               const nsACString& aPersistenceType,
+                                               const nsAString& aClientType,
+                                               bool aResetAll,
+                                               nsIQuotaRequest** _retval)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+
+  if (NS_WARN_IF(!gTestingMode)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsCString suffix;
+  aPrincipal->OriginAttributesRef().CreateSuffix(suffix);
+
+  if (NS_WARN_IF(aResetAll && !suffix.IsEmpty())) {
+    // The originAttributes should be default originAttributes when the
+    // aClearAll flag is set.
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  RefPtr<Request> request = new Request(aPrincipal);
+
+  ClearResetOriginParams commonParams;
+
+  nsresult rv = GetClearResetOriginParams(aPrincipal,
+                                          aPersistenceType,
+                                          aClientType,
+                                          aResetAll,
+                                          commonParams);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  RequestParams params;
+  params = ResetOriginParams(commonParams);
+
+  nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+  rv = InitiateRequest(info);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 QuotaManagerService::Persisted(nsIPrincipal* aPrincipal,
                                nsIQuotaRequest** _retval)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(_retval);
 
   RefPtr<Request> request = new Request(aPrincipal);
--- a/dom/quota/nsIQuotaManagerService.idl
+++ b/dom/quota/nsIQuotaManagerService.idl
@@ -136,16 +136,50 @@ interface nsIQuotaManagerService : nsISu
    *
    * If the dom.quotaManager.testing preference is not true the call will be
    * a no-op.
    */
   [must_use] nsIQuotaRequest
   reset();
 
   /**
+   * Resets all storages stored for the given principal.
+   *
+   * If the dom.quotaManager.testing preference is not true the call will be
+   * a no-op.
+   *
+   * @param aPrincipal
+   *        A principal for the origin whose storages are to be reset.
+   * @param aPersistenceType
+   *        An optional string that tells what persistence type of storages
+   *        will be reset.  If omitted (or void), all persistence types will
+   *        be cleared for the principal.  If a single persistence type
+   *        ("persistent", "temporary", or "default") is provided, then only
+   *        that persistence directory will be considered.  Note that
+   *        "persistent" is different than being "persisted" via persist() and
+   *        is only for chrome principals.  See bug 1354500 for more info.
+   *        In general, null is the right thing to pass here.
+   * @param aClientType
+   *        An optional string that tells what client type of storages
+   *        will be reset.  If omitted (or void), all client types will be
+   *        cleared for the principal.  If a single client type is provided
+   *        from Client.h, then only that client's storage will be cleared.
+   *        If you want to clear multiple client types (but not all), then you
+   *        must call this method multiple times.
+   * @param aResetAll
+   *        An optional boolean to indicate resetting all storages under the
+   *        given origin.
+   */
+  [must_use] nsIQuotaRequest
+  resetStoragesForPrincipal(in nsIPrincipal aPrincipal,
+                            [optional] in ACString aPersistenceType,
+                            [optional] in AString aClientType,
+                            [optional] in boolean aResetAll);
+
+  /**
    * Check if given origin is persisted.
    *
    * @param aPrincipal
    *        A principal for the origin which we want to check.
    */
   [must_use] nsIQuotaRequest
   persisted(in nsIPrincipal aPrincipal);
 
--- a/dom/storage/Storage.h
+++ b/dom/storage/Storage.h
@@ -105,16 +105,24 @@ public:
     aFound = !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION);
   }
 
   virtual void
   Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
 
   bool IsSessionOnly() const { return mIsSessionOnly; }
 
+  virtual void
+  Open(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
+  { }
+
+  virtual void
+  Close(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
+  { }
+
   // aStorage can be null if this method is called by LocalStorageCacheChild.
   //
   // aImmediateDispatch is for use by child IPC code (LocalStorageCacheChild)
   // so that PBackground ordering can be maintained.  Without this, the event
   // would be/ enqueued and run in a future turn of the event loop, potentially
   // allowing other PBackground Recv* methods to trigger script that wants to
   // assume our localstorage changes have already been applied.  This is the
   // case for message manager messages which are used by ContentTask testing
--- a/dom/tests/mochitest/localstorage/localStorageCommon.js
+++ b/dom/tests/mochitest/localstorage/localStorageCommon.js
@@ -1,42 +1,116 @@
 function localStorageFlush(cb)
 {
+  if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
+    SimpleTest.executeSoon(function () {
+      cb();
+    });
+    return;
+  }
+
   var ob = {
     observe : function(sub, top, dat)
     {
       os().removeObserver(ob, "domstorage-test-flushed");
       cb();
     }
   };
   os().addObserver(ob, "domstorage-test-flushed");
   notify("domstorage-test-flush-force");
 }
 
-function localStorageReload()
+function localStorageReload(callback)
 {
+  if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
+    localStorage.close();
+    let qms = SpecialPowers.Services.qms;
+    let principal = SpecialPowers.wrap(document).nodePrincipal;
+    let request = qms.resetStoragesForPrincipal(principal, "default", "ls");
+    request.callback = SpecialPowers.wrapCallback(function() {
+      localStorage.open();
+      callback();
+    });
+    return;
+  }
+
   notify("domstorage-test-reload");
+  SimpleTest.executeSoon(function () {
+    callback();
+  });
 }
 
-function localStorageFlushAndReload(cb)
+function localStorageFlushAndReload(callback)
 {
+  if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
+    localStorage.close();
+    let qms = SpecialPowers.Services.qms;
+    let principal = SpecialPowers.wrap(document).nodePrincipal;
+    let request = qms.resetStoragesForPrincipal(principal, "default", "ls");
+    request.callback = SpecialPowers.wrapCallback(function() {
+      localStorage.open();
+      callback();
+    });
+    return;
+  }
+
   localStorageFlush(function() {
-    localStorageReload();
-    cb();
+    localStorageReload(callback);
   });
 }
 
-function localStorageClearAll()
+function localStorageClearAll(callback)
 {
+  if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
+    let qms = SpecialPowers.Services.qms;
+    let ssm = SpecialPowers.Services.scriptSecurityManager;
+
+    qms.getUsage(SpecialPowers.wrapCallback(function(request) {
+      if (request.resultCode != SpecialPowers.Cr.NS_OK) {
+        callback();
+        return;
+      }
+
+      let clearRequestCount = 0;
+      for (let item of request.result) {
+        let principal = ssm.createCodebasePrincipalFromOrigin(item.origin);
+        let clearRequest =
+          qms.clearStoragesForPrincipal(principal, "default", "ls");
+        clearRequestCount++;
+        clearRequest.callback = SpecialPowers.wrapCallback(function() {
+          if (--clearRequestCount == 0) {
+            callback();
+          }
+        });
+      }
+    }));
+    return;
+  }
+
   os().notifyObservers(null, "cookie-changed", "cleared");
+  SimpleTest.executeSoon(function () {
+    callback();
+  });
 }
 
-function localStorageClearDomain(domain)
+function localStorageClearDomain(domain, callback)
 {
+  if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
+    let qms = SpecialPowers.Services.qms;
+    let principal = SpecialPowers.wrap(document).nodePrincipal;
+    let request = qms.clearStoragesForPrincipal(principal, "default", "ls");
+    let cb = SpecialPowers.wrapCallback(callback);
+    request.callback = cb;
+    return;
+  }
+
   os().notifyObservers(null, "browser:purge-domain-data", domain);
+  SimpleTest.executeSoon(function () {
+    callback();
+  });
 }
 
 function os()
 {
   return SpecialPowers.Services.obs;
 }
 
 function notify(top)
@@ -44,10 +118,12 @@ function notify(top)
   os().notifyObservers(null, top);
 }
 
 /**
  * Enable testing observer notifications in DOMStorageObserver.cpp.
  */
 function localStorageEnableTestingMode(cb)
 {
-  SpecialPowers.pushPrefEnv({ "set": [["dom.storage.testing", true]] }, cb);
+  SpecialPowers.pushPrefEnv({ set: [["dom.storage.testing", true],
+                                    ["dom.quotaManager.testing", true]] },
+                            cb);
 }
--- a/dom/tests/mochitest/localstorage/mochitest.ini
+++ b/dom/tests/mochitest/localstorage/mochitest.ini
@@ -14,17 +14,17 @@ support-files =
   interOriginFrame.js
   interOriginTest.js
   interOriginTest2.js
   localStorageCommon.js
   frameLocalStorageSessionOnly.html
   file_tryAccessSessionStorage.html
 
 [test_brokenUTF-16.html]
-#[test_bug600307-DBOps.html]
+[test_bug600307-DBOps.html]
 [test_bug746272-1.html]
 [test_bug746272-2.html]
 skip-if = os == "android" || verify # bug 962029
 [test_cookieBlock.html]
 [test_cookieSession.html]
 [test_embededNulls.html]
 [test_keySync.html]
 [test_localStorageBase.html]
--- a/dom/tests/mochitest/localstorage/test_bug600307-DBOps.html
+++ b/dom/tests/mochitest/localstorage/test_bug600307-DBOps.html
@@ -79,65 +79,66 @@ function startTest()
   localStorageFlushAndReload(function() {
   is(localStorage.length, 0, "localStorage clean in case 2");
 
   // Case 3:
   localStorageFlush(function() {
   localStorage.setItem("item", "value");
   localStorage.setItem("item2", "value2");
   localStorage.clear();
-  localStorageReload();
+  localStorageReload(function() {
   is(localStorage.length, 0, "localStorage clean in case 4");
 
-  if (SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(
+  if (!SpecialPowers.Services.lsm.nextGenLocalStorageEnabled &&
+      SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(
       SpecialPowers.Ci.nsIXULRuntime).processType != SpecialPowers.Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
     // Following tests cannot be run in a child/plugin process type
     SimpleTest.finish();
     return;
   }
 
   // Cookies clean 1
   localStorageFlush(function() {
   localStorage.setItem("item", "value");
-  localStorageClearAll();
+  localStorageClearAll(function() {
   is(localStorage.length, 0, "localStorage clean after cookies deletion");
   localStorage.setItem("item2", "value2");
   is(localStorage.getItem("item"), null, "Unexpected key 1, cookies delete");
   is(localStorage.getItem("item2"), "value2", "Expected key 2, cookies delete");
   localStorageFlushAndReload(function() {
   is(localStorage.getItem("item"), null, "Unexpected key 1, cookies delete");
   is(localStorage.getItem("item2"), "value2", "Expected key 2, cookies delete");
 
   // Cookies clean 2
   localStorage.clear();
   localStorageFlush(function() {
   localStorage.setItem("item", "value");
-  localStorageClearAll();
+  localStorageClearAll(function() {
   is(localStorage.length, 0, "localStorage clean after cookies deletion 2");
   localStorageFlushAndReload(function() {
   is(localStorage.length, 0, "localStorage clean after cookies deletion 2");
 
 
   // Domain clean 1
   localStorageFlush(function() {
   localStorage.setItem("item", "value");
-  localStorageClearDomain("test");
+  localStorageClearDomain("test", function() {
   is(localStorage.length, 0, "localStorage clean after domain deletion");
   localStorage.setItem("item2", "value2");
   is(localStorage.getItem("item"), null, "Unexpected key 1, domain delete");
   is(localStorage.getItem("item2"), "value2", "Expected key 2, domain delete");
   localStorageFlushAndReload(function() {
   is(localStorage.getItem("item"), null, "Unexpected key 1, domain delete");
   is(localStorage.getItem("item2"), "value2", "Expected key 2, domain delete");
 
   // Domain clean 2
   localStorage.clear();
   localStorageFlush(function() {
   localStorage.setItem("item", "value");
-  localStorageClearDomain("test");
+  localStorageClearDomain("test", function() {
   is(localStorage.length, 0, "localStorage clean after domain deletion 2");
   localStorageFlushAndReload(function() {
   is(localStorage.length, 0, "localStorage clean after domain deletion 2");
 
   SimpleTest.finish();
 
   });
   });
@@ -152,16 +153,21 @@ function startTest()
   });
   });
   });
   });
   });
   });
   });
   });
+  });
+  });
+  });
+  });
+  });
 }
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
 </head>
 
--- a/dom/webidl/Storage.webidl
+++ b/dom/webidl/Storage.webidl
@@ -28,8 +28,17 @@ interface Storage {
   deleter void removeItem(DOMString key);
 
   [Throws, NeedsSubjectPrincipal]
   void clear();
 
   [ChromeOnly]
   readonly attribute boolean isSessionOnly;
 };
+
+// Testing only.
+partial interface Storage {
+  [Throws, NeedsSubjectPrincipal, Pref="dom.storage.testing"]
+  void open();
+
+  [Throws, NeedsSubjectPrincipal, Pref="dom.storage.testing"]
+  void close();
+};