Bug 1286717 - Part 1: Expose persist/persisted to StorageManager, r=janv, r=baku
authorShawn Huang <shuang@mozilla.com>
Mon, 17 Apr 2017 15:06:27 +0800
changeset 353407 1cbdf0c95214922cd2deaed6fe6a75fdadb46644
parent 353406 d29bf11528ececa49c9f560264bbc2770703b6be
child 353408 3972f05c4d41c88327491d559a917936927a6fff
push id31666
push userarchaeopteryx@coole-files.de
push dateMon, 17 Apr 2017 14:21:17 +0000
treeherdermozilla-central@05c212a94183 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv, baku
bugs1286717
milestone55.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 1286717 - Part 1: Expose persist/persisted to StorageManager, r=janv, r=baku
dom/quota/StorageManager.cpp
dom/quota/StorageManager.h
dom/webidl/StorageManager.webidl
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -6,241 +6,610 @@
 
 #include "StorageManager.h"
 
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/StorageManagerBinding.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/ErrorResult.h"
+#include "nsContentPermissionHelper.h"
 #include "nsIQuotaCallbacks.h"
 #include "nsIQuotaRequests.h"
 #include "nsPIDOMWindow.h"
 
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-// This class is used to get quota usage callback.
-class EstimateResolver final
-  : public nsIQuotaUsageCallback
+// This class is used to get quota usage, request persist and check persisted
+// status callbacks.
+class RequestResolver final
+  : public nsIQuotaCallback
+  , public nsIQuotaUsageCallback
 {
+public:
+  enum Type
+  {
+    Estimate,
+    Persist,
+    Persisted
+  };
+
+private:
   class FinishWorkerRunnable;
 
   // If this resolver was created for a window then mPromise must be non-null.
   // Otherwise mProxy must be non-null.
   RefPtr<Promise> mPromise;
   RefPtr<PromiseWorkerProxy> mProxy;
 
   nsresult mResultCode;
   StorageEstimate mStorageEstimate;
+  const Type mType;
+  bool mPersisted;
 
 public:
-  explicit EstimateResolver(Promise* aPromise)
+  RequestResolver(Type aType, Promise* aPromise)
     : mPromise(aPromise)
     , mResultCode(NS_OK)
+    , mType(aType)
+    , mPersisted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aPromise);
   }
 
-  explicit EstimateResolver(PromiseWorkerProxy* aProxy)
+  RequestResolver(Type aType, PromiseWorkerProxy* aProxy)
     : mProxy(aProxy)
     , mResultCode(NS_OK)
+    , mType(aType)
+    , mPersisted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aProxy);
   }
 
+  Type
+  GetType() const
+  {
+    return mType;
+  }
+
   void
-  ResolveOrReject(Promise* aPromise);
+  ResolveOrReject();
 
   NS_DECL_THREADSAFE_ISUPPORTS
-
+  NS_DECL_NSIQUOTACALLBACK
   NS_DECL_NSIQUOTAUSAGECALLBACK
 
 private:
-  ~EstimateResolver()
+  ~RequestResolver()
   { }
+
+  nsresult
+  GetStorageEstimate(nsIVariant* aResult);
+
+  nsresult
+  GetPersisted(nsIVariant* aResult);
+
+  template <typename T>
+  nsresult
+  OnCompleteOrUsageResult(T* aRequest);
+
+  nsresult
+  Finish();
 };
 
 // This class is used to return promise on worker thread.
-class EstimateResolver::FinishWorkerRunnable final
+class RequestResolver::FinishWorkerRunnable final
   : public WorkerRunnable
 {
-  RefPtr<EstimateResolver> mResolver;
+  RefPtr<RequestResolver> mResolver;
 
 public:
-  explicit FinishWorkerRunnable(EstimateResolver* aResolver)
+  explicit FinishWorkerRunnable(RequestResolver* aResolver)
     : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate())
     , mResolver(aResolver)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aResolver);
   }
 
-  virtual bool
+  bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 };
 
-class EstimateWorkerMainThreadRunnable
+class EstimateWorkerMainThreadRunnable final
   : public WorkerMainThreadRunnable
 {
   RefPtr<PromiseWorkerProxy> mProxy;
 
 public:
   EstimateWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
                                    PromiseWorkerProxy* aProxy)
     : WorkerMainThreadRunnable(aWorkerPrivate,
                                NS_LITERAL_CSTRING("StorageManager :: Estimate"))
     , mProxy(aProxy)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_ASSERT(aProxy);
   }
 
-  virtual bool
+  bool
+  MainThreadRun() override;
+};
+
+class PersistedWorkerMainThreadRunnable final
+  : public WorkerMainThreadRunnable
+{
+  RefPtr<PromiseWorkerProxy> mProxy;
+
+public:
+  PersistedWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+                                    PromiseWorkerProxy* aProxy)
+    : WorkerMainThreadRunnable(aWorkerPrivate,
+                               NS_LITERAL_CSTRING("StorageManager :: Persisted"))
+    , mProxy(aProxy)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(aProxy);
+  }
+
+  bool
   MainThreadRun() override;
 };
 
+/*******************************************************************************
+ * PersistentStoragePermissionRequest
+ ******************************************************************************/
+
+class PersistentStoragePermissionRequest final
+  : public nsIContentPermissionRequest
+{
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  RefPtr<Promise> mPromise;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
+
+public:
+  PersistentStoragePermissionRequest(nsIPrincipal* aPrincipal,
+                                     nsPIDOMWindowInner* aWindow,
+                                     Promise* aPromise)
+    : mPrincipal(aPrincipal)
+    , mWindow(aWindow)
+    , mPromise(aPromise)
+  {
+    MOZ_ASSERT(aPrincipal);
+    MOZ_ASSERT(aWindow);
+    MOZ_ASSERT(aPromise);
+
+    mRequester = new nsContentPermissionRequester(mWindow);
+  }
+
+  nsresult
+  Start();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+
+private:
+  ~PersistentStoragePermissionRequest()
+  { }
+};
+
 nsresult
 GetUsageForPrincipal(nsIPrincipal* aPrincipal,
                      nsIQuotaUsageCallback* aCallback,
                      nsIQuotaUsageRequest** aRequest)
 {
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(aRequest);
 
   nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
   if (NS_WARN_IF(!qms)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = qms->GetUsageForPrincipal(aPrincipal, aCallback, true, aRequest);
+  nsresult rv = qms->GetUsageForPrincipal(aPrincipal,
+                                          aCallback,
+                                          true,
+                                          aRequest);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 };
 
 nsresult
-GetStorageEstimate(nsIQuotaUsageRequest* aRequest,
-                   StorageEstimate& aStorageEstimate)
+Persisted(nsIPrincipal* aPrincipal,
+          nsIQuotaCallback* aCallback,
+          nsIQuotaRequest** aRequest)
 {
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(aCallback);
   MOZ_ASSERT(aRequest);
 
-  nsCOMPtr<nsIVariant> result;
-  nsresult rv = aRequest->GetResult(getter_AddRefs(result));
+  nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+  if (NS_WARN_IF(!qms)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIQuotaRequest> request;
+  nsresult rv = qms->Persisted(aPrincipal, getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // All the methods in nsIQuotaManagerService shouldn't synchronously fire
+  // any callbacks when they are being executed. Even when a result is ready,
+  // a new runnable should be dispatched to current thread to fire the callback
+  // asynchronously. It's safe to set the callback after we call Persisted().
+  MOZ_ALWAYS_SUCCEEDS(request->SetCallback(aCallback));
+
+  request.forget(aRequest);
+
+  return NS_OK;
+};
+
+already_AddRefed<Promise>
+ExecuteOpOnMainOrWorkerThread(nsIGlobalObject* aGlobal,
+                              RequestResolver::Type aType,
+                              ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT_IF(aType == RequestResolver::Type::Persist,
+                NS_IsMainThread());
+
+  RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+  if (NS_WARN_IF(!promise)) {
+    return nullptr;
+  }
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+    if (NS_WARN_IF(!window)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+    if (NS_WARN_IF(!doc)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+    MOZ_ASSERT(principal);
+
+    switch (aType) {
+      case RequestResolver::Type::Persisted: {
+        RefPtr<RequestResolver> resolver =
+          new RequestResolver(RequestResolver::Type::Persisted, promise);
+
+        RefPtr<nsIQuotaRequest> request;
+        aRv = Persisted(principal, resolver, getter_AddRefs(request));
+
+        break;
+      }
+
+      case RequestResolver::Type::Persist: {
+        RefPtr<PersistentStoragePermissionRequest> request =
+          new PersistentStoragePermissionRequest(principal, window, promise);
+
+        aRv = request->Start();
+
+        break;
+      }
+
+      case RequestResolver::Type::Estimate: {
+        RefPtr<RequestResolver> resolver =
+          new RequestResolver(RequestResolver::Type::Estimate, promise);
+
+        RefPtr<nsIQuotaUsageRequest> request;
+        aRv = GetUsageForPrincipal(principal,
+                                   resolver,
+                                   getter_AddRefs(request));
+
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Invalid aRequest type!");
+    }
+
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    return promise.forget();
+  }
+
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+
+  RefPtr<PromiseWorkerProxy> promiseProxy =
+    PromiseWorkerProxy::Create(workerPrivate, promise);
+  if (NS_WARN_IF(!promiseProxy)) {
+    return nullptr;
+  }
+
+  switch (aType) {
+    case RequestResolver::Type::Estimate: {
+      RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
+        new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
+                                             promiseProxy);
+      runnnable->Dispatch(Terminating, aRv);
+
+      break;
+    }
+
+    case RequestResolver::Type::Persisted: {
+      RefPtr<PersistedWorkerMainThreadRunnable> runnnable =
+        new PersistedWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
+                                              promiseProxy);
+      runnnable->Dispatch(Terminating, aRv);
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Invalid aRequest type");
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return promise.forget();
+};
+
+} // namespace
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+RequestResolver::ResolveOrReject()
+{
+  class MOZ_STACK_CLASS AutoCleanup final
+  {
+    RefPtr<PromiseWorkerProxy> mProxy;
+
+  public:
+    explicit AutoCleanup(PromiseWorkerProxy* aProxy)
+      : mProxy(aProxy)
+    {
+      MOZ_ASSERT(aProxy);
+    }
+
+    ~AutoCleanup()
+    {
+      MOZ_ASSERT(mProxy);
+
+      mProxy->CleanUp();
+    }
+  };
+
+  RefPtr<Promise> promise;
+  Maybe<AutoCleanup> autoCleanup;
+
+  if (mPromise) {
+    promise = mPromise;
+  } else {
+    MOZ_ASSERT(mProxy);
+
+    promise = mProxy->WorkerPromise();
+
+    // Only clean up for worker case.
+    autoCleanup.emplace(mProxy);
+  }
+
+  MOZ_ASSERT(promise);
+
+  if (mType == Type::Estimate) {
+    if (NS_SUCCEEDED(mResultCode)) {
+      promise->MaybeResolve(mStorageEstimate);
+    } else {
+      promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
+    }
+
+    return;
+  }
+
+  MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
+
+  if (NS_SUCCEEDED(mResultCode)) {
+    promise->MaybeResolve(mPersisted);
+  } else {
+    promise->MaybeResolve(false);
+  }
+}
+
+NS_IMPL_ISUPPORTS(RequestResolver, nsIQuotaUsageCallback, nsIQuotaCallback)
+
+nsresult
+RequestResolver::GetStorageEstimate(nsIVariant* aResult)
+{
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(mType == Type::Estimate);
+
+#ifdef DEBUG
+  uint16_t dataType;
+  MOZ_ALWAYS_SUCCEEDS(aResult->GetDataType(&dataType));
+  MOZ_ASSERT(dataType == nsIDataType::VTYPE_INTERFACE_IS);
+#endif
+
   nsID* iid;
   nsCOMPtr<nsISupports> supports;
-  rv = result->GetAsInterface(&iid, getter_AddRefs(supports));
+  nsresult rv = aResult->GetAsInterface(&iid, getter_AddRefs(supports));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   free(iid);
 
   nsCOMPtr<nsIQuotaOriginUsageResult> originUsageResult =
     do_QueryInterface(supports);
   MOZ_ASSERT(originUsageResult);
 
   MOZ_ALWAYS_SUCCEEDS(
-    originUsageResult->GetUsage(&aStorageEstimate.mUsage.Construct()));
+    originUsageResult->GetUsage(&mStorageEstimate.mUsage.Construct()));
 
   MOZ_ALWAYS_SUCCEEDS(
-    originUsageResult->GetLimit(&aStorageEstimate.mQuota.Construct()));
+    originUsageResult->GetLimit(&mStorageEstimate.mQuota.Construct()));
 
   return NS_OK;
 }
 
-} // namespace
+nsresult
+RequestResolver::GetPersisted(nsIVariant* aResult)
+{
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
 
-/*******************************************************************************
- * Local class implementations
- ******************************************************************************/
+#ifdef DEBUG
+  uint16_t dataType;
+  MOZ_ALWAYS_SUCCEEDS(aResult->GetDataType(&dataType));
+#endif
+
+  if (mType == Type::Persist) {
+    MOZ_ASSERT(dataType == nsIDataType::VTYPE_VOID);
 
-void
-EstimateResolver::ResolveOrReject(Promise* aPromise)
-{
-  MOZ_ASSERT(aPromise);
+    mPersisted = true;
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(dataType == nsIDataType::VTYPE_BOOL);
 
-  if (NS_SUCCEEDED(mResultCode)) {
-    aPromise->MaybeResolve(mStorageEstimate);
-  } else {
-    aPromise->MaybeReject(mResultCode);
+  bool persisted;
+  nsresult rv = aResult->GetAsBool(&persisted);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
+
+  mPersisted = persisted;
+  return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(EstimateResolver, nsIQuotaUsageCallback)
-
-NS_IMETHODIMP
-EstimateResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest)
+template <typename T>
+nsresult
+RequestResolver::OnCompleteOrUsageResult(T* aRequest)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRequest);
 
-  nsresult rv = aRequest->GetResultCode(&mResultCode);
+  nsresult resultCode;
+  nsresult rv = aRequest->GetResultCode(&resultCode);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mResultCode = rv;
-  } else if (NS_SUCCEEDED(mResultCode)) {
-    rv = GetStorageEstimate(aRequest, mStorageEstimate);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mResultCode = rv;
-    }
+    return rv;
+  }
+
+  if (NS_FAILED(resultCode)) {
+    return resultCode;
+  }
+
+  nsCOMPtr<nsIVariant> result;
+  rv = aRequest->GetResult(getter_AddRefs(result));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
+  if (mType == Type::Estimate) {
+    rv = GetStorageEstimate(result);
+  } else {
+    MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
+
+    rv = GetPersisted(result);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+RequestResolver::Finish()
+{
   // In a main thread request.
   if (!mProxy) {
     MOZ_ASSERT(mPromise);
 
-    ResolveOrReject(mPromise);
+    ResolveOrReject();
     return NS_OK;
   }
 
-  // In a worker thread request.
-  MutexAutoLock lock(mProxy->Lock());
+  {
+    // In a worker thread request.
+    MutexAutoLock lock(mProxy->Lock());
 
-  if (NS_WARN_IF(mProxy->CleanedUp())) {
-    return NS_ERROR_FAILURE;
+    if (NS_WARN_IF(mProxy->CleanedUp())) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
+    if (NS_WARN_IF(!runnable->Dispatch())) {
+      return NS_ERROR_FAILURE;
+    }
   }
 
-  RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
-  if (NS_WARN_IF(!runnable->Dispatch())) {
-    return NS_ERROR_FAILURE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestResolver::OnComplete(nsIQuotaRequest *aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequest);
+
+  mResultCode = OnCompleteOrUsageResult(aRequest);
+
+  nsresult rv = Finish();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequest);
+
+  mResultCode = OnCompleteOrUsageResult(aRequest);
+
+  nsresult rv = Finish();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   return NS_OK;
 }
 
 bool
-EstimateResolver::
+RequestResolver::
 FinishWorkerRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
+  MOZ_ASSERT(aCx);
   MOZ_ASSERT(aWorkerPrivate);
   aWorkerPrivate->AssertIsOnWorkerThread();
 
-  RefPtr<PromiseWorkerProxy> proxy = mResolver->mProxy;
-  MOZ_ASSERT(proxy);
-
-  RefPtr<Promise> promise = proxy->WorkerPromise();
-  MOZ_ASSERT(promise);
-
-  mResolver->ResolveOrReject(promise);
-
-  proxy->CleanUp();
+  MOZ_ASSERT(mResolver);
+  mResolver->ResolveOrReject();
 
   return true;
 }
 
 bool
 EstimateWorkerMainThreadRunnable::MainThreadRun()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -252,100 +621,221 @@ EstimateWorkerMainThreadRunnable::MainTh
     if (mProxy->CleanedUp()) {
       return true;
     }
     principal = mProxy->GetWorkerPrivate()->GetPrincipal();
   }
 
   MOZ_ASSERT(principal);
 
-  RefPtr<EstimateResolver> resolver = new EstimateResolver(mProxy);
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Estimate, mProxy);
 
   RefPtr<nsIQuotaUsageRequest> request;
   nsresult rv =
     GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   return true;
 }
 
