Bug 1110487 P2 Implement the nsIOfflineStorage interface in Cache. r=janv,ehsan

This commit is contained in:
Ben Kelly 2015-03-16 07:10:36 -07:00
Родитель 92143814c0
Коммит 30333a7a7d
13 изменённых файлов: 643 добавлений и 88 удалений

240
dom/cache/Context.cpp поставляемый
Просмотреть файл

@ -10,6 +10,7 @@
#include "mozilla/dom/cache/Action.h"
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/ManagerId.h"
#include "mozilla/dom/cache/OfflineStorage.h"
#include "mozilla/dom/quota/OriginOrPatternString.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "nsIFile.h"
@ -21,19 +22,18 @@ namespace {
using mozilla::dom::Nullable;
using mozilla::dom::cache::QuotaInfo;
using mozilla::dom::quota::Client;
using mozilla::dom::quota::OriginOrPatternString;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
// Executed when the context is destroyed to release our lock on the
// QuotaManager.
// Release our lock on the QuotaManager directory asynchronously.
class QuotaReleaseRunnable MOZ_FINAL : public nsRunnable
{
public:
QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo, const nsACString& aQuotaId)
explicit QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo)
: mQuotaInfo(aQuotaInfo)
, mQuotaId(aQuotaId)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
@ -43,7 +43,7 @@ public:
MOZ_ASSERT(qm);
qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
mQuotaId);
mQuotaInfo.mStorageId);
return NS_OK;
}
@ -51,7 +51,6 @@ private:
~QuotaReleaseRunnable() { }
const QuotaInfo mQuotaInfo;
const nsCString mQuotaId;
};
} // anonymous namespace
@ -75,15 +74,15 @@ class Context::QuotaInitRunnable MOZ_FINAL : public nsIRunnable
public:
QuotaInitRunnable(Context* aContext,
Manager* aManager,
const nsACString& aQuotaId,
Action* aQuotaIOThreadAction)
: mContext(aContext)
, mThreadsafeHandle(aContext->CreateThreadsafeHandle())
, mManager(aManager)
, mQuotaId(aQuotaId)
, mQuotaIOThreadAction(aQuotaIOThreadAction)
, mInitiatingThread(NS_GetCurrentThread())
, mState(STATE_INIT)
, mResult(NS_OK)
, mState(STATE_INIT)
, mNeedsQuotaRelease(false)
{
MOZ_ASSERT(mContext);
MOZ_ASSERT(mManager);
@ -152,13 +151,15 @@ private:
}
nsRefPtr<Context> mContext;
nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
nsRefPtr<Manager> mManager;
const nsCString mQuotaId;
nsRefPtr<Action> mQuotaIOThreadAction;
nsCOMPtr<nsIThread> mInitiatingThread;
State mState;
nsresult mResult;
QuotaInfo mQuotaInfo;
nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
State mState;
bool mNeedsQuotaRelease;
public:
NS_DECL_ISUPPORTS_INHERITED
@ -230,13 +231,19 @@ Context::QuotaInitRunnable::Run()
return NS_OK;
}
QuotaManager::GetStorageId(PERSISTENCE_TYPE_DEFAULT,
mQuotaInfo.mOrigin,
Client::DOMCACHE,
NS_LITERAL_STRING("cache"),
mQuotaInfo.mStorageId);
// QuotaManager::WaitForOpenAllowed() will hold a reference to us as
// a callback. We will then get executed again on the main thread when
// it is safe to open the quota directory.
mState = STATE_WAIT_FOR_OPEN_ALLOWED;
rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
mQuotaId, this);
mQuotaInfo.mStorageId, this);
if (NS_FAILED(rv)) {
Resolve(rv);
return NS_OK;
@ -247,8 +254,16 @@ Context::QuotaInitRunnable::Run()
case STATE_WAIT_FOR_OPEN_ALLOWED:
{
MOZ_ASSERT(NS_IsMainThread());
mNeedsQuotaRelease = true;
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm);
nsRefPtr<OfflineStorage> offlineStorage =
OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo);
mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage);
mState = STATE_ENSURE_ORIGIN_INITIALIZED;
nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -298,8 +313,15 @@ Context::QuotaInitRunnable::Run()
if (mQuotaIOThreadAction) {
mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult);
}
mContext->OnQuotaInit(mResult, mQuotaInfo);
mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
mState = STATE_COMPLETE;
if (mNeedsQuotaRelease) {
// Unlock the quota dir if we locked it previously
nsCOMPtr<nsIRunnable> runnable = new QuotaReleaseRunnable(mQuotaInfo);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
}
// Explicitly cleanup here as the destructor could fire on any of
// the threads we have bounced through.
Clear();
@ -321,6 +343,7 @@ Context::QuotaInitRunnable::Run()
// is initialized.
class Context::ActionRunnable MOZ_FINAL : public nsIRunnable
, public Action::Resolver
, public Context::Activity
{
public:
ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
@ -354,12 +377,15 @@ public:
return rv;
}
bool MatchesCacheId(CacheId aCacheId) {
virtual bool
MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(Action::Resolver);
return mAction->MatchesCacheId(aCacheId);
}
void Cancel()
virtual void
Cancel() MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(Action::Resolver);
mAction->CancelOnInitiatingThread();
@ -392,7 +418,7 @@ private:
NS_ASSERT_OWNINGTHREAD(Action::Resolver);
MOZ_ASSERT(mContext);
MOZ_ASSERT(mAction);
mContext->OnActionRunnableComplete(this);
mContext->RemoveActivity(this);
mContext = nullptr;
mAction = nullptr;
}
@ -482,6 +508,99 @@ Context::ActionRunnable::Run()
return NS_OK;
}
void
Context::ThreadsafeHandle::AllowToClose()
{
if (mOwningThread == NS_GetCurrentThread()) {
AllowToCloseOnOwningThread();
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &ThreadsafeHandle::AllowToCloseOnOwningThread);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
}
void
Context::ThreadsafeHandle::InvalidateAndAllowToClose()
{
if (mOwningThread == NS_GetCurrentThread()) {
InvalidateAndAllowToCloseOnOwningThread();
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
}
Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
: mStrongRef(aContext)
, mWeakRef(aContext)
, mOwningThread(NS_GetCurrentThread())
{
}
Context::ThreadsafeHandle::~ThreadsafeHandle()
{
// Normally we only touch mStrongRef on the owning thread. This is safe,
// however, because when we do use mStrongRef on the owning thread we are
// always holding a strong ref to the ThreadsafeHandle via the owning
// runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
if (!mStrongRef || mOwningThread == NS_GetCurrentThread()) {
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable =
NS_NewNonOwningRunnableMethod(mStrongRef.forget().take(), &Context::Release);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
}
void
Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
// A Context "closes" when its ref count drops to zero. Dropping this
// strong ref is necessary, but not sufficient for the close to occur.
// Any outstanding IO will continue and keep the Context alive. Once
// the Context is idle, it will be destroyed.
mStrongRef = nullptr;
}
void
Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread()
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
// Cancel the Context through the weak reference. This means we can
// allow the Context to close by dropping the strong ref, but then
// still cancel ongoing IO if necessary.
if (mWeakRef) {
mWeakRef->Invalidate();
}
// We should synchronously have AllowToCloseOnOwningThread called when
// the Context is canceled.
MOZ_ASSERT(!mStrongRef);
}
void
Context::ThreadsafeHandle::ContextDestroyed(Context* aContext)
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
MOZ_ASSERT(!mStrongRef);
MOZ_ASSERT(mWeakRef);
MOZ_ASSERT(mWeakRef == aContext);
mWeakRef = nullptr;
}
// static
already_AddRefed<Context>
Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
@ -489,8 +608,7 @@ Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
nsRefPtr<Context> context = new Context(aManager);
nsRefPtr<QuotaInitRunnable> runnable =
new QuotaInitRunnable(context, aManager, NS_LITERAL_CSTRING("Cache"),
aQuotaIOThreadAction);
new QuotaInitRunnable(context, aManager, aQuotaIOThreadAction);
nsresult rv = runnable->Dispatch();
if (NS_FAILED(rv)) {
// Shutdown must be delayed until all Contexts are destroyed. Shutdown
@ -535,9 +653,29 @@ Context::CancelAll()
NS_ASSERT_OWNINGTHREAD(Context);
mState = STATE_CONTEXT_CANCELED;
mPendingActions.Clear();
for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
runnable->Cancel();
{
ActivityList::ForwardIterator iter(mActivityList);
while (iter.HasMore()) {
iter.GetNext()->Cancel();
}
}
AllowToClose();
}
void
Context::Invalidate()
{
NS_ASSERT_OWNINGTHREAD(Context);
mManager->Invalidate();
CancelAll();
}
void
Context::AllowToClose()
{
NS_ASSERT_OWNINGTHREAD(Context);
if (mThreadsafeHandle) {
mThreadsafeHandle->AllowToClose();
}
}
@ -545,15 +683,20 @@ void
Context::CancelForCacheId(CacheId aCacheId)
{
NS_ASSERT_OWNINGTHREAD(Context);
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
// Remove matching pending actions
for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
mPendingActions.RemoveElementAt(i);
}
}
for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
if (runnable->MatchesCacheId(aCacheId)) {
runnable->Cancel();
// Cancel activities and let them remove themselves
ActivityList::ForwardIterator iter(mActivityList);
while (iter.HasMore()) {
Activity* activity = iter.GetNext();
if (activity->MatchesCacheId(aCacheId)) {
activity->Cancel();
}
}
}
@ -563,14 +706,8 @@ Context::~Context()
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(mManager);
// Unlock the quota dir as we go out of scope.
nsCOMPtr<nsIRunnable> runnable =
new QuotaReleaseRunnable(mQuotaInfo, NS_LITERAL_CSTRING("Cache"));
nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
// Shutdown must be delayed until all Contexts are destroyed. Crash
// for this invariant violation.
MOZ_CRASH("Failed to dispatch QuotaReleaseRunnable to main thread.");
if (mThreadsafeHandle) {
mThreadsafeHandle->ContextDestroyed(this);
}
mManager->RemoveContext(this);
@ -589,11 +726,12 @@ Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction)
// for this invariant violation.
MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
}
mActionRunnables.AppendElement(runnable);
AddActivity(runnable);
}
void
Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage)
{
NS_ASSERT_OWNINGTHREAD(Context);
@ -604,6 +742,7 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
}
mPendingActions.Clear();
mThreadsafeHandle = nullptr;
// Context will destruct after return here and last ref is released.
return;
}
@ -611,6 +750,8 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
mState = STATE_CONTEXT_READY;
mOfflineStorage = aOfflineStorage;
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction);
}
@ -618,11 +759,32 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
}
void
Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable)
Context::AddActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActionRunnable);
MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable));
MOZ_ASSERT(aActivity);
MOZ_ASSERT(!mActivityList.Contains(aActivity));
mActivityList.AppendElement(aActivity);
}
void
Context::RemoveActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActivity);
MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity));
MOZ_ASSERT(!mActivityList.Contains(aActivity));
}
already_AddRefed<Context::ThreadsafeHandle>
Context::CreateThreadsafeHandle()
{
NS_ASSERT_OWNINGTHREAD(Context);
if (!mThreadsafeHandle) {
mThreadsafeHandle = new ThreadsafeHandle(this);
}
nsRefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
return ref.forget();
}
} // namespace cache

