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/Action.h"
#include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/ManagerId.h" #include "mozilla/dom/cache/ManagerId.h"
#include "mozilla/dom/cache/OfflineStorage.h"
#include "mozilla/dom/quota/OriginOrPatternString.h" #include "mozilla/dom/quota/OriginOrPatternString.h"
#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaManager.h"
#include "nsIFile.h" #include "nsIFile.h"
@ -21,19 +22,18 @@ namespace {
using mozilla::dom::Nullable; using mozilla::dom::Nullable;
using mozilla::dom::cache::QuotaInfo; using mozilla::dom::cache::QuotaInfo;
using mozilla::dom::quota::Client;
using mozilla::dom::quota::OriginOrPatternString; using mozilla::dom::quota::OriginOrPatternString;
using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType; using mozilla::dom::quota::PersistenceType;
// Executed when the context is destroyed to release our lock on the // Release our lock on the QuotaManager directory asynchronously.
// QuotaManager.
class QuotaReleaseRunnable MOZ_FINAL : public nsRunnable class QuotaReleaseRunnable MOZ_FINAL : public nsRunnable
{ {
public: public:
QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo, const nsACString& aQuotaId) explicit QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo)
: mQuotaInfo(aQuotaInfo) : mQuotaInfo(aQuotaInfo)
, mQuotaId(aQuotaId)
{ } { }
NS_IMETHOD Run() MOZ_OVERRIDE NS_IMETHOD Run() MOZ_OVERRIDE
@ -43,7 +43,7 @@ public:
MOZ_ASSERT(qm); MOZ_ASSERT(qm);
qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin), qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT), Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
mQuotaId); mQuotaInfo.mStorageId);
return NS_OK; return NS_OK;
} }
@ -51,7 +51,6 @@ private:
~QuotaReleaseRunnable() { } ~QuotaReleaseRunnable() { }
const QuotaInfo mQuotaInfo; const QuotaInfo mQuotaInfo;
const nsCString mQuotaId;
}; };
} // anonymous namespace } // anonymous namespace
@ -75,15 +74,15 @@ class Context::QuotaInitRunnable MOZ_FINAL : public nsIRunnable
public: public:
QuotaInitRunnable(Context* aContext, QuotaInitRunnable(Context* aContext,
Manager* aManager, Manager* aManager,
const nsACString& aQuotaId,
Action* aQuotaIOThreadAction) Action* aQuotaIOThreadAction)
: mContext(aContext) : mContext(aContext)
, mThreadsafeHandle(aContext->CreateThreadsafeHandle())
, mManager(aManager) , mManager(aManager)
, mQuotaId(aQuotaId)
, mQuotaIOThreadAction(aQuotaIOThreadAction) , mQuotaIOThreadAction(aQuotaIOThreadAction)
, mInitiatingThread(NS_GetCurrentThread()) , mInitiatingThread(NS_GetCurrentThread())
, mState(STATE_INIT)
, mResult(NS_OK) , mResult(NS_OK)
, mState(STATE_INIT)
, mNeedsQuotaRelease(false)
{ {
MOZ_ASSERT(mContext); MOZ_ASSERT(mContext);
MOZ_ASSERT(mManager); MOZ_ASSERT(mManager);
@ -152,13 +151,15 @@ private:
} }
nsRefPtr<Context> mContext; nsRefPtr<Context> mContext;
nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
nsRefPtr<Manager> mManager; nsRefPtr<Manager> mManager;
const nsCString mQuotaId;
nsRefPtr<Action> mQuotaIOThreadAction; nsRefPtr<Action> mQuotaIOThreadAction;
nsCOMPtr<nsIThread> mInitiatingThread; nsCOMPtr<nsIThread> mInitiatingThread;
State mState;
nsresult mResult; nsresult mResult;
QuotaInfo mQuotaInfo; QuotaInfo mQuotaInfo;
nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
State mState;
bool mNeedsQuotaRelease;
public: public:
NS_DECL_ISUPPORTS_INHERITED NS_DECL_ISUPPORTS_INHERITED
@ -230,13 +231,19 @@ Context::QuotaInitRunnable::Run()
return NS_OK; 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 // QuotaManager::WaitForOpenAllowed() will hold a reference to us as
// a callback. We will then get executed again on the main thread when // a callback. We will then get executed again on the main thread when
// it is safe to open the quota directory. // it is safe to open the quota directory.
mState = STATE_WAIT_FOR_OPEN_ALLOWED; mState = STATE_WAIT_FOR_OPEN_ALLOWED;
rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin), rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT), Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
mQuotaId, this); mQuotaInfo.mStorageId, this);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
Resolve(rv); Resolve(rv);
return NS_OK; return NS_OK;
@ -247,8 +254,16 @@ Context::QuotaInitRunnable::Run()
case STATE_WAIT_FOR_OPEN_ALLOWED: case STATE_WAIT_FOR_OPEN_ALLOWED:
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mNeedsQuotaRelease = true;
QuotaManager* qm = QuotaManager::Get(); QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm); MOZ_ASSERT(qm);
nsRefPtr<OfflineStorage> offlineStorage =
OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo);
mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage);
mState = STATE_ENSURE_ORIGIN_INITIALIZED; mState = STATE_ENSURE_ORIGIN_INITIALIZED;
nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL); nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
@ -298,8 +313,15 @@ Context::QuotaInitRunnable::Run()
if (mQuotaIOThreadAction) { if (mQuotaIOThreadAction) {
mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult); mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult);
} }
mContext->OnQuotaInit(mResult, mQuotaInfo); mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
mState = STATE_COMPLETE; 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 // Explicitly cleanup here as the destructor could fire on any of
// the threads we have bounced through. // the threads we have bounced through.
Clear(); Clear();
@ -321,6 +343,7 @@ Context::QuotaInitRunnable::Run()
// is initialized. // is initialized.
class Context::ActionRunnable MOZ_FINAL : public nsIRunnable class Context::ActionRunnable MOZ_FINAL : public nsIRunnable
, public Action::Resolver , public Action::Resolver
, public Context::Activity
{ {
public: public:
ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction, ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
@ -354,12 +377,15 @@ public:
return rv; return rv;
} }
bool MatchesCacheId(CacheId aCacheId) { virtual bool
MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(Action::Resolver); NS_ASSERT_OWNINGTHREAD(Action::Resolver);
return mAction->MatchesCacheId(aCacheId); return mAction->MatchesCacheId(aCacheId);
} }
void Cancel() virtual void
Cancel() MOZ_OVERRIDE
{ {
NS_ASSERT_OWNINGTHREAD(Action::Resolver); NS_ASSERT_OWNINGTHREAD(Action::Resolver);
mAction->CancelOnInitiatingThread(); mAction->CancelOnInitiatingThread();
@ -392,7 +418,7 @@ private:
NS_ASSERT_OWNINGTHREAD(Action::Resolver); NS_ASSERT_OWNINGTHREAD(Action::Resolver);
MOZ_ASSERT(mContext); MOZ_ASSERT(mContext);
MOZ_ASSERT(mAction); MOZ_ASSERT(mAction);
mContext->OnActionRunnableComplete(this); mContext->RemoveActivity(this);
mContext = nullptr; mContext = nullptr;
mAction = nullptr; mAction = nullptr;
} }
@ -482,6 +508,99 @@ Context::ActionRunnable::Run()
return NS_OK; 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 // static
already_AddRefed<Context> already_AddRefed<Context>
Context::Create(Manager* aManager, Action* aQuotaIOThreadAction) Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
@ -489,8 +608,7 @@ Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
nsRefPtr<Context> context = new Context(aManager); nsRefPtr<Context> context = new Context(aManager);
nsRefPtr<QuotaInitRunnable> runnable = nsRefPtr<QuotaInitRunnable> runnable =
new QuotaInitRunnable(context, aManager, NS_LITERAL_CSTRING("Cache"), new QuotaInitRunnable(context, aManager, aQuotaIOThreadAction);
aQuotaIOThreadAction);
nsresult rv = runnable->Dispatch(); nsresult rv = runnable->Dispatch();
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
// Shutdown must be delayed until all Contexts are destroyed. Shutdown // Shutdown must be delayed until all Contexts are destroyed. Shutdown
@ -535,9 +653,29 @@ Context::CancelAll()
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
mState = STATE_CONTEXT_CANCELED; mState = STATE_CONTEXT_CANCELED;
mPendingActions.Clear(); mPendingActions.Clear();
for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) { {
nsRefPtr<ActionRunnable> runnable = mActionRunnables[i]; ActivityList::ForwardIterator iter(mActivityList);
runnable->Cancel(); 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) Context::CancelForCacheId(CacheId aCacheId)
{ {
NS_ASSERT_OWNINGTHREAD(Context); 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)) { if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
mPendingActions.RemoveElementAt(i); mPendingActions.RemoveElementAt(i);
} }
} }
for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
nsRefPtr<ActionRunnable> runnable = mActionRunnables[i]; // Cancel activities and let them remove themselves
if (runnable->MatchesCacheId(aCacheId)) { ActivityList::ForwardIterator iter(mActivityList);
runnable->Cancel(); while (iter.HasMore()) {
Activity* activity = iter.GetNext();
if (activity->MatchesCacheId(aCacheId)) {
activity->Cancel();
} }
} }
} }
@ -563,14 +706,8 @@ Context::~Context()
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(mManager); MOZ_ASSERT(mManager);
// Unlock the quota dir as we go out of scope. if (mThreadsafeHandle) {
nsCOMPtr<nsIRunnable> runnable = mThreadsafeHandle->ContextDestroyed(this);
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.");
} }
mManager->RemoveContext(this); mManager->RemoveContext(this);
@ -589,11 +726,12 @@ Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction)
// for this invariant violation. // for this invariant violation.
MOZ_CRASH("Failed to dispatch ActionRunnable to target thread."); MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
} }
mActionRunnables.AppendElement(runnable); AddActivity(runnable);
} }
void void
Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo) Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage)
{ {
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
@ -604,6 +742,7 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv); mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
} }
mPendingActions.Clear(); mPendingActions.Clear();
mThreadsafeHandle = nullptr;
// Context will destruct after return here and last ref is released. // Context will destruct after return here and last ref is released.
return; return;
} }
@ -611,6 +750,8 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
MOZ_ASSERT(mState == STATE_CONTEXT_INIT); MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
mState = STATE_CONTEXT_READY; mState = STATE_CONTEXT_READY;
mOfflineStorage = aOfflineStorage;
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction); DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction);
} }
@ -618,11 +759,32 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
} }
void void
Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable) Context::AddActivity(Activity* aActivity)
{ {
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActionRunnable); MOZ_ASSERT(aActivity);
MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable)); 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 } // namespace cache

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

@ -11,10 +11,13 @@
#include "nsAutoPtr.h" #include "nsAutoPtr.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsISupportsImpl.h" #include "nsISupportsImpl.h"
#include "nsProxyRelease.h"
#include "nsString.h" #include "nsString.h"
#include "nsTArray.h" #include "nsTArray.h"
#include "nsTObserverArray.h"
class nsIEventTarget; class nsIEventTarget;
class nsIThread;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -22,6 +25,7 @@ namespace cache {
class Action; class Action;
class Manager; class Manager;
class OfflineStorage;
// The Context class is RAII-style class for managing IO operations within the // The Context class is RAII-style class for managing IO operations within the
// Cache. // Cache.
@ -31,8 +35,20 @@ class Manager;
// delayed until this initialization is complete. They are then allow to // delayed until this initialization is complete. They are then allow to
// execute on any specified thread. Once all references to the Context are // execute on any specified thread. Once all references to the Context are
// gone, then the steps necessary to release the QuotaManager are performed. // gone, then the steps necessary to release the QuotaManager are performed.
// Since pending Action objects reference the Context, this allows overlapping // After initialization the Context holds a self reference, so it will stay
// IO to opportunistically run without re-initializing the QuotaManager again. // 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 // While the Context performs operations asynchronously on threads, all of
// methods in its public interface must be called on the same thread // methods in its public interface must be called on the same thread
@ -44,6 +60,53 @@ class Manager;
class Context MOZ_FINAL class Context MOZ_FINAL
{ {
public: 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> static already_AddRefed<Context>
Create(Manager* aManager, Action* aQuotaIOThreadAction); Create(Manager* aManager, Action* aQuotaIOThreadAction);
@ -55,17 +118,33 @@ public:
// Cancel any Actions running or waiting to run. This should allow the // Cancel any Actions running or waiting to run. This should allow the
// Context to be released and Listener::RemoveContext() will be called // 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. // Only callable from the thread that created the Context.
void CancelAll(); 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 // Cancel any Actions running or waiting to run that operate on the given
// cache ID. // cache ID.
// //
// Only callable from the thread that created the Context. // Only callable from the thread that created the Context.
void CancelForCacheId(CacheId aCacheId); void CancelForCacheId(CacheId aCacheId);
void AddActivity(Activity* aActivity);
void RemoveActivity(Activity* aActivity);
const QuotaInfo&
GetQuotaInfo() const
{
return mQuotaInfo;
}
private: private:
class QuotaInitRunnable; class QuotaInitRunnable;
class ActionRunnable; class ActionRunnable;
@ -86,16 +165,28 @@ private:
explicit Context(Manager* aManager); explicit Context(Manager* aManager);
~Context(); ~Context();
void DispatchAction(nsIEventTarget* aTarget, Action* aAction); void DispatchAction(nsIEventTarget* aTarget, Action* aAction);
void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo); void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
void OnActionRunnableComplete(ActionRunnable* const aAction); nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage);
already_AddRefed<ThreadsafeHandle>
CreateThreadsafeHandle();
nsRefPtr<Manager> mManager; nsRefPtr<Manager> mManager;
State mState; State mState;
QuotaInfo mQuotaInfo; QuotaInfo mQuotaInfo;
nsTArray<PendingAction> mPendingActions; nsTArray<PendingAction> mPendingActions;
// weak refs since ~ActionRunnable() removes itself from this list // Weak refs since activites must remove themselves from this list before
nsTArray<ActionRunnable*> mActionRunnables; // 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: public:
NS_INLINE_DECL_REFCOUNTING(cache::Context) NS_INLINE_DECL_REFCOUNTING(cache::Context)

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

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

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

@ -125,6 +125,11 @@ public:
// Must be called by Context objects before they are destroyed. // Must be called by Context objects before they are destroyed.
void RemoveContext(Context* aContext); 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, // If an actor represents a long term reference to a cache or body stream,
// then they must call AddRefCacheId() or AddRefBodyId(). This will // then they must call AddRefCacheId() or AddRefBodyId(). This will
// cause the Manager to keep the backing data store alive for the given // cause the Manager to keep the backing data store alive for the given
@ -214,8 +219,8 @@ private:
struct ListenerEntry struct ListenerEntry
{ {
ListenerEntry() ListenerEntry()
: mId(UINT64_MAX), : mId(UINT64_MAX)
mListener(nullptr) , mListener(nullptr)
{ {
} }
@ -255,6 +260,7 @@ private:
nsTArray<StreamList*> mStreamLists; nsTArray<StreamList*> mStreamLists;
bool mShuttingDown; bool mShuttingDown;
bool mValid;
struct CacheIdRefCounter 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/DebugOnly.h"
#include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/OfflineStorage.h"
#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h" #include "mozilla/dom/quota/UsageInfo.h"
#include "nsIFile.h" #include "nsIFile.h"
@ -18,6 +19,7 @@ namespace {
using mozilla::DebugOnly; using mozilla::DebugOnly;
using mozilla::dom::cache::Manager; using mozilla::dom::cache::Manager;
using mozilla::dom::cache::OfflineStorage;
using mozilla::dom::quota::Client; using mozilla::dom::quota::Client;
using mozilla::dom::quota::PersistenceType; using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::QuotaManager;
@ -60,6 +62,41 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
return NS_OK; 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 class CacheQuotaClient MOZ_FINAL : public Client
{ {
public: public:
@ -151,13 +188,14 @@ public:
OnOriginClearCompleted(PersistenceType aPersistenceType, OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) MOZ_OVERRIDE const nsACString& aOrigin) MOZ_OVERRIDE
{ {
// nothing to do // Nothing to do here.
} }
virtual void virtual void
ReleaseIOThreadObjects() MOZ_OVERRIDE ReleaseIOThreadObjects() MOZ_OVERRIDE
{ {
// nothing to do // Nothing to do here as the Context handles cleaning everything up
// automatically.
} }
virtual bool virtual bool
@ -169,15 +207,26 @@ public:
virtual bool virtual bool
IsTransactionServiceActivated() MOZ_OVERRIDE IsTransactionServiceActivated() MOZ_OVERRIDE
{ {
// TODO: implement nsIOfflineStorage interface (bug 1110487) return true;
return false;
} }
virtual void virtual void
WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages, WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
nsIRunnable* aCallback) MOZ_OVERRIDE 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: private:
~CacheQuotaClient() { } ~CacheQuotaClient()
{
MOZ_ASSERT(NS_IsMainThread());
}
public:
NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, MOZ_OVERRIDE) NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, MOZ_OVERRIDE)
}; };

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

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

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

@ -23,7 +23,7 @@ StreamList::StreamList(Manager* aManager, Context* aContext)
, mActivated(false) , mActivated(false)
{ {
MOZ_ASSERT(mManager); MOZ_ASSERT(mManager);
MOZ_ASSERT(mContext); mContext->AddActivity(this);
} }
void 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() StreamList::~StreamList()
{ {
NS_ASSERT_OWNINGTHREAD(StreamList); NS_ASSERT_OWNINGTHREAD(StreamList);
@ -153,6 +167,7 @@ StreamList::~StreamList()
} }
mManager->ReleaseCacheId(mCacheId); mManager->ReleaseCacheId(mCacheId);
} }
mContext->RemoveActivity(this);
} }
} // namespace cache } // namespace cache

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

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

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

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

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

@ -26,6 +26,7 @@ EXPORTS.mozilla.dom.cache += [
'IPCUtils.h', 'IPCUtils.h',
'Manager.h', 'Manager.h',
'ManagerId.h', 'ManagerId.h',
'OfflineStorage.h',
'PrincipalVerifier.h', 'PrincipalVerifier.h',
'QuotaClient.h', 'QuotaClient.h',
'ReadStream.h', 'ReadStream.h',
@ -56,6 +57,7 @@ UNIFIED_SOURCES += [
'FileUtils.cpp', 'FileUtils.cpp',
'Manager.cpp', 'Manager.cpp',
'ManagerId.cpp', 'ManagerId.cpp',
'OfflineStorage.cpp',
'PrincipalVerifier.cpp', 'PrincipalVerifier.cpp',
'QuotaClient.cpp', 'QuotaClient.cpp',
'ReadStream.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) { function loadScript(script) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var s = document.createElement("script"); var s = document.createElement("script");
@ -100,8 +116,11 @@ function runTests(testFile, order) {
return setupPrefs() return setupPrefs()
.then(importDrivers) .then(importDrivers)
.then(runWorkerTest) .then(runWorkerTest)
.then(clearStorage)
.then(runServiceWorkerTest) .then(runServiceWorkerTest)
.then(clearStorage)
.then(runFrameTest) .then(runFrameTest)
.then(clearStorage)
.catch(function(e) { .catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e); ok(false, "A promise was rejected during test execution: " + e);
}); });
@ -109,6 +128,7 @@ function runTests(testFile, order) {
return setupPrefs() return setupPrefs()
.then(importDrivers) .then(importDrivers)
.then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()])) .then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()]))
.then(clearStorage)
.catch(function(e) { .catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e); ok(false, "A promise was rejected during test execution: " + e);
}); });