+bool
+PersistedWorkerMainThreadRunnable::MainThreadRun()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIPrincipal> principal;
+
+  {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
+      return true;
+    }
+    principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+  }
+
+  MOZ_ASSERT(principal);
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persisted, mProxy);
+
+  RefPtr<nsIQuotaRequest> request;
+  nsresult rv = Persisted(principal, resolver, getter_AddRefs(request));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+PersistentStoragePermissionRequest::Start()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Grant permission if pref'ed on.
+  if (Preferences::GetBool("dom.storageManager.prompt.testing", false)) {
+    if (Preferences::GetBool("dom.storageManager.prompt.testing.allow",
+                             false)) {
+      return Allow(JS::UndefinedHandleValue);
+    }
+
+    return Cancel();
+  }
+
+  return nsContentPermissionUtils::AskPermission(this, mWindow);
+}
+
+NS_IMPL_ISUPPORTS(PersistentStoragePermissionRequest,
+                  nsIContentPermissionRequest)
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(mPrincipal);
+
+  NS_ADDREF(*aPrincipal = mPrincipal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequestingWindow);
+  MOZ_ASSERT(mWindow);
+
+  NS_ADDREF(*aRequestingWindow = mWindow);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetElement(nsIDOMElement** aElement)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aElement);
+
+  *aElement = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::Cancel()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mPromise);
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persisted, mPromise);
+
+  RefPtr<nsIQuotaRequest> request;
+
+  return Persisted(mPrincipal, resolver, getter_AddRefs(request));
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::Allow(JS::HandleValue aChoices)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persist, mPromise);
+
+  nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+  if (NS_WARN_IF(!qms)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<nsIQuotaRequest> request;
+
+  nsresult rv = qms->Persist(mPrincipal, getter_AddRefs(request));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(request->SetCallback(resolver));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetRequester(
+                                     nsIContentPermissionRequester** aRequester)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetTypes(nsIArray** aTypes)
+{
+  MOZ_ASSERT(aTypes);
+
+  nsTArray<nsString> emptyOptions;
+
+  return nsContentPermissionUtils::CreatePermissionArray(
+                                       NS_LITERAL_CSTRING("persistent-storage"),
+                                       NS_LITERAL_CSTRING("unused"),
+                                       emptyOptions,
+                                       aTypes);
+}
+
 /*******************************************************************************
  * StorageManager
  ******************************************************************************/
 
 StorageManager::StorageManager(nsIGlobalObject* aGlobal)
   : mOwner(aGlobal)
 {
   MOZ_ASSERT(aGlobal);
 }
 
 StorageManager::~StorageManager()
 {
 }
 
 already_AddRefed<Promise>
+StorageManager::Persisted(ErrorResult& aRv)
+{
+  MOZ_ASSERT(mOwner);
+
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Persisted,
+                                       aRv);
+}
+
+already_AddRefed<Promise>
+StorageManager::Persist(ErrorResult& aRv)
+{
+  MOZ_ASSERT(mOwner);
+
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Persist,
+                                       aRv);
+}
+
+already_AddRefed<Promise>
 StorageManager::Estimate(ErrorResult& aRv)
 {
   MOZ_ASSERT(mOwner);
 
-  RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
-  if (NS_WARN_IF(!promise)) {
-    return nullptr;
-  }
-
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mOwner);
-    if (NS_WARN_IF(!window)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return nullptr;
-    }
-
-    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
-    if (NS_WARN_IF(!doc)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return nullptr;
-    }
-
-    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
-    MOZ_ASSERT(principal);
-
-    RefPtr<EstimateResolver> resolver = new EstimateResolver(promise);
-
-    RefPtr<nsIQuotaUsageRequest> request;
-    nsresult rv =
-      GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      aRv.Throw(rv);
-      return nullptr;
-    }
-
-    return promise.forget();
-  }
-
-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
-  RefPtr<PromiseWorkerProxy> promiseProxy =
-    PromiseWorkerProxy::Create(workerPrivate, promise);
-  if (NS_WARN_IF(!promiseProxy)) {
-    return nullptr;
-  }
-
-  RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
-    new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
-                                         promiseProxy);
-
-  runnnable->Dispatch(Terminating, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  return promise.forget();
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Estimate,
+                                       aRv);
 }
 
 // static
 bool
 StorageManager::PrefEnabled(JSContext* aCx, JSObject* aObj)
 {
   if (NS_IsMainThread()) {
     return Preferences::GetBool("dom.storageManager.enabled");
--- a/dom/quota/StorageManager.h
+++ b/dom/quota/StorageManager.h
@@ -35,16 +35,22 @@ public:
   nsIGlobalObject*
   GetParentObject() const
   {
     return mOwner;
   }
 
   // WebIDL
   already_AddRefed<Promise>
+  Persisted(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Persist(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
   Estimate(ErrorResult& aRv);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StorageManager)
 
   // nsWrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/webidl/StorageManager.webidl
+++ b/dom/webidl/StorageManager.webidl
@@ -7,20 +7,22 @@
  * https://storage.spec.whatwg.org/#storagemanager
  *
  */
 
 [SecureContext,
  Exposed=(Window,Worker),
  Func="mozilla::dom::StorageManager::PrefEnabled"]
 interface StorageManager {
-  // [Throws]
-  // Promise<boolean> persisted();
-  // [Throws]
-  // [Exposed=Window] Promise<boolean> persist();
+  [Throws]
+  Promise<boolean> persisted();
+
+  [Exposed=Window, Throws]
+  Promise<boolean> persist();
+
   [Throws]
   Promise<StorageEstimate> estimate();
 };
 
 dictionary StorageEstimate {
   unsigned long long usage;
   unsigned long long quota;
 };