105
dom/cache/Context.h поставляемый
Просмотреть файл

@ -11,10 +11,13 @@
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTObserverArray.h"
class nsIEventTarget;
class nsIThread;
namespace mozilla {
namespace dom {
@ -22,6 +25,7 @@ namespace cache {
class Action;
class Manager;
class OfflineStorage;
// The Context class is RAII-style class for managing IO operations within the
// Cache.
@ -31,8 +35,20 @@ class Manager;
// delayed until this initialization is complete. They are then allow to
// execute on any specified thread. Once all references to the Context are
// gone, then the steps necessary to release the QuotaManager are performed.
// Since pending Action objects reference the Context, this allows overlapping
// IO to opportunistically run without re-initializing the QuotaManager again.
// After initialization the Context holds a self reference, so it will stay
// alive until one of three conditions occur:
//
// 1) The Manager will call Context::AllowToClose() when all of the actors
// have removed themselves as listener. This means an idle context with
// no active DOM objects will close gracefully.
// 2) The QuotaManager invalidates the storage area so it can delete the
// files. In this case the OfflineStorage calls Cache::Invalidate() which
// in turn cancels all existing Action objects and then marks the Manager
// as invalid.
// 3) Browser shutdown occurs and the Manager calls Context::CancelAll().
//
// In either case, though, the Action objects must be destroyed first to
// allow the Context to be destroyed.
//
// While the Context performs operations asynchronously on threads, all of
// methods in its public interface must be called on the same thread
@ -44,6 +60,53 @@ class Manager;
class Context MOZ_FINAL
{
public:
// Define a class allowing other threads to hold the Context alive. This also
// allows these other threads to safely close or cancel the Context.
class ThreadsafeHandle MOZ_FINAL
{
friend class Context;
public:
void AllowToClose();
void InvalidateAndAllowToClose();
private:
explicit ThreadsafeHandle(Context* aContext);
~ThreadsafeHandle();
// disallow copying
ThreadsafeHandle(const ThreadsafeHandle&) = delete;
ThreadsafeHandle& operator=(const ThreadsafeHandle&) = delete;
void AllowToCloseOnOwningThread();
void InvalidateAndAllowToCloseOnOwningThread();
void ContextDestroyed(Context* aContext);
// Cleared to allow the Context to close. Only safe to access on
// owning thread.
nsRefPtr<Context> mStrongRef;
// Used to support cancelation even while the Context is already allowed
// to close. Cleared by ~Context() calling ContextDestroyed(). Only
// safe to access on owning thread.
Context* mWeakRef;
nsCOMPtr<nsIThread> mOwningThread;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Context::ThreadsafeHandle)
};
// Different objects hold references to the Context while some work is being
// performed asynchronously. These objects must implement the Activity
// interface and register themselves with the AddActivity(). When they are
// destroyed they must call RemoveActivity(). This allows the Context to
// cancel any outstanding Activity work when the Context is cancelled.
class Activity
{
public:
virtual void Cancel() = 0;
virtual bool MatchesCacheId(CacheId aCacheId) const = 0;
};
static already_AddRefed<Context>
Create(Manager* aManager, Action* aQuotaIOThreadAction);
@ -55,17 +118,33 @@ public:
// Cancel any Actions running or waiting to run. This should allow the
// Context to be released and Listener::RemoveContext() will be called
// when complete.
// when complete. This effectively implies AllowToClose() as well.
//
// Only callable from the thread that created the Context.
void CancelAll();
// Like CancelAll(), but also marks the Manager as "invalid".
void Invalidate();
// Remove any self references and allow the Context to be released when
// there are no more Actions to process.
void AllowToClose();
// Cancel any Actions running or waiting to run that operate on the given
// cache ID.
//
// Only callable from the thread that created the Context.
void CancelForCacheId(CacheId aCacheId);
void AddActivity(Activity* aActivity);
void RemoveActivity(Activity* aActivity);
const QuotaInfo&
GetQuotaInfo() const
{
return mQuotaInfo;
}
private:
class QuotaInitRunnable;
class ActionRunnable;
@ -86,16 +165,28 @@ private:
explicit Context(Manager* aManager);
~Context();
void DispatchAction(nsIEventTarget* aTarget, Action* aAction);
void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo);
void OnActionRunnableComplete(ActionRunnable* const aAction);
void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage);
already_AddRefed<ThreadsafeHandle>
CreateThreadsafeHandle();
nsRefPtr<Manager> mManager;
State mState;
QuotaInfo mQuotaInfo;
nsTArray<PendingAction> mPendingActions;
// weak refs since ~ActionRunnable() removes itself from this list
nsTArray<ActionRunnable*> mActionRunnables;
// Weak refs since activites must remove themselves from this list before
// being destroyed by calling RemoveActivity().
typedef nsTObserverArray<Activity*> ActivityList;
ActivityList mActivityList;
// The ThreadsafeHandle may have a strong ref back to us. This creates
// a ref-cycle that keeps the Context alive. The ref-cycle is broken
// when ThreadsafeHandle::AllowToClose() is called.
nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
public:
NS_INLINE_DECL_REFCOUNTING(cache::Context)

75
dom/cache/Manager.cpp поставляемый
Просмотреть файл

@ -180,7 +180,10 @@ public:
ManagerList::ForwardIterator iter(sFactory->mManagerList);
while (iter.HasMore()) {
nsRefPtr<Manager> manager = iter.GetNext();
if (*manager->mManagerId == *aManagerId) {
// If there is an invalid Manager finishing up and a new Manager
// is created for the same origin, then the new Manager will
// be blocked until QuotaManager finishes clearing the origin.
if (manager->IsValid() && *manager->mManagerId == *aManagerId) {
return manager.forget();
}
}
@ -1407,6 +1410,9 @@ Manager::RemoveListener(Listener* aListener)
mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
MOZ_ASSERT(!mListeners.Contains(aListener,
ListenerEntryListenerComparator()));
if (mListeners.IsEmpty() && mContext) {
mContext->AllowToClose();
}
}
void
@ -1424,6 +1430,21 @@ Manager::RemoveContext(Context* aContext)
}
}
void
Manager::Invalidate()
{
NS_ASSERT_OWNINGTHREAD(Manager);
// QuotaManager can trigger this more than once.
mValid = false;
}
bool
Manager::IsValid() const
{
NS_ASSERT_OWNINGTHREAD(Manager);
return mValid;
}
void
Manager::AddRefCacheId(CacheId aCacheId)
{
@ -1453,7 +1474,7 @@ Manager::ReleaseCacheId(CacheId aCacheId)
bool orphaned = mCacheIdRefs[i].mOrphaned;
mCacheIdRefs.RemoveElementAt(i);
// TODO: note that we need to check this cache for staleness on startup (bug 1110446)
if (orphaned && !mShuttingDown) {
if (orphaned && !mShuttingDown && mValid) {
nsRefPtr<Context> context = CurrentContext();
context->CancelForCacheId(aCacheId);
nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this,
@ -1496,7 +1517,7 @@ Manager::ReleaseBodyId(const nsID& aBodyId)
bool orphaned = mBodyIdRefs[i].mOrphaned;
mBodyIdRefs.RemoveElementAt(i);
// TODO: note that we need to check this body for staleness on startup (bug 1110446)
if (orphaned && !mShuttingDown) {
if (orphaned && !mShuttingDown && mValid) {
nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
nsRefPtr<Context> context = CurrentContext();
context->Dispatch(mIOThread, action);
@ -1538,9 +1559,8 @@ Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnCacheMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
nullptr, nullptr);
if (mShuttingDown || !mValid) {
aListener->OnCacheMatch(aRequestId, NS_ERROR_FAILURE, nullptr, nullptr);
return;
}
nsRefPtr<Context> context = CurrentContext();
@ -1559,8 +1579,8 @@ Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnCacheMatchAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnCacheMatchAll(aRequestId, NS_ERROR_FAILURE,
nsTArray<SavedResponse>(), nullptr);
return;
}
@ -1581,8 +1601,8 @@ Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnCachePutAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
if (mShuttingDown || !mValid) {
aListener->OnCachePutAll(aRequestId, NS_ERROR_FAILURE);
return;
}
ListenerId listenerId = SaveListener(aListener);
@ -1601,8 +1621,8 @@ Manager::CacheDelete(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnCacheDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, false);
if (mShuttingDown || !mValid) {
aListener->OnCacheDelete(aRequestId, NS_ERROR_FAILURE, false);
return;
}
ListenerId listenerId = SaveListener(aListener);
@ -1619,8 +1639,8 @@ Manager::CacheKeys(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnCacheKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnCacheKeys(aRequestId, NS_ERROR_FAILURE,
nsTArray<SavedRequest>(), nullptr);
return;
}
@ -1640,8 +1660,8 @@ Manager::StorageMatch(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnStorageMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnStorageMatch(aRequestId, NS_ERROR_FAILURE,
nullptr, nullptr);
return;
}
@ -1660,8 +1680,8 @@ Manager::StorageHas(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnStorageHas(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnStorageHas(aRequestId, NS_ERROR_FAILURE,
false);
return;
}
@ -1678,8 +1698,8 @@ Manager::StorageOpen(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnStorageOpen(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, 0);
if (mShuttingDown || !mValid) {
aListener->OnStorageOpen(aRequestId, NS_ERROR_FAILURE, 0);
return;
}
ListenerId listenerId = SaveListener(aListener);
@ -1695,8 +1715,8 @@ Manager::StorageDelete(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnStorageDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnStorageDelete(aRequestId, NS_ERROR_FAILURE,
false);
return;
}
@ -1713,8 +1733,8 @@ Manager::StorageKeys(Listener* aListener, RequestId aRequestId,
{
NS_ASSERT_OWNINGTHREAD(Manager);
MOZ_ASSERT(aListener);
if (mShuttingDown) {
aListener->OnStorageKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
if (mShuttingDown || !mValid) {
aListener->OnStorageKeys(aRequestId, NS_ERROR_FAILURE,
nsTArray<nsString>());
return;
}
@ -1730,6 +1750,7 @@ Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread)
, mIOThread(aIOThread)
, mContext(nullptr)
, mShuttingDown(false)
, mValid(true)
{
MOZ_ASSERT(mManagerId);
MOZ_ASSERT(mIOThread);
@ -1768,11 +1789,6 @@ Manager::Shutdown()
// complete before shutdown proceeds.
mShuttingDown = true;
for (uint32_t i = 0; i < mStreamLists.Length(); ++i) {
nsRefPtr<StreamList> streamList = mStreamLists[i];
streamList->CloseAll();
}
// If there is a context, then we must wait for it to complete. Cancel and
// only note that we are done after its cleaned up.
if (mContext) {
@ -1792,6 +1808,7 @@ Manager::CurrentContext()
nsRefPtr<Context> ref = mContext;
if (!ref) {
MOZ_ASSERT(!mShuttingDown);
MOZ_ASSERT(mValid);
nsRefPtr<Action> setupAction = new SetupAction();
ref = Context::Create(this, setupAction);
mContext = ref;

10
dom/cache/Manager.h поставляемый
Просмотреть файл

@ -125,6 +125,11 @@ public:
// Must be called by Context objects before they are destroyed.
void RemoveContext(Context* aContext);
// Marks the Manager "invalid". Once the Context completes no new operations
// will be permitted with this Manager. New actors will get a new Manager.
void Invalidate();
bool IsValid() const;
// If an actor represents a long term reference to a cache or body stream,
// then they must call AddRefCacheId() or AddRefBodyId(). This will
// cause the Manager to keep the backing data store alive for the given
@ -214,8 +219,8 @@ private:
struct ListenerEntry
{
ListenerEntry()
: mId(UINT64_MAX),
mListener(nullptr)
: mId(UINT64_MAX)
, mListener(nullptr)
{
}
@ -255,6 +260,7 @@ private:
nsTArray<StreamList*> mStreamLists;
bool mShuttingDown;
bool mValid;
struct CacheIdRefCounter
{

135
dom/cache/OfflineStorage.cpp поставляемый Normal file
Просмотреть файл

@ -0,0 +1,135 @@
/* -*- 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/OfflineStorage.h"
#include "mozilla/dom/cache/Context.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::Client;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::QuotaManager;
NS_IMPL_ISUPPORTS(OfflineStorage, nsIOfflineStorage);
// static
already_AddRefed<OfflineStorage>
OfflineStorage::Register(Context::ThreadsafeHandle* aContext,
const QuotaInfo& aQuotaInfo)
{
MOZ_ASSERT(NS_IsMainThread());
QuotaManager* qm = QuotaManager::Get();
if (NS_WARN_IF(!qm)) {
return nullptr;
}
nsRefPtr<Client> client = qm->GetClient(Client::DOMCACHE);
nsRefPtr<OfflineStorage> storage =
new OfflineStorage(aContext, aQuotaInfo, client);
if (NS_WARN_IF(!qm->RegisterStorage(storage))) {
return nullptr;
}
return storage.forget();
}
void
OfflineStorage::AddDestroyCallback(nsIRunnable* aCallback)
{
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!mDestroyCallbacks.Contains(aCallback));
mDestroyCallbacks.AppendElement(aCallback);
}
OfflineStorage::OfflineStorage(Context::ThreadsafeHandle* aContext,
const QuotaInfo& aQuotaInfo,
Client* aClient)
: mContext(aContext)
, mQuotaInfo(aQuotaInfo)
, mClient(aClient)
{
MOZ_ASSERT(mContext);
MOZ_ASSERT(mClient);
mPersistenceType = PERSISTENCE_TYPE_DEFAULT;
mGroup = mQuotaInfo.mGroup;
}
OfflineStorage::~OfflineStorage()
{
MOZ_ASSERT(NS_IsMainThread());
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm);
qm->UnregisterStorage(this);
for (uint32_t i = 0; i < mDestroyCallbacks.Length(); ++i) {
mDestroyCallbacks[i]->Run();
}
}
NS_IMETHODIMP_(const nsACString&)
OfflineStorage::Id()
{
MOZ_ASSERT(NS_IsMainThread());
return mQuotaInfo.mStorageId;
}
NS_IMETHODIMP_(Client*)
OfflineStorage::GetClient()
{
MOZ_ASSERT(NS_IsMainThread());
return mClient;
}
NS_IMETHODIMP_(bool)
OfflineStorage::IsOwnedByProcess(ContentParent* aOwner)
{
MOZ_ASSERT(NS_IsMainThread());
// The Cache and Context can be shared by multiple client processes. They
// are not exclusively owned by a single process.
//
// As far as I can tell this is used by QuotaManager to shutdown storages
// when a particular process goes away. We definitely don't want this
// since we are shared. Also, the Cache actor code already properly
// handles asynchronous actor destruction when the child process dies.
//
// Therefore, always return false here.
return false;
}
NS_IMETHODIMP_(const nsACString&)
OfflineStorage::Origin()
{
MOZ_ASSERT(NS_IsMainThread());
return mQuotaInfo.mOrigin;
}
NS_IMETHODIMP_(nsresult)
OfflineStorage::Close()
{
MOZ_ASSERT(NS_IsMainThread());
mContext->AllowToClose();
return NS_OK;
}
NS_IMETHODIMP_(void)
OfflineStorage::Invalidate()
{
MOZ_ASSERT(NS_IsMainThread());
mContext->InvalidateAndAllowToClose();
}
} // namespace cache
} // namespace dom
} // namespace mozilla

50
dom/cache/OfflineStorage.h поставляемый Normal file
Просмотреть файл

@ -0,0 +1,50 @@
/* -*- 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/. */
#ifndef mozilla_dom_cache_QuotaOfflineStorage_h
#define mozilla_dom_cache_QuotaOfflineStorage_h
#include "nsISupportsImpl.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/cache/Context.h"
#include "nsIOfflineStorage.h"
#include "nsTArray.h"
class nsIThread;
namespace mozilla {
namespace dom {
namespace cache {
class OfflineStorage MOZ_FINAL : public nsIOfflineStorage
{
public:
static already_AddRefed<OfflineStorage>
Register(Context::ThreadsafeHandle* aContext, const QuotaInfo& aQuotaInfo);
void
AddDestroyCallback(nsIRunnable* aCallback);
private:
OfflineStorage(Context::ThreadsafeHandle* aContext,
const QuotaInfo& aQuotaInfo,
Client* aClient);
~OfflineStorage();
nsRefPtr<Context::ThreadsafeHandle> mContext;
const QuotaInfo mQuotaInfo;
nsRefPtr<Client> mClient;
nsTArray<nsCOMPtr<nsIRunnable>> mDestroyCallbacks;
NS_DECL_ISUPPORTS
NS_DECL_NSIOFFLINESTORAGE
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_QuotaOfflineStorage_h

65
dom/cache/QuotaClient.cpp поставляемый
Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/OfflineStorage.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "nsIFile.h"
@ -18,6 +19,7 @@ namespace {
using mozilla::DebugOnly;
using mozilla::dom::cache::Manager;
using mozilla::dom::cache::OfflineStorage;
using mozilla::dom::quota::Client;
using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::QuotaManager;
@ -60,6 +62,41 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
return NS_OK;
}
class StoragesDestroyedRunnable MOZ_FINAL : public nsRunnable
{
uint32_t mExpectedCalls;
nsCOMPtr<nsIRunnable> mCallback;
public:
StoragesDestroyedRunnable(uint32_t aExpectedCalls, nsIRunnable* aCallback)
: mExpectedCalls(aExpectedCalls)
, mCallback(aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mExpectedCalls);
MOZ_ASSERT(mCallback);
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mExpectedCalls);
mExpectedCalls -= 1;
if (!mExpectedCalls) {
mCallback->Run();
}
return NS_OK;
}
private:
~StoragesDestroyedRunnable()
{
// This is a callback runnable and not used for thread dispatch. It should
// always be destroyed on the main thread.
MOZ_ASSERT(NS_IsMainThread());
}
};
class CacheQuotaClient MOZ_FINAL : public Client
{
public:
@ -151,13 +188,14 @@ public:
OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) MOZ_OVERRIDE
{
// nothing to do
// Nothing to do here.
}
virtual void
ReleaseIOThreadObjects() MOZ_OVERRIDE
{
// nothing to do
// Nothing to do here as the Context handles cleaning everything up
// automatically.
}
virtual bool
@ -169,15 +207,26 @@ public:
virtual bool
IsTransactionServiceActivated() MOZ_OVERRIDE
{
// TODO: implement nsIOfflineStorage interface (bug 1110487)
return false;
return true;
}
virtual void
WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
nsIRunnable* aCallback) MOZ_OVERRIDE
{
// TODO: implement nsIOfflineStorage interface (bug 1110487)
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!aStorages.IsEmpty());
nsCOMPtr<nsIRunnable> callback =
new StoragesDestroyedRunnable(aStorages.Length(), aCallback);
for (uint32_t i = 0; i < aStorages.Length(); ++i) {
MOZ_ASSERT(aStorages[i]->GetClient());
MOZ_ASSERT(aStorages[i]->GetClient()->GetType() == Client::DOMCACHE);
nsRefPtr<OfflineStorage> storage =
static_cast<OfflineStorage*>(aStorages[i]);
storage->AddDestroyCallback(callback);
}
}
@ -191,9 +240,11 @@ public:
}
private:
~CacheQuotaClient() { }
~CacheQuotaClient()
{
MOZ_ASSERT(NS_IsMainThread());
}
public:
NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, MOZ_OVERRIDE)
};

3
dom/cache/QuotaClient.h поставляемый
Просмотреть файл

@ -14,7 +14,8 @@ namespace mozilla {
namespace dom {
namespace cache {
already_AddRefed<quota::Client> CreateQuotaClient();
already_AddRefed<quota::Client>
CreateQuotaClient();
} // namespace cache
} // namespace dom

17
dom/cache/StreamList.cpp поставляемый
Просмотреть файл

@ -23,7 +23,7 @@ StreamList::StreamList(Manager* aManager, Context* aContext)
, mActivated(false)
{
MOZ_ASSERT(mManager);
MOZ_ASSERT(mContext);
mContext->AddActivity(this);
}
void
@ -142,6 +142,20 @@ StreamList::CloseAll()
}
}
void
StreamList::Cancel()
{
NS_ASSERT_OWNINGTHREAD(StreamList);
CloseAll();
}
bool
StreamList::MatchesCacheId(CacheId aCacheId) const
{
NS_ASSERT_OWNINGTHREAD(StreamList);
return aCacheId == mCacheId;
}
StreamList::~StreamList()
{
NS_ASSERT_OWNINGTHREAD(StreamList);
@ -153,6 +167,7 @@ StreamList::~StreamList()
}
mManager->ReleaseCacheId(mCacheId);
}
mContext->RemoveActivity(this);
}
} // namespace cache

8
dom/cache/StreamList.h поставляемый
Просмотреть файл

@ -7,6 +7,7 @@
#ifndef mozilla_dom_cache_StreamList_h
#define mozilla_dom_cache_StreamList_h
#include "mozilla/dom/cache/Context.h"
#include "mozilla/dom/cache/Types.h"
#include "nsRefPtr.h"
#include "nsTArray.h"
@ -18,10 +19,9 @@ namespace dom {
namespace cache {
class CacheStreamControlParent;
class Context;
class Manager;
class StreamList
class StreamList MOZ_FINAL : public Context::Activity
{
public:
StreamList(Manager* aManager, Context* aContext);
@ -39,6 +39,10 @@ public:
void Close(const nsID& aId);
void CloseAll();
// Context::Activity methods
virtual void Cancel() MOZ_OVERRIDE;
virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE;
private:
~StreamList();
struct Entry

1
dom/cache/Types.h поставляемый
Просмотреть файл

@ -34,6 +34,7 @@ struct QuotaInfo
nsCOMPtr<nsIFile> mDir;
nsCString mGroup;
nsCString mOrigin;
nsCString mStorageId;
bool mIsApp;
};

2
dom/cache/moz.build поставляемый
Просмотреть файл

@ -26,6 +26,7 @@ EXPORTS.mozilla.dom.cache += [
'IPCUtils.h',
'Manager.h',
'ManagerId.h',
'OfflineStorage.h',
'PrincipalVerifier.h',
'QuotaClient.h',
'ReadStream.h',
@ -56,6 +57,7 @@ UNIFIED_SOURCES += [
'FileUtils.cpp',
'Manager.cpp',
'ManagerId.cpp',
'OfflineStorage.cpp',
'PrincipalVerifier.cpp',
'QuotaClient.cpp',
'ReadStream.cpp',

20
dom/cache/test/mochitest/driver.js поставляемый
Просмотреть файл

@ -30,6 +30,22 @@ function runTests(testFile, order) {
});
}
// adapted from dom/indexedDB/test/helpers.js
function clearStorage() {
return new Promise(function(resolve, reject) {
var principal = SpecialPowers.wrap(document).nodePrincipal;
var appId, inBrowser;
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
principal.appId != nsIPrincipal.NO_APP_ID) {
appId = principal.appId;
inBrowser = principal.isInBrowserElement;
}
SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
inBrowser);
});
}
function loadScript(script) {
return new Promise(function(resolve, reject) {
var s = document.createElement("script");
@ -100,8 +116,11 @@ function runTests(testFile, order) {
return setupPrefs()
.then(importDrivers)
.then(runWorkerTest)
.then(clearStorage)
.then(runServiceWorkerTest)
.then(clearStorage)
.then(runFrameTest)
.then(clearStorage)
.catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e);
});
@ -109,6 +128,7 @@ function runTests(testFile, order) {
return setupPrefs()
.then(importDrivers)
.then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()]))
.then(clearStorage)
.catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e);
});