summaryrefslogtreecommitdiffstats
path: root/dom/localstorage/LocalStorageManager2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/localstorage/LocalStorageManager2.cpp')
-rw-r--r--dom/localstorage/LocalStorageManager2.cpp661
1 files changed, 661 insertions, 0 deletions
diff --git a/dom/localstorage/LocalStorageManager2.cpp b/dom/localstorage/LocalStorageManager2.cpp
new file mode 100644
index 0000000000..69e8c3b4dd
--- /dev/null
+++ b/dom/localstorage/LocalStorageManager2.cpp
@@ -0,0 +1,661 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "LocalStorageManager2.h"
+
+// Local includes
+#include "ActorsChild.h"
+#include "LSObject.h"
+
+// Global includes
+#include <utility>
+#include "MainThreadUtils.h"
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RemoteLazyInputStreamThread.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/dom/PBackgroundLSRequest.h"
+#include "mozilla/dom/PBackgroundLSSharedTypes.h"
+#include "mozilla/dom/PBackgroundLSSimpleRequest.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIEventTarget.h"
+#include "nsILocalStorageManager.h"
+#include "nsIPrincipal.h"
+#include "nsIRunnable.h"
+#include "nsPIDOMWindow.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class AsyncRequestHelper final : public Runnable,
+ public LSRequestChildCallback {
+ enum class State {
+ /**
+ * The AsyncRequestHelper has been created and dispatched to the
+ * RemoteLazyInputStream Thread.
+ */
+ Initial,
+ /**
+ * Start() has been invoked on the RemoteLazyInputStream Thread and
+ * LocalStorageManager2::StartRequest has been invoked from there, sending
+ * an IPC message to PBackground to service the request. We stay in this
+ * state until a response is received.
+ */
+ ResponsePending,
+ /**
+ * A response has been received and AsyncRequestHelper has been dispatched
+ * back to the owning event target to call Finish().
+ */
+ Finishing,
+ /**
+ * Finish() has been called on the main thread. The promise will be resolved
+ * according to the received response.
+ */
+ Complete
+ };
+
+ // The object we are issuing a request on behalf of. Present because of the
+ // need to invoke LocalStorageManager2::StartRequest off the main thread.
+ // Dropped on return to the main-thread in Finish().
+ RefPtr<LocalStorageManager2> mManager;
+ // The thread the AsyncRequestHelper was created on. This should be the main
+ // thread.
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+ // The IPC actor handling the request with standard IPC allocation rules.
+ // Our reference is nulled in OnResponse which corresponds to the actor's
+ // __destroy__ method.
+ LSRequestChild* mActor;
+ RefPtr<Promise> mPromise;
+ const LSRequestParams mParams;
+ LSRequestResponse mResponse;
+ nsresult mResultCode;
+ State mState;
+
+ public:
+ AsyncRequestHelper(LocalStorageManager2* aManager, Promise* aPromise,
+ const LSRequestParams& aParams)
+ : Runnable("dom::LocalStorageManager2::AsyncRequestHelper"),
+ mManager(aManager),
+ mOwningEventTarget(GetCurrentEventTarget()),
+ mActor(nullptr),
+ mPromise(aPromise),
+ mParams(aParams),
+ mResultCode(NS_OK),
+ mState(State::Initial) {}
+
+ bool IsOnOwningThread() const {
+ MOZ_ASSERT(mOwningEventTarget);
+
+ bool current;
+ return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
+ current;
+ }
+
+ void AssertIsOnOwningThread() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsOnOwningThread());
+ }
+
+ nsresult Dispatch();
+
+ private:
+ ~AsyncRequestHelper() = default;
+
+ nsresult Start();
+
+ void Finish();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIRUNNABLE
+
+ // LSRequestChildCallback
+ void OnResponse(const LSRequestResponse& aResponse) override;
+};
+
+class SimpleRequestResolver final : public LSSimpleRequestChildCallback {
+ RefPtr<Promise> mPromise;
+
+ public:
+ explicit SimpleRequestResolver(Promise* aPromise) : mPromise(aPromise) {}
+
+ NS_INLINE_DECL_REFCOUNTING(SimpleRequestResolver, override);
+
+ private:
+ ~SimpleRequestResolver() = default;
+
+ void HandleResponse(nsresult aResponse);
+
+ void HandleResponse(bool aResponse);
+
+ void HandleResponse(const nsTArray<LSItemInfo>& aResponse);
+
+ // LSRequestChildCallback
+ void OnResponse(const LSSimpleRequestResponse& aResponse) override;
+};
+
+nsresult CreatePromise(JSContext* aContext, Promise** aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aContext);
+
+ nsIGlobalObject* global =
+ xpc::NativeGlobal(JS::CurrentGlobalOrNull(aContext));
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(global, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+nsresult CheckedPrincipalToPrincipalInfo(
+ nsIPrincipal* aPrincipal, mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!quota::QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPrincipalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
+ aPrincipalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+LocalStorageManager2::LocalStorageManager2() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NextGenLocalStorageEnabled());
+}
+
+LocalStorageManager2::~LocalStorageManager2() { MOZ_ASSERT(NS_IsMainThread()); }
+
+NS_IMPL_ISUPPORTS(LocalStorageManager2, nsIDOMStorageManager,
+ nsILocalStorageManager)
+
+NS_IMETHODIMP
+LocalStorageManager2::PrecacheStorage(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ Storage** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aStoragePrincipal);
+ MOZ_ASSERT(_retval);
+
+ // This method was created as part of the e10s-ification of the old LS
+ // implementation to perform a preload in the content/current process. That's
+ // not how things work in LSNG. Instead everything happens in the parent
+ // process, triggered by the official preloading spot,
+ // ContentParent::AboutToLoadHttpFtpDocumentForChild.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::CreateStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate, Storage** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aStoragePrincipal);
+ MOZ_ASSERT(_retval);
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
+
+ RefPtr<LSObject> object;
+ nsresult rv = LSObject::CreateForPrincipal(inner, aPrincipal,
+ aStoragePrincipal, aDocumentURI,
+ aPrivate, getter_AddRefs(object));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ object.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::GetStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, bool aPrivate,
+ Storage** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aStoragePrincipal);
+ MOZ_ASSERT(_retval);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::CloneStorage(Storage* aStorageToCloneFrom) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aStorageToCloneFrom);
+
+ // Cloning is specific to sessionStorage; state is forked when a new tab is
+ // opened from an existing tab.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage,
+ bool* _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aStorage);
+ MOZ_ASSERT(_retval);
+
+ // Only used by sessionStorage.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::GetNextGenLocalStorageEnabled(bool* aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aResult);
+
+ *aResult = NextGenLocalStorageEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::Preload(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ nsCString originAttrSuffix;
+ nsCString originKey;
+ nsresult rv = aPrincipal->GetStorageOriginKey(originKey);
+ aPrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ rv = CheckedPrincipalToPrincipalInfo(aPrincipal, principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<Promise> promise;
+
+ if (aContext) {
+ rv = CreatePromise(aContext, getter_AddRefs(promise));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ LSRequestCommonParams commonParams;
+ commonParams.principalInfo() = principalInfo;
+ commonParams.storagePrincipalInfo() = principalInfo;
+ commonParams.originKey() = originKey;
+
+ LSRequestPreloadDatastoreParams params(commonParams);
+
+ RefPtr<AsyncRequestHelper> helper =
+ new AsyncRequestHelper(this, promise, params);
+
+ // This will start and finish the async request on the RemoteLazyInputStream
+ // thread.
+ // This must be done on RemoteLazyInputStream Thread because it's very likely
+ // that a content process will issue a prepare datastore request for the same
+ // principal while blocking the content process on the main thread.
+ // There would be a potential for deadlock if the preloading was initialized
+ // from the main thread of the parent process and a11y issued a synchronous
+ // message from the parent process to the content process (approximately at
+ // the same time) because the preload request wouldn't be able to respond
+ // to the Ready message by sending the Finish message which is needed to
+ // finish the preload request and unblock the prepare datastore request.
+ rv = helper->Dispatch();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::IsPreloaded(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ RefPtr<Promise> promise;
+ nsresult rv = CreatePromise(aContext, getter_AddRefs(promise));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ LSSimpleRequestPreloadedParams params;
+
+ rv = CheckedPrincipalToPrincipalInfo(aPrincipal, params.principalInfo());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ params.storagePrincipalInfo() = params.principalInfo();
+
+ rv = StartSimpleRequest(promise, params);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager2::GetState(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ RefPtr<Promise> promise;
+ nsresult rv = CreatePromise(aContext, getter_AddRefs(promise));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ LSSimpleRequestGetStateParams params;
+
+ rv = CheckedPrincipalToPrincipalInfo(aPrincipal, params.principalInfo());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ params.storagePrincipalInfo() = params.principalInfo();
+
+ rv = StartSimpleRequest(promise, params);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(_retval);
+ return NS_OK;
+}
+
+LSRequestChild* LocalStorageManager2::StartRequest(
+ const LSRequestParams& aParams, LSRequestChildCallback* aCallback) {
+ AssertIsOnDOMFileThread();
+
+ mozilla::ipc::PBackgroundChild* backgroundActor =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return nullptr;
+ }
+
+ auto actor = new LSRequestChild();
+
+ if (!backgroundActor->SendPBackgroundLSRequestConstructor(actor, aParams)) {
+ return nullptr;
+ }
+
+ // Must set callback after calling SendPBackgroundLSRequestConstructor since
+ // it can be called synchronously when SendPBackgroundLSRequestConstructor
+ // fails.
+ actor->SetCallback(aCallback);
+
+ return actor;
+}
+
+nsresult LocalStorageManager2::StartSimpleRequest(
+ Promise* aPromise, const LSSimpleRequestParams& aParams) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+
+ mozilla::ipc::PBackgroundChild* backgroundActor =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto actor = new LSSimpleRequestChild();
+
+ if (!backgroundActor->SendPBackgroundLSSimpleRequestConstructor(actor,
+ aParams)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<SimpleRequestResolver> resolver = new SimpleRequestResolver(aPromise);
+
+ // Must set callback after calling SendPBackgroundLSRequestConstructor since
+ // it can be called synchronously when SendPBackgroundLSRequestConstructor
+ // fails.
+ actor->SetCallback(resolver);
+
+ return NS_OK;
+}
+
+nsresult AsyncRequestHelper::Dispatch() {
+ AssertIsOnOwningThread();
+
+ nsCOMPtr<nsIEventTarget> domFileThread =
+ RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!domFileThread)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ nsresult rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult AsyncRequestHelper::Start() {
+ AssertIsOnDOMFileThread();
+ MOZ_ASSERT(mState == State::Initial);
+
+ mState = State::ResponsePending;
+
+ LSRequestChild* actor = mManager->StartRequest(mParams, this);
+ if (NS_WARN_IF(!actor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mActor = actor;
+
+ return NS_OK;
+}
+
+void AsyncRequestHelper::Finish() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::Finishing);
+
+ if (NS_WARN_IF(NS_FAILED(mResultCode))) {
+ if (mPromise) {
+ mPromise->MaybeReject(mResultCode);
+ }
+ } else {
+ switch (mResponse.type()) {
+ case LSRequestResponse::Tnsresult:
+ if (mPromise) {
+ mPromise->MaybeReject(mResponse.get_nsresult());
+ }
+ break;
+
+ case LSRequestResponse::TLSRequestPreloadDatastoreResponse:
+ if (mPromise) {
+ mPromise->MaybeResolveWithUndefined();
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown response type!");
+ }
+ }
+
+ mManager = nullptr;
+
+ mState = State::Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(AsyncRequestHelper, Runnable)
+
+NS_IMETHODIMP
+AsyncRequestHelper::Run() {
+ nsresult rv;
+
+ switch (mState) {
+ case State::Initial:
+ rv = Start();
+ break;
+
+ case State::Finishing:
+ Finish();
+ return NS_OK;
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ mState = State::Finishing;
+
+ if (IsOnOwningThread()) {
+ Finish();
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+ }
+
+ return NS_OK;
+}
+
+void AsyncRequestHelper::OnResponse(const LSRequestResponse& aResponse) {
+ AssertIsOnDOMFileThread();
+ MOZ_ASSERT(mState == State::ResponsePending);
+
+ mActor = nullptr;
+
+ mResponse = aResponse;
+
+ mState = State::Finishing;
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void SimpleRequestResolver::HandleResponse(nsresult aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPromise);
+
+ mPromise->MaybeReject(aResponse);
+}
+
+void SimpleRequestResolver::HandleResponse(bool aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPromise);
+
+ mPromise->MaybeResolve(aResponse);
+}
+
+[[nodiscard]] static bool ToJSValue(JSContext* aCx,
+ const nsTArray<LSItemInfo>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return false;
+ }
+
+ for (size_t i = 0; i < aArgument.Length(); ++i) {
+ const LSItemInfo& itemInfo = aArgument[i];
+
+ const nsString& key = itemInfo.key();
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, itemInfo.value().AsString(), &value)) {
+ return false;
+ }
+
+ if (!JS_DefineUCProperty(aCx, obj, key.BeginReading(), key.Length(), value,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ aValue.setObject(*obj);
+ return true;
+}
+
+void SimpleRequestResolver::HandleResponse(
+ const nsTArray<LSItemInfo>& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPromise);
+
+ mPromise->MaybeResolve(aResponse);
+}
+
+void SimpleRequestResolver::OnResponse(
+ const LSSimpleRequestResponse& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ switch (aResponse.type()) {
+ case LSSimpleRequestResponse::Tnsresult:
+ HandleResponse(aResponse.get_nsresult());
+ break;
+
+ case LSSimpleRequestResponse::TLSSimpleRequestPreloadedResponse:
+ HandleResponse(
+ aResponse.get_LSSimpleRequestPreloadedResponse().preloaded());
+ break;
+
+ case LSSimpleRequestResponse::TLSSimpleRequestGetStateResponse:
+ HandleResponse(
+ aResponse.get_LSSimpleRequestGetStateResponse().itemInfos());
+ break;
+
+ default:
+ MOZ_CRASH("Unknown response type!");
+ }
+}
+
+} // namespace mozilla::dom