summaryrefslogtreecommitdiffstats
path: root/dom/cache/Manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/cache/Manager.cpp')
-rw-r--r--dom/cache/Manager.cpp1952
1 files changed, 1952 insertions, 0 deletions
diff --git a/dom/cache/Manager.cpp b/dom/cache/Manager.cpp
new file mode 100644
index 000000000..ee7cc51ac
--- /dev/null
+++ b/dom/cache/Manager.cpp
@@ -0,0 +1,1952 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/dom/cache/Manager.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/cache/Context.h"
+#include "mozilla/dom/cache/DBAction.h"
+#include "mozilla/dom/cache/DBSchema.h"
+#include "mozilla/dom/cache/FileUtils.h"
+#include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/dom/cache/SavedTypes.h"
+#include "mozilla/dom/cache/StreamList.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozStorageHelper.h"
+#include "nsIInputStream.h"
+#include "nsID.h"
+#include "nsIFile.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsTObserverArray.h"
+
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+namespace {
+
+// An Action that is executed when a Context is first created. It ensures that
+// the directory and database are setup properly. This lets other actions
+// not worry about these details.
+class SetupAction final : public SyncDBAction
+{
+public:
+ SetupAction()
+ : SyncDBAction(DBAction::Create)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ nsresult rv = BodyCreateDir(aDBDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // executes in its own transaction
+ rv = db::CreateOrMigrateSchema(aConn);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // If the Context marker file exists, then the last session was
+ // not cleanly shutdown. In these cases sqlite will ensure that
+ // the database is valid, but we might still orphan data. Both
+ // Cache objects and body files can be referenced by DOM objects
+ // after they are "removed" from their parent. So we need to
+ // look and see if any of these late access objects have been
+ // orphaned.
+ //
+ // Note, this must be done after any schema version updates to
+ // ensure our DBSchema methods work correctly.
+ if (MarkerFileExists(aQuotaInfo)) {
+ NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
+ mozStorageTransaction trans(aConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ // Clean up orphaned Cache objects
+ AutoTArray<CacheId, 8> orphanedCacheIdList;
+ nsresult rv = db::FindOrphanedCacheIds(aConn, orphanedCacheIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ for (uint32_t i = 0; i < orphanedCacheIdList.Length(); ++i) {
+ AutoTArray<nsID, 16> deletedBodyIdList;
+ rv = db::DeleteCacheId(aConn, orphanedCacheIdList[i], deletedBodyIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = BodyDeleteFiles(aDBDir, deletedBodyIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ }
+
+ // Clean up orphaned body objects
+ AutoTArray<nsID, 64> knownBodyIdList;
+ rv = db::GetKnownBodyIds(aConn, knownBodyIdList);
+
+ rv = BodyDeleteOrphanedFiles(aDBDir, knownBodyIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ }
+
+ return rv;
+ }
+};
+
+// ----------------------------------------------------------------------------
+
+// Action that is executed when we determine that content has stopped using
+// a body file that has been orphaned.
+class DeleteOrphanedBodyAction final : public Action
+{
+public:
+ explicit DeleteOrphanedBodyAction(const nsTArray<nsID>& aDeletedBodyIdList)
+ : mDeletedBodyIdList(aDeletedBodyIdList)
+ { }
+
+ explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
+ {
+ mDeletedBodyIdList.AppendElement(aBodyId);
+ }
+
+ virtual void
+ RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo, Data*) override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aResolver);
+ MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.mDir);
+
+ // Note that since DeleteOrphanedBodyAction isn't used while the context is
+ // being initialized, we don't need to check for cancellation here.
+
+ nsCOMPtr<nsIFile> dbDir;
+ nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResolver->Resolve(rv);
+ return;
+ }
+
+ rv = dbDir->Append(NS_LITERAL_STRING("cache"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResolver->Resolve(rv);
+ return;
+ }
+
+ rv = BodyDeleteFiles(dbDir, mDeletedBodyIdList);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ aResolver->Resolve(rv);
+ }
+
+private:
+ nsTArray<nsID> mDeletedBodyIdList;
+};
+
+bool IsHeadRequest(const CacheRequest& aRequest, const CacheQueryParams& aParams)
+{
+ return !aParams.ignoreMethod() && aRequest.method().LowerCaseEqualsLiteral("head");
+}
+
+bool IsHeadRequest(const CacheRequestOrVoid& aRequest, const CacheQueryParams& aParams)
+{
+ if (aRequest.type() == CacheRequestOrVoid::TCacheRequest) {
+ return !aParams.ignoreMethod() &&
+ aRequest.get_CacheRequest().method().LowerCaseEqualsLiteral("head");
+ }
+ return false;
+}
+
+} // namespace
+
+// ----------------------------------------------------------------------------
+
+// Singleton class to track Manager instances and ensure there is only
+// one for each unique ManagerId.
+class Manager::Factory
+{
+public:
+ friend class StaticAutoPtr<Manager::Factory>;
+
+ static nsresult
+ GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut)
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // Ensure there is a factory instance. This forces the Get() call
+ // below to use the same factory.
+ nsresult rv = MaybeCreateInstance();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ RefPtr<Manager> ref = Get(aManagerId);
+ if (!ref) {
+ // TODO: replace this with a thread pool (bug 1119864)
+ nsCOMPtr<nsIThread> ioThread;
+ rv = NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ ref = new Manager(aManagerId, ioThread);
+
+ // There may be an old manager for this origin in the process of
+ // cleaning up. We need to tell the new manager about this so
+ // that it won't actually start until the old manager is done.
+ RefPtr<Manager> oldManager = Get(aManagerId, Closing);
+ ref->Init(oldManager);
+
+ MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
+ sFactory->mManagerList.AppendElement(ref);
+ }
+
+ ref.forget(aManagerOut);
+
+ return NS_OK;
+ }
+
+ static already_AddRefed<Manager>
+ Get(ManagerId* aManagerId, State aState = Open)
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ nsresult rv = MaybeCreateInstance();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
+
+ // Iterate in reverse to find the most recent, matching Manager. This
+ // is important when looking for a Closing Manager. If a new Manager
+ // chains to an old Manager we want it to be the most recent one.
+ ManagerList::BackwardIterator iter(sFactory->mManagerList);
+ while (iter.HasMore()) {
+ RefPtr<Manager> manager = iter.GetNext();
+ if (aState == manager->GetState() && *manager->mManagerId == *aManagerId) {
+ return manager.forget();
+ }
+ }
+
+ return nullptr;
+ }
+
+ static void
+ Remove(Manager* aManager)
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(aManager);
+ MOZ_DIAGNOSTIC_ASSERT(sFactory);
+
+ MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(aManager));
+
+ // clean up the factory singleton if there are no more managers
+ MaybeDestroyInstance();
+ }
+
+ static void
+ Abort(const nsACString& aOrigin)
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!sFactory) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+ {
+ ManagerList::ForwardIterator iter(sFactory->mManagerList);
+ while (iter.HasMore()) {
+ RefPtr<Manager> manager = iter.GetNext();
+ if (aOrigin.IsVoid() ||
+ manager->mManagerId->QuotaOrigin() == aOrigin) {
+ manager->Abort();
+ }
+ }
+ }
+ }
+
+ static void
+ ShutdownAll()
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!sFactory) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+ {
+ // Note that we are synchronously calling shutdown code here. If any
+ // of the shutdown code synchronously decides to delete the Factory
+ // we need to delay that delete until the end of this method.
+ AutoRestore<bool> restore(sFactory->mInSyncShutdown);
+ sFactory->mInSyncShutdown = true;
+
+ ManagerList::ForwardIterator iter(sFactory->mManagerList);
+ while (iter.HasMore()) {
+ RefPtr<Manager> manager = iter.GetNext();
+ manager->Shutdown();
+ }
+ }
+
+ MaybeDestroyInstance();
+ }
+
+ static bool
+ IsShutdownAllComplete()
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ return !sFactory;
+ }
+
+private:
+ Factory()
+ : mInSyncShutdown(false)
+ {
+ MOZ_COUNT_CTOR(cache::Manager::Factory);
+ }
+
+ ~Factory()
+ {
+ MOZ_COUNT_DTOR(cache::Manager::Factory);
+ MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(!mInSyncShutdown);
+ }
+
+ static nsresult
+ MaybeCreateInstance()
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!sFactory) {
+ // Be clear about what we are locking. sFactory is bg thread only, so
+ // we don't need to lock it here. Just protect sFactoryShutdown and
+ // sBackgroundThread.
+ {
+ StaticMutexAutoLock lock(sMutex);
+
+ if (sFactoryShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ }
+
+ // We cannot use ClearOnShutdown() here because we're not on the main
+ // thread. Instead, we delete sFactory in Factory::Remove() after the
+ // last manager is removed. ShutdownObserver ensures this happens
+ // before shutdown.
+ sFactory = new Factory();
+ }
+
+ // Never return sFactory to code outside Factory. We need to delete it
+ // out from under ourselves just before we return from Remove(). This
+ // would be (even more) dangerous if other code had a pointer to the
+ // factory itself.
+
+ return NS_OK;
+ }
+
+ static void
+ MaybeDestroyInstance()
+ {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(sFactory);
+
+ // If the factory is is still in use then we cannot delete yet. This
+ // could be due to managers still existing or because we are in the
+ // middle of shutting down. We need to be careful not to delete ourself
+ // synchronously during shutdown.
+ if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncShutdown) {
+ return;
+ }
+
+ sFactory = nullptr;
+ }
+
+ // Singleton created on demand and deleted when last Manager is cleared
+ // in Remove().
+ // PBackground thread only.
+ static StaticAutoPtr<Factory> sFactory;
+
+ // protects following static attribute
+ static StaticMutex sMutex;
+
+ // Indicate if shutdown has occurred to block re-creation of sFactory.
+ // Must hold sMutex to access.
+ static bool sFactoryShutdown;
+
+ // Weak references as we don't want to keep Manager objects alive forever.
+ // When a Manager is destroyed it calls Factory::Remove() to clear itself.
+ // PBackground thread only.
+ typedef nsTObserverArray<Manager*> ManagerList;
+ ManagerList mManagerList;
+
+ // This flag is set when we are looping through the list and calling
+ // Shutdown() on each Manager. We need to be careful not to synchronously
+ // trigger the deletion of the factory while still executing this loop.
+ bool mInSyncShutdown;
+};
+
+// static
+StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
+
+// static
+StaticMutex Manager::Factory::sMutex;
+
+// static
+bool Manager::Factory::sFactoryShutdown = false;
+
+// ----------------------------------------------------------------------------
+
+// Abstract class to help implement the various Actions. The vast majority
+// of Actions are synchronous and need to report back to a Listener on the
+// Manager.
+class Manager::BaseAction : public SyncDBAction
+{
+protected:
+ BaseAction(Manager* aManager, ListenerId aListenerId)
+ : SyncDBAction(DBAction::Existing)
+ , mManager(aManager)
+ , mListenerId(aListenerId)
+ {
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) = 0;
+
+ virtual void
+ CompleteOnInitiatingThread(nsresult aRv) override
+ {
+ NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
+ Listener* listener = mManager->GetListener(mListenerId);
+ if (listener) {
+ Complete(listener, ErrorResult(aRv));
+ }
+
+ // ensure we release the manager on the initiating thread
+ mManager = nullptr;
+ }
+
+ RefPtr<Manager> mManager;
+ const ListenerId mListenerId;
+};
+
+// ----------------------------------------------------------------------------
+
+// Action that is executed when we determine that content has stopped using
+// a Cache object that has been orphaned.
+class Manager::DeleteOrphanedCacheAction final : public SyncDBAction
+{
+public:
+ DeleteOrphanedCacheAction(Manager* aManager, CacheId aCacheId)
+ : SyncDBAction(DBAction::Existing)
+ , mManager(aManager)
+ , mCacheId(aCacheId)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ mozStorageTransaction trans(aConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ nsresult rv = db::DeleteCacheId(aConn, mCacheId, mDeletedBodyIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = trans.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ return rv;
+ }
+
+ virtual void
+ CompleteOnInitiatingThread(nsresult aRv) override
+ {
+ mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+
+ // ensure we release the manager on the initiating thread
+ mManager = nullptr;
+ }
+
+private:
+ RefPtr<Manager> mManager;
+ const CacheId mCacheId;
+ nsTArray<nsID> mDeletedBodyIdList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheMatchAction final : public Manager::BaseAction
+{
+public:
+ CacheMatchAction(Manager* aManager, ListenerId aListenerId,
+ CacheId aCacheId, const CacheMatchArgs& aArgs,
+ StreamList* aStreamList)
+ : BaseAction(aManager, aListenerId)
+ , mCacheId(aCacheId)
+ , mArgs(aArgs)
+ , mStreamList(aStreamList)
+ , mFoundResponse(false)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ nsresult rv = db::CacheMatch(aConn, mCacheId, mArgs.request(),
+ mArgs.params(), &mFoundResponse, &mResponse);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ if (!mFoundResponse || !mResponse.mHasBodyId
+ || IsHeadRequest(mArgs.request(), mArgs.params())) {
+ mResponse.mHasBodyId = false;
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = BodyOpen(aQuotaInfo, aDBDir, mResponse.mBodyId, getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+ mStreamList->Add(mResponse.mBodyId, stream);
+
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ if (!mFoundResponse) {
+ aListener->OnOpComplete(Move(aRv), CacheMatchResult(void_t()));
+ } else {
+ mStreamList->Activate(mCacheId);
+ aListener->OnOpComplete(Move(aRv), CacheMatchResult(void_t()), mResponse,
+ mStreamList);
+ }
+ mStreamList = nullptr;
+ }
+
+ virtual bool MatchesCacheId(CacheId aCacheId) const override
+ {
+ return aCacheId == mCacheId;
+ }
+
+private:
+ const CacheId mCacheId;
+ const CacheMatchArgs mArgs;
+ RefPtr<StreamList> mStreamList;
+ bool mFoundResponse;
+ SavedResponse mResponse;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheMatchAllAction final : public Manager::BaseAction
+{
+public:
+ CacheMatchAllAction(Manager* aManager, ListenerId aListenerId,
+ CacheId aCacheId, const CacheMatchAllArgs& aArgs,
+ StreamList* aStreamList)
+ : BaseAction(aManager, aListenerId)
+ , mCacheId(aCacheId)
+ , mArgs(aArgs)
+ , mStreamList(aStreamList)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ nsresult rv = db::CacheMatchAll(aConn, mCacheId, mArgs.requestOrVoid(),
+ mArgs.params(), mSavedResponses);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
+ if (!mSavedResponses[i].mHasBodyId
+ || IsHeadRequest(mArgs.requestOrVoid(), mArgs.params())) {
+ mSavedResponses[i].mHasBodyId = false;
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = BodyOpen(aQuotaInfo, aDBDir, mSavedResponses[i].mBodyId,
+ getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+ mStreamList->Add(mSavedResponses[i].mBodyId, stream);
+ }
+
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ mStreamList->Activate(mCacheId);
+ aListener->OnOpComplete(Move(aRv), CacheMatchAllResult(), mSavedResponses,
+ mStreamList);
+ mStreamList = nullptr;
+ }
+
+ virtual bool MatchesCacheId(CacheId aCacheId) const override
+ {
+ return aCacheId == mCacheId;
+ }
+
+private:
+ const CacheId mCacheId;
+ const CacheMatchAllArgs mArgs;
+ RefPtr<StreamList> mStreamList;
+ nsTArray<SavedResponse> mSavedResponses;
+};
+
+// ----------------------------------------------------------------------------
+
+// This is the most complex Action. It puts a request/response pair into the
+// Cache. It does not complete until all of the body data has been saved to
+// disk. This means its an asynchronous Action.
+class Manager::CachePutAllAction final : public DBAction
+{
+public:
+ CachePutAllAction(Manager* aManager, ListenerId aListenerId,
+ CacheId aCacheId,
+ const nsTArray<CacheRequestResponse>& aPutList,
+ const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
+ const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
+ : DBAction(DBAction::Existing)
+ , mManager(aManager)
+ , mListenerId(aListenerId)
+ , mCacheId(aCacheId)
+ , mList(aPutList.Length())
+ , mExpectedAsyncCopyCompletions(1)
+ , mAsyncResult(NS_OK)
+ , mMutex("cache::Manager::CachePutAllAction")
+ {
+ MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
+ MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
+
+ for (uint32_t i = 0; i < aPutList.Length(); ++i) {
+ Entry* entry = mList.AppendElement();
+ entry->mRequest = aPutList[i].request();
+ entry->mRequestStream = aRequestStreamList[i];
+ entry->mResponse = aPutList[i].response();
+ entry->mResponseStream = aResponseStreamList[i];
+ }
+ }
+
+private:
+ ~CachePutAllAction() { }
+
+ virtual void
+ RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
+ nsIFile* aDBDir, mozIStorageConnection* aConn) override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aResolver);
+ MOZ_DIAGNOSTIC_ASSERT(aDBDir);
+ MOZ_DIAGNOSTIC_ASSERT(aConn);
+ MOZ_DIAGNOSTIC_ASSERT(!mResolver);
+ MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
+ MOZ_DIAGNOSTIC_ASSERT(!mConn);
+
+ MOZ_DIAGNOSTIC_ASSERT(!mTargetThread);
+ mTargetThread = NS_GetCurrentThread();
+ MOZ_DIAGNOSTIC_ASSERT(mTargetThread);
+
+ // We should be pre-initialized to expect one async completion. This is
+ // the "manual" completion we call at the end of this method in all
+ // cases.
+ MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
+
+ mResolver = aResolver;
+ mDBDir = aDBDir;
+ mConn = aConn;
+
+ // File bodies are streamed to disk via asynchronous copying. Start
+ // this copying now. Each copy will eventually result in a call
+ // to OnAsyncCopyComplete().
+ nsresult rv = NS_OK;
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ rv = StartStreamCopy(aQuotaInfo, mList[i], RequestStream,
+ &mExpectedAsyncCopyCompletions);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ rv = StartStreamCopy(aQuotaInfo, mList[i], ResponseStream,
+ &mExpectedAsyncCopyCompletions);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+ }
+
+
+ // Always call OnAsyncCopyComplete() manually here. This covers the
+ // case where there is no async copying and also reports any startup
+ // errors correctly. If we hit an error, then OnAsyncCopyComplete()
+ // will cancel any async copying.
+ OnAsyncCopyComplete(rv);
+ }
+
+ // Called once for each asynchronous file copy whether it succeeds or
+ // fails. If a file copy is canceled, it still calls this method with
+ // an error code.
+ void
+ OnAsyncCopyComplete(nsresult aRv)
+ {
+ MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+ MOZ_DIAGNOSTIC_ASSERT(mConn);
+ MOZ_DIAGNOSTIC_ASSERT(mResolver);
+ MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
+
+ // Explicitly check for cancellation here to catch a race condition.
+ // Consider:
+ //
+ // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
+ // copy context yet.
+ // 2) CancelAllStreamCopying() occurs on PBackground thread
+ // 3) Copy context from (1) is saved on IO thread.
+ //
+ // Checking for cancellation here catches this condition when we
+ // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
+ //
+ // This explicit cancellation check also handles the case where we
+ // are canceled just after all stream copying completes. We should
+ // abort the synchronous DB operations in this case if we have not
+ // started them yet.
+ if (NS_SUCCEEDED(aRv) && IsCanceled()) {
+ aRv = NS_ERROR_ABORT;
+ }
+
+ // If any of the async copies fail, we need to still wait for them all to
+ // complete. Cancel any other streams still working and remember the
+ // error. All canceled streams will call OnAsyncCopyComplete().
+ if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
+ CancelAllStreamCopying();
+ mAsyncResult = aRv;
+ }
+
+ // Check to see if async copying is still on-going. If so, then simply
+ // return for now. We must wait for a later OnAsyncCopyComplete() call.
+ mExpectedAsyncCopyCompletions -= 1;
+ if (mExpectedAsyncCopyCompletions > 0) {
+ return;
+ }
+
+ // We have finished with all async copying. Indicate this by clearing all
+ // our copy contexts.
+ {
+ MutexAutoLock lock(mMutex);
+ mCopyContextList.Clear();
+ }
+
+ // An error occurred while async copying. Terminate the Action.
+ // DoResolve() will clean up any files we may have written.
+ if (NS_FAILED(mAsyncResult)) {
+ DoResolve(mAsyncResult);
+ return;
+ }
+
+ mozStorageTransaction trans(mConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ nsresult rv = NS_OK;
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ Entry& e = mList[i];
+ if (e.mRequestStream) {
+ rv = BodyFinalizeWrite(mDBDir, e.mRequestBodyId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoResolve(rv);
+ return;
+ }
+ }
+ if (e.mResponseStream) {
+ rv = BodyFinalizeWrite(mDBDir, e.mResponseBodyId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoResolve(rv);
+ return;
+ }
+ }
+
+ rv = db::CachePut(mConn, mCacheId, e.mRequest,
+ e.mRequestStream ? &e.mRequestBodyId : nullptr,
+ e.mResponse,
+ e.mResponseStream ? &e.mResponseBodyId : nullptr,
+ mDeletedBodyIdList);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoResolve(rv);
+ return;
+ }
+ }
+
+ rv = trans.Commit();
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ DoResolve(rv);
+ }
+
+ virtual void
+ CompleteOnInitiatingThread(nsresult aRv) override
+ {
+ NS_ASSERT_OWNINGTHREAD(Action);
+
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ mList[i].mRequestStream = nullptr;
+ mList[i].mResponseStream = nullptr;
+ }
+
+ mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+
+ Listener* listener = mManager->GetListener(mListenerId);
+ mManager = nullptr;
+ if (listener) {
+ listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
+ }
+ }
+
+ virtual void
+ CancelOnInitiatingThread() override
+ {
+ NS_ASSERT_OWNINGTHREAD(Action);
+ Action::CancelOnInitiatingThread();
+ CancelAllStreamCopying();
+ }
+
+ virtual bool MatchesCacheId(CacheId aCacheId) const override
+ {
+ NS_ASSERT_OWNINGTHREAD(Action);
+ return aCacheId == mCacheId;
+ }
+
+ struct Entry
+ {
+ CacheRequest mRequest;
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ nsID mRequestBodyId;
+ nsCOMPtr<nsISupports> mRequestCopyContext;
+
+ CacheResponse mResponse;
+ nsCOMPtr<nsIInputStream> mResponseStream;
+ nsID mResponseBodyId;
+ nsCOMPtr<nsISupports> mResponseCopyContext;
+ };
+
+ enum StreamId
+ {
+ RequestStream,
+ ResponseStream
+ };
+
+ nsresult
+ StartStreamCopy(const QuotaInfo& aQuotaInfo, Entry& aEntry,
+ StreamId aStreamId, uint32_t* aCopyCountOut)
+ {
+ MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+ MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
+
+ if (IsCanceled()) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIInputStream> source;
+ nsID* bodyId;
+
+ if (aStreamId == RequestStream) {
+ source = aEntry.mRequestStream;
+ bodyId = &aEntry.mRequestBodyId;
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(aStreamId == ResponseStream);
+ source = aEntry.mResponseStream;
+ bodyId = &aEntry.mResponseBodyId;
+ }
+
+ if (!source) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> copyContext;
+
+ nsresult rv = BodyStartWriteStream(aQuotaInfo, mDBDir, source, this,
+ AsyncCopyCompleteFunc, bodyId,
+ getter_AddRefs(copyContext));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ mBodyIdWrittenList.AppendElement(*bodyId);
+
+ if (copyContext) {
+ MutexAutoLock lock(mMutex);
+ mCopyContextList.AppendElement(copyContext);
+ }
+
+ *aCopyCountOut += 1;
+
+ return rv;
+ }
+
+ void
+ CancelAllStreamCopying()
+ {
+ // May occur on either owning thread or target thread
+ MutexAutoLock lock(mMutex);
+ for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
+ BodyCancelWrite(mDBDir, mCopyContextList[i]);
+ }
+ mCopyContextList.Clear();
+ }
+
+ static void
+ AsyncCopyCompleteFunc(void* aClosure, nsresult aRv)
+ {
+ // May be on any thread, including STS event target.
+ MOZ_DIAGNOSTIC_ASSERT(aClosure);
+ // Weak ref as we are guaranteed to the action is alive until
+ // CompleteOnInitiatingThread is called.
+ CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
+ action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
+ }
+
+ void
+ CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv)
+ {
+ // May be on any thread, including STS event target. Non-owning runnable
+ // here since we are guaranteed the Action will survive until
+ // CompleteOnInitiatingThread is called.
+ nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
+ this, &CachePutAllAction::OnAsyncCopyComplete, aRv);
+ MOZ_ALWAYS_SUCCEEDS(
+ mTargetThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
+ }
+
+ void
+ DoResolve(nsresult aRv)
+ {
+ MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+
+ // DoResolve() must not be called until all async copying has completed.
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mCopyContextList.IsEmpty());
+ }
+#endif
+
+ // Clean up any files we might have written before hitting the error.
+ if (NS_FAILED(aRv)) {
+ BodyDeleteFiles(mDBDir, mBodyIdWrittenList);
+ }
+
+ // Must be released on the target thread where it was opened.
+ mConn = nullptr;
+
+ // Drop our ref to the target thread as we are done with this thread.
+ // Also makes our thread assertions catch any incorrect method calls
+ // after resolve.
+ mTargetThread = nullptr;
+
+ // Make sure to de-ref the resolver per the Action API contract.
+ RefPtr<Action::Resolver> resolver;
+ mResolver.swap(resolver);
+ resolver->Resolve(aRv);
+ }
+
+ // initiating thread only
+ RefPtr<Manager> mManager;
+ const ListenerId mListenerId;
+
+ // Set on initiating thread, read on target thread. State machine guarantees
+ // these are not modified while being read by the target thread.
+ const CacheId mCacheId;
+ nsTArray<Entry> mList;
+ uint32_t mExpectedAsyncCopyCompletions;
+
+ // target thread only
+ RefPtr<Resolver> mResolver;
+ nsCOMPtr<nsIFile> mDBDir;
+ nsCOMPtr<mozIStorageConnection> mConn;
+ nsCOMPtr<nsIThread> mTargetThread;
+ nsresult mAsyncResult;
+ nsTArray<nsID> mBodyIdWrittenList;
+
+ // Written to on target thread, accessed on initiating thread after target
+ // thread activity is guaranteed complete
+ nsTArray<nsID> mDeletedBodyIdList;
+
+ // accessed from any thread while mMutex locked
+ Mutex mMutex;
+ nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheDeleteAction final : public Manager::BaseAction
+{
+public:
+ CacheDeleteAction(Manager* aManager, ListenerId aListenerId,
+ CacheId aCacheId, const CacheDeleteArgs& aArgs)
+ : BaseAction(aManager, aListenerId)
+ , mCacheId(aCacheId)
+ , mArgs(aArgs)
+ , mSuccess(false)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ mozStorageTransaction trans(aConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ nsresult rv = db::CacheDelete(aConn, mCacheId, mArgs.request(),
+ mArgs.params(), mDeletedBodyIdList,
+ &mSuccess);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = trans.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mSuccess = false;
+ return rv;
+ }
+
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+ aListener->OnOpComplete(Move(aRv), CacheDeleteResult(mSuccess));
+ }
+
+ virtual bool MatchesCacheId(CacheId aCacheId) const override
+ {
+ return aCacheId == mCacheId;
+ }
+
+private:
+ const CacheId mCacheId;
+ const CacheDeleteArgs mArgs;
+ bool mSuccess;
+ nsTArray<nsID> mDeletedBodyIdList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheKeysAction final : public Manager::BaseAction
+{
+public:
+ CacheKeysAction(Manager* aManager, ListenerId aListenerId,
+ CacheId aCacheId, const CacheKeysArgs& aArgs,
+ StreamList* aStreamList)
+ : BaseAction(aManager, aListenerId)
+ , mCacheId(aCacheId)
+ , mArgs(aArgs)
+ , mStreamList(aStreamList)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ nsresult rv = db::CacheKeys(aConn, mCacheId, mArgs.requestOrVoid(),
+ mArgs.params(), mSavedRequests);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
+ if (!mSavedRequests[i].mHasBodyId
+ || IsHeadRequest(mArgs.requestOrVoid(), mArgs.params())) {
+ mSavedRequests[i].mHasBodyId = false;
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = BodyOpen(aQuotaInfo, aDBDir, mSavedRequests[i].mBodyId,
+ getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+ mStreamList->Add(mSavedRequests[i].mBodyId, stream);
+ }
+
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ mStreamList->Activate(mCacheId);
+ aListener->OnOpComplete(Move(aRv), CacheKeysResult(), mSavedRequests,
+ mStreamList);
+ mStreamList = nullptr;
+ }
+
+ virtual bool MatchesCacheId(CacheId aCacheId) const override
+ {
+ return aCacheId == mCacheId;
+ }
+
+private:
+ const CacheId mCacheId;
+ const CacheKeysArgs mArgs;
+ RefPtr<StreamList> mStreamList;
+ nsTArray<SavedRequest> mSavedRequests;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageMatchAction final : public Manager::BaseAction
+{
+public:
+ StorageMatchAction(Manager* aManager, ListenerId aListenerId,
+ Namespace aNamespace,
+ const StorageMatchArgs& aArgs,
+ StreamList* aStreamList)
+ : BaseAction(aManager, aListenerId)
+ , mNamespace(aNamespace)
+ , mArgs(aArgs)
+ , mStreamList(aStreamList)
+ , mFoundResponse(false)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ nsresult rv = db::StorageMatch(aConn, mNamespace, mArgs.request(),
+ mArgs.params(), &mFoundResponse,
+ &mSavedResponse);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ if (!mFoundResponse || !mSavedResponse.mHasBodyId
+ || IsHeadRequest(mArgs.request(), mArgs.params())) {
+ mSavedResponse.mHasBodyId = false;
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = BodyOpen(aQuotaInfo, aDBDir, mSavedResponse.mBodyId,
+ getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+ mStreamList->Add(mSavedResponse.mBodyId, stream);
+
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ if (!mFoundResponse) {
+ aListener->OnOpComplete(Move(aRv), StorageMatchResult(void_t()));
+ } else {
+ mStreamList->Activate(mSavedResponse.mCacheId);
+ aListener->OnOpComplete(Move(aRv), StorageMatchResult(void_t()), mSavedResponse,
+ mStreamList);
+ }
+ mStreamList = nullptr;
+ }
+
+private:
+ const Namespace mNamespace;
+ const StorageMatchArgs mArgs;
+ RefPtr<StreamList> mStreamList;
+ bool mFoundResponse;
+ SavedResponse mSavedResponse;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageHasAction final : public Manager::BaseAction
+{
+public:
+ StorageHasAction(Manager* aManager, ListenerId aListenerId,
+ Namespace aNamespace, const StorageHasArgs& aArgs)
+ : BaseAction(aManager, aListenerId)
+ , mNamespace(aNamespace)
+ , mArgs(aArgs)
+ , mCacheFound(false)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ CacheId cacheId;
+ return db::StorageGetCacheId(aConn, mNamespace, mArgs.key(),
+ &mCacheFound, &cacheId);
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ aListener->OnOpComplete(Move(aRv), StorageHasResult(mCacheFound));
+ }
+
+private:
+ const Namespace mNamespace;
+ const StorageHasArgs mArgs;
+ bool mCacheFound;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageOpenAction final : public Manager::BaseAction
+{
+public:
+ StorageOpenAction(Manager* aManager, ListenerId aListenerId,
+ Namespace aNamespace, const StorageOpenArgs& aArgs)
+ : BaseAction(aManager, aListenerId)
+ , mNamespace(aNamespace)
+ , mArgs(aArgs)
+ , mCacheId(INVALID_CACHE_ID)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ // Cache does not exist, create it instead
+ mozStorageTransaction trans(aConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ // Look for existing cache
+ bool cacheFound;
+ nsresult rv = db::StorageGetCacheId(aConn, mNamespace, mArgs.key(),
+ &cacheFound, &mCacheId);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (cacheFound) {
+ MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
+ return rv;
+ }
+
+ rv = db::CreateCacheId(aConn, &mCacheId);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = db::StoragePutCache(aConn, mNamespace, mArgs.key(), mCacheId);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = trans.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
+ aListener->OnOpComplete(Move(aRv), StorageOpenResult(), mCacheId);
+ }
+
+private:
+ const Namespace mNamespace;
+ const StorageOpenArgs mArgs;
+ CacheId mCacheId;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageDeleteAction final : public Manager::BaseAction
+{
+public:
+ StorageDeleteAction(Manager* aManager, ListenerId aListenerId,
+ Namespace aNamespace, const StorageDeleteArgs& aArgs)
+ : BaseAction(aManager, aListenerId)
+ , mNamespace(aNamespace)
+ , mArgs(aArgs)
+ , mCacheDeleted(false)
+ , mCacheId(INVALID_CACHE_ID)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ mozStorageTransaction trans(aConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ bool exists;
+ nsresult rv = db::StorageGetCacheId(aConn, mNamespace, mArgs.key(),
+ &exists, &mCacheId);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ if (!exists) {
+ mCacheDeleted = false;
+ return NS_OK;
+ }
+
+ rv = db::StorageForgetCache(aConn, mNamespace, mArgs.key());
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = trans.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ mCacheDeleted = true;
+ return rv;
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ if (mCacheDeleted) {
+ // If content is referencing this cache, mark it orphaned to be
+ // deleted later.
+ if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
+
+ // no outstanding references, delete immediately
+ RefPtr<Context> context = mManager->mContext;
+
+ if (context->IsCanceled()) {
+ context->NoteOrphanedData();
+ } else {
+ context->CancelForCacheId(mCacheId);
+ RefPtr<Action> action =
+ new DeleteOrphanedCacheAction(mManager, mCacheId);
+ context->Dispatch(action);
+ }
+ }
+ }
+
+ aListener->OnOpComplete(Move(aRv), StorageDeleteResult(mCacheDeleted));
+ }
+
+private:
+ const Namespace mNamespace;
+ const StorageDeleteArgs mArgs;
+ bool mCacheDeleted;
+ CacheId mCacheId;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageKeysAction final : public Manager::BaseAction
+{
+public:
+ StorageKeysAction(Manager* aManager, ListenerId aListenerId,
+ Namespace aNamespace)
+ : BaseAction(aManager, aListenerId)
+ , mNamespace(aNamespace)
+ { }
+
+ virtual nsresult
+ RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+ mozIStorageConnection* aConn) override
+ {
+ return db::StorageGetKeys(aConn, mNamespace, mKeys);
+ }
+
+ virtual void
+ Complete(Listener* aListener, ErrorResult&& aRv) override
+ {
+ if (aRv.Failed()) {
+ mKeys.Clear();
+ }
+ aListener->OnOpComplete(Move(aRv), StorageKeysResult(mKeys));
+ }
+
+private:
+ const Namespace mNamespace;
+ nsTArray<nsString> mKeys;
+};
+
+// ----------------------------------------------------------------------------
+
+//static
+Manager::ListenerId Manager::sNextListenerId = 0;
+
+void
+Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult)
+{
+ OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, nsTArray<SavedResponse>(),
+ nsTArray<SavedRequest>(), nullptr);
+}
+
+void
+Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
+ CacheId aOpenedCacheId)
+{
+ OnOpComplete(Move(aRv), aResult, aOpenedCacheId, nsTArray<SavedResponse>(),
+ nsTArray<SavedRequest>(), nullptr);
+}
+
+void
+Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
+ const SavedResponse& aSavedResponse,
+ StreamList* aStreamList)
+{
+ AutoTArray<SavedResponse, 1> responseList;
+ responseList.AppendElement(aSavedResponse);
+ OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, responseList,
+ nsTArray<SavedRequest>(), aStreamList);
+}
+
+void
+Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
+ const nsTArray<SavedResponse>& aSavedResponseList,
+ StreamList* aStreamList)
+{
+ OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, aSavedResponseList,
+ nsTArray<SavedRequest>(), aStreamList);
+}
+
+void
+Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
+ const nsTArray<SavedRequest>& aSavedRequestList,
+ StreamList* aStreamList)
+{
+ OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, nsTArray<SavedResponse>(),
+ aSavedRequestList, aStreamList);
+}
+
+// static
+nsresult
+Manager::GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut)
+{
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ return Factory::GetOrCreate(aManagerId, aManagerOut);
+}
+
+// static
+already_AddRefed<Manager>
+Manager::Get(ManagerId* aManagerId)
+{
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ return Factory::Get(aManagerId);
+}
+
+// static
+void
+Manager::ShutdownAll()
+{
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ Factory::ShutdownAll();
+
+ while (!Factory::IsShutdownAllComplete()) {
+ if (!NS_ProcessNextEvent()) {
+ NS_WARNING("Something bad happened!");
+ break;
+ }
+ }
+}
+
+// static
+void
+Manager::Abort(const nsACString& aOrigin)
+{
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ Factory::Abort(aOrigin);
+}
+
+void
+Manager::RemoveListener(Listener* aListener)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ // There may not be a listener here in the case where an actor is killed
+ // before it can perform any actual async requests on Manager.
+ mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
+ MOZ_ASSERT(!mListeners.Contains(aListener,
+ ListenerEntryListenerComparator()));
+ MaybeAllowContextToClose();
+}
+
+void
+Manager::RemoveContext(Context* aContext)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(mContext);
+ MOZ_DIAGNOSTIC_ASSERT(mContext == aContext);
+
+ // Whether the Context destruction was triggered from the Manager going
+ // idle or the underlying storage being invalidated, we should know we
+ // are closing before the Context is destroyed.
+ MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
+
+ // Before forgetting the Context, check to see if we have any outstanding
+ // cache or body objects waiting for deletion. If so, note that we've
+ // orphaned data so it will be cleaned up on the next open.
+ for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+ if (mCacheIdRefs[i].mOrphaned) {
+ aContext->NoteOrphanedData();
+ break;
+ }
+ }
+
+ for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+ if (mBodyIdRefs[i].mOrphaned) {
+ aContext->NoteOrphanedData();
+ break;
+ }
+ }
+
+ mContext = nullptr;
+
+ // Once the context is gone, we can immediately remove ourself from the
+ // Factory list. We don't need to block shutdown by staying in the list
+ // any more.
+ Factory::Remove(this);
+}
+
+void
+Manager::NoteClosing()
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ // This can be called more than once legitimately through different paths.
+ mState = Closing;
+}
+
+Manager::State
+Manager::GetState() const
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ return mState;
+}
+
+void
+Manager::AddRefCacheId(CacheId aCacheId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+ if (mCacheIdRefs[i].mCacheId == aCacheId) {
+ mCacheIdRefs[i].mCount += 1;
+ return;
+ }
+ }
+ CacheIdRefCounter* entry = mCacheIdRefs.AppendElement();
+ entry->mCacheId = aCacheId;
+ entry->mCount = 1;
+ entry->mOrphaned = false;
+}
+
+void
+Manager::ReleaseCacheId(CacheId aCacheId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+ if (mCacheIdRefs[i].mCacheId == aCacheId) {
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+ uint32_t oldRef = mCacheIdRefs[i].mCount;
+#endif
+ mCacheIdRefs[i].mCount -= 1;
+ MOZ_DIAGNOSTIC_ASSERT(mCacheIdRefs[i].mCount < oldRef);
+ if (mCacheIdRefs[i].mCount == 0) {
+ bool orphaned = mCacheIdRefs[i].mOrphaned;
+ mCacheIdRefs.RemoveElementAt(i);
+ RefPtr<Context> context = mContext;
+ // If the context is already gone, then orphan flag should have been
+ // set in RemoveContext().
+ if (orphaned && context) {
+ if (context->IsCanceled()) {
+ context->NoteOrphanedData();
+ } else {
+ context->CancelForCacheId(aCacheId);
+ RefPtr<Action> action = new DeleteOrphanedCacheAction(this,
+ aCacheId);
+ context->Dispatch(action);
+ }
+ }
+ }
+ MaybeAllowContextToClose();
+ return;
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
+}
+
+void
+Manager::AddRefBodyId(const nsID& aBodyId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+ if (mBodyIdRefs[i].mBodyId == aBodyId) {
+ mBodyIdRefs[i].mCount += 1;
+ return;
+ }
+ }
+ BodyIdRefCounter* entry = mBodyIdRefs.AppendElement();
+ entry->mBodyId = aBodyId;
+ entry->mCount = 1;
+ entry->mOrphaned = false;
+}
+
+void
+Manager::ReleaseBodyId(const nsID& aBodyId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+ if (mBodyIdRefs[i].mBodyId == aBodyId) {
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+ uint32_t oldRef = mBodyIdRefs[i].mCount;
+#endif
+ mBodyIdRefs[i].mCount -= 1;
+ MOZ_DIAGNOSTIC_ASSERT(mBodyIdRefs[i].mCount < oldRef);
+ if (mBodyIdRefs[i].mCount < 1) {
+ bool orphaned = mBodyIdRefs[i].mOrphaned;
+ mBodyIdRefs.RemoveElementAt(i);
+ RefPtr<Context> context = mContext;
+ // If the context is already gone, then orphan flag should have been
+ // set in RemoveContext().
+ if (orphaned && context) {
+ if (context->IsCanceled()) {
+ context->NoteOrphanedData();
+ } else {
+ RefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
+ context->Dispatch(action);
+ }
+ }
+ }
+ MaybeAllowContextToClose();
+ return;
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
+}
+
+already_AddRefed<ManagerId>
+Manager::GetManagerId() const
+{
+ RefPtr<ManagerId> ref = mManagerId;
+ return ref.forget();
+}
+
+void
+Manager::AddStreamList(StreamList* aStreamList)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(aStreamList);
+ mStreamLists.AppendElement(aStreamList);
+}
+
+void
+Manager::RemoveStreamList(StreamList* aStreamList)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(aStreamList);
+ mStreamLists.RemoveElement(aStreamList);
+}
+
+void
+Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
+ const CacheOpArgs& aOpArgs)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(aListener);
+ MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
+
+ if (NS_WARN_IF(mState == Closing)) {
+ aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
+ return;
+ }
+
+ RefPtr<Context> context = mContext;
+ MOZ_DIAGNOSTIC_ASSERT(!context->IsCanceled());
+
+ RefPtr<StreamList> streamList = new StreamList(this, context);
+ ListenerId listenerId = SaveListener(aListener);
+
+ RefPtr<Action> action;
+ switch(aOpArgs.type()) {
+ case CacheOpArgs::TCacheMatchArgs:
+ action = new CacheMatchAction(this, listenerId, aCacheId,
+ aOpArgs.get_CacheMatchArgs(), streamList);
+ break;
+ case CacheOpArgs::TCacheMatchAllArgs:
+ action = new CacheMatchAllAction(this, listenerId, aCacheId,
+ aOpArgs.get_CacheMatchAllArgs(),
+ streamList);
+ break;
+ case CacheOpArgs::TCacheDeleteArgs:
+ action = new CacheDeleteAction(this, listenerId, aCacheId,
+ aOpArgs.get_CacheDeleteArgs());
+ break;
+ case CacheOpArgs::TCacheKeysArgs:
+ action = new CacheKeysAction(this, listenerId, aCacheId,
+ aOpArgs.get_CacheKeysArgs(), streamList);
+ break;
+ default:
+ MOZ_CRASH("Unknown Cache operation!");
+ }
+
+ context->Dispatch(action);
+}
+
+void
+Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
+ const CacheOpArgs& aOpArgs)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(aListener);
+
+ if (NS_WARN_IF(mState == Closing)) {
+ aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
+ return;
+ }
+
+ RefPtr<Context> context = mContext;
+ MOZ_DIAGNOSTIC_ASSERT(!context->IsCanceled());
+
+ RefPtr<StreamList> streamList = new StreamList(this, context);
+ ListenerId listenerId = SaveListener(aListener);
+
+ RefPtr<Action> action;
+ switch(aOpArgs.type()) {
+ case CacheOpArgs::TStorageMatchArgs:
+ action = new StorageMatchAction(this, listenerId, aNamespace,
+ aOpArgs.get_StorageMatchArgs(),
+ streamList);
+ break;
+ case CacheOpArgs::TStorageHasArgs:
+ action = new StorageHasAction(this, listenerId, aNamespace,
+ aOpArgs.get_StorageHasArgs());
+ break;
+ case CacheOpArgs::TStorageOpenArgs:
+ action = new StorageOpenAction(this, listenerId, aNamespace,
+ aOpArgs.get_StorageOpenArgs());
+ break;
+ case CacheOpArgs::TStorageDeleteArgs:
+ action = new StorageDeleteAction(this, listenerId, aNamespace,
+ aOpArgs.get_StorageDeleteArgs());
+ break;
+ case CacheOpArgs::TStorageKeysArgs:
+ action = new StorageKeysAction(this, listenerId, aNamespace);
+ break;
+ default:
+ MOZ_CRASH("Unknown CacheStorage operation!");
+ }
+
+ context->Dispatch(action);
+}
+
+void
+Manager::ExecutePutAll(Listener* aListener, CacheId aCacheId,
+ const nsTArray<CacheRequestResponse>& aPutList,
+ const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
+ const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(aListener);
+
+ if (NS_WARN_IF(mState == Closing)) {
+ aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
+ return;
+ }
+
+ RefPtr<Context> context = mContext;
+ MOZ_DIAGNOSTIC_ASSERT(!context->IsCanceled());
+
+ ListenerId listenerId = SaveListener(aListener);
+
+ RefPtr<Action> action = new CachePutAllAction(this, listenerId, aCacheId,
+ aPutList, aRequestStreamList,
+ aResponseStreamList);
+
+ context->Dispatch(action);
+}
+
+Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread)
+ : mManagerId(aManagerId)
+ , mIOThread(aIOThread)
+ , mContext(nullptr)
+ , mShuttingDown(false)
+ , mState(Open)
+{
+ MOZ_DIAGNOSTIC_ASSERT(mManagerId);
+ MOZ_DIAGNOSTIC_ASSERT(mIOThread);
+}
+
+Manager::~Manager()
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
+ MOZ_DIAGNOSTIC_ASSERT(!mContext);
+
+ nsCOMPtr<nsIThread> ioThread;
+ mIOThread.swap(ioThread);
+
+ // Don't spin the event loop in the destructor waiting for the thread to
+ // shutdown. Defer this to the main thread, instead.
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(ioThread, &nsIThread::Shutdown)));
+}
+
+void
+Manager::Init(Manager* aOldManager)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+
+ RefPtr<Context> oldContext;
+ if (aOldManager) {
+ oldContext = aOldManager->mContext;
+ }
+
+ // Create the context immediately. Since there can at most be one Context
+ // per Manager now, this lets us cleanly call Factory::Remove() once the
+ // Context goes away.
+ RefPtr<Action> setupAction = new SetupAction();
+ RefPtr<Context> ref = Context::Create(this, mIOThread, setupAction,
+ oldContext);
+ mContext = ref;
+}
+
+void
+Manager::Shutdown()
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+
+ // Ignore duplicate attempts to shutdown. This can occur when we start
+ // a browser initiated shutdown and then run ~Manager() which also
+ // calls Shutdown().
+ if (mShuttingDown) {
+ return;
+ }
+
+ mShuttingDown = true;
+
+ // Note that we are closing to prevent any new requests from coming in and
+ // creating a new Context. We must ensure all Contexts and IO operations are
+ // complete before shutdown proceeds.
+ NoteClosing();
+
+ // If there is a context, then cancel and only note that we are done after
+ // its cleaned up.
+ if (mContext) {
+ RefPtr<Context> context = mContext;
+ context->CancelAll();
+ return;
+ }
+}
+
+void
+Manager::Abort()
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ MOZ_DIAGNOSTIC_ASSERT(mContext);
+
+ // Note that we are closing to prevent any new requests from coming in and
+ // creating a new Context. We must ensure all Contexts and IO operations are
+ // complete before origin clear proceeds.
+ NoteClosing();
+
+ // Cancel and only note that we are done after the context is cleaned up.
+ RefPtr<Context> context = mContext;
+ context->CancelAll();
+}
+
+Manager::ListenerId
+Manager::SaveListener(Listener* aListener)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+
+ // Once a Listener is added, we keep a reference to it until its
+ // removed. Since the same Listener might make multiple requests,
+ // ensure we only have a single reference in our list.
+ ListenerList::index_type index =
+ mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
+ if (index != ListenerList::NoIndex) {
+ return mListeners[index].mId;
+ }
+
+ ListenerId id = sNextListenerId;
+ sNextListenerId += 1;
+
+ mListeners.AppendElement(ListenerEntry(id, aListener));
+ return id;
+}
+
+Manager::Listener*
+Manager::GetListener(ListenerId aListenerId) const
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ ListenerList::index_type index =
+ mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
+ if (index != ListenerList::NoIndex) {
+ return mListeners[index].mListener;
+ }
+
+ // This can legitimately happen if the actor is deleted while a request is
+ // in process. For example, the child process OOMs.
+ return nullptr;
+}
+
+bool
+Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+ if (mCacheIdRefs[i].mCacheId == aCacheId) {
+ MOZ_DIAGNOSTIC_ASSERT(mCacheIdRefs[i].mCount > 0);
+ MOZ_DIAGNOSTIC_ASSERT(!mCacheIdRefs[i].mOrphaned);
+ mCacheIdRefs[i].mOrphaned = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+// TODO: provide way to set body non-orphaned if its added back to a cache (bug 1110479)
+
+bool
+Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+ for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+ if (mBodyIdRefs[i].mBodyId == aBodyId) {
+ MOZ_DIAGNOSTIC_ASSERT(mBodyIdRefs[i].mCount > 0);
+ MOZ_DIAGNOSTIC_ASSERT(!mBodyIdRefs[i].mOrphaned);
+ mBodyIdRefs[i].mOrphaned = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList)
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+
+ AutoTArray<nsID, 64> deleteNowList;
+ deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
+
+ for (uint32_t i = 0; i < aDeletedBodyIdList.Length(); ++i) {
+ if (!SetBodyIdOrphanedIfRefed(aDeletedBodyIdList[i])) {
+ deleteNowList.AppendElement(aDeletedBodyIdList[i]);
+ }
+ }
+
+ // TODO: note that we need to check these bodies for staleness on startup (bug 1110446)
+ RefPtr<Context> context = mContext;
+ if (!deleteNowList.IsEmpty() && context && !context->IsCanceled()) {
+ RefPtr<Action> action = new DeleteOrphanedBodyAction(deleteNowList);
+ context->Dispatch(action);
+ }
+}
+
+void
+Manager::MaybeAllowContextToClose()
+{
+ NS_ASSERT_OWNINGTHREAD(Manager);
+
+ // If we have an active context, but we have no more users of the Manager,
+ // then let it shut itself down. We must wait for all possible users of
+ // Cache state information to complete before doing this. Once we allow
+ // the Context to close we may not reliably get notified of storage
+ // invalidation.
+ RefPtr<Context> context = mContext;
+ if (context && mListeners.IsEmpty()
+ && mCacheIdRefs.IsEmpty()
+ && mBodyIdRefs.IsEmpty()) {
+
+ // Mark this Manager as invalid so that it won't get used again. We don't
+ // want to start any new operations once we allow the Context to close since
+ // it may race with the underlying storage getting invalidated.
+ NoteClosing();
+
+ context->AllowToClose();
+ }
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla