import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

40
dom/cache/Action.cpp vendored Normal file
View file

@ -0,0 +1,40 @@
/* -*- 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/Action.h"
namespace mozilla {
namespace dom {
namespace cache {
void
Action::CancelOnInitiatingThread()
{
NS_ASSERT_OWNINGTHREAD(Action);
// It is possible for cancellation to be duplicated. For example, an
// individual Cache could have its Actions canceled and then shutdown
// could trigger a second action.
mCanceled = true;
}
Action::Action()
: mCanceled(false)
{
}
Action::~Action()
{
}
bool
Action::IsCanceled() const
{
return mCanceled;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

105
dom/cache/Action.h vendored Normal file
View file

@ -0,0 +1,105 @@
/* -*- 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_Action_h
#define mozilla_dom_cache_Action_h
#include "mozilla/Atomics.h"
#include "mozilla/dom/cache/Types.h"
#include "nsISupportsImpl.h"
class mozIStorageConnection;
namespace mozilla {
namespace dom {
namespace cache {
class Action
{
public:
class Resolver
{
public:
// Note: Action must drop Resolver ref after calling Resolve()!
// Note: Must be called on the same thread used to execute
// Action::RunOnTarget().
virtual void Resolve(nsresult aRv) = 0;
NS_IMETHOD_(MozExternalRefCountType)
AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType)
Release(void) = 0;
};
// Class containing data that can be opportunistically shared between
// multiple Actions running on the same thread/Context. In theory
// this could be abstracted to a generic key/value map, but for now
// just explicitly provide accessors for the data we need.
class Data
{
public:
virtual mozIStorageConnection*
GetConnection() const = 0;
virtual void
SetConnection(mozIStorageConnection* aConn) = 0;
};
// Execute operations on the target thread. Once complete call
// Resolver::Resolve(). This can be done sync or async.
// Note: Action should hold Resolver ref until its ready to call Resolve().
// Note: The "target" thread is determined when the Action is scheduled on
// Context. The Action should not assume any particular thread is used.
virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
Data* aOptionalData) = 0;
// Called on initiating thread when the Action is canceled. The Action is
// responsible for calling Resolver::Resolve() as normal; either with a
// normal error code or NS_ERROR_ABORT. If CancelOnInitiatingThread() is
// called after Resolve() has already occurred, then the cancel can be
// ignored.
//
// Cancellation is a best effort to stop processing as soon as possible, but
// does not guarantee the Action will not run.
//
// CancelOnInitiatingThread() may be called more than once. Subsequent
// calls should have no effect.
//
// Default implementation sets an internal cancellation flag that can be
// queried with IsCanceled().
virtual void CancelOnInitiatingThread();
// Executed on the initiating thread and is passed the nsresult given to
// Resolver::Resolve().
virtual void CompleteOnInitiatingThread(nsresult aRv) { }
// Executed on the initiating thread. If this Action will operate on the
// given cache ID then override this to return true.
virtual bool MatchesCacheId(CacheId aCacheId) const { return false; }
NS_INLINE_DECL_REFCOUNTING(cache::Action)
protected:
Action();
// virtual because deleted through base class pointer
virtual ~Action();
// Check if this Action has been canceled. May be called from any thread,
// but typically used from the target thread.
bool IsCanceled() const;
private:
// Accessible from any thread.
Atomic<bool> mCanceled;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_Action_h

66
dom/cache/ActorChild.cpp vendored Normal file
View file

@ -0,0 +1,66 @@
/* -*- 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/ActorChild.h"
#include "mozilla/dom/cache/CacheWorkerHolder.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
void
ActorChild::SetWorkerHolder(CacheWorkerHolder* aWorkerHolder)
{
// Some of the Cache actors can have multiple DOM objects associated with
// them. In this case the workerHolder will be added multiple times. This is
// permitted, but the workerHolder should be the same each time.
if (mWorkerHolder) {
MOZ_DIAGNOSTIC_ASSERT(mWorkerHolder == aWorkerHolder);
return;
}
mWorkerHolder = aWorkerHolder;
if (mWorkerHolder) {
mWorkerHolder->AddActor(this);
}
}
void
ActorChild::RemoveWorkerHolder()
{
MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerHolder);
if (mWorkerHolder) {
mWorkerHolder->RemoveActor(this);
mWorkerHolder = nullptr;
}
}
CacheWorkerHolder*
ActorChild::GetWorkerHolder() const
{
return mWorkerHolder;
}
bool
ActorChild::WorkerHolderNotified() const
{
return mWorkerHolder && mWorkerHolder->Notified();
}
ActorChild::ActorChild()
{
}
ActorChild::~ActorChild()
{
MOZ_DIAGNOSTIC_ASSERT(!mWorkerHolder);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

48
dom/cache/ActorChild.h vendored Normal file
View file

@ -0,0 +1,48 @@
/* -*- 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_ActioChild_h
#define mozilla_dom_cache_ActioChild_h
#include "mozilla/RefPtr.h"
namespace mozilla {
namespace dom {
namespace cache {
class CacheWorkerHolder;
class ActorChild
{
public:
virtual void
StartDestroy() = 0;
void
SetWorkerHolder(CacheWorkerHolder* aWorkerHolder);
void
RemoveWorkerHolder();
CacheWorkerHolder*
GetWorkerHolder() const;
bool
WorkerHolderNotified() const;
protected:
ActorChild();
~ActorChild();
private:
RefPtr<CacheWorkerHolder> mWorkerHolder;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_ActioChild_h

65
dom/cache/ActorUtils.h vendored Normal file
View file

@ -0,0 +1,65 @@
/* -*- 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_ActorUtils_h
#define mozilla_dom_cache_ActorUtils_h
#include "mozilla/dom/cache/Types.h"
namespace mozilla {
namespace ipc {
class PBackgroundParent;
class PrincipalInfo;
} // namespace ipc
namespace dom {
namespace cache {
class PCacheChild;
class PCacheParent;
class PCacheStreamControlChild;
class PCacheStreamControlParent;
class PCacheStorageChild;
class PCacheStorageParent;
// Factory methods for use in ipc/glue methods. Implemented in individual actor
// cpp files.
PCacheChild*
AllocPCacheChild();
void
DeallocPCacheChild(PCacheChild* aActor);
void
DeallocPCacheParent(PCacheParent* aActor);
PCacheStreamControlChild*
AllocPCacheStreamControlChild();
void
DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor);
void
DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor);
PCacheStorageParent*
AllocPCacheStorageParent(mozilla::ipc::PBackgroundParent* aManagingActor,
Namespace aNamespace,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
void
DeallocPCacheStorageChild(PCacheStorageChild* aActor);
void
DeallocPCacheStorageParent(PCacheStorageParent* aActor);
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_ActorUtils_h

565
dom/cache/AutoUtils.cpp vendored Normal file
View file

@ -0,0 +1,565 @@
/* -*- 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/AutoUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/cache/CacheParent.h"
#include "mozilla/dom/cache/CacheStreamControlParent.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/SavedTypes.h"
#include "mozilla/dom/cache/StreamList.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "nsCRT.h"
#include "nsHttp.h"
using mozilla::Unused;
using mozilla::dom::cache::CacheReadStream;
using mozilla::dom::cache::CacheReadStreamOrVoid;
using mozilla::ipc::AutoIPCStream;
using mozilla::ipc::PBackgroundParent;
namespace {
enum CleanupAction
{
Forget,
Delete
};
void
CleanupChild(CacheReadStream& aReadStream, CleanupAction aAction)
{
// fds cleaned up by mStreamCleanupList
// PSendStream actors cleaned up by mStreamCleanupList
}
void
CleanupChild(CacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction)
{
if (aReadStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) {
return;
}
CleanupChild(aReadStreamOrVoid.get_CacheReadStream(), aAction);
}
} // namespace
namespace mozilla {
namespace dom {
namespace cache {
// --------------------------------------------
AutoChildOpArgs::AutoChildOpArgs(TypeUtils* aTypeUtils,
const CacheOpArgs& aOpArgs,
uint32_t aEntryCount)
: mTypeUtils(aTypeUtils)
, mOpArgs(aOpArgs)
, mSent(false)
{
MOZ_DIAGNOSTIC_ASSERT(mTypeUtils);
MOZ_RELEASE_ASSERT(aEntryCount != 0);
// We are using AutoIPCStream objects to cleanup target IPCStream
// structures embedded in our CacheOpArgs. These IPCStream structs
// must not move once we attach our AutoIPCStream to them. Therefore,
// its important that any arrays containing streams are pre-sized for
// the number of entries we have in order to avoid realloc moving
// things around on us.
if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
args.requestResponseList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoChildOpArgs::~AutoChildOpArgs()
{
CleanupAction action = mSent ? Forget : Delete;
switch(mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs:
{
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheMatchAllArgs:
{
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
if (args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t) {
break;
}
CleanupChild(args.requestOrVoid().get_CacheRequest().body(), action);
break;
}
case CacheOpArgs::TCachePutAllArgs:
{
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
auto& list = args.requestResponseList();
for (uint32_t i = 0; i < list.Length(); ++i) {
CleanupChild(list[i].request().body(), action);
CleanupChild(list[i].response().body(), action);
}
break;
}
case CacheOpArgs::TCacheDeleteArgs:
{
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheKeysArgs:
{
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
if (args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t) {
break;
}
CleanupChild(args.requestOrVoid().get_CacheRequest().body(), action);
break;
}
case CacheOpArgs::TStorageMatchArgs:
{
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
default:
// Other types do not need cleanup
break;
}
mStreamCleanupList.Clear();
}
void
AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch(mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs:
{
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
break;
}
case CacheOpArgs::TCacheMatchAllArgs:
{
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
MOZ_DIAGNOSTIC_ASSERT(args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t);
args.requestOrVoid() = CacheRequest();
mTypeUtils->ToCacheRequest(args.requestOrVoid().get_CacheRequest(),
aRequest, aBodyAction, aSchemeAction,
mStreamCleanupList, aRv);
break;
}
case CacheOpArgs::TCacheDeleteArgs:
{
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
break;
}
case CacheOpArgs::TCacheKeysArgs:
{
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
MOZ_DIAGNOSTIC_ASSERT(args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t);
args.requestOrVoid() = CacheRequest();
mTypeUtils->ToCacheRequest(args.requestOrVoid().get_CacheRequest(),
aRequest, aBodyAction, aSchemeAction,
mStreamCleanupList, aRv);
break;
}
case CacheOpArgs::TStorageMatchArgs:
{
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request!");
}
}
namespace {
bool
MatchInPutList(InternalRequest* aRequest,
const nsTArray<CacheRequestResponse>& aPutList)
{
MOZ_DIAGNOSTIC_ASSERT(aRequest);
// This method implements the SW spec QueryCache algorithm against an
// in memory array of Request/Response objects. This essentially the
// same algorithm that is implemented in DBSchema.cpp. Unfortunately
// we cannot unify them because when operating against the real database
// we don't want to load all request/response objects into memory.
// Note, we can skip the check for a invalid request method because
// Cache should only call into here with a GET or HEAD.
#ifdef DEBUG
nsAutoCString method;
aRequest->GetMethod(method);
MOZ_ASSERT(method.LowerCaseEqualsLiteral("get") ||
method.LowerCaseEqualsLiteral("head"));
#endif
RefPtr<InternalHeaders> requestHeaders = aRequest->Headers();
for (uint32_t i = 0; i < aPutList.Length(); ++i) {
const CacheRequest& cachedRequest = aPutList[i].request();
const CacheResponse& cachedResponse = aPutList[i].response();
nsAutoCString url;
aRequest->GetURL(url);
nsAutoCString requestUrl(cachedRequest.urlWithoutQuery());
requestUrl.Append(cachedRequest.urlQuery());
// If the URLs don't match, then just skip to the next entry.
if (url != requestUrl) {
continue;
}
RefPtr<InternalHeaders> cachedRequestHeaders =
TypeUtils::ToInternalHeaders(cachedRequest.headers());
RefPtr<InternalHeaders> cachedResponseHeaders =
TypeUtils::ToInternalHeaders(cachedResponse.headers());
nsCString varyHeaders;
ErrorResult rv;
cachedResponseHeaders->Get(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
// Assume the vary headers match until we find a conflict
bool varyHeadersMatch = true;
char* rawBuffer = varyHeaders.BeginWriting();
char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer);
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"),
"We should have already caught this in "
"TypeUtils::ToPCacheResponseWithoutBody()");
ErrorResult headerRv;
nsAutoCString value;
requestHeaders->Get(header, value, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(value.IsEmpty());
}
nsAutoCString cachedValue;
cachedRequestHeaders->Get(header, cachedValue, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty());
}
if (value != cachedValue) {
varyHeadersMatch = false;
break;
}
}
// URL was equal and all vary headers match!
if (varyHeadersMatch) {
return true;
}
}
return false;
}
} // namespace
void
AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, Response& aResponse,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch(mOpArgs.type()) {
case CacheOpArgs::TCachePutAllArgs:
{
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
// Throw an error if a request/response pair would mask another
// request/response pair in the same PutAll operation. This is
// step 2.3.2.3 from the "Batch Cache Operations" spec algorithm.
if (MatchInPutList(aRequest, args.requestResponseList())) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Ensure that we don't realloc the array since this can result
// in our AutoIPCStream objects to reference the wrong memory
// location. This should never happen and is a UAF if it does.
// Therefore make this a release assertion.
MOZ_RELEASE_ASSERT(args.requestResponseList().Length() <
args.requestResponseList().Capacity());
// The FileDescriptorSetChild asserts in its destructor that all fds have
// been removed. The copy constructor, however, simply duplicates the
// fds without removing any. This means each temporary and copy must be
// explicitly cleaned up.
//
// Avoid a lot of this hassle by making sure we only create one here. On
// error we remove it.
CacheRequestResponse& pair = *args.requestResponseList().AppendElement();
pair.request().body() = void_t();
pair.response().body() = void_t();
mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
if (!aRv.Failed()) {
mTypeUtils->ToCacheResponse(pair.response(), aResponse,
mStreamCleanupList, aRv);
}
if (aRv.Failed()) {
CleanupChild(pair.request().body(), Delete);
args.requestResponseList().RemoveElementAt(
args.requestResponseList().Length() - 1);
}
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request/Response pair!");
}
}
const CacheOpArgs&
AutoChildOpArgs::SendAsOpArgs()
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
for (UniquePtr<AutoIPCStream>& autoStream : mStreamCleanupList) {
autoStream->TakeValue();
}
return mOpArgs;
}
// --------------------------------------------
AutoParentOpResult::AutoParentOpResult(mozilla::ipc::PBackgroundParent* aManager,
const CacheOpResult& aOpResult,
uint32_t aEntryCount)
: mManager(aManager)
, mOpResult(aOpResult)
, mStreamControl(nullptr)
, mSent(false)
{
MOZ_DIAGNOSTIC_ASSERT(mManager);
MOZ_RELEASE_ASSERT(aEntryCount != 0);
// We are using AutoIPCStream objects to cleanup target IPCStream
// structures embedded in our CacheOpArgs. These IPCStream structs
// must not move once we attach our AutoIPCStream to them. Therefore,
// its important that any arrays containing streams are pre-sized for
// the number of entries we have in order to avoid realloc moving
// things around on us.
if (mOpResult.type() == CacheOpResult::TCacheMatchAllResult) {
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
result.responseList().SetCapacity(aEntryCount);
} else if (mOpResult.type() == CacheOpResult::TCacheKeysResult) {
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
result.requestList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoParentOpResult::~AutoParentOpResult()
{
CleanupAction action = mSent ? Forget : Delete;
switch (mOpResult.type()) {
case CacheOpResult::TStorageOpenResult:
{
StorageOpenResult& result = mOpResult.get_StorageOpenResult();
if (action == Forget || result.actorParent() == nullptr) {
break;
}
Unused << PCacheParent::Send__delete__(result.actorParent());
break;
}
default:
// other types do not need additional clean up
break;
}
if (action == Delete && mStreamControl) {
Unused << PCacheStreamControlParent::Send__delete__(mStreamControl);
}
mStreamCleanupList.Clear();
}
void
AutoParentOpResult::Add(CacheId aOpenedCacheId, Manager* aManager)
{
MOZ_DIAGNOSTIC_ASSERT(mOpResult.type() == CacheOpResult::TStorageOpenResult);
MOZ_DIAGNOSTIC_ASSERT(mOpResult.get_StorageOpenResult().actorParent() == nullptr);
mOpResult.get_StorageOpenResult().actorParent() =
mManager->SendPCacheConstructor(new CacheParent(aManager, aOpenedCacheId));
}
void
AutoParentOpResult::Add(const SavedResponse& aSavedResponse,
StreamList* aStreamList)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheMatchResult:
{
CacheMatchResult& result = mOpResult.get_CacheMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t);
result.responseOrVoid() = aSavedResponse.mValue;
SerializeResponseBody(aSavedResponse, aStreamList,
&result.responseOrVoid().get_CacheResponse());
break;
}
case CacheOpResult::TCacheMatchAllResult:
{
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
// Ensure that we don't realloc the array since this can result
// in our AutoIPCStream objects to reference the wrong memory
// location. This should never happen and is a UAF if it does.
// Therefore make this a release assertion.
MOZ_RELEASE_ASSERT(result.responseList().Length() <
result.responseList().Capacity());
result.responseList().AppendElement(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.responseList().LastElement());
break;
}
case CacheOpResult::TStorageMatchResult:
{
StorageMatchResult& result = mOpResult.get_StorageMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t);
result.responseOrVoid() = aSavedResponse.mValue;
SerializeResponseBody(aSavedResponse, aStreamList,
&result.responseOrVoid().get_CacheResponse());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Response!");
}
}
void
AutoParentOpResult::Add(const SavedRequest& aSavedRequest,
StreamList* aStreamList)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheKeysResult:
{
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
// Ensure that we don't realloc the array since this can result
// in our AutoIPCStream objects to reference the wrong memory
// location. This should never happen and is a UAF if it does.
// Therefore make this a release assertion.
MOZ_RELEASE_ASSERT(result.requestList().Length() <
result.requestList().Capacity());
result.requestList().AppendElement(aSavedRequest.mValue);
CacheRequest& request = result.requestList().LastElement();
if (!aSavedRequest.mHasBodyId) {
request.body() = void_t();
break;
}
request.body() = CacheReadStream();
SerializeReadStream(aSavedRequest.mBodyId, aStreamList,
&request.body().get_CacheReadStream());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Request!");
}
}
const CacheOpResult&
AutoParentOpResult::SendAsOpResult()
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
for (UniquePtr<AutoIPCStream>& autoStream : mStreamCleanupList) {
autoStream->TakeValue();
}
return mOpResult;
}
void
AutoParentOpResult::SerializeResponseBody(const SavedResponse& aSavedResponse,
StreamList* aStreamList,
CacheResponse* aResponseOut)
{
MOZ_DIAGNOSTIC_ASSERT(aResponseOut);
if (!aSavedResponse.mHasBodyId) {
aResponseOut->body() = void_t();
return;
}
aResponseOut->body() = CacheReadStream();
SerializeReadStream(aSavedResponse.mBodyId, aStreamList,
&aResponseOut->body().get_CacheReadStream());
}
void
AutoParentOpResult::SerializeReadStream(const nsID& aId, StreamList* aStreamList,
CacheReadStream* aReadStreamOut)
{
MOZ_DIAGNOSTIC_ASSERT(aStreamList);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mSent);
nsCOMPtr<nsIInputStream> stream = aStreamList->Extract(aId);
MOZ_DIAGNOSTIC_ASSERT(stream);
if (!mStreamControl) {
mStreamControl = static_cast<CacheStreamControlParent*>(
mManager->SendPCacheStreamControlConstructor(new CacheStreamControlParent()));
// If this failed, then the child process is gone. Warn and allow actor
// cleanup to proceed as normal.
if (!mStreamControl) {
NS_WARNING("Cache failed to create stream control actor.");
return;
}
}
aStreamList->SetStreamControl(mStreamControl);
RefPtr<ReadStream> readStream = ReadStream::Create(mStreamControl,
aId, stream);
ErrorResult rv;
readStream->Serialize(aReadStreamOut, mStreamCleanupList, rv);
MOZ_DIAGNOSTIC_ASSERT(!rv.Failed());
}
} // namespace cache
} // namespace dom
} // namespace mozilla

104
dom/cache/AutoUtils.h vendored Normal file
View file

@ -0,0 +1,104 @@
/* -*- 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_AutoUtils_h
#define mozilla_dom_cache_AutoUtils_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "nsTArray.h"
struct nsID;
namespace mozilla {
class ErrorResult;
namespace ipc {
class PBackgroundParent;
class AutoIPCStream;
} // namespace ipc
namespace dom {
class InternalRequest;
namespace cache {
class CacheStreamControlParent;
class Manager;
struct SavedRequest;
struct SavedResponse;
class StreamList;
// A collection of RAII-style helper classes to ensure that IPC
// FileDescriptorSet actors are properly cleaned up. The user of these actors
// must manually either Forget() the Fds or Send__delete__() the actor
// depending on if the descriptors were actually sent.
//
// Note, these should only be used when *sending* streams across IPC. The
// deserialization case is handled by creating a ReadStream object.
class MOZ_STACK_CLASS AutoChildOpArgs final
{
public:
typedef TypeUtils::BodyAction BodyAction;
typedef TypeUtils::SchemeAction SchemeAction;
AutoChildOpArgs(TypeUtils* aTypeUtils, const CacheOpArgs& aOpArgs,
uint32_t aEntryCount);
~AutoChildOpArgs();
void Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, ErrorResult& aRv);
void Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, Response& aResponse, ErrorResult& aRv);
const CacheOpArgs& SendAsOpArgs();
private:
TypeUtils* mTypeUtils;
CacheOpArgs mOpArgs;
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>> mStreamCleanupList;
bool mSent;
};
class MOZ_STACK_CLASS AutoParentOpResult final
{
public:
AutoParentOpResult(mozilla::ipc::PBackgroundParent* aManager,
const CacheOpResult& aOpResult,
uint32_t aEntryCount);
~AutoParentOpResult();
void Add(CacheId aOpenedCacheId, Manager* aManager);
void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList);
void Add(const SavedRequest& aSavedRequest, StreamList* aStreamList);
const CacheOpResult& SendAsOpResult();
private:
void SerializeResponseBody(const SavedResponse& aSavedResponse,
StreamList* aStreamList,
CacheResponse* aResponseOut);
void SerializeReadStream(const nsID& aId, StreamList* aStreamList,
CacheReadStream* aReadStreamOut);
mozilla::ipc::PBackgroundParent* mManager;
CacheOpResult mOpResult;
CacheStreamControlParent* mStreamControl;
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>> mStreamCleanupList;
bool mSent;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_AutoUtils_h

659
dom/cache/Cache.cpp vendored Normal file
View file

@ -0,0 +1,659 @@
/* -*- 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/Cache.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/InternalResponse.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/CacheBinding.h"
#include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheWorkerHolder.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Preferences.h"
#include "mozilla/Unused.h"
#include "nsIGlobalObject.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
using mozilla::dom::workers::WorkerPrivate;
using mozilla::ipc::PBackgroundChild;
namespace {
bool
IsValidPutRequestURL(const nsAString& aUrl, ErrorResult& aRv)
{
bool validScheme = false;
// make a copy because ProcessURL strips the fragmet
NS_ConvertUTF16toUTF8 url(aUrl);
TypeUtils::ProcessURL(url, &validScheme, nullptr, nullptr, aRv);
if (aRv.Failed()) {
return false;
}
if (!validScheme) {
aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
aUrl);
return false;
}
return true;
}
static bool
IsValidPutRequestMethod(const Request& aRequest, ErrorResult& aRv)
{
nsAutoCString method;
aRequest.GetMethod(method);
if (!method.LowerCaseEqualsLiteral("get")) {
NS_ConvertASCIItoUTF16 label(method);
aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label);
return false;
}
return true;
}
static bool
IsValidPutRequestMethod(const RequestOrUSVString& aRequest, ErrorResult& aRv)
{
// If the provided request is a string URL, then it will default to
// a valid http method automatically.
if (!aRequest.IsRequest()) {
return true;
}
return IsValidPutRequestMethod(aRequest.GetAsRequest(), aRv);
}
} // namespace
// Helper class to wait for Add()/AddAll() fetch requests to complete and
// then perform a PutAll() with the responses. This class holds a WorkerHolder
// to keep the Worker thread alive. This is mainly to ensure that Add/AddAll
// act the same as other Cache operations that directly create a CacheOpChild
// actor.
class Cache::FetchHandler final : public PromiseNativeHandler
{
public:
FetchHandler(CacheWorkerHolder* aWorkerHolder, Cache* aCache,
nsTArray<RefPtr<Request>>&& aRequestList, Promise* aPromise)
: mWorkerHolder(aWorkerHolder)
, mCache(aCache)
, mRequestList(Move(aRequestList))
, mPromise(aPromise)
{
MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerHolder);
MOZ_DIAGNOSTIC_ASSERT(mCache);
MOZ_DIAGNOSTIC_ASSERT(mPromise);
}
virtual void
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
NS_ASSERT_OWNINGTHREAD(FetchHandler);
// Stop holding the worker alive when we leave this method.
RefPtr<CacheWorkerHolder> workerHolder;
workerHolder.swap(mWorkerHolder);
// Promise::All() passed an array of fetch() Promises should give us
// an Array of Response objects. The following code unwraps these
// JS values back to an nsTArray<RefPtr<Response>>.
AutoTArray<RefPtr<Response>, 256> responseList;
responseList.SetCapacity(mRequestList.Length());
bool isArray;
if (NS_WARN_IF(!JS_IsArrayObject(aCx, aValue, &isArray) || !isArray)) {
Fail();
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
uint32_t length;
if (NS_WARN_IF(!JS_GetArrayLength(aCx, obj, &length))) {
Fail();
return;
}
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(aCx);
if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &value))) {
Fail();
return;
}
if (NS_WARN_IF(!value.isObject())) {
Fail();
return;
}
JS::Rooted<JSObject*> responseObj(aCx, &value.toObject());
RefPtr<Response> response;
nsresult rv = UNWRAP_OBJECT(Response, responseObj, response);
if (NS_WARN_IF(NS_FAILED(rv))) {
Fail();
return;
}
if (NS_WARN_IF(response->Type() == ResponseType::Error)) {
Fail();
return;
}
// Do not allow the convenience methods .add()/.addAll() to store failed
// responses. A consequence of this is that these methods cannot be
// used to store opaque or opaqueredirect responses since they always
// expose a 0 status value.
if (!response->Ok()) {
uint32_t t = static_cast<uint32_t>(response->Type());
NS_ConvertASCIItoUTF16 type(ResponseTypeValues::strings[t].value,
ResponseTypeValues::strings[t].length);
nsAutoString status;
status.AppendInt(response->Status());
nsAutoString url;
mRequestList[i]->GetUrl(url);
ErrorResult rv;
rv.ThrowTypeError<MSG_CACHE_ADD_FAILED_RESPONSE>(type, status, url);
// TODO: abort the fetch requests we have running (bug 1157434)
mPromise->MaybeReject(rv);
return;
}
responseList.AppendElement(Move(response));
}
MOZ_DIAGNOSTIC_ASSERT(mRequestList.Length() == responseList.Length());
// Now store the unwrapped Response list in the Cache.
ErrorResult result;
RefPtr<Promise> put = mCache->PutAll(mRequestList, responseList, result);
if (NS_WARN_IF(result.Failed())) {
// TODO: abort the fetch requests we have running (bug 1157434)
mPromise->MaybeReject(result);
return;
}
// Chain the Cache::Put() promise to the original promise returned to
// the content script.
mPromise->MaybeResolve(put);
}
virtual void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
NS_ASSERT_OWNINGTHREAD(FetchHandler);
Fail();
}
private:
~FetchHandler()
{
}
void
Fail()
{
ErrorResult rv;
rv.ThrowTypeError<MSG_FETCH_FAILED>();
mPromise->MaybeReject(rv);
}
RefPtr<CacheWorkerHolder> mWorkerHolder;
RefPtr<Cache> mCache;
nsTArray<RefPtr<Request>> mRequestList;
RefPtr<Promise> mPromise;
NS_DECL_ISUPPORTS
};
NS_IMPL_ISUPPORTS0(Cache::FetchHandler)
NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::Cache);
NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::Cache);
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::Cache, mGlobal);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor)
: mGlobal(aGlobal)
, mActor(aActor)
{
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
MOZ_DIAGNOSTIC_ASSERT(mActor);
mActor->SetListener(this);
}
already_AddRefed<Promise>
Cache::Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
CacheQueryParams params;
ToCacheQueryParams(params, aOptions);
AutoChildOpArgs args(this, CacheMatchArgs(CacheRequest(), params), 1);
args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return ExecuteOp(args, aRv);
}
already_AddRefed<Promise>
Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
CacheQueryParams params;
ToCacheQueryParams(params, aOptions);
AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
IgnoreBody, aRv);
if (aRv.Failed()) {
return nullptr;
}
args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
if (aRv.Failed()) {
return nullptr;
}
}
return ExecuteOp(args, aRv);
}
already_AddRefed<Promise>
Cache::Add(JSContext* aContext, const RequestOrUSVString& aRequest,
ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
if (!IsValidPutRequestMethod(aRequest, aRv)) {
return nullptr;
}
GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
nsTArray<RefPtr<Request>> requestList(1);
RefPtr<Request> request = Request::Constructor(global, aRequest,
RequestInit(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsAutoString url;
request->GetUrl(url);
if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
return nullptr;
}
requestList.AppendElement(Move(request));
return AddAll(global, Move(requestList), aRv);
}
already_AddRefed<Promise>
Cache::AddAll(JSContext* aContext,
const Sequence<OwningRequestOrUSVString>& aRequestList,
ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
nsTArray<RefPtr<Request>> requestList(aRequestList.Length());
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RequestOrUSVString requestOrString;
if (aRequestList[i].IsRequest()) {
requestOrString.SetAsRequest() = aRequestList[i].GetAsRequest();
if (NS_WARN_IF(!IsValidPutRequestMethod(requestOrString.GetAsRequest(),
aRv))) {
return nullptr;
}
} else {
requestOrString.SetAsUSVString().Rebind(
aRequestList[i].GetAsUSVString().Data(),
aRequestList[i].GetAsUSVString().Length());
}
RefPtr<Request> request = Request::Constructor(global, requestOrString,
RequestInit(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsAutoString url;
request->GetUrl(url);
if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
return nullptr;
}
requestList.AppendElement(Move(request));
}
return AddAll(global, Move(requestList), aRv);
}
already_AddRefed<Promise>
Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
if (NS_WARN_IF(!IsValidPutRequestMethod(aRequest, aRv))) {
return nullptr;
}
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
AutoChildOpArgs args(this, CachePutAllArgs(), 1);
args.Add(ir, ReadBody, TypeErrorOnInvalidScheme,
aResponse, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return ExecuteOp(args, aRv);
}
already_AddRefed<Promise>
Cache::Delete(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
CacheQueryParams params;
ToCacheQueryParams(params, aOptions);
AutoChildOpArgs args(this, CacheDeleteArgs(CacheRequest(), params), 1);
args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return ExecuteOp(args, aRv);
}
already_AddRefed<Promise>
Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
CacheQueryParams params;
ToCacheQueryParams(params, aOptions);
AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
return ExecuteOp(args, aRv);
}
// static
bool
Cache::PrefEnabled(JSContext* aCx, JSObject* aObj)
{
using mozilla::dom::workers::WorkerPrivate;
using mozilla::dom::workers::GetWorkerPrivateFromContext;
// If we're on the main thread, then check the pref directly.
if (NS_IsMainThread()) {
bool enabled = false;
Preferences::GetBool("dom.caches.enabled", &enabled);
return enabled;
}
// Otherwise check the pref via the work private helper
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (!workerPrivate) {
return false;
}
return workerPrivate->DOMCachesEnabled();
}
nsISupports*
Cache::GetParentObject() const
{
return mGlobal;
}
JSObject*
Cache::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
{
return CacheBinding::Wrap(aContext, this, aGivenProto);
}
void
Cache::DestroyInternal(CacheChild* aActor)
{
MOZ_DIAGNOSTIC_ASSERT(mActor);
MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
mActor->ClearListener();
mActor = nullptr;
}
nsIGlobalObject*
Cache::GetGlobalObject() const
{
return mGlobal;
}
#ifdef DEBUG
void
Cache::AssertOwningThread() const
{
NS_ASSERT_OWNINGTHREAD(Cache);
}
#endif
PBackgroundChild*
Cache::GetIPCManager()
{
NS_ASSERT_OWNINGTHREAD(Cache);
MOZ_DIAGNOSTIC_ASSERT(mActor);
return mActor->Manager();
}
Cache::~Cache()
{
NS_ASSERT_OWNINGTHREAD(Cache);
if (mActor) {
mActor->StartDestroyFromListener();
// DestroyInternal() is called synchronously by StartDestroyFromListener().
// So we should have already cleared the mActor.
MOZ_DIAGNOSTIC_ASSERT(!mActor);
}
}
already_AddRefed<Promise>
Cache::ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(mActor);
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
mActor->ExecuteOp(mGlobal, promise, this, aOpArgs.SendAsOpArgs());
return promise.forget();
}
already_AddRefed<Promise>
Cache::AddAll(const GlobalObject& aGlobal,
nsTArray<RefPtr<Request>>&& aRequestList, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(mActor);
// If there is no work to do, then resolve immediately
if (aRequestList.IsEmpty()) {
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
promise->MaybeResolveWithUndefined();
return promise.forget();
}
AutoTArray<RefPtr<Promise>, 256> fetchList;
fetchList.SetCapacity(aRequestList.Length());
// Begin fetching each request in parallel. For now, if an error occurs just
// abandon our previous fetch calls. In theory we could cancel them in the
// future once fetch supports it.
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RequestOrUSVString requestOrString;
requestOrString.SetAsRequest() = aRequestList[i];
RefPtr<Promise> fetch = FetchRequest(mGlobal, requestOrString,
RequestInit(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
fetchList.AppendElement(Move(fetch));
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RefPtr<FetchHandler> handler =
new FetchHandler(mActor->GetWorkerHolder(), this,
Move(aRequestList), promise);
RefPtr<Promise> fetchPromise = Promise::All(aGlobal, fetchList, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
fetchPromise->AppendNativeHandler(handler);
return promise.forget();
}
already_AddRefed<Promise>
Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aRequestList.Length() == aResponseList.Length());
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
AutoChildOpArgs args(this, CachePutAllArgs(), aRequestList.Length());
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RefPtr<InternalRequest> ir = aRequestList[i]->GetInternalRequest();
args.Add(ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i], aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
return ExecuteOp(args, aRv);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

120
dom/cache/Cache.h vendored Normal file
View file

@ -0,0 +1,120 @@
/* -*- 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_Cache_h
#define mozilla_dom_cache_Cache_h
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nsWrapperCache.h"
class nsIGlobalObject;
namespace mozilla {
class ErrorResult;
namespace dom {
class OwningRequestOrUSVString;
class Promise;
struct CacheQueryOptions;
class RequestOrUSVString;
class Response;
template<typename T> class Optional;
template<typename T> class Sequence;
namespace cache {
class AutoChildOpArgs;
class CacheChild;
class Cache final : public nsISupports
, public nsWrapperCache
, public TypeUtils
{
public:
Cache(nsIGlobalObject* aGlobal, CacheChild* aActor);
// webidl interface methods
already_AddRefed<Promise>
Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
MatchAll(const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
Add(JSContext* aContext, const RequestOrUSVString& aRequest,
ErrorResult& aRv);
already_AddRefed<Promise>
AddAll(JSContext* aContext,
const Sequence<OwningRequestOrUSVString>& aRequests, ErrorResult& aRv);
already_AddRefed<Promise>
Put(const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv);
already_AddRefed<Promise>
Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
Keys(const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aParams, ErrorResult& aRv);
// binding methods
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
nsISupports* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto) override;
// Called when CacheChild actor is being destroyed
void DestroyInternal(CacheChild* aActor);
// TypeUtils methods
virtual nsIGlobalObject*
GetGlobalObject() const override;
#ifdef DEBUG
virtual void AssertOwningThread() const override;
#endif
virtual mozilla::ipc::PBackgroundChild*
GetIPCManager() override;
private:
class FetchHandler;
~Cache();
// Called when we're destroyed or CCed.
void DisconnectFromActor();
already_AddRefed<Promise>
ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv);
already_AddRefed<Promise>
AddAll(const GlobalObject& aGlobal, nsTArray<RefPtr<Request>>&& aRequestList,
ErrorResult& aRv);
already_AddRefed<Promise>
PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv);
nsCOMPtr<nsIGlobalObject> mGlobal;
CacheChild* mActor;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Cache)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_Cache_h

185
dom/cache/CacheChild.cpp vendored Normal file
View file

@ -0,0 +1,185 @@
/* -*- 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/CacheChild.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/CacheOpChild.h"
namespace mozilla {
namespace dom {
namespace cache {
// Declared in ActorUtils.h
PCacheChild*
AllocPCacheChild()
{
return new CacheChild();
}
// Declared in ActorUtils.h
void
DeallocPCacheChild(PCacheChild* aActor)
{
delete aActor;
}
CacheChild::CacheChild()
: mListener(nullptr)
, mNumChildActors(0)
, mDelayedDestroy(false)
, mLocked(false)
{
MOZ_COUNT_CTOR(cache::CacheChild);
}
CacheChild::~CacheChild()
{
MOZ_COUNT_DTOR(cache::CacheChild);
NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_DIAGNOSTIC_ASSERT(!mListener);
MOZ_DIAGNOSTIC_ASSERT(!mNumChildActors);
MOZ_DIAGNOSTIC_ASSERT(!mLocked);
}
void
CacheChild::SetListener(Cache* aListener)
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_DIAGNOSTIC_ASSERT(!mListener);
mListener = aListener;
MOZ_DIAGNOSTIC_ASSERT(mListener);
}
void
CacheChild::ClearListener()
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_DIAGNOSTIC_ASSERT(mListener);
mListener = nullptr;
}
void
CacheChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
nsISupports* aParent, const CacheOpArgs& aArgs)
{
mNumChildActors += 1;
MOZ_ALWAYS_TRUE(SendPCacheOpConstructor(
new CacheOpChild(GetWorkerHolder(), aGlobal, aParent, aPromise), aArgs));
}
void
CacheChild::StartDestroyFromListener()
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
// The listener should be held alive by any async operations, so if it
// is going away then there must not be any child actors. This in turn
// ensures that StartDestroy() will not trigger the delayed path.
MOZ_DIAGNOSTIC_ASSERT(!mNumChildActors);
StartDestroy();
}
void
CacheChild::StartDestroy()
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
// If we have outstanding child actors, then don't destroy ourself yet.
// The child actors should be short lived and we should allow them to complete
// if possible. NoteDeletedActor() will call back into this Shutdown()
// method when the last child actor is gone. Also, delay destruction if we
// have been explicitly locked by someone using us on the stack.
if (mNumChildActors || mLocked) {
mDelayedDestroy = true;
return;
}
RefPtr<Cache> listener = mListener;
// StartDestroy() can get called from either Cache or the WorkerHolder.
// Theoretically we can get double called if the right race happens. Handle
// that by just ignoring the second StartDestroy() call.
if (!listener) {
return;
}
listener->DestroyInternal(this);
// Cache listener should call ClearListener() in DestroyInternal()
MOZ_DIAGNOSTIC_ASSERT(!mListener);
// Start actor destruction from parent process
Unused << SendTeardown();
}
void
CacheChild::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
RefPtr<Cache> listener = mListener;
if (listener) {
listener->DestroyInternal(this);
// Cache listener should call ClearListener() in DestroyInternal()
MOZ_DIAGNOSTIC_ASSERT(!mListener);
}
RemoveWorkerHolder();
}
PCacheOpChild*
CacheChild::AllocPCacheOpChild(const CacheOpArgs& aOpArgs)
{
MOZ_CRASH("CacheOpChild should be manually constructed.");
return nullptr;
}
bool
CacheChild::DeallocPCacheOpChild(PCacheOpChild* aActor)
{
delete aActor;
NoteDeletedActor();
return true;
}
void
CacheChild::NoteDeletedActor()
{
mNumChildActors -= 1;
MaybeFlushDelayedDestroy();
}
void
CacheChild::MaybeFlushDelayedDestroy()
{
if (!mNumChildActors && !mLocked && mDelayedDestroy) {
StartDestroy();
}
}
void
CacheChild::Lock()
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_DIAGNOSTIC_ASSERT(!mLocked);
mLocked = true;
}
void
CacheChild::Unlock()
{
NS_ASSERT_OWNINGTHREAD(CacheChild);
MOZ_DIAGNOSTIC_ASSERT(mLocked);
mLocked = false;
MaybeFlushDelayedDestroy();
}
} // namespace cache
} // namespace dom
} // namespace mozilla

111
dom/cache/CacheChild.h vendored Normal file
View file

@ -0,0 +1,111 @@
/* -*- 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_CacheChild_h
#define mozilla_dom_cache_CacheChild_h
#include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/PCacheChild.h"
class nsIAsyncInputStream;
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Promise;
namespace cache {
class Cache;
class CacheOpArgs;
class CacheChild final : public PCacheChild
, public ActorChild
{
public:
class MOZ_RAII AutoLock final
{
CacheChild* mActor;
public:
explicit AutoLock(CacheChild* aActor)
: mActor(aActor)
{
MOZ_DIAGNOSTIC_ASSERT(mActor);
mActor->Lock();
}
~AutoLock()
{
mActor->Unlock();
}
};
CacheChild();
~CacheChild();
void SetListener(Cache* aListener);
// Must be called by the associated Cache listener in its DestroyInternal()
// method. Also, Cache must call StartDestroyFromListener() on the actor in
// its destructor to trigger ActorDestroy() if it has not been called yet.
void ClearListener();
void
ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
nsISupports* aParent, const CacheOpArgs& aArgs);
// Our parent Listener object has gone out of scope and is being destroyed.
void StartDestroyFromListener();
private:
// ActorChild methods
// WorkerHolder is trying to destroy due to worker shutdown.
virtual void StartDestroy() override;
// PCacheChild methods
virtual void
ActorDestroy(ActorDestroyReason aReason) override;
virtual PCacheOpChild*
AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override;
virtual bool
DeallocPCacheOpChild(PCacheOpChild* aActor) override;
// utility methods
void
NoteDeletedActor();
void
MaybeFlushDelayedDestroy();
// Methods used to temporarily force the actor alive. Only called from
// AutoLock.
void
Lock();
void
Unlock();
// Use a weak ref so actor does not hold DOM object alive past content use.
// The Cache object must call ClearListener() to null this before its
// destroyed.
Cache* MOZ_NON_OWNING_REF mListener;
uint32_t mNumChildActors;
bool mDelayedDestroy;
bool mLocked;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheChild_h

268
dom/cache/CacheOpChild.cpp vendored Normal file
View file

@ -0,0 +1,268 @@
/* -*- 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/CacheOpChild.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheStreamControlChild.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::ipc::PBackgroundChild;
namespace {
void
AddWorkerHolderToStreamChild(const CacheReadStream& aReadStream,
CacheWorkerHolder* aWorkerHolder)
{
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
CacheStreamControlChild* cacheControl =
static_cast<CacheStreamControlChild*>(aReadStream.controlChild());
if (cacheControl) {
cacheControl->SetWorkerHolder(aWorkerHolder);
}
}
void
AddWorkerHolderToStreamChild(const CacheResponse& aResponse,
CacheWorkerHolder* aWorkerHolder)
{
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
if (aResponse.body().type() == CacheReadStreamOrVoid::Tvoid_t) {
return;
}
AddWorkerHolderToStreamChild(aResponse.body().get_CacheReadStream(),
aWorkerHolder);
}
void
AddWorkerHolderToStreamChild(const CacheRequest& aRequest,
CacheWorkerHolder* aWorkerHolder)
{
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
if (aRequest.body().type() == CacheReadStreamOrVoid::Tvoid_t) {
return;
}
AddWorkerHolderToStreamChild(aRequest.body().get_CacheReadStream(),
aWorkerHolder);
}
} // namespace
CacheOpChild::CacheOpChild(CacheWorkerHolder* aWorkerHolder,
nsIGlobalObject* aGlobal,
nsISupports* aParent, Promise* aPromise)
: mGlobal(aGlobal)
, mParent(aParent)
, mPromise(aPromise)
{
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
MOZ_DIAGNOSTIC_ASSERT(mParent);
MOZ_DIAGNOSTIC_ASSERT(mPromise);
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
SetWorkerHolder(aWorkerHolder);
}
CacheOpChild::~CacheOpChild()
{
NS_ASSERT_OWNINGTHREAD(CacheOpChild);
MOZ_DIAGNOSTIC_ASSERT(!mPromise);
}
void
CacheOpChild::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheOpChild);
// If the actor was terminated for some unknown reason, then indicate the
// operation is dead.
if (mPromise) {
mPromise->MaybeReject(NS_ERROR_FAILURE);
mPromise = nullptr;
}
RemoveWorkerHolder();
}
bool
CacheOpChild::Recv__delete__(const ErrorResult& aRv,
const CacheOpResult& aResult)
{
NS_ASSERT_OWNINGTHREAD(CacheOpChild);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_DIAGNOSTIC_ASSERT(aResult.type() == CacheOpResult::Tvoid_t);
// TODO: Remove this const_cast (bug 1152078).
// It is safe for now since this ErrorResult is handed off to us by IPDL
// and is thrown into the trash afterwards.
mPromise->MaybeReject(const_cast<ErrorResult&>(aRv));
mPromise = nullptr;
return true;
}
switch (aResult.type()) {
case CacheOpResult::TCacheMatchResult:
{
HandleResponse(aResult.get_CacheMatchResult().responseOrVoid());
break;
}
case CacheOpResult::TCacheMatchAllResult:
{
HandleResponseList(aResult.get_CacheMatchAllResult().responseList());
break;
}
case CacheOpResult::TCachePutAllResult:
{
mPromise->MaybeResolveWithUndefined();
break;
}
case CacheOpResult::TCacheDeleteResult:
{
mPromise->MaybeResolve(aResult.get_CacheDeleteResult().success());
break;
}
case CacheOpResult::TCacheKeysResult:
{
HandleRequestList(aResult.get_CacheKeysResult().requestList());
break;
}
case CacheOpResult::TStorageMatchResult:
{
HandleResponse(aResult.get_StorageMatchResult().responseOrVoid());
break;
}
case CacheOpResult::TStorageHasResult:
{
mPromise->MaybeResolve(aResult.get_StorageHasResult().success());
break;
}
case CacheOpResult::TStorageOpenResult:
{
auto actor = static_cast<CacheChild*>(
aResult.get_StorageOpenResult().actorChild());
// If we have a success status then we should have an actor. Gracefully
// reject instead of crashing, though, if we get a nullptr here.
MOZ_DIAGNOSTIC_ASSERT(actor);
if (!actor) {
ErrorResult status;
status.ThrowTypeError<MSG_CACHE_OPEN_FAILED>();
mPromise->MaybeReject(status);
break;
}
actor->SetWorkerHolder(GetWorkerHolder());
RefPtr<Cache> cache = new Cache(mGlobal, actor);
mPromise->MaybeResolve(cache);
break;
}
case CacheOpResult::TStorageDeleteResult:
{
mPromise->MaybeResolve(aResult.get_StorageDeleteResult().success());
break;
}
case CacheOpResult::TStorageKeysResult:
{
mPromise->MaybeResolve(aResult.get_StorageKeysResult().keyList());
break;
}
default:
MOZ_CRASH("Unknown Cache op result type!");
}
mPromise = nullptr;
return true;
}
void
CacheOpChild::StartDestroy()
{
NS_ASSERT_OWNINGTHREAD(CacheOpChild);
// Do not cancel on-going operations when WorkerHolder calls this. Instead,
// keep the Worker alive until we are done.
}
nsIGlobalObject*
CacheOpChild::GetGlobalObject() const
{
return mGlobal;
}
#ifdef DEBUG
void
CacheOpChild::AssertOwningThread() const
{
NS_ASSERT_OWNINGTHREAD(CacheOpChild);
}
#endif
PBackgroundChild*
CacheOpChild::GetIPCManager()
{
MOZ_CRASH("CacheOpChild does not implement TypeUtils::GetIPCManager()");
}
void
CacheOpChild::HandleResponse(const CacheResponseOrVoid& aResponseOrVoid)
{
if (aResponseOrVoid.type() == CacheResponseOrVoid::Tvoid_t) {
mPromise->MaybeResolveWithUndefined();
return;
}
const CacheResponse& cacheResponse = aResponseOrVoid.get_CacheResponse();
AddWorkerHolderToStreamChild(cacheResponse, GetWorkerHolder());
RefPtr<Response> response = ToResponse(cacheResponse);
mPromise->MaybeResolve(response);
}
void
CacheOpChild::HandleResponseList(const nsTArray<CacheResponse>& aResponseList)
{
AutoTArray<RefPtr<Response>, 256> responses;
responses.SetCapacity(aResponseList.Length());
for (uint32_t i = 0; i < aResponseList.Length(); ++i) {
AddWorkerHolderToStreamChild(aResponseList[i], GetWorkerHolder());
responses.AppendElement(ToResponse(aResponseList[i]));
}
mPromise->MaybeResolve(responses);
}
void
CacheOpChild::HandleRequestList(const nsTArray<CacheRequest>& aRequestList)
{
AutoTArray<RefPtr<Request>, 256> requests;
requests.SetCapacity(aRequestList.Length());
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
AddWorkerHolderToStreamChild(aRequestList[i], GetWorkerHolder());
requests.AppendElement(ToRequest(aRequestList[i]));
}
mPromise->MaybeResolve(requests);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

84
dom/cache/CacheOpChild.h vendored Normal file
View file

@ -0,0 +1,84 @@
/* -*- 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_CacheOpChild_h
#define mozilla_dom_cache_CacheOpChild_h
#include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/PCacheOpChild.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/RefPtr.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Promise;
namespace cache {
class CacheOpChild final : public PCacheOpChild
, public ActorChild
, public TypeUtils
{
friend class CacheChild;
friend class CacheStorageChild;
private:
// This class must be constructed by CacheChild or CacheStorageChild using
// their ExecuteOp() factory method.
CacheOpChild(CacheWorkerHolder* aWorkerHolder, nsIGlobalObject* aGlobal,
nsISupports* aParent, Promise* aPromise);
~CacheOpChild();
// PCacheOpChild methods
virtual void
ActorDestroy(ActorDestroyReason aReason) override;
virtual bool
Recv__delete__(const ErrorResult& aRv, const CacheOpResult& aResult) override;
// ActorChild methods
virtual void
StartDestroy() override;
// TypeUtils methods
virtual nsIGlobalObject*
GetGlobalObject() const override;
#ifdef DEBUG
virtual void
AssertOwningThread() const override;
#endif
virtual mozilla::ipc::PBackgroundChild*
GetIPCManager() override;
// Utility methods
void
HandleResponse(const CacheResponseOrVoid& aResponseOrVoid);
void
HandleResponseList(const nsTArray<CacheResponse>& aResponseList);
void
HandleRequestList(const nsTArray<CacheRequest>& aRequestList);
nsCOMPtr<nsIGlobalObject> mGlobal;
// Hold the parent Cache or CacheStorage object alive until this async
// operation completes.
nsCOMPtr<nsISupports> mParent;
RefPtr<Promise> mPromise;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheOpChild_h

230
dom/cache/CacheOpParent.cpp vendored Normal file
View file

@ -0,0 +1,230 @@
/* -*- 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/CacheOpParent.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/SavedTypes.h"
#include "mozilla/ipc/FileDescriptorSetParent.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/SendStream.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::ipc::FileDescriptorSetParent;
using mozilla::ipc::PBackgroundParent;
using mozilla::ipc::SendStreamParent;
CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager, CacheId aCacheId,
const CacheOpArgs& aOpArgs)
: mIpcManager(aIpcManager)
, mCacheId(aCacheId)
, mNamespace(INVALID_NAMESPACE)
, mOpArgs(aOpArgs)
{
MOZ_DIAGNOSTIC_ASSERT(mIpcManager);
}
CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager,
Namespace aNamespace, const CacheOpArgs& aOpArgs)
: mIpcManager(aIpcManager)
, mCacheId(INVALID_CACHE_ID)
, mNamespace(aNamespace)
, mOpArgs(aOpArgs)
{
MOZ_DIAGNOSTIC_ASSERT(mIpcManager);
}
CacheOpParent::~CacheOpParent()
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
}
void
CacheOpParent::Execute(ManagerId* aManagerId)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
MOZ_DIAGNOSTIC_ASSERT(!mManager);
MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
RefPtr<cache::Manager> manager;
nsresult rv = cache::Manager::GetOrCreate(aManagerId, getter_AddRefs(manager));
if (NS_WARN_IF(NS_FAILED(rv))) {
ErrorResult result(rv);
Unused << Send__delete__(this, result, void_t());
result.SuppressException();
return;
}
Execute(manager);
}
void
CacheOpParent::Execute(cache::Manager* aManager)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
MOZ_DIAGNOSTIC_ASSERT(!mManager);
MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
mManager = aManager;
// Handle put op
if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) {
MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
const CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
const nsTArray<CacheRequestResponse>& list = args.requestResponseList();
AutoTArray<nsCOMPtr<nsIInputStream>, 256> requestStreamList;
AutoTArray<nsCOMPtr<nsIInputStream>, 256> responseStreamList;
for (uint32_t i = 0; i < list.Length(); ++i) {
requestStreamList.AppendElement(
DeserializeCacheStream(list[i].request().body()));
responseStreamList.AppendElement(
DeserializeCacheStream(list[i].response().body()));
}
mManager->ExecutePutAll(this, mCacheId, args.requestResponseList(),
requestStreamList, responseStreamList);
return;
}
// Handle all other cache ops
if (mCacheId != INVALID_CACHE_ID) {
MOZ_DIAGNOSTIC_ASSERT(mNamespace == INVALID_NAMESPACE);
mManager->ExecuteCacheOp(this, mCacheId, mOpArgs);
return;
}
// Handle all storage ops
MOZ_DIAGNOSTIC_ASSERT(mNamespace != INVALID_NAMESPACE);
mManager->ExecuteStorageOp(this, mNamespace, mOpArgs);
}
void
CacheOpParent::WaitForVerification(PrincipalVerifier* aVerifier)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
MOZ_DIAGNOSTIC_ASSERT(!mManager);
MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
mVerifier = aVerifier;
mVerifier->AddListener(this);
}
void
CacheOpParent::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
if (mVerifier) {
mVerifier->RemoveListener(this);
mVerifier = nullptr;
}
if (mManager) {
mManager->RemoveListener(this);
mManager = nullptr;
}
mIpcManager = nullptr;
}
void
CacheOpParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
mVerifier->RemoveListener(this);
mVerifier = nullptr;
if (NS_WARN_IF(NS_FAILED(aRv))) {
ErrorResult result(aRv);
Unused << Send__delete__(this, result, void_t());
result.SuppressException();
return;
}
Execute(aManagerId);
}
void
CacheOpParent::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
CacheId aOpenedCacheId,
const nsTArray<SavedResponse>& aSavedResponseList,
const nsTArray<SavedRequest>& aSavedRequestList,
StreamList* aStreamList)
{
NS_ASSERT_OWNINGTHREAD(CacheOpParent);
MOZ_DIAGNOSTIC_ASSERT(mIpcManager);
MOZ_DIAGNOSTIC_ASSERT(mManager);
// Never send an op-specific result if we have an error. Instead, send
// void_t() to ensure that we don't leak actors on the child side.
if (NS_WARN_IF(aRv.Failed())) {
Unused << Send__delete__(this, aRv, void_t());
aRv.SuppressException(); // We serialiazed it, as best we could.
return;
}
uint32_t entryCount = std::max(1lu, static_cast<unsigned long>(
std::max(aSavedResponseList.Length(),
aSavedRequestList.Length())));
// The result must contain the appropriate type at this point. It may
// or may not contain the additional result data yet. For types that
// do not need special processing, it should already be set. If the
// result requires actor-specific operations, then we do that below.
// If the type and data types don't match, then we will trigger an
// assertion in AutoParentOpResult::Add().
AutoParentOpResult result(mIpcManager, aResult, entryCount);
if (aOpenedCacheId != INVALID_CACHE_ID) {
result.Add(aOpenedCacheId, mManager);
}
for (uint32_t i = 0; i < aSavedResponseList.Length(); ++i) {
result.Add(aSavedResponseList[i], aStreamList);
}
for (uint32_t i = 0; i < aSavedRequestList.Length(); ++i) {
result.Add(aSavedRequestList[i], aStreamList);
}
Unused << Send__delete__(this, aRv, result.SendAsOpResult());
}
already_AddRefed<nsIInputStream>
CacheOpParent::DeserializeCacheStream(const CacheReadStreamOrVoid& aStreamOrVoid)
{
if (aStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) {
return nullptr;
}
nsCOMPtr<nsIInputStream> stream;
const CacheReadStream& readStream = aStreamOrVoid.get_CacheReadStream();
// Option 1: One of our own ReadStreams was passed back to us with a stream
// control actor.
stream = ReadStream::Create(readStream);
if (stream) {
return stream.forget();
}
// Option 2: A stream was serialized using normal methods or passed
// as a PSendStream actor. Use the standard method for
// extracting the resulting stream.
return DeserializeIPCStream(readStream.stream());
}
} // namespace cache
} // namespace dom
} // namespace mozilla

80
dom/cache/CacheOpParent.h vendored Normal file
View file

@ -0,0 +1,80 @@
/* -*- 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_CacheOpParent_h
#define mozilla_dom_cache_CacheOpParent_h
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/PCacheOpParent.h"
#include "mozilla/dom/cache/PrincipalVerifier.h"
#include "nsTArray.h"
namespace mozilla {
namespace ipc {
class PBackgroundParent;
} // namespace ipc
namespace dom {
namespace cache {
class CacheOpParent final : public PCacheOpParent
, public PrincipalVerifier::Listener
, public Manager::Listener
{
// to allow use of convenience overrides
using Manager::Listener::OnOpComplete;
public:
CacheOpParent(mozilla::ipc::PBackgroundParent* aIpcManager, CacheId aCacheId,
const CacheOpArgs& aOpArgs);
CacheOpParent(mozilla::ipc::PBackgroundParent* aIpcManager,
Namespace aNamespace, const CacheOpArgs& aOpArgs);
~CacheOpParent();
void
Execute(ManagerId* aManagerId);
void
Execute(cache::Manager* aManager);
void
WaitForVerification(PrincipalVerifier* aVerifier);
private:
// PCacheOpParent methods
virtual void
ActorDestroy(ActorDestroyReason aReason) override;
// PrincipalVerifier::Listener methods
virtual void
OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) override;
// Manager::Listener methods
virtual void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
CacheId aOpenedCacheId,
const nsTArray<SavedResponse>& aSavedResponseList,
const nsTArray<SavedRequest>& aSavedRequestList,
StreamList* aStreamList) override;
// utility methods
already_AddRefed<nsIInputStream>
DeserializeCacheStream(const CacheReadStreamOrVoid& aStreamOrVoid);
mozilla::ipc::PBackgroundParent* mIpcManager;
const CacheId mCacheId;
const Namespace mNamespace;
const CacheOpArgs mOpArgs;
RefPtr<cache::Manager> mManager;
RefPtr<PrincipalVerifier> mVerifier;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheOpParent_h

89
dom/cache/CacheParent.cpp vendored Normal file
View file

@ -0,0 +1,89 @@
/* -*- 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/CacheParent.h"
#include "mozilla/dom/cache/CacheOpParent.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace dom {
namespace cache {
// Declared in ActorUtils.h
void
DeallocPCacheParent(PCacheParent* aActor)
{
delete aActor;
}
CacheParent::CacheParent(cache::Manager* aManager, CacheId aCacheId)
: mManager(aManager)
, mCacheId(aCacheId)
{
MOZ_COUNT_CTOR(cache::CacheParent);
MOZ_DIAGNOSTIC_ASSERT(mManager);
mManager->AddRefCacheId(mCacheId);
}
CacheParent::~CacheParent()
{
MOZ_COUNT_DTOR(cache::CacheParent);
MOZ_DIAGNOSTIC_ASSERT(!mManager);
}
void
CacheParent::ActorDestroy(ActorDestroyReason aReason)
{
MOZ_DIAGNOSTIC_ASSERT(mManager);
mManager->ReleaseCacheId(mCacheId);
mManager = nullptr;
}
PCacheOpParent*
CacheParent::AllocPCacheOpParent(const CacheOpArgs& aOpArgs)
{
if (aOpArgs.type() != CacheOpArgs::TCacheMatchArgs &&
aOpArgs.type() != CacheOpArgs::TCacheMatchAllArgs &&
aOpArgs.type() != CacheOpArgs::TCachePutAllArgs &&
aOpArgs.type() != CacheOpArgs::TCacheDeleteArgs &&
aOpArgs.type() != CacheOpArgs::TCacheKeysArgs)
{
MOZ_CRASH("Invalid operation sent to Cache actor!");
}
return new CacheOpParent(Manager(), mCacheId, aOpArgs);
}
bool
CacheParent::DeallocPCacheOpParent(PCacheOpParent* aActor)
{
delete aActor;
return true;
}
bool
CacheParent::RecvPCacheOpConstructor(PCacheOpParent* aActor,
const CacheOpArgs& aOpArgs)
{
auto actor = static_cast<CacheOpParent*>(aActor);
actor->Execute(mManager);
return true;
}
bool
CacheParent::RecvTeardown()
{
if (!Send__delete__(this)) {
// child process is gone, warn and allow actor to clean up normally
NS_WARNING("Cache failed to send delete.");
}
return true;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

50
dom/cache/CacheParent.h vendored Normal file
View 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_CacheParent_h
#define mozilla_dom_cache_CacheParent_h
#include "mozilla/dom/cache/PCacheParent.h"
#include "mozilla/dom/cache/Types.h"
namespace mozilla {
namespace dom {
namespace cache {
class Manager;
class CacheParent final : public PCacheParent
{
public:
CacheParent(cache::Manager* aManager, CacheId aCacheId);
virtual ~CacheParent();
private:
// PCacheParent methods
virtual void ActorDestroy(ActorDestroyReason aReason) override;
virtual PCacheOpParent*
AllocPCacheOpParent(const CacheOpArgs& aOpArgs) override;
virtual bool
DeallocPCacheOpParent(PCacheOpParent* aActor) override;
virtual bool
RecvPCacheOpConstructor(PCacheOpParent* actor,
const CacheOpArgs& aOpArgs) override;
virtual bool
RecvTeardown() override;
RefPtr<cache::Manager> mManager;
const CacheId mCacheId;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheParent_h

628
dom/cache/CacheStorage.cpp vendored Normal file
View file

@ -0,0 +1,628 @@
/* -*- 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/CacheStorage.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/CacheStorageBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheStorageChild.h"
#include "mozilla/dom/cache/CacheWorkerHolder.h"
#include "mozilla/dom/cache/PCacheChild.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsContentUtils.h"
#include "nsIDocument.h"
#include "nsIGlobalObject.h"
#include "nsIScriptSecurityManager.h"
#include "nsURLParsers.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::Unused;
using mozilla::ErrorResult;
using mozilla::dom::workers::WorkerPrivate;
using mozilla::ipc::BackgroundChild;
using mozilla::ipc::PBackgroundChild;
using mozilla::ipc::IProtocol;
using mozilla::ipc::PrincipalInfo;
using mozilla::ipc::PrincipalToPrincipalInfo;
NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage);
NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage);
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::CacheStorage,
mGlobal);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
NS_INTERFACE_MAP_END
// We cannot reference IPC types in a webidl binding implementation header. So
// define this in the .cpp and use heap storage in the mPendingRequests list.
struct CacheStorage::Entry final
{
RefPtr<Promise> mPromise;
CacheOpArgs mArgs;
// We cannot add the requests until after the actor is present. So store
// the request data separately for now.
RefPtr<InternalRequest> mRequest;
};
namespace {
bool
IsTrusted(const PrincipalInfo& aPrincipalInfo, bool aTestingPrefEnabled)
{
// Can happen on main thread or worker thread
if (aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
return true;
}
// Require a ContentPrincipal to avoid null principal, etc.
//
// Also, an unknown appId means that this principal was created for the
// codebase without all the security information from the end document or
// worker. We require exact knowledge of this information before allowing
// the caller to touch the disk using the Cache API.
if (NS_WARN_IF(aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo ||
aPrincipalInfo.get_ContentPrincipalInfo().attrs().mAppId ==
nsIScriptSecurityManager::UNKNOWN_APP_ID)) {
return false;
}
// If we're in testing mode, then don't do any more work to determing if
// the origin is trusted. We have to run some tests as http.
if (aTestingPrefEnabled) {
return true;
}
// Now parse the scheme of the principal's origin. This is a short term
// method for determining "trust". In the long term we need to implement
// the full algorithm here:
//
// https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure
//
// TODO: Implement full secure setting algorithm. (bug 1177856)
const nsCString& flatURL = aPrincipalInfo.get_ContentPrincipalInfo().spec();
const char* url = flatURL.get();
// off the main thread URL parsing using nsStdURLParser.
nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
uint32_t schemePos;
int32_t schemeLen;
uint32_t authPos;
int32_t authLen;
nsresult rv = urlParser->ParseURL(url, flatURL.Length(),
&schemePos, &schemeLen,
&authPos, &authLen,
nullptr, nullptr); // ignore path
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
if (scheme.LowerCaseEqualsLiteral("https") ||
scheme.LowerCaseEqualsLiteral("file")) {
return true;
}
uint32_t hostPos;
int32_t hostLen;
rv = urlParser->ParseAuthority(url + authPos, authLen,
nullptr, nullptr, // ignore username
nullptr, nullptr, // ignore password
&hostPos, &hostLen,
nullptr); // ignore port
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
nsDependentCSubstring hostname(url + authPos + hostPos, hostLen);
return hostname.EqualsLiteral("localhost") ||
hostname.EqualsLiteral("127.0.0.1") ||
hostname.EqualsLiteral("::1");
}
} // namespace
// static
already_AddRefed<CacheStorage>
CacheStorage::CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
nsIPrincipal* aPrincipal, bool aStorageDisabled,
bool aForceTrustedOrigin, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aGlobal);
MOZ_DIAGNOSTIC_ASSERT(aPrincipal);
MOZ_ASSERT(NS_IsMainThread());
if (aStorageDisabled) {
NS_WARNING("CacheStorage has been disabled.");
RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
PrincipalInfo principalInfo;
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
bool testingEnabled = aForceTrustedOrigin ||
Preferences::GetBool("dom.caches.testing.enabled", false) ||
Preferences::GetBool("dom.serviceWorkers.testing.enabled", false);
if (!IsTrusted(principalInfo, testingEnabled)) {
NS_WARNING("CacheStorage not supported on untrusted origins.");
RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
RefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
principalInfo, nullptr);
return ref.forget();
}
// static
already_AddRefed<CacheStorage>
CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
WorkerPrivate* aWorkerPrivate, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aGlobal);
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
if (!aWorkerPrivate->IsStorageAllowed()) {
NS_WARNING("CacheStorage is not allowed.");
RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
if (aWorkerPrivate->GetOriginAttributes().mPrivateBrowsingId > 0) {
NS_WARNING("CacheStorage not supported during private browsing.");
RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
RefPtr<CacheWorkerHolder> workerHolder =
CacheWorkerHolder::Create(aWorkerPrivate);
if (!workerHolder) {
NS_WARNING("Worker thread is shutting down.");
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
const PrincipalInfo& principalInfo = aWorkerPrivate->GetPrincipalInfo();
// We have a number of cases where we want to skip the https scheme
// validation:
//
// 1) Any worker when dom.caches.testing.enabled pref is true.
// 2) Any worker when dom.serviceWorkers.testing.enabled pref is true. This
// is mainly because most sites using SWs will expect Cache to work if
// SWs are enabled.
// 3) If the window that created this worker has the devtools SW testing
// option enabled. Same reasoning as (2).
// 4) If the worker itself is a ServiceWorker, then we always skip the
// origin checks. The ServiceWorker has its own trusted origin checks
// that are better than ours. In addition, we don't have information
// about the window any more, so we can't do our own checks.
bool testingEnabled = aWorkerPrivate->DOMCachesTestingEnabled() ||
aWorkerPrivate->ServiceWorkersTestingEnabled() ||
aWorkerPrivate->ServiceWorkersTestingInWindow() ||
aWorkerPrivate->IsServiceWorker();
if (!IsTrusted(principalInfo, testingEnabled)) {
NS_WARNING("CacheStorage not supported on untrusted origins.");
RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
RefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
principalInfo, workerHolder);
return ref.forget();
}
// static
bool
CacheStorage::DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
"Passed object is not a global object!");
js::AssertSameCompartment(aCx, aGlobal);
if (NS_WARN_IF(!CacheStorageBinding::GetConstructorObject(aCx) ||
!CacheBinding::GetConstructorObject(aCx))) {
return false;
}
nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);
MOZ_DIAGNOSTIC_ASSERT(principal);
ErrorResult rv;
RefPtr<CacheStorage> storage =
CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal,
false, /* private browsing */
true, /* force trusted */
rv);
if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
return false;
}
JS::Rooted<JS::Value> caches(aCx);
if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) {
return false;
}
return JS_DefineProperty(aCx, aGlobal, "caches", caches, JSPROP_ENUMERATE);
}
CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
const PrincipalInfo& aPrincipalInfo,
CacheWorkerHolder* aWorkerHolder)
: mNamespace(aNamespace)
, mGlobal(aGlobal)
, mPrincipalInfo(MakeUnique<PrincipalInfo>(aPrincipalInfo))
, mWorkerHolder(aWorkerHolder)
, mActor(nullptr)
, mStatus(NS_OK)
{
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
// If the PBackground actor is already initialized then we can
// immediately use it
PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
if (actor) {
ActorCreated(actor);
return;
}
// Otherwise we must begin the PBackground initialization process and
// wait for the async ActorCreated() callback.
MOZ_ASSERT(NS_IsMainThread());
bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
if (NS_WARN_IF(!ok)) {
ActorFailed();
}
}
CacheStorage::CacheStorage(nsresult aFailureResult)
: mNamespace(INVALID_NAMESPACE)
, mActor(nullptr)
, mStatus(aFailureResult)
{
MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mStatus));
}
already_AddRefed<Promise>
CacheStorage::Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
RefPtr<InternalRequest> request = ToInternalRequest(aRequest, IgnoreBody,
aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
CacheQueryParams params;
ToCacheQueryParams(params, aOptions);
nsAutoPtr<Entry> entry(new Entry());
entry->mPromise = promise;
entry->mArgs = StorageMatchArgs(CacheRequest(), params);
entry->mRequest = request;
mPendingRequests.AppendElement(entry.forget());
MaybeRunPendingRequests();
return promise.forget();
}
already_AddRefed<Promise>
CacheStorage::Has(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsAutoPtr<Entry> entry(new Entry());
entry->mPromise = promise;
entry->mArgs = StorageHasArgs(nsString(aKey));
mPendingRequests.AppendElement(entry.forget());
MaybeRunPendingRequests();
return promise.forget();
}
already_AddRefed<Promise>
CacheStorage::Open(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsAutoPtr<Entry> entry(new Entry());
entry->mPromise = promise;
entry->mArgs = StorageOpenArgs(nsString(aKey));
mPendingRequests.AppendElement(entry.forget());
MaybeRunPendingRequests();
return promise.forget();
}
already_AddRefed<Promise>
CacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsAutoPtr<Entry> entry(new Entry());
entry->mPromise = promise;
entry->mArgs = StorageDeleteArgs(nsString(aKey));
mPendingRequests.AppendElement(entry.forget());
MaybeRunPendingRequests();
return promise.forget();
}
already_AddRefed<Promise>
CacheStorage::Keys(ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsAutoPtr<Entry> entry(new Entry());
entry->mPromise = promise;
entry->mArgs = StorageKeysArgs();
mPendingRequests.AppendElement(entry.forget());
MaybeRunPendingRequests();
return promise.forget();
}
// static
bool
CacheStorage::PrefEnabled(JSContext* aCx, JSObject* aObj)
{
return Cache::PrefEnabled(aCx, aObj);
}
// static
already_AddRefed<CacheStorage>
CacheStorage::Constructor(const GlobalObject& aGlobal,
CacheStorageNamespace aNamespace,
nsIPrincipal* aPrincipal, ErrorResult& aRv)
{
if (NS_WARN_IF(!NS_IsMainThread())) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// TODO: remove Namespace in favor of CacheStorageNamespace
static_assert(DEFAULT_NAMESPACE == (uint32_t)CacheStorageNamespace::Content,
"Default namespace should match webidl Content enum");
static_assert(CHROME_ONLY_NAMESPACE == (uint32_t)CacheStorageNamespace::Chrome,
"Chrome namespace should match webidl Chrome enum");
static_assert(NUMBER_OF_NAMESPACES == (uint32_t)CacheStorageNamespace::EndGuard_,
"Number of namespace should match webidl endguard enum");
Namespace ns = static_cast<Namespace>(aNamespace);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
bool privateBrowsing = false;
if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global)) {
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (doc) {
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
privateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
}
}
// Create a CacheStorage object bypassing the trusted origin checks
// since this is a chrome-only constructor.
return CreateOnMainThread(ns, global, aPrincipal, privateBrowsing,
true /* force trusted origin */, aRv);
}
nsISupports*
CacheStorage::GetParentObject() const
{
return mGlobal;
}
JSObject*
CacheStorage::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
{
return mozilla::dom::CacheStorageBinding::Wrap(aContext, this, aGivenProto);
}
void
CacheStorage::ActorCreated(PBackgroundChild* aActor)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
MOZ_DIAGNOSTIC_ASSERT(aActor);
if (NS_WARN_IF(mWorkerHolder && mWorkerHolder->Notified())) {
ActorFailed();
return;
}
// WorkerHolder ownership is passed to the CacheStorageChild actor and any
// actors it may create. The WorkerHolder will keep the worker thread alive
// until the actors can gracefully shutdown.
CacheStorageChild* newActor = new CacheStorageChild(this, mWorkerHolder);
PCacheStorageChild* constructedActor =
aActor->SendPCacheStorageConstructor(newActor, mNamespace, *mPrincipalInfo);
if (NS_WARN_IF(!constructedActor)) {
ActorFailed();
return;
}
mWorkerHolder = nullptr;
MOZ_DIAGNOSTIC_ASSERT(constructedActor == newActor);
mActor = newActor;
MaybeRunPendingRequests();
MOZ_DIAGNOSTIC_ASSERT(mPendingRequests.IsEmpty());
}
void
CacheStorage::ActorFailed()
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
MOZ_DIAGNOSTIC_ASSERT(!NS_FAILED(mStatus));
mStatus = NS_ERROR_UNEXPECTED;
mWorkerHolder = nullptr;
for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
nsAutoPtr<Entry> entry(mPendingRequests[i].forget());
entry->mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
}
mPendingRequests.Clear();
}
void
CacheStorage::DestroyInternal(CacheStorageChild* aActor)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
MOZ_DIAGNOSTIC_ASSERT(mActor);
MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
mActor->ClearListener();
mActor = nullptr;
// Note that we will never get an actor again in case another request is
// made before this object is destructed.
ActorFailed();
}
nsIGlobalObject*
CacheStorage::GetGlobalObject() const
{
return mGlobal;
}
#ifdef DEBUG
void
CacheStorage::AssertOwningThread() const
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
}
#endif
PBackgroundChild*
CacheStorage::GetIPCManager()
{
// This is true because CacheStorage always uses IgnoreBody for requests.
// So we should never need to get the IPC manager during Request or
// Response serialization.
MOZ_CRASH("CacheStorage does not implement TypeUtils::GetIPCManager()");
}
CacheStorage::~CacheStorage()
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (mActor) {
mActor->StartDestroyFromListener();
// DestroyInternal() is called synchronously by StartDestroyFromListener().
// So we should have already cleared the mActor.
MOZ_DIAGNOSTIC_ASSERT(!mActor);
}
}
void
CacheStorage::MaybeRunPendingRequests()
{
if (!mActor) {
return;
}
for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
ErrorResult rv;
nsAutoPtr<Entry> entry(mPendingRequests[i].forget());
AutoChildOpArgs args(this, entry->mArgs, 1);
if (entry->mRequest) {
args.Add(entry->mRequest, IgnoreBody, IgnoreInvalidScheme, rv);
}
if (NS_WARN_IF(rv.Failed())) {
entry->mPromise->MaybeReject(rv);
continue;
}
mActor->ExecuteOp(mGlobal, entry->mPromise, this, args.SendAsOpArgs());
}
mPendingRequests.Clear();
}
} // namespace cache
} // namespace dom
} // namespace mozilla

130
dom/cache/CacheStorage.h vendored Normal file
View file

@ -0,0 +1,130 @@
/* -*- 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_CacheStorage_h
#define mozilla_dom_cache_CacheStorage_h
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
class nsIGlobalObject;
namespace mozilla {
class ErrorResult;
namespace ipc {
class PrincipalInfo;
} // namespace ipc
namespace dom {
enum class CacheStorageNamespace : uint32_t;
class Promise;
namespace workers {
class WorkerPrivate;
} // namespace workers
namespace cache {
class CacheStorageChild;
class CacheWorkerHolder;
class CacheStorage final : public nsIIPCBackgroundChildCreateCallback
, public nsWrapperCache
, public TypeUtils
{
typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
public:
static already_AddRefed<CacheStorage>
CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
nsIPrincipal* aPrincipal, bool aStorageDisabled,
bool aForceTrustedOrigin, ErrorResult& aRv);
static already_AddRefed<CacheStorage>
CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
static bool
DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
// webidl interface methods
already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Keys(ErrorResult& aRv);
// chrome-only webidl interface methods
static already_AddRefed<CacheStorage>
Constructor(const GlobalObject& aGlobal, CacheStorageNamespace aNamespace,
nsIPrincipal* aPrincipal, ErrorResult& aRv);
// binding methods
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
nsISupports* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto) override;
// nsIIPCbackgroundChildCreateCallback methods
virtual void ActorCreated(PBackgroundChild* aActor) override;
virtual void ActorFailed() override;
// Called when CacheStorageChild actor is being destroyed
void DestroyInternal(CacheStorageChild* aActor);
// TypeUtils methods
virtual nsIGlobalObject* GetGlobalObject() const override;
#ifdef DEBUG
virtual void AssertOwningThread() const override;
#endif
virtual mozilla::ipc::PBackgroundChild*
GetIPCManager() override;
private:
CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
CacheWorkerHolder* aWorkerHolder);
explicit CacheStorage(nsresult aFailureResult);
~CacheStorage();
void MaybeRunPendingRequests();
const Namespace mNamespace;
nsCOMPtr<nsIGlobalObject> mGlobal;
UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
RefPtr<CacheWorkerHolder> mWorkerHolder;
// weak ref cleared in DestroyInternal
CacheStorageChild* mActor;
struct Entry;
nsTArray<nsAutoPtr<Entry>> mPendingRequests;
nsresult mStatus;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CacheStorage,
nsIIPCBackgroundChildCreateCallback)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheStorage_h

148
dom/cache/CacheStorageChild.cpp vendored Normal file
View file

@ -0,0 +1,148 @@
/* -*- 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/CacheStorageChild.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/CacheChild.h"
#include "mozilla/dom/cache/CacheOpChild.h"
#include "mozilla/dom/cache/CacheStorage.h"
namespace mozilla {
namespace dom {
namespace cache {
// declared in ActorUtils.h
void
DeallocPCacheStorageChild(PCacheStorageChild* aActor)
{
delete aActor;
}
CacheStorageChild::CacheStorageChild(CacheStorage* aListener,
CacheWorkerHolder* aWorkerHolder)
: mListener(aListener)
, mNumChildActors(0)
, mDelayedDestroy(false)
{
MOZ_COUNT_CTOR(cache::CacheStorageChild);
MOZ_DIAGNOSTIC_ASSERT(mListener);
SetWorkerHolder(aWorkerHolder);
}
CacheStorageChild::~CacheStorageChild()
{
MOZ_COUNT_DTOR(cache::CacheStorageChild);
NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
MOZ_DIAGNOSTIC_ASSERT(!mListener);
}
void
CacheStorageChild::ClearListener()
{
NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
MOZ_DIAGNOSTIC_ASSERT(mListener);
mListener = nullptr;
}
void
CacheStorageChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
nsISupports* aParent, const CacheOpArgs& aArgs)
{
mNumChildActors += 1;
Unused << SendPCacheOpConstructor(
new CacheOpChild(GetWorkerHolder(), aGlobal, aParent, aPromise), aArgs);
}
void
CacheStorageChild::StartDestroyFromListener()
{
NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
// The listener should be held alive by any async operations, so if it
// is going away then there must not be any child actors. This in turn
// ensures that StartDestroy() will not trigger the delayed path.
MOZ_DIAGNOSTIC_ASSERT(!mNumChildActors);
StartDestroy();
}
void
CacheStorageChild::StartDestroy()
{
NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
// If we have outstanding child actors, then don't destroy ourself yet.
// The child actors should be short lived and we should allow them to complete
// if possible. NoteDeletedActor() will call back into this Shutdown()
// method when the last child actor is gone.
if (mNumChildActors) {
mDelayedDestroy = true;
return;
}
RefPtr<CacheStorage> listener = mListener;
// StartDestroy() can get called from either CacheStorage or the
// CacheWorkerHolder.
// Theoretically we can get double called if the right race happens. Handle
// that by just ignoring the second StartDestroy() call.
if (!listener) {
return;
}
listener->DestroyInternal(this);
// CacheStorage listener should call ClearListener() in DestroyInternal()
MOZ_DIAGNOSTIC_ASSERT(!mListener);
// Start actor destruction from parent process
Unused << SendTeardown();
}
void
CacheStorageChild::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
RefPtr<CacheStorage> listener = mListener;
if (listener) {
listener->DestroyInternal(this);
// CacheStorage listener should call ClearListener() in DestroyInternal()
MOZ_DIAGNOSTIC_ASSERT(!mListener);
}
RemoveWorkerHolder();
}
PCacheOpChild*
CacheStorageChild::AllocPCacheOpChild(const CacheOpArgs& aOpArgs)
{
MOZ_CRASH("CacheOpChild should be manually constructed.");
return nullptr;
}
bool
CacheStorageChild::DeallocPCacheOpChild(PCacheOpChild* aActor)
{
delete aActor;
NoteDeletedActor();
return true;
}
void
CacheStorageChild::NoteDeletedActor()
{
MOZ_DIAGNOSTIC_ASSERT(mNumChildActors);
mNumChildActors -= 1;
if (!mNumChildActors && mDelayedDestroy) {
StartDestroy();
}
}
} // namespace cache
} // namespace dom
} // namespace mozilla

81
dom/cache/CacheStorageChild.h vendored Normal file
View file

@ -0,0 +1,81 @@
/* -*- 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_CacheStorageChild_h
#define mozilla_dom_cache_CacheStorageChild_h
#include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/cache/PCacheStorageChild.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Promise;
namespace cache {
class CacheOpArgs;
class CacheStorage;
class CacheWorkerHolder;
class PCacheChild;
class CacheStorageChild final : public PCacheStorageChild
, public ActorChild
{
public:
CacheStorageChild(CacheStorage* aListener, CacheWorkerHolder* aWorkerHolder);
~CacheStorageChild();
// Must be called by the associated CacheStorage listener in its
// DestroyInternal() method. Also, CacheStorage must call
// SendDestroyFromListener() on the actor in its destructor to trigger
// ActorDestroy() if it has not been called yet.
void ClearListener();
void
ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
nsISupports* aParent, const CacheOpArgs& aArgs);
// Our parent Listener object has gone out of scope and is being destroyed.
void StartDestroyFromListener();
private:
// ActorChild methods
// CacheWorkerHolder is trying to destroy due to worker shutdown.
virtual void StartDestroy() override;
// PCacheStorageChild methods
virtual void ActorDestroy(ActorDestroyReason aReason) override;
virtual PCacheOpChild*
AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override;
virtual bool
DeallocPCacheOpChild(PCacheOpChild* aActor) override;
// utility methods
void
NoteDeletedActor();
// Use a weak ref so actor does not hold DOM object alive past content use.
// The CacheStorage object must call ClearListener() to null this before its
// destroyed.
CacheStorage* MOZ_NON_OWNING_REF mListener;
uint32_t mNumChildActors;
bool mDelayedDestroy;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheStorageChild_h

143
dom/cache/CacheStorageParent.cpp vendored Normal file
View file

@ -0,0 +1,143 @@
/* -*- 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/CacheStorageParent.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/cache/CacheOpParent.h"
#include "mozilla/dom/cache/ManagerId.h"
#include "mozilla/ipc/PBackgroundParent.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::ipc::PBackgroundParent;
using mozilla::ipc::PrincipalInfo;
// declared in ActorUtils.h
PCacheStorageParent*
AllocPCacheStorageParent(PBackgroundParent* aManagingActor,
Namespace aNamespace,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo)
{
return new CacheStorageParent(aManagingActor, aNamespace, aPrincipalInfo);
}
// declared in ActorUtils.h
void
DeallocPCacheStorageParent(PCacheStorageParent* aActor)
{
delete aActor;
}
CacheStorageParent::CacheStorageParent(PBackgroundParent* aManagingActor,
Namespace aNamespace,
const PrincipalInfo& aPrincipalInfo)
: mNamespace(aNamespace)
, mVerifiedStatus(NS_OK)
{
MOZ_COUNT_CTOR(cache::CacheStorageParent);
MOZ_DIAGNOSTIC_ASSERT(aManagingActor);
// Start the async principal verification process immediately.
mVerifier = PrincipalVerifier::CreateAndDispatch(this, aManagingActor,
aPrincipalInfo);
MOZ_DIAGNOSTIC_ASSERT(mVerifier);
}
CacheStorageParent::~CacheStorageParent()
{
MOZ_COUNT_DTOR(cache::CacheStorageParent);
MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
}
void
CacheStorageParent::ActorDestroy(ActorDestroyReason aReason)
{
if (mVerifier) {
mVerifier->RemoveListener(this);
mVerifier = nullptr;
}
}
PCacheOpParent*
CacheStorageParent::AllocPCacheOpParent(const CacheOpArgs& aOpArgs)
{
if (aOpArgs.type() != CacheOpArgs::TStorageMatchArgs &&
aOpArgs.type() != CacheOpArgs::TStorageHasArgs &&
aOpArgs.type() != CacheOpArgs::TStorageOpenArgs &&
aOpArgs.type() != CacheOpArgs::TStorageDeleteArgs &&
aOpArgs.type() != CacheOpArgs::TStorageKeysArgs)
{
MOZ_CRASH("Invalid operation sent to CacheStorage actor!");
}
return new CacheOpParent(Manager(), mNamespace, aOpArgs);
}
bool
CacheStorageParent::DeallocPCacheOpParent(PCacheOpParent* aActor)
{
delete aActor;
return true;
}
bool
CacheStorageParent::RecvPCacheOpConstructor(PCacheOpParent* aActor,
const CacheOpArgs& aOpArgs)
{
auto actor = static_cast<CacheOpParent*>(aActor);
if (mVerifier) {
MOZ_DIAGNOSTIC_ASSERT(!mManagerId);
actor->WaitForVerification(mVerifier);
return true;
}
if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
ErrorResult result(mVerifiedStatus);
Unused << CacheOpParent::Send__delete__(actor, result, void_t());
result.SuppressException();
return true;
}
MOZ_DIAGNOSTIC_ASSERT(mManagerId);
actor->Execute(mManagerId);
return true;
}
bool
CacheStorageParent::RecvTeardown()
{
if (!Send__delete__(this)) {
// child process is gone, warn and allow actor to clean up normally
NS_WARNING("CacheStorage failed to delete actor.");
}
return true;
}
void
CacheStorageParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId)
{
MOZ_DIAGNOSTIC_ASSERT(mVerifier);
MOZ_DIAGNOSTIC_ASSERT(!mManagerId);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mVerifiedStatus));
if (NS_WARN_IF(NS_FAILED(aRv))) {
mVerifiedStatus = aRv;
}
mManagerId = aManagerId;
mVerifier->RemoveListener(this);
mVerifier = nullptr;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

60
dom/cache/CacheStorageParent.h vendored Normal file
View file

@ -0,0 +1,60 @@
/* -*- 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_CacheStorageParent_h
#define mozilla_dom_cache_CacheStorageParent_h
#include "mozilla/dom/cache/PCacheStorageParent.h"
#include "mozilla/dom/cache/PrincipalVerifier.h"
#include "mozilla/dom/cache/Types.h"
namespace mozilla {
namespace dom {
namespace cache {
class ManagerId;
class CacheStorageParent final : public PCacheStorageParent
, public PrincipalVerifier::Listener
{
public:
CacheStorageParent(PBackgroundParent* aManagingActor, Namespace aNamespace,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
virtual ~CacheStorageParent();
private:
// PCacheStorageParent methods
virtual void
ActorDestroy(ActorDestroyReason aReason) override;
virtual PCacheOpParent*
AllocPCacheOpParent(const CacheOpArgs& aOpArgs) override;
virtual bool
DeallocPCacheOpParent(PCacheOpParent* aActor) override;
virtual bool
RecvPCacheOpConstructor(PCacheOpParent* actor,
const CacheOpArgs& aOpArgs) override;
virtual bool
RecvTeardown() override;
// PrincipalVerifier::Listener methods
virtual void OnPrincipalVerified(nsresult aRv,
ManagerId* aManagerId) override;
const Namespace mNamespace;
RefPtr<PrincipalVerifier> mVerifier;
nsresult mVerifiedStatus;
RefPtr<ManagerId> mManagerId;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheStorageParent_h

159
dom/cache/CacheStreamControlChild.cpp vendored Normal file
View file

@ -0,0 +1,159 @@
/* -*- 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/CacheStreamControlChild.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/ipc/FileDescriptorSetChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PFileDescriptorSetChild.h"
#include "nsISupportsImpl.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::OptionalFileDescriptorSet;
using mozilla::ipc::AutoIPCStream;
using mozilla::ipc::FileDescriptor;
using mozilla::ipc::FileDescriptorSetChild;
using mozilla::ipc::PFileDescriptorSetChild;
// declared in ActorUtils.h
PCacheStreamControlChild*
AllocPCacheStreamControlChild()
{
return new CacheStreamControlChild();
}
// declared in ActorUtils.h
void
DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor)
{
delete aActor;
}
CacheStreamControlChild::CacheStreamControlChild()
: mDestroyStarted(false)
, mDestroyDelayed(false)
{
MOZ_COUNT_CTOR(cache::CacheStreamControlChild);
}
CacheStreamControlChild::~CacheStreamControlChild()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
MOZ_COUNT_DTOR(cache::CacheStreamControlChild);
}
void
CacheStreamControlChild::StartDestroy()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
// This can get called twice under some circumstances. For example, if the
// actor is added to a CacheWorkerHolder that has already been notified and
// the Cache actor has no mListener.
if (mDestroyStarted) {
return;
}
mDestroyStarted = true;
// If any of the streams have started to be read, then wait for them to close
// naturally.
if (HasEverBeenRead()) {
// Note that we are delaying so that we can re-check for active streams
// in NoteClosedAfterForget().
mDestroyDelayed = true;
return;
}
// Otherwise, if the streams have not been touched then just pre-emptively
// close them now. This handles the case where someone retrieves a Response
// from the Cache, but never accesses the body. We should not keep the
// Worker alive until that Response is GC'd just because of its ignored
// body stream.
// Begin shutting down all streams. This is the same as if the parent had
// asked us to shutdown. So simulate the CloseAll IPC message.
RecvCloseAll();
}
void
CacheStreamControlChild::SerializeControl(CacheReadStream* aReadStreamOut)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
aReadStreamOut->controlParent() = nullptr;
aReadStreamOut->controlChild() = this;
}
void
CacheStreamControlChild::SerializeStream(CacheReadStream* aReadStreamOut,
nsIInputStream* aStream,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
MOZ_DIAGNOSTIC_ASSERT(aStream);
UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream(aReadStreamOut->stream()));
autoStream->Serialize(aStream, Manager());
aStreamCleanupList.AppendElement(Move(autoStream));
}
void
CacheStreamControlChild::NoteClosedAfterForget(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
Unused << SendNoteClosed(aId);
// A stream has closed. If we delayed StartDestry() due to this stream
// being read, then we should check to see if any of the remaining streams
// are active. If none of our other streams have been read, then we can
// proceed with the shutdown now.
if (mDestroyDelayed && !HasEverBeenRead()) {
mDestroyDelayed = false;
RecvCloseAll();
}
}
#ifdef DEBUG
void
CacheStreamControlChild::AssertOwningThread()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
}
#endif
void
CacheStreamControlChild::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
CloseAllReadStreamsWithoutReporting();
RemoveWorkerHolder();
}
bool
CacheStreamControlChild::RecvClose(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
CloseReadStreams(aId);
return true;
}
bool
CacheStreamControlChild::RecvCloseAll()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
CloseAllReadStreams();
return true;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

67
dom/cache/CacheStreamControlChild.h vendored Normal file
View file

@ -0,0 +1,67 @@
/* -*- 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_CacheStreamControlChild_h
#define mozilla_dom_cache_CacheStreamControlChild_h
#include "mozilla/dom/cache/ActorChild.h"
#include "mozilla/dom/cache/PCacheStreamControlChild.h"
#include "mozilla/dom/cache/StreamControl.h"
#include "nsTObserverArray.h"
namespace mozilla {
namespace ipc {
class AutoIPCStream;
} // namespace ipc
namespace dom {
namespace cache {
class ReadStream;
class CacheStreamControlChild final : public PCacheStreamControlChild
, public StreamControl
, public ActorChild
{
public:
CacheStreamControlChild();
~CacheStreamControlChild();
// ActorChild methods
virtual void StartDestroy() override;
// StreamControl methods
virtual void
SerializeControl(CacheReadStream* aReadStreamOut) override;
virtual void
SerializeStream(CacheReadStream* aReadStreamOut, nsIInputStream* aStream,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList) override;
private:
virtual void
NoteClosedAfterForget(const nsID& aId) override;
#ifdef DEBUG
virtual void
AssertOwningThread() override;
#endif
// PCacheStreamControlChild methods
virtual void ActorDestroy(ActorDestroyReason aReason) override;
virtual bool RecvClose(const nsID& aId) override;
virtual bool RecvCloseAll() override;
bool mDestroyStarted;
bool mDestroyDelayed;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheStreamControlChild_h

158
dom/cache/CacheStreamControlParent.cpp vendored Normal file
View file

@ -0,0 +1,158 @@
/* -*- 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/CacheStreamControlParent.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/StreamList.h"
#include "mozilla/ipc/FileDescriptorSetParent.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/PFileDescriptorSetParent.h"
#include "nsISupportsImpl.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::OptionalFileDescriptorSet;
using mozilla::ipc::FileDescriptor;
using mozilla::ipc::FileDescriptorSetParent;
using mozilla::ipc::PFileDescriptorSetParent;
// declared in ActorUtils.h
void
DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor)
{
delete aActor;
}
CacheStreamControlParent::CacheStreamControlParent()
{
MOZ_COUNT_CTOR(cache::CacheStreamControlParent);
}
CacheStreamControlParent::~CacheStreamControlParent()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_DIAGNOSTIC_ASSERT(!mStreamList);
MOZ_COUNT_DTOR(cache::CacheStreamControlParent);
}
void
CacheStreamControlParent::SerializeControl(CacheReadStream* aReadStreamOut)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
aReadStreamOut->controlChild() = nullptr;
aReadStreamOut->controlParent() = this;
}
void
CacheStreamControlParent::SerializeStream(CacheReadStream* aReadStreamOut,
nsIInputStream* aStream,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
MOZ_DIAGNOSTIC_ASSERT(aStream);
UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream(aReadStreamOut->stream()));
autoStream->Serialize(aStream, Manager());
aStreamCleanupList.AppendElement(Move(autoStream));
}
void
CacheStreamControlParent::NoteClosedAfterForget(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
RecvNoteClosed(aId);
}
#ifdef DEBUG
void
CacheStreamControlParent::AssertOwningThread()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
}
#endif
void
CacheStreamControlParent::ActorDestroy(ActorDestroyReason aReason)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
CloseAllReadStreamsWithoutReporting();
// If the initial SendPStreamControlConstructor() fails we will
// be called before mStreamList is set.
if (!mStreamList) {
return;
}
mStreamList->RemoveStreamControl(this);
mStreamList->NoteClosedAll();
mStreamList = nullptr;
}
bool
CacheStreamControlParent::RecvNoteClosed(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_DIAGNOSTIC_ASSERT(mStreamList);
mStreamList->NoteClosed(aId);
return true;
}
void
CacheStreamControlParent::SetStreamList(StreamList* aStreamList)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
MOZ_DIAGNOSTIC_ASSERT(!mStreamList);
mStreamList = aStreamList;
}
void
CacheStreamControlParent::Close(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
NotifyClose(aId);
Unused << SendClose(aId);
}
void
CacheStreamControlParent::CloseAll()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
NotifyCloseAll();
Unused << SendCloseAll();
}
void
CacheStreamControlParent::Shutdown()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
if (!Send__delete__(this)) {
// child process is gone, allow actor to be destroyed normally
NS_WARNING("Cache failed to delete stream actor.");
return;
}
}
void
CacheStreamControlParent::NotifyClose(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
CloseReadStreams(aId);
}
void
CacheStreamControlParent::NotifyCloseAll()
{
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
CloseAllReadStreams();
}
} // namespace cache
} // namespace dom
} // namespace mozilla

72
dom/cache/CacheStreamControlParent.h vendored Normal file
View file

@ -0,0 +1,72 @@
/* -*- 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_CacheStreamControlParent_h
#define mozilla_dom_cache_CacheStreamControlParent_h
#include "mozilla/dom/cache/PCacheStreamControlParent.h"
#include "mozilla/dom/cache/StreamControl.h"
#include "nsTObserverArray.h"
namespace mozilla {
namespace ipc {
class AutoIPCStream;
} // namespace ipc
namespace dom {
namespace cache {
class ReadStream;
class StreamList;
class CacheStreamControlParent final : public PCacheStreamControlParent
, public StreamControl
{
public:
CacheStreamControlParent();
~CacheStreamControlParent();
void SetStreamList(StreamList* aStreamList);
void Close(const nsID& aId);
void CloseAll();
void Shutdown();
// StreamControl methods
virtual void
SerializeControl(CacheReadStream* aReadStreamOut) override;
virtual void
SerializeStream(CacheReadStream* aReadStreamOut, nsIInputStream* aStream,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList) override;
private:
virtual void
NoteClosedAfterForget(const nsID& aId) override;
#ifdef DEBUG
virtual void
AssertOwningThread() override;
#endif
// PCacheStreamControlParent methods
virtual void ActorDestroy(ActorDestroyReason aReason) override;
virtual bool RecvNoteClosed(const nsID& aId) override;
void NotifyClose(const nsID& aId);
void NotifyCloseAll();
// Cycle with StreamList via a weak-ref to us. Cleanup occurs when the actor
// is deleted by the PBackground manager. ActorDestroy() then calls
// StreamList::RemoveStreamControl() to clear the weak ref.
RefPtr<StreamList> mStreamList;
NS_DECL_OWNINGTHREAD
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheStreamControlParent_h

237
dom/cache/CacheTypes.ipdlh vendored Normal file
View file

@ -0,0 +1,237 @@
/* 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 protocol PCache;
include protocol PCacheStreamControl;
include protocol PSendStream;
include IPCStream;
include ChannelInfo;
include PBackgroundSharedTypes;
using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h";
using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h";
using RequestCredentials from "mozilla/dom/FetchIPCTypes.h";
using RequestMode from "mozilla/dom/FetchIPCTypes.h";
using RequestCache from "mozilla/dom/FetchIPCTypes.h";
using RequestRedirect from "mozilla/dom/FetchIPCTypes.h";
using ResponseType from "mozilla/dom/FetchIPCTypes.h";
using mozilla::void_t from "ipc/IPCMessageUtils.h";
using struct nsID from "nsID.h";
namespace mozilla {
namespace dom {
namespace cache {
struct CacheQueryParams
{
bool ignoreSearch;
bool ignoreMethod;
bool ignoreVary;
bool cacheNameSet;
nsString cacheName;
};
struct CacheReadStream
{
nsID id;
nullable PCacheStreamControl control;
IPCStream stream;
};
union CacheReadStreamOrVoid
{
void_t;
CacheReadStream;
};
struct HeadersEntry
{
nsCString name;
nsCString value;
};
struct CacheRequest
{
nsCString method;
nsCString urlWithoutQuery;
nsCString urlQuery;
nsCString urlFragment;
HeadersEntry[] headers;
HeadersGuardEnum headersGuard;
nsString referrer;
ReferrerPolicy referrerPolicy;
RequestMode mode;
RequestCredentials credentials;
CacheReadStreamOrVoid body;
uint32_t contentPolicyType;
RequestCache requestCache;
RequestRedirect requestRedirect;
nsString integrity;
};
union CacheRequestOrVoid
{
void_t;
CacheRequest;
};
struct CacheResponse
{
ResponseType type;
nsCString[] urlList;
uint32_t status;
nsCString statusText;
HeadersEntry[] headers;
HeadersGuardEnum headersGuard;
CacheReadStreamOrVoid body;
IPCChannelInfo channelInfo;
OptionalPrincipalInfo principalInfo;
};
union CacheResponseOrVoid
{
void_t;
CacheResponse;
};
struct CacheRequestResponse
{
CacheRequest request;
CacheResponse response;
};
struct CacheMatchArgs
{
CacheRequest request;
CacheQueryParams params;
};
struct CacheMatchAllArgs
{
CacheRequestOrVoid requestOrVoid;
CacheQueryParams params;
};
struct CachePutAllArgs
{
CacheRequestResponse[] requestResponseList;
};
struct CacheDeleteArgs
{
CacheRequest request;
CacheQueryParams params;
};
struct CacheKeysArgs
{
CacheRequestOrVoid requestOrVoid;
CacheQueryParams params;
};
struct StorageMatchArgs
{
CacheRequest request;
CacheQueryParams params;
};
struct StorageHasArgs
{
nsString key;
};
struct StorageOpenArgs
{
nsString key;
};
struct StorageDeleteArgs
{
nsString key;
};
struct StorageKeysArgs
{
};
union CacheOpArgs
{
CacheMatchArgs;
CacheMatchAllArgs;
CachePutAllArgs;
CacheDeleteArgs;
CacheKeysArgs;
StorageMatchArgs;
StorageHasArgs;
StorageOpenArgs;
StorageDeleteArgs;
StorageKeysArgs;
};
struct CacheMatchResult
{
CacheResponseOrVoid responseOrVoid;
};
struct CacheMatchAllResult
{
CacheResponse[] responseList;
};
struct CachePutAllResult
{
};
struct CacheDeleteResult
{
bool success;
};
struct CacheKeysResult
{
CacheRequest[] requestList;
};
struct StorageMatchResult
{
CacheResponseOrVoid responseOrVoid;
};
struct StorageHasResult
{
bool success;
};
struct StorageOpenResult
{
nullable PCache actor;
};
struct StorageDeleteResult
{
bool success;
};
struct StorageKeysResult
{
nsString[] keyList;
};
union CacheOpResult
{
void_t;
CacheMatchResult;
CacheMatchAllResult;
CachePutAllResult;
CacheDeleteResult;
CacheKeysResult;
StorageMatchResult;
StorageHasResult;
StorageOpenResult;
StorageDeleteResult;
StorageKeysResult;
};
} // namespace cache
} // namespace dom
} // namespace mozilla

109
dom/cache/CacheWorkerHolder.cpp vendored Normal file
View file

@ -0,0 +1,109 @@
/* -*- 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/CacheWorkerHolder.h"
#include "mozilla/dom/cache/ActorChild.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::workers::Terminating;
using mozilla::dom::workers::Status;
using mozilla::dom::workers::WorkerPrivate;
// static
already_AddRefed<CacheWorkerHolder>
CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
{
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder();
if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Terminating))) {
return nullptr;
}
return workerHolder.forget();
}
void
CacheWorkerHolder::AddActor(ActorChild* aActor)
{
NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
MOZ_DIAGNOSTIC_ASSERT(aActor);
MOZ_ASSERT(!mActorList.Contains(aActor));
mActorList.AppendElement(aActor);
// Allow an actor to be added after we've entered the Notifying case. We
// can't stop the actor creation from racing with out destruction of the
// other actors and we need to wait for this extra one to close as well.
// Signal it should destroy itself right away.
if (mNotified) {
aActor->StartDestroy();
}
}
void
CacheWorkerHolder::RemoveActor(ActorChild* aActor)
{
NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
MOZ_DIAGNOSTIC_ASSERT(aActor);
#if defined(RELEASE_OR_BETA)
mActorList.RemoveElement(aActor);
#else
MOZ_DIAGNOSTIC_ASSERT(mActorList.RemoveElement(aActor));
#endif
MOZ_ASSERT(!mActorList.Contains(aActor));
}
bool
CacheWorkerHolder::Notified() const
{
return mNotified;
}
bool
CacheWorkerHolder::Notify(Status aStatus)
{
NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
// When the service worker thread is stopped we will get Terminating,
// but nothing higher than that. We must shut things down at Terminating.
if (aStatus < Terminating || mNotified) {
return true;
}
mNotified = true;
// Start the asynchronous destruction of our actors. These will call back
// into RemoveActor() once the actor is destroyed.
for (uint32_t i = 0; i < mActorList.Length(); ++i) {
MOZ_DIAGNOSTIC_ASSERT(mActorList[i]);
mActorList[i]->StartDestroy();
}
return true;
}
CacheWorkerHolder::CacheWorkerHolder()
: mNotified(false)
{
}
CacheWorkerHolder::~CacheWorkerHolder()
{
NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
MOZ_DIAGNOSTIC_ASSERT(mActorList.IsEmpty());
}
} // namespace cache
} // namespace dom
} // namespace mozilla

54
dom/cache/CacheWorkerHolder.h vendored Normal file
View file

@ -0,0 +1,54 @@
/* -*- 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_CacheWorkerHolder_h
#define mozilla_dom_cache_CacheWorkerHolder_h
#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "WorkerHolder.h"
namespace mozilla {
namespace workers {
class WorkerPrivate;
} // namespace workers
namespace dom {
namespace cache {
class ActorChild;
class CacheWorkerHolder final : public workers::WorkerHolder
{
public:
static already_AddRefed<CacheWorkerHolder>
Create(workers::WorkerPrivate* aWorkerPrivate);
void AddActor(ActorChild* aActor);
void RemoveActor(ActorChild* aActor);
bool Notified() const;
// WorkerHolder methods
virtual bool Notify(workers::Status aStatus) override;
private:
CacheWorkerHolder();
~CacheWorkerHolder();
nsTArray<ActorChild*> mActorList;
bool mNotified;
public:
NS_INLINE_DECL_REFCOUNTING(mozilla::dom::cache::CacheWorkerHolder)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_CacheWorkerHolder_h

286
dom/cache/Connection.cpp vendored Normal file
View file

@ -0,0 +1,286 @@
/* -*- 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/Connection.h"
#include "mozilla/dom/cache/DBSchema.h"
#include "mozStorageHelper.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::QuotaObject;
NS_IMPL_ISUPPORTS(cache::Connection, mozIStorageAsyncConnection,
mozIStorageConnection);
Connection::Connection(mozIStorageConnection* aBase)
: mBase(aBase)
, mClosed(false)
{
MOZ_DIAGNOSTIC_ASSERT(mBase);
}
Connection::~Connection()
{
NS_ASSERT_OWNINGTHREAD(Connection);
MOZ_ALWAYS_SUCCEEDS(Close());
}
NS_IMETHODIMP
Connection::Close()
{
NS_ASSERT_OWNINGTHREAD(Connection);
if (mClosed) {
return NS_OK;
}
mClosed = true;
// If we are closing here, then Cache must not have a transaction
// open anywhere else. This should be guaranteed to succeed.
MOZ_ALWAYS_SUCCEEDS(db::IncrementalVacuum(this));
return mBase->Close();
}
// The following methods are all boilerplate that either forward to the
// base connection or block the method. All the async execution methods
// are blocked because Cache does not use them and they would require more
// work to wrap properly.
// mozIStorageAsyncConnection methods
NS_IMETHODIMP
Connection::AsyncClose(mozIStorageCompletionCallback*)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::AsyncClone(bool, mozIStorageCompletionCallback*)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::GetDatabaseFile(nsIFile** aFileOut)
{
return mBase->GetDatabaseFile(aFileOut);
}
NS_IMETHODIMP
Connection::CreateAsyncStatement(const nsACString&, mozIStorageAsyncStatement**)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::ExecuteAsync(mozIStorageBaseStatement**, uint32_t,
mozIStorageStatementCallback*,
mozIStoragePendingStatement**)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::ExecuteSimpleSQLAsync(const nsACString&,
mozIStorageStatementCallback*,
mozIStoragePendingStatement**)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::CreateFunction(const nsACString& aFunctionName,
int32_t aNumArguments,
mozIStorageFunction* aFunction)
{
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
Connection::CreateAggregateFunction(const nsACString& aFunctionName,
int32_t aNumArguments,
mozIStorageAggregateFunction* aFunction)
{
return mBase->CreateAggregateFunction(aFunctionName, aNumArguments,
aFunction);
}
NS_IMETHODIMP
Connection::RemoveFunction(const nsACString& aFunctionName)
{
return mBase->RemoveFunction(aFunctionName);
}
NS_IMETHODIMP
Connection::SetProgressHandler(int32_t aGranularity,
mozIStorageProgressHandler* aHandler,
mozIStorageProgressHandler** aHandlerOut)
{
return mBase->SetProgressHandler(aGranularity, aHandler, aHandlerOut);
}
NS_IMETHODIMP
Connection::RemoveProgressHandler(mozIStorageProgressHandler** aHandlerOut)
{
return mBase->RemoveProgressHandler(aHandlerOut);
}
// mozIStorageConnection methods
NS_IMETHODIMP
Connection::Clone(bool aReadOnly, mozIStorageConnection** aConnectionOut)
{
nsCOMPtr<mozIStorageConnection> conn;
nsresult rv = mBase->Clone(aReadOnly, getter_AddRefs(conn));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
wrapped.forget(aConnectionOut);
return rv;
}
NS_IMETHODIMP
Connection::GetDefaultPageSize(int32_t* aSizeOut)
{
return mBase->GetDefaultPageSize(aSizeOut);
}
NS_IMETHODIMP
Connection::GetConnectionReady(bool* aReadyOut)
{
return mBase->GetConnectionReady(aReadyOut);
}
NS_IMETHODIMP
Connection::GetLastInsertRowID(int64_t* aRowIdOut)
{
return mBase->GetLastInsertRowID(aRowIdOut);
}
NS_IMETHODIMP
Connection::GetAffectedRows(int32_t* aCountOut)
{
return mBase->GetAffectedRows(aCountOut);
}
NS_IMETHODIMP
Connection::GetLastError(int32_t* aErrorOut)
{
return mBase->GetLastError(aErrorOut);
}
NS_IMETHODIMP
Connection::GetLastErrorString(nsACString& aErrorOut)
{
return mBase->GetLastErrorString(aErrorOut);
}
NS_IMETHODIMP
Connection::GetSchemaVersion(int32_t* aVersionOut)
{
return mBase->GetSchemaVersion(aVersionOut);
}
NS_IMETHODIMP
Connection::SetSchemaVersion(int32_t aVersion)
{
return mBase->SetSchemaVersion(aVersion);
}
NS_IMETHODIMP
Connection::CreateStatement(const nsACString& aQuery,
mozIStorageStatement** aStatementOut)
{
return mBase->CreateStatement(aQuery, aStatementOut);
}
NS_IMETHODIMP
Connection::ExecuteSimpleSQL(const nsACString& aQuery)
{
return mBase->ExecuteSimpleSQL(aQuery);
}
NS_IMETHODIMP
Connection::TableExists(const nsACString& aTableName, bool* aExistsOut)
{
return mBase->TableExists(aTableName, aExistsOut);
}
NS_IMETHODIMP
Connection::IndexExists(const nsACString& aIndexName, bool* aExistsOut)
{
return mBase->IndexExists(aIndexName, aExistsOut);
}
NS_IMETHODIMP
Connection::GetTransactionInProgress(bool* aResultOut)
{
return mBase->GetTransactionInProgress(aResultOut);
}
NS_IMETHODIMP
Connection::BeginTransaction()
{
return mBase->BeginTransaction();
}
NS_IMETHODIMP
Connection::BeginTransactionAs(int32_t aType)
{
return mBase->BeginTransactionAs(aType);
}
NS_IMETHODIMP
Connection::CommitTransaction()
{
return mBase->CommitTransaction();
}
NS_IMETHODIMP
Connection::RollbackTransaction()
{
return mBase->RollbackTransaction();
}
NS_IMETHODIMP
Connection::CreateTable(const char* aTable, const char* aSchema)
{
return mBase->CreateTable(aTable, aSchema);
}
NS_IMETHODIMP
Connection::SetGrowthIncrement(int32_t aIncrement, const nsACString& aDatabase)
{
return mBase->SetGrowthIncrement(aIncrement, aDatabase);
}
NS_IMETHODIMP
Connection::EnableModule(const nsACString& aModule)
{
return mBase->EnableModule(aModule);
}
NS_IMETHODIMP
Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
QuotaObject** aJournalQuotaObject)
{
return mBase->GetQuotaObjects(aDatabaseQuotaObject, aJournalQuotaObject);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

37
dom/cache/Connection.h vendored Normal file
View file

@ -0,0 +1,37 @@
/* -*- 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_Connection_h
#define mozilla_dom_cache_Connection_h
#include "mozIStorageConnection.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace dom {
namespace cache {
class Connection final : public mozIStorageConnection
{
public:
explicit Connection(mozIStorageConnection* aBase);
private:
~Connection();
nsCOMPtr<mozIStorageConnection> mBase;
bool mClosed;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEASYNCCONNECTION
NS_DECL_MOZISTORAGECONNECTION
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_Connection_h

1149
dom/cache/Context.cpp vendored Normal file

File diff suppressed because it is too large Load diff

235
dom/cache/Context.h vendored Normal file
View file

@ -0,0 +1,235 @@
/* -*- 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_Context_h
#define mozilla_dom_cache_Context_h
#include "mozilla/dom/cache/Types.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 {
namespace quota {
class DirectoryLock;
} // namespace quota
namespace cache {
class Action;
class Manager;
// The Context class is RAII-style class for managing IO operations within the
// Cache.
//
// When a Context is created it performs the complicated steps necessary to
// initialize the QuotaManager. Action objects dispatched on the Context are
// 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.
// 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 aborts all operations so it can delete the files.
// In this case the QuotaManager calls Client::AbortOperations() 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
// originally used to create the Context.
//
// As an invariant, all Context objects must be destroyed before permitting
// the "profile-before-change" shutdown event to complete. This is ensured
// via the code in ShutdownObserver.cpp.
class Context final
{
typedef mozilla::dom::quota::DirectoryLock DirectoryLock;
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 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.
RefPtr<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;
};
// Create a Context attached to the given Manager. The given Action
// will run on the QuotaManager IO thread. Note, this Action must
// be execute synchronously.
static already_AddRefed<Context>
Create(Manager* aManager, nsIThread* aTarget,
Action* aInitAction, Context* aOldContext);
// Execute given action on the target once the quota manager has been
// initialized.
//
// Only callable from the thread that created the Context.
void Dispatch(Action* aAction);
// Cancel any Actions running or waiting to run. This should allow the
// Context to be released and Listener::RemoveContext() will be called
// when complete.
//
// Only callable from the thread that created the Context.
void CancelAll();
// True if CancelAll() has been called.
bool IsCanceled() const;
// 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;
}
// Tell the Context that some state information has been orphaned in the
// data store and won't be cleaned up. The Context will leave the marker
// in place to trigger cleanup the next times its opened.
void NoteOrphanedData();
private:
class Data;
class QuotaInitRunnable;
class ActionRunnable;
enum State
{
STATE_CONTEXT_PREINIT,
STATE_CONTEXT_INIT,
STATE_CONTEXT_READY,
STATE_CONTEXT_CANCELED
};
struct PendingAction
{
nsCOMPtr<nsIEventTarget> mTarget;
RefPtr<Action> mAction;
};
Context(Manager* aManager, nsIThread* aTarget, Action* aInitAction);
~Context();
void Init(Context* aOldContext);
void Start();
void DispatchAction(Action* aAction, bool aDoomData = false);
void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
already_AddRefed<DirectoryLock> aDirectoryLock);
already_AddRefed<ThreadsafeHandle>
CreateThreadsafeHandle();
void
SetNextContext(Context* aNextContext);
void
DoomTargetData();
RefPtr<Manager> mManager;
nsCOMPtr<nsIThread> mTarget;
RefPtr<Data> mData;
State mState;
bool mOrphanedData;
QuotaInfo mQuotaInfo;
RefPtr<QuotaInitRunnable> mInitRunnable;
RefPtr<Action> mInitAction;
nsTArray<PendingAction> mPendingActions;
// 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.
RefPtr<ThreadsafeHandle> mThreadsafeHandle;
RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<Context> mNextContext;
public:
NS_INLINE_DECL_REFCOUNTING(cache::Context)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_Context_h

231
dom/cache/DBAction.cpp vendored Normal file
View file

@ -0,0 +1,231 @@
/* -*- 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/DBAction.h"
#include "mozilla/dom/cache/Connection.h"
#include "mozilla/dom/cache/DBSchema.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/net/nsFileProtocolHandler.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "nsIFile.h"
#include "nsIURI.h"
#include "nsIFileURL.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
DBAction::DBAction(Mode aMode)
: mMode(aMode)
{
}
DBAction::~DBAction()
{
}
void
DBAction::RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
Data* aOptionalData)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.mDir);
if (IsCanceled()) {
aResolver->Resolve(NS_ERROR_ABORT);
return;
}
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;
}
nsCOMPtr<mozIStorageConnection> conn;
// Attempt to reuse the connection opened by a previous Action.
if (aOptionalData) {
conn = aOptionalData->GetConnection();
}
// If there is no previous Action, then we must open one.
if (!conn) {
rv = OpenConnection(aQuotaInfo, dbDir, getter_AddRefs(conn));
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver->Resolve(rv);
return;
}
MOZ_DIAGNOSTIC_ASSERT(conn);
// Save this connection in the shared Data object so later Actions can
// use it. This avoids opening a new connection for every Action.
if (aOptionalData) {
// Since we know this connection will be around for as long as the
// Cache is open, use our special wrapped connection class. This
// will let us perform certain operations once the Cache origin
// is closed.
nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
aOptionalData->SetConnection(wrapped);
}
}
RunWithDBOnTarget(aResolver, aQuotaInfo, dbDir, conn);
}
nsresult
DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection** aConnOut)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aDBDir);
MOZ_DIAGNOSTIC_ASSERT(aConnOut);
nsCOMPtr<mozIStorageConnection> conn;
bool exists;
nsresult rv = aDBDir->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (!exists) {
if (NS_WARN_IF(mMode != Create)) { return NS_ERROR_FILE_NOT_FOUND; }
rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
nsCOMPtr<nsIFile> dbFile;
rv = aDBDir->Clone(getter_AddRefs(dbFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = dbFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Use our default file:// protocol handler directly to construct the database
// URL. This avoids any problems if a plugin registers a custom file://
// handler. If such a custom handler used javascript, then we would have a
// bad time running off the main thread here.
RefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
rv = handler->Init();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIURI> uri;
rv = handler->NewFileURI(dbFile, getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFileURL> dbFileUrl = do_QueryInterface(uri);
if (NS_WARN_IF(!dbFileUrl)) { return NS_ERROR_UNEXPECTED; }
nsAutoCString type;
PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);
rv = dbFileUrl->SetQuery(
NS_LITERAL_CSTRING("persistenceType=") + type +
NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin +
NS_LITERAL_CSTRING("&cache=private"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
if (rv == NS_ERROR_FILE_CORRUPTED) {
NS_WARNING("Cache database corrupted. Recreating empty database.");
conn = nullptr;
// There is nothing else we can do to recover. Also, this data can
// be deleted by QuotaManager at any time anyways.
rv = WipeDatabase(dbFile, aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Check the schema to make sure it is not too old.
int32_t schemaVersion = 0;
rv = conn->GetSchemaVersion(&schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
conn = nullptr;
rv = WipeDatabase(dbFile, aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
rv = db::InitializeConnection(conn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
conn.forget(aConnOut);
return rv;
}
nsresult
DBAction::WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir)
{
nsresult rv = aDBFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Note, the -wal journal file will be automatically deleted by sqlite when
// the new database is created. No need to explicitly delete it here.
// Delete the morgue as well.
rv = BodyDeleteDir(aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
SyncDBAction::SyncDBAction(Mode aMode)
: DBAction(aMode)
{
}
SyncDBAction::~SyncDBAction()
{
}
void
SyncDBAction::RunWithDBOnTarget(Resolver* aResolver,
const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aDBDir);
MOZ_DIAGNOSTIC_ASSERT(aConn);
nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn);
aResolver->Resolve(rv);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

79
dom/cache/DBAction.h vendored Normal file
View file

@ -0,0 +1,79 @@
/* -*- 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_DBAction_h
#define mozilla_dom_cache_DBAction_h
#include "mozilla/dom/cache/Action.h"
#include "mozilla/RefPtr.h"
#include "nsString.h"
class mozIStorageConnection;
class nsIFile;
namespace mozilla {
namespace dom {
namespace cache {
class DBAction : public Action
{
protected:
// The mode specifies whether the database should already exist or if its
// ok to create a new database.
enum Mode
{
Existing,
Create
};
explicit DBAction(Mode aMode);
// Action objects are deleted through their base pointer
virtual ~DBAction();
// Just as the resolver must be ref'd until resolve, you may also
// ref the DB connection. The connection can only be referenced from the
// target thread and must be released upon resolve.
virtual void
RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
nsIFile* aDBDir, mozIStorageConnection* aConn) = 0;
private:
virtual void
RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
Data* aOptionalData) override;
nsresult OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aQuotaDir,
mozIStorageConnection** aConnOut);
nsresult WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir);
const Mode mMode;
};
class SyncDBAction : public DBAction
{
protected:
explicit SyncDBAction(Mode aMode);
// Action objects are deleted through their base pointer
virtual ~SyncDBAction();
virtual nsresult
RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn) = 0;
private:
virtual void
RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
nsIFile* aDBDir, mozIStorageConnection* aConn) override;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_DBAction_h

3018
dom/cache/DBSchema.cpp vendored Normal file

File diff suppressed because it is too large Load diff

129
dom/cache/DBSchema.h vendored Normal file
View file

@ -0,0 +1,129 @@
/* -*- 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_DBSchema_h
#define mozilla_dom_cache_DBSchema_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/cache/Types.h"
#include "nsError.h"
#include "nsString.h"
#include "nsTArrayForwardDeclare.h"
class mozIStorageConnection;
struct nsID;
namespace mozilla {
namespace dom {
namespace cache {
class CacheQueryParams;
class CacheRequest;
class CacheRequestOrVoid;
class CacheResponse;
struct SavedRequest;
struct SavedResponse;
namespace db {
// Note, this cannot be executed within a transaction.
nsresult
CreateOrMigrateSchema(mozIStorageConnection* aConn);
// Note, this cannot be executed within a transaction.
nsresult
InitializeConnection(mozIStorageConnection* aConn);
nsresult
CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut);
nsresult
DeleteCacheId(mozIStorageConnection* aConn, CacheId aCacheId,
nsTArray<nsID>& aDeletedBodyIdListOut);
// TODO: Consider removing unused IsCacheOrphaned after writing cleanup code. (bug 1110446)
nsresult
IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId,
bool* aOrphanedOut);
nsresult
FindOrphanedCacheIds(mozIStorageConnection* aConn,
nsTArray<CacheId>& aOrphanedListOut);
nsresult
GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray<nsID>& aBodyIdListOut);
nsresult
CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId,
const CacheRequest& aRequest, const CacheQueryParams& aParams,
bool* aFoundResponseOut, SavedResponse* aSavedResponseOut);
nsresult
CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId,
const CacheRequestOrVoid& aRequestOrVoid,
const CacheQueryParams& aParams,
nsTArray<SavedResponse>& aSavedResponsesOut);
nsresult
CachePut(mozIStorageConnection* aConn, CacheId aCacheId,
const CacheRequest& aRequest,
const nsID* aRequestBodyId,
const CacheResponse& aResponse,
const nsID* aResponseBodyId,
nsTArray<nsID>& aDeletedBodyIdListOut);
nsresult
CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId,
const CacheRequest& aRequest,
const CacheQueryParams& aParams,
nsTArray<nsID>& aDeletedBodyIdListOut,
bool* aSuccessOut);
nsresult
CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId,
const CacheRequestOrVoid& aRequestOrVoid,
const CacheQueryParams& aParams,
nsTArray<SavedRequest>& aSavedRequestsOut);
nsresult
StorageMatch(mozIStorageConnection* aConn,
Namespace aNamespace,
const CacheRequest& aRequest,
const CacheQueryParams& aParams,
bool* aFoundResponseOut,
SavedResponse* aSavedResponseOut);
nsresult
StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace,
const nsAString& aKey, bool* aFoundCacheOut,
CacheId* aCacheIdOut);
nsresult
StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace,
const nsAString& aKey, CacheId aCacheId);
nsresult
StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace,
const nsAString& aKey);
nsresult
StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace,
nsTArray<nsString>& aKeysOut);
// Note, this works best when its NOT executed within a transaction.
nsresult
IncrementalVacuum(mozIStorageConnection* aConn);
// We will wipe out databases with a schema versions less than this. Newer
// versions will be migrated on open to the latest schema version.
extern const int32_t kFirstShippedSchemaVersion;
} // namespace db
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_DBSchema_h

501
dom/cache/FileUtils.cpp vendored Normal file
View file

@ -0,0 +1,501 @@
/* -*- 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/FileUtils.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/Unused.h"
#include "nsIFile.h"
#include "nsIUUIDGenerator.h"
#include "nsNetCID.h"
#include "nsISimpleEnumerator.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::FileInputStream;
using mozilla::dom::quota::FileOutputStream;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
namespace {
enum BodyFileType
{
BODY_FILE_FINAL,
BODY_FILE_TMP
};
nsresult
BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
nsIFile** aBodyFileOut);
} // namespace
// static
nsresult
BodyCreateDir(nsIFile* aBaseDir)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
nsCOMPtr<nsIFile> aBodyDir;
nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aBodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
return NS_OK;
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
// static
nsresult
BodyDeleteDir(nsIFile* aBaseDir)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
nsCOMPtr<nsIFile> aBodyDir;
nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aBodyDir->Remove(/* recursive = */ true);
if (rv == NS_ERROR_FILE_NOT_FOUND ||
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
rv = NS_OK;
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
// static
nsresult
BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, nsIFile** aCacheDirOut)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
MOZ_DIAGNOSTIC_ASSERT(aCacheDirOut);
*aCacheDirOut = nullptr;
nsresult rv = aBaseDir->Clone(aCacheDirOut);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_DIAGNOSTIC_ASSERT(*aCacheDirOut);
rv = (*aCacheDirOut)->Append(NS_LITERAL_STRING("morgue"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Some file systems have poor performance when there are too many files
// in a single directory. Mitigate this issue by spreading the body
// files out into sub-directories. We use the last byte of the ID for
// the name of the sub-directory.
nsAutoString subDirName;
subDirName.AppendInt(aId.m3[7]);
rv = (*aCacheDirOut)->Append(subDirName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = (*aCacheDirOut)->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
return NS_OK;
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
// static
nsresult
BodyStartWriteStream(const QuotaInfo& aQuotaInfo,
nsIFile* aBaseDir, nsIInputStream* aSource,
void* aClosure,
nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
nsISupports** aCopyContextOut)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
MOZ_DIAGNOSTIC_ASSERT(aSource);
MOZ_DIAGNOSTIC_ASSERT(aClosure);
MOZ_DIAGNOSTIC_ASSERT(aCallback);
MOZ_DIAGNOSTIC_ASSERT(aIdOut);
MOZ_DIAGNOSTIC_ASSERT(aCopyContextOut);
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> idGen =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = idGen->GenerateUUIDInPlace(aIdOut);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> finalFile;
rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_FINAL,
getter_AddRefs(finalFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool exists;
rv = finalFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; }
nsCOMPtr<nsIFile> tmpFile;
rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_TMP, getter_AddRefs(tmpFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = tmpFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; }
nsCOMPtr<nsIOutputStream> fileStream =
FileOutputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
aQuotaInfo.mOrigin, tmpFile);
if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
RefPtr<SnappyCompressOutputStream> compressed =
new SnappyCompressOutputStream(fileStream);
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
rv = NS_AsyncCopy(aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
compressed->BlockSize(), aCallback, aClosure,
true, true, // close streams
aCopyContextOut);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
// static
void
BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
MOZ_DIAGNOSTIC_ASSERT(aCopyContext);
nsresult rv = NS_CancelAsyncCopy(aCopyContext, NS_ERROR_ABORT);
Unused << NS_WARN_IF(NS_FAILED(rv));
// The partially written file must be cleaned up after the async copy
// makes its callback.
}
// static
nsresult
BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
nsCOMPtr<nsIFile> tmpFile;
nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> finalFile;
rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsAutoString finalFileName;
rv = finalFile->GetLeafName(finalFileName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = tmpFile->RenameTo(nullptr, finalFileName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
// static
nsresult
BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId,
nsIInputStream** aStreamOut)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
MOZ_DIAGNOSTIC_ASSERT(aStreamOut);
nsCOMPtr<nsIFile> finalFile;
nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL,
getter_AddRefs(finalFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool exists;
rv = finalFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(!exists)) { return NS_ERROR_FILE_NOT_FOUND; }
nsCOMPtr<nsIInputStream> fileStream =
FileInputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
aQuotaInfo.mOrigin, finalFile);
if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
fileStream.forget(aStreamOut);
return rv;
}
// static
nsresult
BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray<nsID>& aIdList)
{
nsresult rv = NS_OK;
for (uint32_t i = 0; i < aIdList.Length(); ++i) {
nsCOMPtr<nsIFile> tmpFile;
rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_TMP,
getter_AddRefs(tmpFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = tmpFile->Remove(false /* recursive */);
if (rv == NS_ERROR_FILE_NOT_FOUND ||
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
rv = NS_OK;
}
// Only treat file deletion as a hard failure in DEBUG builds. Users
// can unfortunately hit this on windows if anti-virus is scanning files,
// etc.
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsIFile> finalFile;
rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_FINAL,
getter_AddRefs(finalFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = finalFile->Remove(false /* recursive */);
if (rv == NS_ERROR_FILE_NOT_FOUND ||
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
rv = NS_OK;
}
// Again, only treat removal as hard failure in debug build.
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
return NS_OK;
}
namespace {
nsresult
BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
nsIFile** aBodyFileOut)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
MOZ_DIAGNOSTIC_ASSERT(aBodyFileOut);
*aBodyFileOut = nullptr;
nsresult rv = BodyGetCacheDir(aBaseDir, aId, aBodyFileOut);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_DIAGNOSTIC_ASSERT(*aBodyFileOut);
char idString[NSID_LENGTH];
aId.ToProvidedString(idString);
NS_ConvertASCIItoUTF16 fileName(idString);
if (aType == BODY_FILE_FINAL) {
fileName.AppendLiteral(".final");
} else {
fileName.AppendLiteral(".tmp");
}
rv = (*aBodyFileOut)->Append(fileName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
} // namespace
nsresult
BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList)
{
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
// body files are stored in a directory structure like:
//
// /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
// /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
nsCOMPtr<nsIFile> dir;
nsresult rv = aBaseDir->Clone(getter_AddRefs(dir));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Add the root morgue directory
rv = dir->Append(NS_LITERAL_STRING("morgue"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsISimpleEnumerator> entries;
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Iterate over all the intermediate morgue subdirs
bool hasMore = false;
while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> subdir = do_QueryInterface(entry);
bool isDir = false;
rv = subdir->IsDirectory(&isDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// If a file got in here somehow, try to remove it and move on
if (NS_WARN_IF(!isDir)) {
rv = subdir->Remove(false /* recursive */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
continue;
}
nsCOMPtr<nsISimpleEnumerator> subEntries;
rv = subdir->GetDirectoryEntries(getter_AddRefs(subEntries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Now iterate over all the files in the subdir
bool subHasMore = false;
while(NS_SUCCEEDED(rv = subEntries->HasMoreElements(&subHasMore)) &&
subHasMore) {
nsCOMPtr<nsISupports> subEntry;
rv = subEntries->GetNext(getter_AddRefs(subEntry));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> file = do_QueryInterface(subEntry);
nsAutoCString leafName;
rv = file->GetNativeLeafName(leafName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Delete all tmp files regardless of known bodies. These are
// all considered orphans.
if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".tmp"))) {
// remove recursively in case its somehow a directory
rv = file->Remove(true /* recursive */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
continue;
}
nsCString suffix(NS_LITERAL_CSTRING(".final"));
// Otherwise, it must be a .final file. If its not, then just
// skip it.
if (NS_WARN_IF(!StringEndsWith(leafName, suffix) ||
leafName.Length() != NSID_LENGTH - 1 + suffix.Length())) {
continue;
}
// Finally, parse the uuid out of the name. If its fails to parse,
// the ignore the file.
nsID id;
if (NS_WARN_IF(!id.Parse(leafName.BeginReading()))) {
continue;
}
if (!aKnownBodyIdList.Contains(id)) {
// remove recursively in case its somehow a directory
rv = file->Remove(true /* recursive */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
}
return rv;
}
namespace {
nsresult
GetMarkerFileHandle(const QuotaInfo& aQuotaInfo, nsIFile** aFileOut)
{
MOZ_DIAGNOSTIC_ASSERT(aFileOut);
nsCOMPtr<nsIFile> marker;
nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(marker));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = marker->Append(NS_LITERAL_STRING("cache"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = marker->Append(NS_LITERAL_STRING("context_open.marker"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
marker.forget(aFileOut);
return rv;
}
} // namespace
nsresult
CreateMarkerFile(const QuotaInfo& aQuotaInfo)
{
nsCOMPtr<nsIFile> marker;
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
rv = NS_OK;
}
// Note, we don't need to fsync here. We only care about actually
// writing the marker if later modifications to the Cache are
// actually flushed to the disk. If the OS crashes before the marker
// is written then we are ensured no other changes to the Cache were
// flushed either.
return rv;
}
nsresult
DeleteMarkerFile(const QuotaInfo& aQuotaInfo)
{
nsCOMPtr<nsIFile> marker;
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = marker->Remove(/* recursive = */ false);
if (rv == NS_ERROR_FILE_NOT_FOUND ||
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
rv = NS_OK;
}
// Again, no fsync is necessary. If the OS crashes before the file
// removal is flushed, then the Cache will search for stale data on
// startup. This will cause the next Cache access to be a bit slow, but
// it seems appropriate after an OS crash.
return NS_OK;
}
bool
MarkerFileExists(const QuotaInfo& aQuotaInfo)
{
nsCOMPtr<nsIFile> marker;
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
bool exists = false;
rv = marker->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
return exists;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

69
dom/cache/FileUtils.h vendored Normal file
View file

@ -0,0 +1,69 @@
/* -*- 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_FileUtils_h
#define mozilla_dom_cache_FileUtils_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/cache/Types.h"
#include "nsStreamUtils.h"
#include "nsTArrayForwardDeclare.h"
struct nsID;
class nsIFile;
namespace mozilla {
namespace dom {
namespace cache {
nsresult
BodyCreateDir(nsIFile* aBaseDir);
// Note that this function can only be used during the initialization of the
// database. We're unlikely to be able to delete the DB successfully past
// that point due to the file being in use.
nsresult
BodyDeleteDir(nsIFile* aBaseDir);
nsresult
BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, nsIFile** aCacheDirOut);
nsresult
BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
nsIInputStream* aSource, void* aClosure,
nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
nsISupports** aCopyContextOut);
void
BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext);
nsresult
BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId);
nsresult
BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId,
nsIInputStream** aStreamOut);
nsresult
BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray<nsID>& aIdList);
nsresult
BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList);
nsresult
CreateMarkerFile(const QuotaInfo& aQuotaInfo);
nsresult
DeleteMarkerFile(const QuotaInfo& aQuotaInfo);
bool
MarkerFileExists(const QuotaInfo& aQuotaInfo);
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_FileUtils_h

23
dom/cache/IPCUtils.h vendored Normal file
View file

@ -0,0 +1,23 @@
/* -*- 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_IPCUtils_h
#define mozilla_dom_cache_IPCUtils_h
#include "ipc/IPCMessageUtils.h"
#include "mozilla/dom/cache/Types.h"
namespace IPC {
template<>
struct ParamTraits<mozilla::dom::cache::Namespace> :
public ContiguousEnumSerializer<mozilla::dom::cache::Namespace,
mozilla::dom::cache::DEFAULT_NAMESPACE,
mozilla::dom::cache::NUMBER_OF_NAMESPACES>
{};
} // namespace IPC
#endif // mozilla_dom_cache_IPCUtils_h

1952
dom/cache/Manager.cpp vendored Normal file

File diff suppressed because it is too large Load diff

292
dom/cache/Manager.h vendored Normal file
View file

@ -0,0 +1,292 @@
/* -*- 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_Manager_h
#define mozilla_dom_cache_Manager_h
#include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "mozilla/RefPtr.h"
#include "nsString.h"
#include "nsTArray.h"
class nsIInputStream;
class nsIThread;
namespace mozilla {
class ErrorResult;
namespace dom {
namespace cache {
class CacheOpArgs;
class CacheOpResult;
class CacheRequestResponse;
class Context;
class ManagerId;
struct SavedRequest;
struct SavedResponse;
class StreamList;
// The Manager is class is responsible for performing all of the underlying
// work for a Cache or CacheStorage operation. The DOM objects and IPC actors
// are basically just plumbing to get the request to the right Manager object
// running in the parent process.
//
// There should be exactly one Manager object for each origin or app using the
// Cache API. This uniqueness is defined by the ManagerId equality operator.
// The uniqueness is enforced by the Manager GetOrCreate() factory method.
//
// The life cycle of Manager objects is somewhat complex. While code may
// hold a strong reference to the Manager, it will invalidate itself once it
// believes it has become completely idle. This is currently determined when
// all of the following conditions occur:
//
// 1) There are no more Manager::Listener objects registered with the Manager
// by performing a Cache or Storage operation.
// 2) There are no more CacheId references noted via Manager::AddRefCacheId().
// 3) There are no more BodyId references noted via Manager::AddRefBodyId().
//
// In order to keep your Manager alive you should perform an operation to set
// a Listener, call AddRefCacheId(), or call AddRefBodyId().
//
// Even once a Manager becomes invalid, however, it may still continue to
// exist. This is allowed so that any in-progress Actions can gracefully
// complete.
//
// As an invariant, all Manager objects must cease all IO before shutdown. This
// is enforced by the Manager::Factory. If content still holds references to
// Cache DOM objects during shutdown, then all operations will begin rejecting.
class Manager final
{
public:
// Callback interface implemented by clients of Manager, such as CacheParent
// and CacheStorageParent. In general, if you call a Manager method you
// should expect to receive exactly one On*() callback. For example, if
// you call Manager::CacheMatch(), then you should expect to receive
// OnCacheMatch() back in response.
//
// Listener objects are set on a per-operation basis. So you pass the
// Listener to a call like Manager::CacheMatch(). Once set in this way,
// the Manager will continue to reference the Listener until RemoveListener()
// is called. This is done to allow the same listener to be used for
// multiple operations simultaneously without having to maintain an exact
// count of operations-in-flight.
//
// Note, the Manager only holds weak references to Listener objects.
// Listeners must call Manager::RemoveListener() before they are destroyed
// to clear these weak references.
//
// All public methods should be invoked on the same thread used to create
// the Manager.
class Listener
{
public:
// convenience routines
void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult);
void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
CacheId aOpenedCacheId);
void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
const SavedResponse& aSavedResponse,
StreamList* aStreamList);
void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
const nsTArray<SavedResponse>& aSavedResponseList,
StreamList* aStreamList);
void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
const nsTArray<SavedRequest>& aSavedRequestList,
StreamList* aStreamList);
// interface to be implemented
virtual void
OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
CacheId aOpenedCacheId,
const nsTArray<SavedResponse>& aSavedResponseList,
const nsTArray<SavedRequest>& aSavedRequestList,
StreamList* aStreamList) { }
protected:
~Listener() { }
};
enum State
{
Open,
Closing
};
static nsresult GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut);
static already_AddRefed<Manager> Get(ManagerId* aManagerId);
// Synchronously shutdown. This spins the event loop.
static void ShutdownAll();
// Cancel actions for given origin or all actions if passed string is null.
static void Abort(const nsACString& aOrigin);
// Must be called by Listener objects before they are destroyed.
void RemoveListener(Listener* aListener);
// 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 NoteClosing();
State GetState() 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
// object. The actor must then call ReleaseCacheId() or ReleaseBodyId()
// exactly once for every AddRef*() call it made. Any delayed deletion
// will then be performed.
void AddRefCacheId(CacheId aCacheId);
void ReleaseCacheId(CacheId aCacheId);
void AddRefBodyId(const nsID& aBodyId);
void ReleaseBodyId(const nsID& aBodyId);
already_AddRefed<ManagerId> GetManagerId() const;
// Methods to allow a StreamList to register themselves with the Manager.
// StreamList objects must call RemoveStreamList() before they are destroyed.
void AddStreamList(StreamList* aStreamList);
void RemoveStreamList(StreamList* aStreamList);
void ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
const CacheOpArgs& aOpArgs);
void ExecutePutAll(Listener* aListener, CacheId aCacheId,
const nsTArray<CacheRequestResponse>& aPutList,
const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList);
void ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
const CacheOpArgs& aOpArgs);
private:
class Factory;
class BaseAction;
class DeleteOrphanedCacheAction;
class CacheMatchAction;
class CacheMatchAllAction;
class CachePutAllAction;
class CacheDeleteAction;
class CacheKeysAction;
class StorageMatchAction;
class StorageHasAction;
class StorageOpenAction;
class StorageDeleteAction;
class StorageKeysAction;
typedef uint64_t ListenerId;
Manager(ManagerId* aManagerId, nsIThread* aIOThread);
~Manager();
void Init(Manager* aOldManager);
void Shutdown();
void Abort();
ListenerId SaveListener(Listener* aListener);
Listener* GetListener(ListenerId aListenerId) const;
bool SetCacheIdOrphanedIfRefed(CacheId aCacheId);
bool SetBodyIdOrphanedIfRefed(const nsID& aBodyId);
void NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList);
void MaybeAllowContextToClose();
RefPtr<ManagerId> mManagerId;
nsCOMPtr<nsIThread> mIOThread;
// Weak reference cleared by RemoveContext() in Context destructor.
Context* MOZ_NON_OWNING_REF mContext;
// Weak references cleared by RemoveListener() in Listener destructors.
struct ListenerEntry
{
ListenerEntry()
: mId(UINT64_MAX)
, mListener(nullptr)
{
}
ListenerEntry(ListenerId aId, Listener* aListener)
: mId(aId)
, mListener(aListener)
{
}
ListenerId mId;
Listener* mListener;
};
class ListenerEntryIdComparator
{
public:
bool Equals(const ListenerEntry& aA, const ListenerId& aB) const
{
return aA.mId == aB;
}
};
class ListenerEntryListenerComparator
{
public:
bool Equals(const ListenerEntry& aA, const Listener* aB) const
{
return aA.mListener == aB;
}
};
typedef nsTArray<ListenerEntry> ListenerList;
ListenerList mListeners;
static ListenerId sNextListenerId;
// Weak references cleared by RemoveStreamList() in StreamList destructors.
nsTArray<StreamList*> mStreamLists;
bool mShuttingDown;
State mState;
struct CacheIdRefCounter
{
CacheId mCacheId;
MozRefCountType mCount;
bool mOrphaned;
};
nsTArray<CacheIdRefCounter> mCacheIdRefs;
struct BodyIdRefCounter
{
nsID mBodyId;
MozRefCountType mCount;
bool mOrphaned;
};
nsTArray<BodyIdRefCounter> mBodyIdRefs;
public:
NS_INLINE_DECL_REFCOUNTING(cache::Manager)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_Manager_h

74
dom/cache/ManagerId.cpp vendored Normal file
View file

@ -0,0 +1,74 @@
/* -*- 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/ManagerId.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "nsIPrincipal.h"
#include "nsProxyRelease.h"
#include "mozilla/RefPtr.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::QuotaManager;
// static
nsresult
ManagerId::Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut)
{
MOZ_ASSERT(NS_IsMainThread());
// The QuotaManager::GetInfoFromPrincipal() has special logic for system
// and about: principals. We need to use the same modified origin in
// order to interpret calls from QM correctly.
nsCString quotaOrigin;
nsresult rv = QuotaManager::GetInfoFromPrincipal(aPrincipal,
nullptr, // suffix
nullptr, // group
&quotaOrigin,
nullptr); // is app
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
RefPtr<ManagerId> ref = new ManagerId(aPrincipal, quotaOrigin);
ref.forget(aManagerIdOut);
return NS_OK;
}
already_AddRefed<nsIPrincipal>
ManagerId::Principal() const
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIPrincipal> ref = mPrincipal;
return ref.forget();
}
ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aQuotaOrigin)
: mPrincipal(aPrincipal)
, mQuotaOrigin(aQuotaOrigin)
{
MOZ_DIAGNOSTIC_ASSERT(mPrincipal);
}
ManagerId::~ManagerId()
{
// If we're already on the main thread, then default destruction is fine
if (NS_IsMainThread()) {
return;
}
// Otherwise we need to proxy to main thread to do the release
// The PBackground worker thread shouldn't be running after the main thread
// is stopped. So main thread is guaranteed to exist here.
NS_ReleaseOnMainThread(mPrincipal.forget());
}
} // namespace cache
} // namespace dom
} // namespace mozilla

60
dom/cache/ManagerId.h vendored Normal file
View file

@ -0,0 +1,60 @@
/* -*- 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_ManagerId_h
#define mozilla_dom_cache_ManagerId_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
class nsIPrincipal;
namespace mozilla {
namespace dom {
namespace cache {
class ManagerId final
{
public:
// Main thread only
static nsresult Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut);
// Main thread only
already_AddRefed<nsIPrincipal> Principal() const;
const nsACString& QuotaOrigin() const { return mQuotaOrigin; }
bool operator==(const ManagerId& aOther) const
{
return mQuotaOrigin == aOther.mQuotaOrigin;
}
private:
ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin);
~ManagerId();
ManagerId(const ManagerId&) = delete;
ManagerId& operator=(const ManagerId&) = delete;
// only accessible on main thread
nsCOMPtr<nsIPrincipal> mPrincipal;
// immutable to allow threadsfe access
const nsCString mQuotaOrigin;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::ManagerId)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_ManagerId_h

33
dom/cache/PCache.ipdl vendored Normal file
View file

@ -0,0 +1,33 @@
/* 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 protocol PBackground;
include protocol PBlob; // FIXME: bug 792908
include protocol PCacheOp;
include protocol PCacheStreamControl;
include protocol PFileDescriptorSet;
include protocol PSendStream;
include CacheTypes;
namespace mozilla {
namespace dom {
namespace cache {
protocol PCache
{
manager PBackground;
manages PCacheOp;
parent:
async PCacheOp(CacheOpArgs aOpArgs);
async Teardown();
child:
async __delete__();
};
} // namespace cache
} // namespace dom
} // namespace mozilla

29
dom/cache/PCacheOp.ipdl vendored Normal file
View file

@ -0,0 +1,29 @@
/* 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 protocol PCache;
include protocol PCacheStorage;
include protocol PCacheStreamControl;
include protocol PFileDescriptorSet;
include protocol PSendStream;
include CacheTypes;
using mozilla::ErrorResult from "ipc/ErrorIPCUtils.h";
namespace mozilla {
namespace dom {
namespace cache {
protocol PCacheOp
{
manager PCache or PCacheStorage;
child:
async __delete__(ErrorResult aRv, CacheOpResult aResult);
};
} // namespace cache
} // namespace dom
} // namespace mozilla

34
dom/cache/PCacheStorage.ipdl vendored Normal file
View file

@ -0,0 +1,34 @@
/* 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 protocol PBackground;
include protocol PBlob; // FIXME: bug 792908
include protocol PCache;
include protocol PCacheOp;
include protocol PCacheStreamControl;
include protocol PFileDescriptorSet;
include protocol PSendStream;
include CacheTypes;
namespace mozilla {
namespace dom {
namespace cache {
protocol PCacheStorage
{
manager PBackground;
manages PCacheOp;
parent:
async PCacheOp(CacheOpArgs aOpArgs);
async Teardown();
child:
async __delete__();
};
} // namespace cache
} // namespace dom
} // namespace mozilla

28
dom/cache/PCacheStreamControl.ipdl vendored Normal file
View file

@ -0,0 +1,28 @@
/* 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 protocol PBackground;
using struct nsID from "nsID.h";
namespace mozilla {
namespace dom {
namespace cache {
protocol PCacheStreamControl
{
manager PBackground;
parent:
async NoteClosed(nsID aStreamId);
child:
async Close(nsID aStreamId);
async CloseAll();
async __delete__();
};
} // namespace cache
} // namespace dom
} // namespace mozilla

222
dom/cache/PrincipalVerifier.cpp vendored Normal file
View file

@ -0,0 +1,222 @@
/* -*- 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/PrincipalVerifier.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/cache/ManagerId.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsContentUtils.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsNetUtil.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::ipc::AssertIsOnBackgroundThread;
using mozilla::ipc::BackgroundParent;
using mozilla::ipc::PBackgroundParent;
using mozilla::ipc::PrincipalInfo;
using mozilla::ipc::PrincipalInfoToPrincipal;
// static
already_AddRefed<PrincipalVerifier>
PrincipalVerifier::CreateAndDispatch(Listener* aListener,
PBackgroundParent* aActor,
const PrincipalInfo& aPrincipalInfo)
{
// We must get the ContentParent actor from the PBackgroundParent. This
// only works on the PBackground thread.
AssertIsOnBackgroundThread();
RefPtr<PrincipalVerifier> verifier = new PrincipalVerifier(aListener,
aActor,
aPrincipalInfo);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(verifier));
return verifier.forget();
}
void
PrincipalVerifier::AddListener(Listener* aListener)
{
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(aListener);
MOZ_ASSERT(!mListenerList.Contains(aListener));
mListenerList.AppendElement(aListener);
}
void
PrincipalVerifier::RemoveListener(Listener* aListener)
{
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(aListener);
MOZ_ALWAYS_TRUE(mListenerList.RemoveElement(aListener));
}
PrincipalVerifier::PrincipalVerifier(Listener* aListener,
PBackgroundParent* aActor,
const PrincipalInfo& aPrincipalInfo)
: mActor(BackgroundParent::GetContentParent(aActor))
, mPrincipalInfo(aPrincipalInfo)
, mInitiatingThread(NS_GetCurrentThread())
, mResult(NS_OK)
{
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread);
MOZ_DIAGNOSTIC_ASSERT(aListener);
mListenerList.AppendElement(aListener);
}
PrincipalVerifier::~PrincipalVerifier()
{
// Since the PrincipalVerifier is a Runnable that executes on multiple
// threads, its a race to see which thread de-refs us last. Therefore
// we cannot guarantee which thread we destruct on.
MOZ_DIAGNOSTIC_ASSERT(mListenerList.IsEmpty());
// We should always be able to explicitly release the actor on the main
// thread.
MOZ_DIAGNOSTIC_ASSERT(!mActor);
}
NS_IMETHODIMP
PrincipalVerifier::Run()
{
// Executed twice. First, on the main thread and then back on the
// originating thread.
if (NS_IsMainThread()) {
VerifyOnMainThread();
return NS_OK;
}
CompleteOnInitiatingThread();
return NS_OK;
}
void
PrincipalVerifier::VerifyOnMainThread()
{
MOZ_ASSERT(NS_IsMainThread());
// No matter what happens, we need to release the actor before leaving
// this method.
RefPtr<ContentParent> actor;
actor.swap(mActor);
nsresult rv;
RefPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(mPrincipalInfo,
&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
DispatchToInitiatingThread(rv);
return;
}
// We disallow null principal and unknown app IDs on the client side, but
// double-check here.
if (NS_WARN_IF(principal->GetIsNullPrincipal() ||
principal->GetUnknownAppId())) {
DispatchToInitiatingThread(NS_ERROR_FAILURE);
return;
}
nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
if (NS_WARN_IF(!ssm)) {
DispatchToInitiatingThread(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
return;
}
// Verify if a child process uses system principal, which is not allowed
// to prevent system principal is spoofed.
if (NS_WARN_IF(actor && ssm->IsSystemPrincipal(principal))) {
DispatchToInitiatingThread(NS_ERROR_FAILURE);
return;
}
// Verify that a child process claims to own the app for this principal
if (NS_WARN_IF(actor && !AssertAppPrincipal(actor, principal))) {
DispatchToInitiatingThread(NS_ERROR_FAILURE);
return;
}
actor = nullptr;
#ifdef DEBUG
// Sanity check principal origin by using it to construct a URI and security
// checking it. Don't do this for the system principal, though, as its origin
// is a synthetic [System Principal] string.
if (!ssm->IsSystemPrincipal(principal)) {
nsAutoCString origin;
rv = principal->GetOriginNoSuffix(origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
DispatchToInitiatingThread(rv);
return;
}
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
DispatchToInitiatingThread(rv);
return;
}
rv = principal->CheckMayLoad(uri, false, false);
if (NS_WARN_IF(NS_FAILED(rv))) {
DispatchToInitiatingThread(rv);
return;
}
}
#endif
rv = ManagerId::Create(principal, getter_AddRefs(mManagerId));
if (NS_WARN_IF(NS_FAILED(rv))) {
DispatchToInitiatingThread(rv);
return;
}
DispatchToInitiatingThread(NS_OK);
}
void
PrincipalVerifier::CompleteOnInitiatingThread()
{
AssertIsOnBackgroundThread();
ListenerList::ForwardIterator iter(mListenerList);
while (iter.HasMore()) {
iter.GetNext()->OnPrincipalVerified(mResult, mManagerId);
}
// The listener must clear its reference in OnPrincipalVerified()
MOZ_DIAGNOSTIC_ASSERT(mListenerList.IsEmpty());
}
void
PrincipalVerifier::DispatchToInitiatingThread(nsresult aRv)
{
MOZ_ASSERT(NS_IsMainThread());
mResult = aRv;
// The Cache ShutdownObserver does not track all principal verifiers, so we
// cannot ensure this always succeeds. Instead, simply warn on failures.
// This will result in a new CacheStorage object delaying operations until
// shutdown completes and the browser goes away. This is as graceful as
// we can get here.
nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
NS_WARNING("Cache unable to complete principal verification due to shutdown.");
}
}
} // namespace cache
} // namespace dom
} // namespace mozilla

79
dom/cache/PrincipalVerifier.h vendored Normal file
View file

@ -0,0 +1,79 @@
/* -*- 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_PrincipalVerifier_h
#define mozilla_dom_cache_PrincipalVerifier_h
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsThreadUtils.h"
#include "nsTObserverArray.h"
namespace mozilla {
namespace ipc {
class PBackgroundParent;
} // namespace ipc
namespace dom {
namespace cache {
class ManagerId;
class PrincipalVerifier final : public Runnable
{
public:
// An interface to be implemented by code wishing to use the
// PrincipalVerifier. Note, the Listener implementation is responsible
// for calling RemoveListener() on the PrincipalVerifier to clear the
// weak reference.
class Listener
{
public:
virtual void OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) = 0;
};
static already_AddRefed<PrincipalVerifier>
CreateAndDispatch(Listener* aListener, mozilla::ipc::PBackgroundParent* aActor,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
void AddListener(Listener* aListener);
// The Listener must call RemoveListener() when OnPrincipalVerified() is
// called or when the Listener is destroyed.
void RemoveListener(Listener* aListener);
private:
PrincipalVerifier(Listener* aListener, mozilla::ipc::PBackgroundParent* aActor,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
virtual ~PrincipalVerifier();
void VerifyOnMainThread();
void CompleteOnInitiatingThread();
void DispatchToInitiatingThread(nsresult aRv);
// Weak reference cleared by RemoveListener()
typedef nsTObserverArray<Listener*> ListenerList;
ListenerList mListenerList;
// set in originating thread at construction, but must be accessed and
// released on main thread
RefPtr<ContentParent> mActor;
const mozilla::ipc::PrincipalInfo mPrincipalInfo;
nsCOMPtr<nsIThread> mInitiatingThread;
nsresult mResult;
RefPtr<ManagerId> mManagerId;
public:
NS_DECL_NSIRUNNABLE
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_PrincipalVerifier_h

243
dom/cache/QuotaClient.cpp vendored Normal file
View file

@ -0,0 +1,243 @@
/* -*- 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/QuotaClient.h"
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#include "nsThreadUtils.h"
namespace {
using mozilla::Atomic;
using mozilla::dom::ContentParentId;
using mozilla::dom::cache::Manager;
using mozilla::dom::quota::Client;
using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::UsageInfo;
using mozilla::ipc::AssertIsOnBackgroundThread;
static nsresult
GetBodyUsage(nsIFile* aDir, const Atomic<bool>& aCanceled,
UsageInfo* aUsageInfo)
{
nsCOMPtr<nsISimpleEnumerator> entries;
nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool hasMore;
while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore &&
!aCanceled) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
bool isDir;
rv = file->IsDirectory(&isDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (isDir) {
rv = GetBodyUsage(file, aCanceled, aUsageInfo);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
continue;
}
int64_t fileSize;
rv = file->GetFileSize(&fileSize);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
aUsageInfo->AppendToFileUsage(fileSize);
}
return NS_OK;
}
class CacheQuotaClient final : public Client
{
public:
virtual Type
GetType() override
{
return DOMCACHE;
}
virtual nsresult
InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup,
const nsACString& aOrigin, const AtomicBool& aCanceled,
UsageInfo* aUsageInfo) override
{
// The QuotaManager passes a nullptr UsageInfo if there is no quota being
// enforced against the origin.
if (!aUsageInfo) {
return NS_OK;
}
return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aCanceled,
aUsageInfo);
}
virtual nsresult
GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup,
const nsACString& aOrigin, const AtomicBool& aCanceled,
UsageInfo* aUsageInfo) override
{
MOZ_DIAGNOSTIC_ASSERT(aUsageInfo);
QuotaManager* qm = QuotaManager::Get();
MOZ_DIAGNOSTIC_ASSERT(qm);
nsCOMPtr<nsIFile> dir;
nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
getter_AddRefs(dir));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = dir->Append(NS_LITERAL_STRING(DOMCACHE_DIRECTORY_NAME));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsISimpleEnumerator> entries;
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool hasMore;
while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore &&
!aCanceled) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
nsAutoString leafName;
rv = file->GetLeafName(leafName);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool isDir;
rv = file->IsDirectory(&isDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (isDir) {
if (leafName.EqualsLiteral("morgue")) {
rv = GetBodyUsage(file, aCanceled, aUsageInfo);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
} else {
NS_WARNING("Unknown Cache directory found!");
}
continue;
}
// Ignore transient sqlite files and marker files
if (leafName.EqualsLiteral("caches.sqlite-journal") ||
leafName.EqualsLiteral("caches.sqlite-shm") ||
leafName.Find(NS_LITERAL_CSTRING("caches.sqlite-mj"), false, 0, 0) == 0 ||
leafName.EqualsLiteral("context_open.marker")) {
continue;
}
if (leafName.EqualsLiteral("caches.sqlite") ||
leafName.EqualsLiteral("caches.sqlite-wal")) {
int64_t fileSize;
rv = file->GetFileSize(&fileSize);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
aUsageInfo->AppendToDatabaseUsage(fileSize);
continue;
}
NS_WARNING("Unknown Cache file found!");
}
return NS_OK;
}
virtual void
OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) override
{
// Nothing to do here.
}
virtual void
ReleaseIOThreadObjects() override
{
// Nothing to do here as the Context handles cleaning everything up
// automatically.
}
virtual void
AbortOperations(const nsACString& aOrigin) override
{
AssertIsOnBackgroundThread();
Manager::Abort(aOrigin);
}
virtual void
AbortOperationsForProcess(ContentParentId aContentParentId) override
{
// 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 abort operations
// 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, do nothing here.
}
virtual void
StartIdleMaintenance() override
{ }
virtual void
StopIdleMaintenance() override
{ }
virtual void
ShutdownWorkThreads() override
{
AssertIsOnBackgroundThread();
// spins the event loop and synchronously shuts down all Managers
Manager::ShutdownAll();
}
private:
~CacheQuotaClient()
{
AssertIsOnBackgroundThread();
}
NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override)
};
} // namespace
namespace mozilla {
namespace dom {
namespace cache {
already_AddRefed<quota::Client> CreateQuotaClient()
{
AssertIsOnBackgroundThread();
RefPtr<CacheQuotaClient> ref = new CacheQuotaClient();
return ref.forget();
}
} // namespace cache
} // namespace dom
} // namespace mozilla

24
dom/cache/QuotaClient.h vendored Normal file
View file

@ -0,0 +1,24 @@
/* -*- 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_QuotaClient_h
#define mozilla_dom_cache_QuotaClient_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/quota/Client.h"
namespace mozilla {
namespace dom {
namespace cache {
already_AddRefed<quota::Client>
CreateQuotaClient();
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_QuotaClient_h

578
dom/cache/ReadStream.cpp vendored Normal file
View file

@ -0,0 +1,578 @@
/* -*- 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/ReadStream.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/CacheStreamControlChild.h"
#include "mozilla/dom/cache/CacheStreamControlParent.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/SnappyUncompressInputStream.h"
#include "nsIAsyncInputStream.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::Unused;
using mozilla::ipc::AutoIPCStream;
using mozilla::ipc::IPCStream;
// ----------------------------------------------------------------------------
// The inner stream class. This is where all of the real work is done. As
// an invariant Inner::Close() must be called before ~Inner(). This is
// guaranteed by our outer ReadStream class.
class ReadStream::Inner final : public ReadStream::Controllable
{
public:
Inner(StreamControl* aControl, const nsID& aId,
nsIInputStream* aStream);
void
Serialize(CacheReadStreamOrVoid* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
void
Serialize(CacheReadStream* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
// ReadStream::Controllable methods
virtual void
CloseStream() override;
virtual void
CloseStreamWithoutReporting() override;
virtual bool
MatchId(const nsID& aId) const override;
virtual bool
HasEverBeenRead() const override;
// Simulate nsIInputStream methods, but we don't actually inherit from it
nsresult
Close();
nsresult
Available(uint64_t *aNumAvailableOut);
nsresult
Read(char *aBuf, uint32_t aCount, uint32_t *aNumReadOut);
nsresult
ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount,
uint32_t *aNumReadOut);
nsresult
IsNonBlocking(bool *aNonBlockingOut);
private:
class NoteClosedRunnable;
class ForgetRunnable;
~Inner();
void
NoteClosed();
void
Forget();
void
NoteClosedOnOwningThread();
void
ForgetOnOwningThread();
// Weak ref to the stream control actor. The actor will always call either
// CloseStream() or CloseStreamWithoutReporting() before it's destroyed. The
// weak ref is cleared in the resulting NoteClosedOnOwningThread() or
// ForgetOnOwningThread() method call.
StreamControl* mControl;
const nsID mId;
nsCOMPtr<nsIThread> mOwningThread;
enum State
{
Open,
Closed,
NumStates
};
Atomic<State> mState;
Atomic<bool> mHasEverBeenRead;
// The wrapped stream objects may not be threadsafe. We need to be able
// to close a stream on our owning thread while an IO thread is simultaneously
// reading the same stream. Therefore, protect all access to these stream
// objects with a mutex.
Mutex mMutex;
nsCOMPtr<nsIInputStream> mStream;
nsCOMPtr<nsIInputStream> mSnappyStream;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner, override)
};
// ----------------------------------------------------------------------------
// Runnable to notify actors that the ReadStream has closed. This must
// be done on the thread associated with the PBackground actor. Must be
// cancelable to execute on Worker threads (which can occur when the
// ReadStream is constructed on a child process Worker thread).
class ReadStream::Inner::NoteClosedRunnable final : public CancelableRunnable
{
public:
explicit NoteClosedRunnable(ReadStream::Inner* aStream)
: mStream(aStream)
{ }
NS_IMETHOD Run() override
{
mStream->NoteClosedOnOwningThread();
mStream = nullptr;
return NS_OK;
}
// Note, we must proceed with the Run() method since our actor will not
// clean itself up until we note that the stream is closed.
nsresult Cancel() override
{
Run();
return NS_OK;
}
private:
~NoteClosedRunnable() { }
RefPtr<ReadStream::Inner> mStream;
};
// ----------------------------------------------------------------------------
// Runnable to clear actors without reporting that the ReadStream has
// closed. Since this can trigger actor destruction, we need to do
// it on the thread associated with the PBackground actor. Must be
// cancelable to execute on Worker threads (which can occur when the
// ReadStream is constructed on a child process Worker thread).
class ReadStream::Inner::ForgetRunnable final : public CancelableRunnable
{
public:
explicit ForgetRunnable(ReadStream::Inner* aStream)
: mStream(aStream)
{ }
NS_IMETHOD Run() override
{
mStream->ForgetOnOwningThread();
mStream = nullptr;
return NS_OK;
}
// Note, we must proceed with the Run() method so that we properly
// call RemoveListener on the actor.
nsresult Cancel() override
{
Run();
return NS_OK;
}
private:
~ForgetRunnable() { }
RefPtr<ReadStream::Inner> mStream;
};
// ----------------------------------------------------------------------------
ReadStream::Inner::Inner(StreamControl* aControl, const nsID& aId,
nsIInputStream* aStream)
: mControl(aControl)
, mId(aId)
, mOwningThread(NS_GetCurrentThread())
, mState(Open)
, mHasEverBeenRead(false)
, mMutex("dom::cache::ReadStream")
, mStream(aStream)
, mSnappyStream(new SnappyUncompressInputStream(aStream))
{
MOZ_DIAGNOSTIC_ASSERT(mStream);
MOZ_DIAGNOSTIC_ASSERT(mControl);
mControl->AddReadStream(this);
}
void
ReadStream::Inner::Serialize(CacheReadStreamOrVoid* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
*aReadStreamOut = CacheReadStream();
Serialize(&aReadStreamOut->get_CacheReadStream(), aStreamCleanupList, aRv);
}
void
ReadStream::Inner::Serialize(CacheReadStream* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
if (mState != Open) {
aRv.ThrowTypeError<MSG_CACHE_STREAM_CLOSED>();
return;
}
MOZ_DIAGNOSTIC_ASSERT(mControl);
aReadStreamOut->id() = mId;
mControl->SerializeControl(aReadStreamOut);
{
MutexAutoLock lock(mMutex);
mControl->SerializeStream(aReadStreamOut, mStream, aStreamCleanupList);
}
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut->stream().type() ==
IPCStream::TInputStreamParamsWithFds);
// We're passing ownership across the IPC barrier with the control, so
// do not signal that the stream is closed here.
Forget();
}
void
ReadStream::Inner::CloseStream()
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
Close();
}
void
ReadStream::Inner::CloseStreamWithoutReporting()
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
Forget();
}
bool
ReadStream::Inner::MatchId(const nsID& aId) const
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
return mId.Equals(aId);
}
bool
ReadStream::Inner::HasEverBeenRead() const
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
return mHasEverBeenRead;
}
nsresult
ReadStream::Inner::Close()
{
// stream ops can happen on any thread
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Close();
}
NoteClosed();
return rv;
}
nsresult
ReadStream::Inner::Available(uint64_t* aNumAvailableOut)
{
// stream ops can happen on any thread
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Available(aNumAvailableOut);
}
if (NS_FAILED(rv)) {
Close();
}
return rv;
}
nsresult
ReadStream::Inner::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
{
// stream ops can happen on any thread
MOZ_DIAGNOSTIC_ASSERT(aNumReadOut);
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Read(aBuf, aCount, aNumReadOut);
}
if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) ||
*aNumReadOut == 0) {
Close();
}
mHasEverBeenRead = true;
return rv;
}
nsresult
ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
uint32_t aCount, uint32_t* aNumReadOut)
{
// stream ops can happen on any thread
MOZ_DIAGNOSTIC_ASSERT(aNumReadOut);
if (aCount) {
mHasEverBeenRead = true;
}
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount, aNumReadOut);
}
if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK &&
rv != NS_ERROR_NOT_IMPLEMENTED) || *aNumReadOut == 0) {
Close();
}
// Verify bytes were actually read before marking as being ever read. For
// example, code can test if the stream supports ReadSegments() by calling
// this method with a dummy callback which doesn't read anything. We don't
// want to trigger on that.
if (*aNumReadOut) {
mHasEverBeenRead = true;
}
return rv;
}
nsresult
ReadStream::Inner::IsNonBlocking(bool* aNonBlockingOut)
{
// stream ops can happen on any thread
MutexAutoLock lock(mMutex);
return mSnappyStream->IsNonBlocking(aNonBlockingOut);
}
ReadStream::Inner::~Inner()
{
// Any thread
MOZ_DIAGNOSTIC_ASSERT(mState == Closed);
MOZ_DIAGNOSTIC_ASSERT(!mControl);
}
void
ReadStream::Inner::NoteClosed()
{
// Any thread
if (mState == Closed) {
return;
}
if (NS_GetCurrentThread() == mOwningThread) {
NoteClosedOnOwningThread();
return;
}
nsCOMPtr<nsIRunnable> runnable = new NoteClosedRunnable(this);
MOZ_ALWAYS_SUCCEEDS(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
}
void
ReadStream::Inner::Forget()
{
// Any thread
if (mState == Closed) {
return;
}
if (NS_GetCurrentThread() == mOwningThread) {
ForgetOnOwningThread();
return;
}
nsCOMPtr<nsIRunnable> runnable = new ForgetRunnable(this);
MOZ_ALWAYS_SUCCEEDS(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
}
void
ReadStream::Inner::NoteClosedOnOwningThread()
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
// Mark closed and do nothing if we were already closed
if (!mState.compareExchange(Open, Closed)) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(mControl);
mControl->NoteClosed(this, mId);
mControl = nullptr;
}
void
ReadStream::Inner::ForgetOnOwningThread()
{
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
// Mark closed and do nothing if we were already closed
if (!mState.compareExchange(Open, Closed)) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(mControl);
mControl->ForgetReadStream(this);
mControl = nullptr;
}
// ----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(cache::ReadStream, nsIInputStream, ReadStream);
// static
already_AddRefed<ReadStream>
ReadStream::Create(const CacheReadStreamOrVoid& aReadStreamOrVoid)
{
if (aReadStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) {
return nullptr;
}
return Create(aReadStreamOrVoid.get_CacheReadStream());
}
// static
already_AddRefed<ReadStream>
ReadStream::Create(const CacheReadStream& aReadStream)
{
// The parameter may or may not be for a Cache created stream. The way we
// tell is by looking at the stream control actor. If the actor exists,
// then we know the Cache created it.
if (!aReadStream.controlChild() && !aReadStream.controlParent()) {
return nullptr;
}
MOZ_DIAGNOSTIC_ASSERT(aReadStream.stream().type() ==
IPCStream::TInputStreamParamsWithFds);
// Control is guaranteed to survive this method as ActorDestroy() cannot
// run on this thread until we complete.
StreamControl* control;
if (aReadStream.controlChild()) {
auto actor = static_cast<CacheStreamControlChild*>(aReadStream.controlChild());
control = actor;
} else {
auto actor = static_cast<CacheStreamControlParent*>(aReadStream.controlParent());
control = actor;
}
MOZ_DIAGNOSTIC_ASSERT(control);
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aReadStream.stream());
MOZ_DIAGNOSTIC_ASSERT(stream);
// Currently we expect all cache read streams to be blocking file streams.
#if !defined(RELEASE_OR_BETA)
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(stream);
MOZ_DIAGNOSTIC_ASSERT(!asyncStream);
#endif
RefPtr<Inner> inner = new Inner(control, aReadStream.id(), stream);
RefPtr<ReadStream> ref = new ReadStream(inner);
return ref.forget();
}
// static
already_AddRefed<ReadStream>
ReadStream::Create(PCacheStreamControlParent* aControl, const nsID& aId,
nsIInputStream* aStream)
{
MOZ_DIAGNOSTIC_ASSERT(aControl);
auto actor = static_cast<CacheStreamControlParent*>(aControl);
RefPtr<Inner> inner = new Inner(actor, aId, aStream);
RefPtr<ReadStream> ref = new ReadStream(inner);
return ref.forget();
}
void
ReadStream::Serialize(CacheReadStreamOrVoid* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
mInner->Serialize(aReadStreamOut, aStreamCleanupList, aRv);
}
void
ReadStream::Serialize(CacheReadStream* aReadStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
mInner->Serialize(aReadStreamOut, aStreamCleanupList, aRv);
}
ReadStream::ReadStream(ReadStream::Inner* aInner)
: mInner(aInner)
{
MOZ_DIAGNOSTIC_ASSERT(mInner);
}
ReadStream::~ReadStream()
{
// Explicitly close the inner stream so that it does not have to
// deal with implicitly closing at destruction time.
mInner->Close();
}
NS_IMETHODIMP
ReadStream::Close()
{
return mInner->Close();
}
NS_IMETHODIMP
ReadStream::Available(uint64_t* aNumAvailableOut)
{
return mInner->Available(aNumAvailableOut);
}
NS_IMETHODIMP
ReadStream::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
{
return mInner->Read(aBuf, aCount, aNumReadOut);
}
NS_IMETHODIMP
ReadStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
uint32_t aCount, uint32_t* aNumReadOut)
{
return mInner->ReadSegments(aWriter, aClosure, aCount, aNumReadOut);
}
NS_IMETHODIMP
ReadStream::IsNonBlocking(bool* aNonBlockingOut)
{
return mInner->IsNonBlocking(aNonBlockingOut);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

119
dom/cache/ReadStream.h vendored Normal file
View file

@ -0,0 +1,119 @@
/* -*- 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_ReadStream_h
#define mozilla_dom_cache_ReadStream_h
#include "mozilla/ipc/FileDescriptor.h"
#include "nsCOMPtr.h"
#include "nsID.h"
#include "nsIInputStream.h"
#include "nsISupportsImpl.h"
#include "mozilla/RefPtr.h"
#include "nsTArrayForwardDeclare.h"
namespace mozilla {
namespace ipc {
class AutoIPCStream;
} // namespace ipc
namespace dom {
namespace cache {
class CacheReadStream;
class CacheReadStreamOrVoid;
class PCacheStreamControlParent;
// IID for the dom::cache::ReadStream interface
#define NS_DOM_CACHE_READSTREAM_IID \
{0x8e5da7c9, 0x0940, 0x4f1d, \
{0x97, 0x25, 0x5c, 0x59, 0x38, 0xdd, 0xb9, 0x9f}}
// Custom stream class for Request and Response bodies being read from
// a Cache. The main purpose of this class is to report back to the
// Cache's Manager when the stream is closed. This allows the Cache to
// accurately determine when the underlying body file can be deleted,
// etc.
//
// The ReadStream class also provides us with a convenient QI'able
// interface that we can use to pass additional meta-data with the
// stream channel. For example, Cache.put() can detect that the content
// script is passing a Cache-originated-stream back into the Cache
// again. This enables certain optimizations.
class ReadStream final : public nsIInputStream
{
public:
// Interface that lets the StreamControl classes interact with
// our private inner stream.
class Controllable
{
public:
// Closes the stream, notifies the stream control, and then forgets
// the stream control.
virtual void
CloseStream() = 0;
// Closes the stream and then forgets the stream control. Does not
// notify.
virtual void
CloseStreamWithoutReporting() = 0;
virtual bool
MatchId(const nsID& aId) const = 0;
virtual bool
HasEverBeenRead() const = 0;
NS_IMETHOD_(MozExternalRefCountType)
AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType)
Release(void) = 0;
};
static already_AddRefed<ReadStream>
Create(const CacheReadStreamOrVoid& aReadStreamOrVoid);
static already_AddRefed<ReadStream>
Create(const CacheReadStream& aReadStream);
static already_AddRefed<ReadStream>
Create(PCacheStreamControlParent* aControl, const nsID& aId,
nsIInputStream* aStream);
void Serialize(CacheReadStreamOrVoid* aReadStreamOut,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
void Serialize(CacheReadStream* aReadStreamOut,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
private:
class Inner;
explicit ReadStream(Inner* aInner);
~ReadStream();
// Hold a strong ref to an inner class that actually implements the
// majority of the stream logic. Before releasing this ref the outer
// ReadStream guarantees it will call Close() on the inner stream.
// This is essential for the inner stream to avoid dealing with the
// implicit close that can happen when a stream is destroyed.
RefPtr<Inner> mInner;
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_CACHE_READSTREAM_IID);
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINPUTSTREAM
};
NS_DEFINE_STATIC_IID_ACCESSOR(ReadStream, NS_DOM_CACHE_READSTREAM_IID);
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_ReadStream_h

45
dom/cache/SavedTypes.h vendored Normal file
View file

@ -0,0 +1,45 @@
/* -*- 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_SavedTypes_h
#define mozilla_dom_cache_SavedTypes_h
// NOTE: This cannot be rolled into Types.h because the IPC dependency.
// breaks webidl unified builds.
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h"
#include "nsID.h"
#include "nsIOutputStream.h"
namespace mozilla {
namespace dom {
namespace cache {
struct SavedRequest
{
SavedRequest() : mHasBodyId(false) { mValue.body() = void_t(); }
CacheRequest mValue;
bool mHasBodyId;
nsID mBodyId;
CacheId mCacheId;
};
struct SavedResponse
{
SavedResponse() : mHasBodyId(false) { mValue.body() = void_t(); }
CacheResponse mValue;
bool mHasBodyId;
nsID mBodyId;
CacheId mCacheId;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_SavedTypes_h

105
dom/cache/StreamControl.cpp vendored Normal file
View file

@ -0,0 +1,105 @@
/* -*- 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/StreamControl.h"
namespace mozilla {
namespace dom {
namespace cache {
void
StreamControl::AddReadStream(ReadStream::Controllable* aReadStream)
{
AssertOwningThread();
MOZ_DIAGNOSTIC_ASSERT(aReadStream);
MOZ_ASSERT(!mReadStreamList.Contains(aReadStream));
mReadStreamList.AppendElement(aReadStream);
}
void
StreamControl::ForgetReadStream(ReadStream::Controllable* aReadStream)
{
AssertOwningThread();
MOZ_ALWAYS_TRUE(mReadStreamList.RemoveElement(aReadStream));
}
void
StreamControl::NoteClosed(ReadStream::Controllable* aReadStream,
const nsID& aId)
{
AssertOwningThread();
ForgetReadStream(aReadStream);
NoteClosedAfterForget(aId);
}
StreamControl::~StreamControl()
{
// owning thread only, but can't call virtual AssertOwningThread in destructor
MOZ_DIAGNOSTIC_ASSERT(mReadStreamList.IsEmpty());
}
void
StreamControl::CloseReadStreams(const nsID& aId)
{
AssertOwningThread();
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
uint32_t closedCount = 0;
#endif
ReadStreamList::ForwardIterator iter(mReadStreamList);
while (iter.HasMore()) {
RefPtr<ReadStream::Controllable> stream = iter.GetNext();
if (stream->MatchId(aId)) {
stream->CloseStream();
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
closedCount += 1;
#endif
}
}
MOZ_DIAGNOSTIC_ASSERT(closedCount > 0);
}
void
StreamControl::CloseAllReadStreams()
{
AssertOwningThread();
ReadStreamList::ForwardIterator iter(mReadStreamList);
while (iter.HasMore()) {
iter.GetNext()->CloseStream();
}
}
void
StreamControl::CloseAllReadStreamsWithoutReporting()
{
AssertOwningThread();
ReadStreamList::ForwardIterator iter(mReadStreamList);
while (iter.HasMore()) {
RefPtr<ReadStream::Controllable> stream = iter.GetNext();
// Note, we cannot trigger IPC traffic here. So use
// CloseStreamWithoutReporting().
stream->CloseStreamWithoutReporting();
}
}
bool
StreamControl::HasEverBeenRead() const
{
ReadStreamList::ForwardIterator iter(mReadStreamList);
while (iter.HasMore()) {
if (iter.GetNext()->HasEverBeenRead()) {
return true;
}
}
return false;
}
} // namespace cache
} // namespace dom
} // namespace mozilla

92
dom/cache/StreamControl.h vendored Normal file
View file

@ -0,0 +1,92 @@
/* -*- 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_StreamControl_h
#define mozilla_dom_cache_StreamControl_h
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/RefPtr.h"
#include "nsTObserverArray.h"
struct nsID;
namespace mozilla {
namespace ipc {
class AutoIPCStream;
} // namespace ipc
namespace dom {
namespace cache {
class CacheReadStream;
// Abstract class to help implement the stream control Child and Parent actors.
// This provides an interface to partly help with serialization of IPC types,
// but also an implementation for tracking ReadStream objects.
class StreamControl
{
public:
// abstract interface that must be implemented by child class
virtual void
SerializeControl(CacheReadStream* aReadStreamOut) = 0;
virtual void
SerializeStream(CacheReadStream* aReadStreamOut, nsIInputStream* aStream,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList) = 0;
// inherited implementation of the ReadStream::Controllable list
// Begin controlling the given ReadStream. This causes a strong ref to
// be held by the control. The ReadStream must call NoteClosed() or
// ForgetReadStream() to release this ref.
void
AddReadStream(ReadStream::Controllable* aReadStream);
// Forget the ReadStream without notifying the actor.
void
ForgetReadStream(ReadStream::Controllable* aReadStream);
// Forget the ReadStream and then notify the actor the stream is closed.
void
NoteClosed(ReadStream::Controllable* aReadStream, const nsID& aId);
protected:
~StreamControl();
void
CloseReadStreams(const nsID& aId);
void
CloseAllReadStreams();
void
CloseAllReadStreamsWithoutReporting();
bool
HasEverBeenRead() const;
// protected parts of the abstract interface
virtual void
NoteClosedAfterForget(const nsID& aId) = 0;
#ifdef DEBUG
virtual void
AssertOwningThread() = 0;
#else
void AssertOwningThread() { }
#endif
private:
// Hold strong references to ReadStream object. When the stream is closed
// it should call NoteClosed() or ForgetReadStream() to release this ref.
typedef nsTObserverArray<RefPtr<ReadStream::Controllable>> ReadStreamList;
ReadStreamList mReadStreamList;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_StreamControl_h

175
dom/cache/StreamList.cpp vendored Normal file
View file

@ -0,0 +1,175 @@
/* -*- 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/StreamList.h"
#include "mozilla/dom/cache/CacheStreamControlParent.h"
#include "mozilla/dom/cache/Context.h"
#include "mozilla/dom/cache/Manager.h"
#include "nsIInputStream.h"
namespace mozilla {
namespace dom {
namespace cache {
StreamList::StreamList(Manager* aManager, Context* aContext)
: mManager(aManager)
, mContext(aContext)
, mCacheId(INVALID_CACHE_ID)
, mStreamControl(nullptr)
, mActivated(false)
{
MOZ_DIAGNOSTIC_ASSERT(mManager);
mContext->AddActivity(this);
}
void
StreamList::SetStreamControl(CacheStreamControlParent* aStreamControl)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
MOZ_DIAGNOSTIC_ASSERT(aStreamControl);
// For cases where multiple streams are serialized for a single list
// then the control will get passed multiple times. This is ok, but
// it should be the same control each time.
if (mStreamControl) {
MOZ_DIAGNOSTIC_ASSERT(aStreamControl == mStreamControl);
return;
}
mStreamControl = aStreamControl;
mStreamControl->SetStreamList(this);
}
void
StreamList::RemoveStreamControl(CacheStreamControlParent* aStreamControl)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
MOZ_DIAGNOSTIC_ASSERT(mStreamControl);
MOZ_DIAGNOSTIC_ASSERT(mStreamControl == aStreamControl);
mStreamControl = nullptr;
}
void
StreamList::Activate(CacheId aCacheId)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
MOZ_DIAGNOSTIC_ASSERT(!mActivated);
MOZ_DIAGNOSTIC_ASSERT(mCacheId == INVALID_CACHE_ID);
mActivated = true;
mCacheId = aCacheId;
mManager->AddRefCacheId(mCacheId);
mManager->AddStreamList(this);
for (uint32_t i = 0; i < mList.Length(); ++i) {
mManager->AddRefBodyId(mList[i].mId);
}
}
void
StreamList::Add(const nsID& aId, nsIInputStream* aStream)
{
// All streams should be added on IO thread before we set the stream
// control on the owning IPC thread.
MOZ_DIAGNOSTIC_ASSERT(!mStreamControl);
MOZ_DIAGNOSTIC_ASSERT(aStream);
Entry* entry = mList.AppendElement();
entry->mId = aId;
entry->mStream = aStream;
}
already_AddRefed<nsIInputStream>
StreamList::Extract(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
for (uint32_t i = 0; i < mList.Length(); ++i) {
if (mList[i].mId == aId) {
return mList[i].mStream.forget();
}
}
return nullptr;
}
void
StreamList::NoteClosed(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
for (uint32_t i = 0; i < mList.Length(); ++i) {
if (mList[i].mId == aId) {
mList.RemoveElementAt(i);
mManager->ReleaseBodyId(aId);
break;
}
}
if (mList.IsEmpty() && mStreamControl) {
mStreamControl->Shutdown();
}
}
void
StreamList::NoteClosedAll()
{
NS_ASSERT_OWNINGTHREAD(StreamList);
for (uint32_t i = 0; i < mList.Length(); ++i) {
mManager->ReleaseBodyId(mList[i].mId);
}
mList.Clear();
if (mStreamControl) {
mStreamControl->Shutdown();
}
}
void
StreamList::Close(const nsID& aId)
{
NS_ASSERT_OWNINGTHREAD(StreamList);
if (mStreamControl) {
mStreamControl->Close(aId);
}
}
void
StreamList::CloseAll()
{
NS_ASSERT_OWNINGTHREAD(StreamList);
if (mStreamControl) {
mStreamControl->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);
MOZ_DIAGNOSTIC_ASSERT(!mStreamControl);
if (mActivated) {
mManager->RemoveStreamList(this);
for (uint32_t i = 0; i < mList.Length(); ++i) {
mManager->ReleaseBodyId(mList[i].mId);
}
mManager->ReleaseCacheId(mCacheId);
}
mContext->RemoveActivity(this);
}
} // namespace cache
} // namespace dom
} // namespace mozilla

68
dom/cache/StreamList.h vendored Normal file
View file

@ -0,0 +1,68 @@
/* -*- 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_StreamList_h
#define mozilla_dom_cache_StreamList_h
#include "mozilla/dom/cache/Context.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/RefPtr.h"
#include "nsTArray.h"
class nsIInputStream;
namespace mozilla {
namespace dom {
namespace cache {
class CacheStreamControlParent;
class Manager;
class StreamList final : public Context::Activity
{
public:
StreamList(Manager* aManager, Context* aContext);
void SetStreamControl(CacheStreamControlParent* aStreamControl);
void RemoveStreamControl(CacheStreamControlParent* aStreamControl);
void Activate(CacheId aCacheId);
void Add(const nsID& aId, nsIInputStream* aStream);
already_AddRefed<nsIInputStream> Extract(const nsID& aId);
void NoteClosed(const nsID& aId);
void NoteClosedAll();
void Close(const nsID& aId);
void CloseAll();
// Context::Activity methods
virtual void Cancel() override;
virtual bool MatchesCacheId(CacheId aCacheId) const override;
private:
~StreamList();
struct Entry
{
nsID mId;
nsCOMPtr<nsIInputStream> mStream;
};
RefPtr<Manager> mManager;
RefPtr<Context> mContext;
CacheId mCacheId;
CacheStreamControlParent* mStreamControl;
nsTArray<Entry> mList;
bool mActivated;
public:
NS_INLINE_DECL_REFCOUNTING(cache::StreamList)
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_StreamList_h

504
dom/cache/TypeUtils.cpp vendored Normal file
View file

@ -0,0 +1,504 @@
/* -*- 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/TypeUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/CacheBinding.h"
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PFileDescriptorSetChild.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/SendStream.h"
#include "nsCOMPtr.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsIIPCSerializableInputStream.h"
#include "nsQueryObject.h"
#include "nsPromiseFlatString.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsURLParsers.h"
#include "nsCRT.h"
#include "nsHttp.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::ipc::AutoIPCStream;
using mozilla::ipc::BackgroundChild;
using mozilla::ipc::FileDescriptor;
using mozilla::ipc::PBackgroundChild;
using mozilla::ipc::PFileDescriptorSetChild;
namespace {
static bool
HasVaryStar(mozilla::dom::InternalHeaders* aHeaders)
{
nsCString varyHeaders;
ErrorResult rv;
aHeaders->Get(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
char* rawBuffer = varyHeaders.BeginWriting();
char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer);
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
if (header.EqualsLiteral("*")) {
return true;
}
}
return false;
}
void
ToHeadersEntryList(nsTArray<HeadersEntry>& aOut, InternalHeaders* aHeaders)
{
MOZ_DIAGNOSTIC_ASSERT(aHeaders);
AutoTArray<InternalHeaders::Entry, 16> entryList;
aHeaders->GetEntries(entryList);
for (uint32_t i = 0; i < entryList.Length(); ++i) {
InternalHeaders::Entry& entry = entryList[i];
aOut.AppendElement(HeadersEntry(entry.mName, entry.mValue));
}
}
} // namespace
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
if (aIn.IsRequest()) {
Request& request = aIn.GetAsRequest();
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
CheckAndSetBodyUsed(&request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request.GetInternalRequest();
}
return ToInternalRequest(aIn.GetAsUSVString(), aRv);
}
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
if (aIn.IsRequest()) {
RefPtr<Request> request = aIn.GetAsRequest().get();
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
CheckAndSetBodyUsed(request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request->GetInternalRequest();
}
return ToInternalRequest(aIn.GetAsUSVString(), aRv);
}
void
TypeUtils::ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
BodyAction aBodyAction, SchemeAction aSchemeAction,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aIn);
aIn->GetMethod(aOut.method());
nsCString url(aIn->GetURLWithoutFragment());
bool schemeValid;
ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv);
if (aRv.Failed()) {
return;
}
if (!schemeValid) {
if (aSchemeAction == TypeErrorOnInvalidScheme) {
NS_ConvertUTF8toUTF16 urlUTF16(url);
aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
urlUTF16);
return;
}
}
aOut.urlFragment() = aIn->GetFragment();
aIn->GetReferrer(aOut.referrer());
aOut.referrerPolicy() = aIn->ReferrerPolicy_();
RefPtr<InternalHeaders> headers = aIn->Headers();
MOZ_DIAGNOSTIC_ASSERT(headers);
ToHeadersEntryList(aOut.headers(), headers);
aOut.headersGuard() = headers->Guard();
aOut.mode() = aIn->Mode();
aOut.credentials() = aIn->GetCredentialsMode();
aOut.contentPolicyType() = aIn->ContentPolicyType();
aOut.requestCache() = aIn->GetCacheMode();
aOut.requestRedirect() = aIn->GetRedirectMode();
aOut.integrity() = aIn->GetIntegrity();
if (aBodyAction == IgnoreBody) {
aOut.body() = void_t();
return;
}
// BodyUsed flag is checked and set previously in ToInternalRequest()
nsCOMPtr<nsIInputStream> stream;
aIn->GetBody(getter_AddRefs(stream));
SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
void
TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
InternalResponse& aIn, ErrorResult& aRv)
{
aOut.type() = aIn.Type();
aIn.GetUnfilteredURLList(aOut.urlList());
AutoTArray<nsCString, 4> urlList;
aIn.GetURLList(urlList);
for (uint32_t i = 0; i < aOut.urlList().Length(); i++) {
MOZ_DIAGNOSTIC_ASSERT(!aOut.urlList()[i].IsEmpty());
// Pass all Response URL schemes through... The spec only requires we take
// action on invalid schemes for Request objects.
ProcessURL(aOut.urlList()[i], nullptr, nullptr, nullptr, aRv);
}
aOut.status() = aIn.GetUnfilteredStatus();
aOut.statusText() = aIn.GetUnfilteredStatusText();
RefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
MOZ_DIAGNOSTIC_ASSERT(headers);
if (HasVaryStar(headers)) {
aRv.ThrowTypeError<MSG_RESPONSE_HAS_VARY_STAR>();
return;
}
ToHeadersEntryList(aOut.headers(), headers);
aOut.headersGuard() = headers->Guard();
aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo();
if (aIn.GetPrincipalInfo()) {
aOut.principalInfo() = *aIn.GetPrincipalInfo();
} else {
aOut.principalInfo() = void_t();
}
}
void
TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
if (aIn.BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return;
}
RefPtr<InternalResponse> ir = aIn.GetInternalResponse();
ToCacheResponseWithoutBody(aOut, *ir, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
nsCOMPtr<nsIInputStream> stream;
ir->GetUnfilteredBody(getter_AddRefs(stream));
if (stream) {
aIn.SetBodyUsed();
}
SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
// static
void
TypeUtils::ToCacheQueryParams(CacheQueryParams& aOut,
const CacheQueryOptions& aIn)
{
aOut.ignoreSearch() = aIn.mIgnoreSearch;
aOut.ignoreMethod() = aIn.mIgnoreMethod;
aOut.ignoreVary() = aIn.mIgnoreVary;
aOut.cacheNameSet() = aIn.mCacheName.WasPassed();
if (aOut.cacheNameSet()) {
aOut.cacheName() = aIn.mCacheName.Value();
} else {
aOut.cacheName() = NS_LITERAL_STRING("");
}
}
already_AddRefed<Response>
TypeUtils::ToResponse(const CacheResponse& aIn)
{
if (aIn.type() == ResponseType::Error) {
RefPtr<InternalResponse> error = InternalResponse::NetworkError();
RefPtr<Response> r = new Response(GetGlobalObject(), error);
return r.forget();
}
RefPtr<InternalResponse> ir = new InternalResponse(aIn.status(),
aIn.statusText());
ir->SetURLList(aIn.urlList());
RefPtr<InternalHeaders> internalHeaders =
ToInternalHeaders(aIn.headers(), aIn.headersGuard());
ErrorResult result;
// Be careful to fill the headers before setting the guard in order to
// correctly re-create the original headers.
ir->Headers()->Fill(*internalHeaders, result);
MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
ir->Headers()->SetGuard(aIn.headersGuard(), result);
MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
ir->InitChannelInfo(aIn.channelInfo());
if (aIn.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) {
UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo()));
ir->SetPrincipalInfo(Move(info));
}
nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
ir->SetBody(stream, InternalResponse::UNKNOWN_BODY_SIZE);
switch (aIn.type())
{
case ResponseType::Basic:
ir = ir->BasicResponse();
break;
case ResponseType::Cors:
ir = ir->CORSResponse();
break;
case ResponseType::Default:
break;
case ResponseType::Opaque:
ir = ir->OpaqueResponse();
break;
case ResponseType::Opaqueredirect:
ir = ir->OpaqueRedirectResponse();
break;
default:
MOZ_CRASH("Unexpected ResponseType!");
}
MOZ_DIAGNOSTIC_ASSERT(ir);
RefPtr<Response> ref = new Response(GetGlobalObject(), ir);
return ref.forget();
}
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const CacheRequest& aIn)
{
nsAutoCString url(aIn.urlWithoutQuery());
url.Append(aIn.urlQuery());
RefPtr<InternalRequest> internalRequest =
new InternalRequest(url, aIn.urlFragment());
internalRequest->SetMethod(aIn.method());
internalRequest->SetReferrer(aIn.referrer());
internalRequest->SetReferrerPolicy(aIn.referrerPolicy());
internalRequest->SetMode(aIn.mode());
internalRequest->SetCredentialsMode(aIn.credentials());
internalRequest->SetContentPolicyType(aIn.contentPolicyType());
internalRequest->SetCacheMode(aIn.requestCache());
internalRequest->SetRedirectMode(aIn.requestRedirect());
internalRequest->SetIntegrity(aIn.integrity());
RefPtr<InternalHeaders> internalHeaders =
ToInternalHeaders(aIn.headers(), aIn.headersGuard());
ErrorResult result;
// Be careful to fill the headers before setting the guard in order to
// correctly re-create the original headers.
internalRequest->Headers()->Fill(*internalHeaders, result);
MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
internalRequest->SetBody(stream);
return internalRequest.forget();
}
already_AddRefed<Request>
TypeUtils::ToRequest(const CacheRequest& aIn)
{
RefPtr<InternalRequest> internalRequest = ToInternalRequest(aIn);
RefPtr<Request> request = new Request(GetGlobalObject(), internalRequest);
return request.forget();
}
// static
already_AddRefed<InternalHeaders>
TypeUtils::ToInternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
HeadersGuardEnum aGuard)
{
nsTArray<InternalHeaders::Entry> entryList(aHeadersEntryList.Length());
for (uint32_t i = 0; i < aHeadersEntryList.Length(); ++i) {
const HeadersEntry& headersEntry = aHeadersEntryList[i];
entryList.AppendElement(InternalHeaders::Entry(headersEntry.name(),
headersEntry.value()));
}
RefPtr<InternalHeaders> ref = new InternalHeaders(Move(entryList), aGuard);
return ref.forget();
}
// Utility function to remove the fragment from a URL, check its scheme, and optionally
// provide a URL without the query. We're not using nsIURL or URL to do this because
// they require going to the main thread.
// static
void
TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
nsACString* aUrlWithoutQueryOut,nsACString* aUrlQueryOut,
ErrorResult& aRv)
{
const nsAFlatCString& flatURL = PromiseFlatCString(aUrl);
const char* url = flatURL.get();
// off the main thread URL parsing using nsStdURLParser.
nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
uint32_t pathPos;
int32_t pathLen;
uint32_t schemePos;
int32_t schemeLen;
aRv = urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen,
nullptr, nullptr, // ignore authority
&pathPos, &pathLen);
if (NS_WARN_IF(aRv.Failed())) { return; }
if (aSchemeValidOut) {
nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
*aSchemeValidOut = scheme.LowerCaseEqualsLiteral("http") ||
scheme.LowerCaseEqualsLiteral("https");
}
uint32_t queryPos;
int32_t queryLen;
aRv = urlParser->ParsePath(url + pathPos, flatURL.Length() - pathPos,
nullptr, nullptr, // ignore filepath
&queryPos, &queryLen,
nullptr, nullptr);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (!aUrlWithoutQueryOut) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(aUrlQueryOut);
if (queryLen < 0) {
*aUrlWithoutQueryOut = aUrl;
*aUrlQueryOut = EmptyCString();
return;
}
// ParsePath gives us query position relative to the start of the path
queryPos += pathPos;
*aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1);
*aUrlQueryOut = Substring(aUrl, queryPos - 1, queryLen + 1);
}
void
TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aRequest);
if (aBodyAction == IgnoreBody) {
return;
}
if (aRequest->BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return;
}
nsCOMPtr<nsIInputStream> stream;
aRequest->GetBody(getter_AddRefs(stream));
if (stream) {
aRequest->SetBodyUsed();
}
}
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const nsAString& aIn, ErrorResult& aRv)
{
RequestOrUSVString requestOrString;
requestOrString.SetAsUSVString().Rebind(aIn.Data(), aIn.Length());
// Re-create a GlobalObject stack object so we can use webidl Constructors.
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
JSContext* cx = jsapi.cx();
GlobalObject global(cx, GetGlobalObject()->GetGlobalJSObject());
MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
RefPtr<Request> request = Request::Constructor(global, requestOrString,
RequestInit(), aRv);
if (NS_WARN_IF(aRv.Failed())) { return nullptr; }
return request->GetInternalRequest();
}
void
TypeUtils::SerializeCacheStream(nsIInputStream* aStream,
CacheReadStreamOrVoid* aStreamOut,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
*aStreamOut = void_t();
if (!aStream) {
return;
}
RefPtr<ReadStream> controlled = do_QueryObject(aStream);
if (controlled) {
controlled->Serialize(aStreamOut, aStreamCleanupList, aRv);
return;
}
*aStreamOut = CacheReadStream();
CacheReadStream& cacheStream = aStreamOut->get_CacheReadStream();
cacheStream.controlChild() = nullptr;
cacheStream.controlParent() = nullptr;
UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream(cacheStream.stream()));
autoStream->Serialize(aStream, GetIPCManager());
aStreamCleanupList.AppendElement(Move(autoStream));
}
} // namespace cache
} // namespace dom
} // namespace mozilla

157
dom/cache/TypeUtils.h vendored Normal file
View file

@ -0,0 +1,157 @@
/* -*- 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_TypesUtils_h
#define mozilla_dom_cache_TypesUtils_h
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/InternalHeaders.h"
#include "nsError.h"
class nsIGlobalObject;
class nsIAsyncInputStream;
class nsIInputStream;
namespace mozilla {
namespace ipc {
class PBackgroundChild;
class SendStreamChild;
class AutoIPCStream;
}
namespace dom {
struct CacheQueryOptions;
class InternalRequest;
class InternalResponse;
class OwningRequestOrUSVString;
class Request;
class RequestOrUSVString;
class Response;
namespace cache {
class CacheQueryParams;
class CacheReadStream;
class CacheReadStreamOrVoid;
class CacheRequest;
class CacheResponse;
class HeadersEntry;
class TypeUtils
{
public:
enum BodyAction
{
IgnoreBody,
ReadBody
};
enum SchemeAction
{
IgnoreInvalidScheme,
TypeErrorOnInvalidScheme
};
~TypeUtils() { }
virtual nsIGlobalObject* GetGlobalObject() const = 0;
#ifdef DEBUG
virtual void AssertOwningThread() const = 0;
#else
inline void AssertOwningThread() const { }
#endif
// This is mainly declared to support serializing body streams. Some
// TypeUtils implementations do not expect to be used for this kind of
// serialization. These classes will MOZ_CRASH() if you try to call
// GetIPCManager().
virtual mozilla::ipc::PBackgroundChild*
GetIPCManager() = 0;
already_AddRefed<InternalRequest>
ToInternalRequest(const RequestOrUSVString& aIn, BodyAction aBodyAction,
ErrorResult& aRv);
already_AddRefed<InternalRequest>
ToInternalRequest(const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
ErrorResult& aRv);
void
ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
BodyAction aBodyAction, SchemeAction aSchemeAction,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
void
ToCacheResponseWithoutBody(CacheResponse& aOut, InternalResponse& aIn,
ErrorResult& aRv);
void
ToCacheResponse(CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
void
ToCacheQueryParams(CacheQueryParams& aOut, const CacheQueryOptions& aIn);
already_AddRefed<Response>
ToResponse(const CacheResponse& aIn);
already_AddRefed<InternalRequest>
ToInternalRequest(const CacheRequest& aIn);
already_AddRefed<Request>
ToRequest(const CacheRequest& aIn);
// static methods
static already_AddRefed<InternalHeaders>
ToInternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
HeadersGuardEnum aGuard = HeadersGuardEnum::None);
// Utility method for parsing a URL and doing associated operations. A mix
// of things are done in this one method to avoid duplicated parsing:
//
// 1) The aUrl argument is modified to strip the fragment
// 2) If aSchemaValidOut is set, then a boolean value is set indicating
// if the aUrl's scheme is valid or not for storing in the cache.
// 3) If aUrlWithoutQueryOut is set, then a url string is provided without
// the search section.
// 4) If aUrlQueryOut is set then its populated with the search section
// of the URL. Note, this parameter must be set if aUrlWithoutQueryOut
// is set. They must either both be nullptr or set to valid string
// pointers.
//
// Any errors are thrown on ErrorResult.
static void
ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
nsACString* aUrlWithoutQueryOut, nsACString* aUrlQueryOut,
ErrorResult& aRv);
private:
void
CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
ErrorResult& aRv);
already_AddRefed<InternalRequest>
ToInternalRequest(const nsAString& aIn, ErrorResult& aRv);
void
SerializeCacheStream(nsIInputStream* aStream, CacheReadStreamOrVoid* aStreamOut,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
void
SerializeSendStream(nsIInputStream* aStream, CacheReadStream& aReadStreamOut,
ErrorResult& aRv);
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_TypesUtils_h

44
dom/cache/Types.h vendored Normal file
View file

@ -0,0 +1,44 @@
/* -*- 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_Types_h
#define mozilla_dom_cache_Types_h
#include <stdint.h>
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
namespace cache {
enum Namespace
{
DEFAULT_NAMESPACE,
CHROME_ONLY_NAMESPACE,
NUMBER_OF_NAMESPACES
};
static const Namespace INVALID_NAMESPACE = NUMBER_OF_NAMESPACES;
typedef int64_t CacheId;
static const CacheId INVALID_CACHE_ID = -1;
struct QuotaInfo
{
QuotaInfo() : mIsApp(false) { }
nsCOMPtr<nsIFile> mDir;
nsCString mSuffix;
nsCString mGroup;
nsCString mOrigin;
bool mIsApp;
};
} // namespace cache
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_cache_Types_h

100
dom/cache/moz.build vendored Normal file
View file

@ -0,0 +1,100 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.dom.cache += [
'Action.h',
'ActorChild.h',
'ActorUtils.h',
'AutoUtils.h',
'Cache.h',
'CacheChild.h',
'CacheOpChild.h',
'CacheOpParent.h',
'CacheParent.h',
'CacheStorage.h',
'CacheStorageChild.h',
'CacheStorageParent.h',
'CacheStreamControlChild.h',
'CacheStreamControlParent.h',
'CacheWorkerHolder.h',
'Connection.h',
'Context.h',
'DBAction.h',
'DBSchema.h',
'FileUtils.h',
'IPCUtils.h',
'Manager.h',
'ManagerId.h',
'PrincipalVerifier.h',
'QuotaClient.h',
'ReadStream.h',
'SavedTypes.h',
'StreamControl.h',
'StreamList.h',
'Types.h',
'TypeUtils.h',
]
UNIFIED_SOURCES += [
'Action.cpp',
'ActorChild.cpp',
'AutoUtils.cpp',
'Cache.cpp',
'CacheChild.cpp',
'CacheOpChild.cpp',
'CacheOpParent.cpp',
'CacheParent.cpp',
'CacheStorage.cpp',
'CacheStorageChild.cpp',
'CacheStorageParent.cpp',
'CacheStreamControlChild.cpp',
'CacheStreamControlParent.cpp',
'CacheWorkerHolder.cpp',
'Connection.cpp',
'Context.cpp',
'DBAction.cpp',
'DBSchema.cpp',
'FileUtils.cpp',
'Manager.cpp',
'ManagerId.cpp',
'PrincipalVerifier.cpp',
'QuotaClient.cpp',
'ReadStream.cpp',
'StreamControl.cpp',
'StreamList.cpp',
'TypeUtils.cpp',
]
IPDL_SOURCES += [
'CacheTypes.ipdlh',
'PCache.ipdl',
'PCacheOp.ipdl',
'PCacheStorage.ipdl',
'PCacheStreamControl.ipdl',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'../workers',
]
FINAL_LIBRARY = 'xul'
MOCHITEST_MANIFESTS += [
'test/mochitest/mochitest.ini',
]
BROWSER_CHROME_MANIFESTS += [
'test/mochitest/browser.ini',
]
XPCSHELL_TESTS_MANIFESTS += [
'test/xpcshell/xpcshell.ini',
]
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

1
dom/cache/test/mochitest/browser.ini vendored Normal file
View file

@ -0,0 +1 @@
[browser_cache_pb_window.js]

View file

@ -0,0 +1,81 @@
var name = 'pb-window-cache';
function testMatch(win) {
return new Promise(function(resolve, reject) {
win.caches.match('http://foo.com').then(function(response) {
ok(false, 'caches.match() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.match() should throw SecurityError');
resolve();
});
});
}
function testHas(win) {
return new Promise(function(resolve, reject) {
win.caches.has(name).then(function(result) {
ok(false, 'caches.has() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.has() should throw SecurityError');
resolve();
});
});
}
function testOpen(win) {
return new Promise(function(resolve, reject) {
win.caches.open(name).then(function(c) {
ok(false, 'caches.open() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.open() should throw SecurityError');
resolve();
});
});
}
function testDelete(win) {
return new Promise(function(resolve, reject) {
win.caches.delete(name).then(function(result) {
ok(false, 'caches.delete() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.delete() should throw SecurityError');
resolve();
});
});
}
function testKeys(win) {
return new Promise(function(resolve, reject) {
win.caches.keys().then(function(names) {
ok(false, 'caches.keys() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.keys() should throw SecurityError');
resolve();
});
});
}
function test() {
waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [['dom.caches.enabled', true],
['dom.caches.testing.enabled', true]]},
function() {
var privateWin = OpenBrowserWindow({private: true});
privateWin.addEventListener('load', function() {
Promise.all([
testMatch(privateWin),
testHas(privateWin),
testOpen(privateWin),
testDelete(privateWin),
testKeys(privateWin)
]).then(function() {
BrowserTestUtils.closeWindow(privateWin).then(finish);
});
});
});
}

0
dom/cache/test/mochitest/chrome.ini vendored Normal file
View file

132
dom/cache/test/mochitest/driver.js vendored Normal file
View file

@ -0,0 +1,132 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
//
// This helper script exposes a runTests function that takes the name of a
// test script as its input argument and runs the test in three different
// contexts:
// 1. Regular Worker context
// 2. Service Worker context
// 3. Window context
// The function returns a promise which will get resolved once all tests
// finish. The testFile argument is the name of the test file to be run
// in the different contexts, and the optional order argument can be set
// to either "parallel" or "sequential" depending on how the caller wants
// the tests to be run. If this argument is not provided, the default is
// "both", which runs the tests in both modes.
// The caller of this function is responsible to call SimpleTest.finish
// when the returned promise is resolved.
function runTests(testFile, order) {
function setupPrefs() {
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["dom.serviceWorkers.exemptFromPerDomainMax", true]]
}, function() {
resolve();
});
});
}
// adapted from dom/indexedDB/test/helpers.js
function clearStorage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var principal = SpecialPowers.wrap(document).nodePrincipal;
var request = qms.clearStoragesForPrincipal(principal);
var cb = SpecialPowers.wrapCallback(resolve);
request.callback = cb;
});
}
function loadScript(script) {
return new Promise(function(resolve, reject) {
var s = document.createElement("script");
s.src = script;
s.onerror = reject;
s.onload = resolve;
document.body.appendChild(s);
});
}
function importDrivers() {
return Promise.all([loadScript("worker_driver.js"),
loadScript("serviceworker_driver.js")]);
}
function runWorkerTest() {
return workerTestExec(testFile);
}
function runServiceWorkerTest() {
return serviceWorkerTestExec(testFile);
}
function runFrameTest() {
return new Promise(function(resolve, reject) {
var iframe = document.createElement("iframe");
iframe.src = "frame.html";
iframe.onload = function() {
var doc = iframe.contentDocument;
var s = doc.createElement("script");
s.src = testFile;
window.addEventListener("message", function onMessage(event) {
if (event.data.context != "Window") {
return;
}
if (event.data.type == 'finish') {
window.removeEventListener("message", onMessage);
resolve();
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.context + ": " + event.data.msg);
}
}, false);
doc.body.appendChild(s);
};
document.body.appendChild(iframe);
});
}
SimpleTest.waitForExplicitFinish();
if (typeof order == "undefined") {
order = "sequential"; // sequential by default, see bug 1143222.
// TODO: Make this "both" again.
}
ok(order == "parallel" || order == "sequential" || order == "both",
"order argument should be valid");
if (order == "both") {
info("Running tests in both modes; first: sequential");
return runTests(testFile, "sequential")
.then(function() {
info("Running tests in parallel mode");
return runTests(testFile, "parallel");
});
}
if (order == "sequential") {
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);
});
}
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);
});
}

2
dom/cache/test/mochitest/empty.html vendored Normal file
View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<!-- This is only used to give us access to the caches global after setting the pref. -->

17
dom/cache/test/mochitest/frame.html vendored Normal file
View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<script>
var context = "Window";
function ok(a, msg) {
parent.postMessage({type: 'status', status: !!a,
msg: a + ": " + msg, context: context}, "*");
}
function is(a, b, msg) {
parent.postMessage({type: 'status', status: a === b,
msg: a + " === " + b + ": " + msg, context: context}, "*");
}
function testDone() {
parent.postMessage({type: 'finish', context: context}, "*");
}
</script>

1002
dom/cache/test/mochitest/large_url_list.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<script>
navigator.serviceWorker.onmessage = function(e) {
window.parent.postMessage(e.data, "*");
};
</script>

5
dom/cache/test/mochitest/mirror.sjs vendored Normal file
View file

@ -0,0 +1,5 @@
function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Mirrored", request.getHeader("Mirror"));
response.write(request.getHeader("Mirror"));
}

47
dom/cache/test/mochitest/mochitest.ini vendored Normal file
View file

@ -0,0 +1,47 @@
[DEFAULT]
support-files =
test_cache.js
test_cache_add.js
worker_driver.js
worker_wrapper.js
frame.html
message_receiver.html
driver.js
serviceworker_driver.js
test_cache_match_request.js
test_cache_matchAll_request.js
test_cache_overwrite.js
mirror.sjs
test_cache_match_vary.js
vary.sjs
test_caches.js
test_cache_keys.js
test_cache_put.js
test_cache_requestCache.js
test_cache_delete.js
test_cache_put_reorder.js
test_cache_redirect.js
test_cache_https.js
large_url_list.js
empty.html
[test_cache.html]
[test_cache_add.html]
[test_cache_match_request.html]
[test_cache_matchAll_request.html]
[test_cache_overwrite.html]
[test_cache_match_vary.html]
[test_caches.html]
[test_cache_keys.html]
[test_cache_put.html]
[test_cache_requestCache.html]
[test_cache_delete.html]
[test_cache_put_reorder.html]
[test_cache_https.html]
[test_cache_redirect.html]
[test_cache_restart.html]
[test_cache_shrink.html]
[test_cache_orphaned_cache.html]
[test_cache_orphaned_body.html]
[test_cache_untrusted.html]
[test_chrome_constructor.html]

View file

@ -0,0 +1,44 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
function serviceWorkerTestExec(testFile) {
var isB2G = !navigator.userAgent.includes("Android") &&
/Mobile|Tablet/.test(navigator.userAgent);
if (isB2G) {
// TODO B2G doesn't support running service workers for now due to bug 1137683.
dump("Skipping running the test in SW until bug 1137683 gets fixed.\n");
return Promise.resolve();
}
return new Promise(function(resolve, reject) {
function setupSW(registration) {
var worker = registration.waiting ||
registration.active;
window.addEventListener("message",function onMessage(event) {
if (event.data.context != "ServiceWorker") {
return;
}
if (event.data.type == 'finish') {
window.removeEventListener("message", onMessage);
registration.unregister()
.then(resolve)
.catch(reject);
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.context + ": " + event.data.msg);
}
}, false);
worker.onerror = reject;
var iframe = document.createElement("iframe");
iframe.src = "message_receiver.html";
iframe.onload = function() {
worker.postMessage({ script: testFile });
};
document.body.appendChild(iframe);
}
navigator.serviceWorker.ready.then(setupSW);
navigator.serviceWorker.register("worker_wrapper.js", {scope: "."});
});
}

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Interfaces Exposed to Workers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

131
dom/cache/test/mochitest/test_cache.js vendored Normal file
View file

@ -0,0 +1,131 @@
var c = null
var request = "http://example.com/hmm?q=foobar" + context;
var response = new Response("This is some Response!");
var name = 'snafu' + context;
var foobar = 'foobar' + context;
ok(!!caches, 'caches object should be available on global');
caches.open(name).then(function(openCache) {
ok(openCache instanceof Cache, 'cache object should be resolved from caches.open');
return caches.has(name);
}).then(function(hasResult) {
ok(hasResult, 'caches.has() should resolve true');
return caches.keys();
}).then(function(keys) {
ok(!!keys, 'caches.keys() should resolve to a truthy value');
ok(keys.length >= 1, 'caches.keys() should resolve to an array of length at least 1');
ok(keys.indexOf(name) >= 0, 'caches.keys() should resolve to an array containing key');
return caches.delete(name);
}).then(function(deleteResult) {
ok(deleteResult, 'caches.delete() should resolve true');
return caches.has(name);
}).then(function(hasMissingCache) {
ok(!hasMissingCache, 'missing key should return false from has');
}).then(function() {
return caches.open(name);
}).then(function(snafu) {
return snafu.keys();
}).then(function(empty) {
is(0, empty.length, 'cache.keys() should resolve to an array of length 0');
}).then(function() {
return caches.open(name);
}).then(function(snafu) {
var req = './cachekey';
var res = new Response("Hello world");
return snafu.put('ftp://invalid', res).then(function() {
ok(false, 'This should fail');
}).catch(function (err) {
is(err.name, 'TypeError', 'put() should throw TypeError for invalid scheme');
return snafu.put(req, res);
}).then(function(v) {
return snafu;
});
}).then(function(snafu) {
return Promise.all([snafu, snafu.keys()]);
}).then(function(args) {
var snafu = args[0];
var keys = args[1];
is(1, keys.length, 'cache.keys() should resolve to an array of length 1');
ok(keys[0] instanceof Request, 'key should be a Request');
ok(keys[0].url.match(/cachekey$/), 'Request URL should match original');
return Promise.all([snafu, snafu.match(keys[0]), snafu.match('ftp://invalid')]);
}).then(function(args) {
var snafu = args[0];
var response = args[1];
ok(response instanceof Response, 'value should be a Response');
is(response.status, 200, 'Response status should be 200');
is(undefined, args[2], 'Match with invalid scheme should resolve undefined');
return Promise.all([snafu, snafu.put('./cachekey2', response)]);
}).then(function(args) {
var snafu = args[0]
return snafu.match('./cachekey2');
}).then(function(response) {
return response.text().then(function(v) {
is(v, "Hello world", "Response body should match original");
});
}).then(function() {
// FIXME(nsm): Can't use a Request object for now since the operations
// consume it's 'body'. See
// https://github.com/slightlyoff/ServiceWorker/issues/510.
return caches.open(foobar);
}).then(function(openCache) {
c = openCache;
return c.put(request, response);
}).then(function(putResponse) {
is(putResponse, undefined, 'The promise should resolve to undefined');
return c.keys(request);
}).then(function(keys) {
ok(keys, 'Valid keys object expected');
is(keys.length, 1, 'Only one key is expected');
return c.keys();
}).then(function(keys) {
ok(keys, 'Valid keys object expected');
is(keys.length, 1, 'Only one key is expected');
return c.matchAll(request);
}).then(function(matchAllResponses) {
ok(matchAllResponses, 'matchAll should succeed');
is(matchAllResponses.length, 1, 'Only one match is expected');
return c.match(request);
}).then(function(matchResponse) {
ok(matchResponse, 'match should succeed');
return caches.match(request);
}).then(function(storageMatchResponse) {
ok(storageMatchResponse, 'storage match should succeed');
return caches.match(request, {cacheName:foobar});
}).then(function(storageMatchResponse) {
ok(storageMatchResponse, 'storage match with cacheName should succeed');
var request2 = new Request("http://example.com/hmm?q=snafu" + context);
return c.match(request2, {ignoreSearch:true});
}).then(function(match2Response) {
ok(match2Response, 'match should succeed');
return c.delete(request);
}).then(function(deleteResult) {
ok(deleteResult, 'delete should succeed');
return c.keys();
}).then(function(keys) {
ok(keys, 'Valid keys object expected');
is(keys.length, 0, 'Zero keys is expected');
return c.matchAll(request);
}).then(function(matchAll2Responses) {
ok(matchAll2Responses, 'matchAll should succeed');
is(matchAll2Responses.length, 0, 'Zero matches is expected');
return caches.has(foobar);
}).then(function(hasResult) {
ok(hasResult, 'has should succeed');
return caches.keys();
}).then(function(keys) {
ok(keys, 'Valid keys object expected');
ok(keys.length >= 2, 'At least two keys are expected');
ok(keys.indexOf(name) >= 0, 'snafu should exist');
ok(keys.indexOf(foobar) >= keys.indexOf(name), 'foobar should come after it');
return caches.delete(foobar);
}).then(function(deleteResult) {
ok(deleteResult, 'delete should succeed');
return caches.has(foobar);
}).then(function(hasMissingCache) {
ok(!hasMissingCache, 'has should have a result');
return caches.delete(name);
}).then(function(deleteResult) {
ok(deleteResult, 'delete should succeed');
testDone();
})

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Interfaces Exposed to Workers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_add.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,55 @@
var singleUrl = './test_cache_add.js';
var urlList = [
'./empty.html',
'./frame.html',
'./test_cache.js'
];
var cache;
var name = "adder" + context;
caches.open(name).then(function(openCache) {
cache = openCache;
return cache.add('ftp://example.com/invalid' + context);
}).catch(function (err) {
is(err.name, 'TypeError', 'add() should throw TypeError for invalid scheme');
return cache.addAll(['http://example.com/valid' + context, 'ftp://example.com/invalid' + context]);
}).catch(function (err) {
is(err.name, 'TypeError', 'addAll() should throw TypeError for invalid scheme');
var promiseList = urlList.map(function(url) {
return cache.match(url);
});
promiseList.push(cache.match(singleUrl));
return Promise.all(promiseList);
}).then(function(resultList) {
is(urlList.length + 1, resultList.length, 'Expected number of results');
resultList.every(function(result) {
is(undefined, result, 'URLs should not already be in the cache');
});
return cache.add(singleUrl);
}).then(function(result) {
is(undefined, result, 'Successful add() should resolve undefined');
return cache.addAll(urlList);
}).then(function(result) {
is(undefined, result, 'Successful addAll() should resolve undefined');
var promiseList = urlList.map(function(url) {
return cache.match(url);
});
promiseList.push(cache.match(singleUrl));
return Promise.all(promiseList);
}).then(function(resultList) {
is(urlList.length + 1, resultList.length, 'Expected number of results');
resultList.every(function(result) {
ok(!!result, 'Responses should now be in cache for each URL.');
});
return cache.matchAll();
}).then(function(resultList) {
is(urlList.length + 1, resultList.length, 'Expected number of results');
resultList.every(function(result) {
ok(!!result, 'Responses should now be in cache for each URL.');
});
return caches.delete(name);
}).then(function() {
testDone();
}).catch(function(err) {
ok(false, 'Caught error: ' + err);
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate the Cache.delete() method</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_delete.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,111 @@
var name = "delete" + context;
var c;
function setupTest(reqs) {
return new Promise(function(resolve, reject) {
var cache;
caches.open(name).then(function(c) {
cache = c;
return c.addAll(reqs);
}).then(function() {
resolve(cache);
}).catch(function(err) {
reject(err);
});
});
}
function testBasics() {
var tests = [
"//mochi.test:8888/?foo" + context,
"//mochi.test:8888/?bar" + context,
];
var cache;
return setupTest(tests)
.then(function(c) {
cache = c;
return cache.delete("//mochi.test:8888/?baz");
}).then(function(deleted) {
ok(!deleted, "Deleting a non-existing entry should fail");
return cache.keys();
}).then(function(keys) {
is(keys.length, 2, "No entries from the cache should be deleted");
return cache.delete(tests[0]);
}).then(function(deleted) {
ok(deleted, "Deleting an existing entry should succeed");
return cache.keys();
}).then(function(keys) {
is(keys.length, 1, "Only one entry should exist now");
ok(keys[0].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted");
});
}
function testFragment() {
var tests = [
"//mochi.test:8888/?foo" + context,
"//mochi.test:8888/?bar" + context,
"//mochi.test:8888/?baz" + context + "#fragment",
];
var cache;
return setupTest(tests)
.then(function(c) {
cache = c;
return cache.delete(tests[0] + "#fragment");
}).then(function(deleted) {
ok(deleted, "Deleting an existing entry should succeed");
return cache.keys();
}).then(function(keys) {
is(keys.length, 2, "Only one entry should exist now");
ok(keys[0].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted");
ok(keys[1].url.indexOf(tests[2].replace("#fragment", "")) >= 0, "The correct entry must be deleted");
// Now, delete a request that was added with a fragment
return cache.delete("//mochi.test:8888/?baz" + context);
}).then(function(deleted) {
ok(deleted, "Deleting an existing entry should succeed");
return cache.keys();
}).then(function(keys) {
is(keys.length, 1, "Only one entry should exist now");
ok(keys[0].url.indexOf(tests[1]) >= 0, "3The correct entry must be deleted");
});
}
function testInterleaved() {
var tests = [
"//mochi.test:8888/?foo" + context,
"//mochi.test:8888/?bar" + context,
];
var newURL = "//mochi.test:8888/?baz" + context;
var cache;
return setupTest(tests)
.then(function(c) {
cache = c;
// Simultaneously add and delete a request
return Promise.all([
cache.delete(newURL),
cache.add(newURL),
]);
}).then(function(result) {
ok(!result[1], "deletion should fail");
return cache.keys();
}).then(function(keys) {
is(keys.length, 3, "Tree entries should still exist");
ok(keys[0].url.indexOf(tests[0]) >= 0, "The correct entry must be deleted");
ok(keys[1].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted");
ok(keys[2].url.indexOf(newURL) >= 0, "The new entry should be correctly inserted");
});
}
// Make sure to clean up after each test step.
function step(testPromise) {
return testPromise.then(function() {
caches.delete(name);
});
}
step(testBasics()).then(function() {
return step(testFragment());
}).then(function() {
return step(testInterleaved());
}).then(function() {
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Interfaces Exposed to Workers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_https.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
var cache = null;
var name = 'https_' + context;
var urlBase = 'https://example.com/tests/dom/cache/test/mochitest';
var url1 = urlBase + '/test_cache.js';
var url2 = urlBase + '/test_cache_add.js';
function addOpaque(cache, url) {
return fetch(new Request(url, { mode: 'no-cors' })).then(function(response) {
return cache.put(url, response);
});
}
caches.open(name).then(function(c) {
cache = c;
return Promise.all([
addOpaque(cache, url1),
addOpaque(cache, url2)
]);
}).then(function() {
return cache.delete(url1);
}).then(function(result) {
ok(result, 'Cache entry should be deleted');
return cache.delete(url2);
}).then(function(result) {
ok(result, 'Cache entry should be deleted');
cache = null;
return caches.delete(name);
}).then(function(result) {
ok(result, 'Cache should be deleted');
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate the Cache.keys() method</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_keys.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,74 @@
var name = "keys" + context;
var c;
var tests = [
"//mochi.test:8888/?page" + context,
"//mochi.test:8888/?another" + context,
];
caches.open(name).then(function(cache) {
c = cache;
return c.addAll(tests);
}).then(function() {
// Add another cache entry using Cache.add
var another = "//mochi.test:8888/?yetanother" + context;
tests.push(another);
return c.add(another);
}).then(function() {
// Add another cache entry with URL fragment using Cache.add
var anotherWithFragment = "//mochi.test:8888/?fragment" + context + "#fragment";
tests.push(anotherWithFragment);
return c.add(anotherWithFragment);
}).then(function() {
return c.keys();
}).then(function(keys) {
is(keys.length, tests.length, "Same number of elements");
// Verify both the insertion order of the requests and their validity.
keys.forEach(function(r, i) {
ok(r instanceof Request, "Valid request object");
ok(r.url.indexOf(tests[i]) >= 0, "Valid URL");
});
// Try searching for just one request
return c.keys(tests[1]);
}).then(function(keys) {
is(keys.length, 1, "One match should be found");
ok(keys[0].url.indexOf(tests[1]) >= 0, "Valid URL");
// Try to see if ignoreSearch works as expected.
return c.keys(new Request("//mochi.test:8888/?foo"), {ignoreSearch: true});
}).then(function(keys) {
is(keys.length, tests.length, "Same number of elements");
keys.forEach(function(r, i) {
ok(r instanceof Request, "Valid request object");
ok(r.url.indexOf(tests[i]) >= 0, "Valid URL");
});
// Try to see if ignoreMethod works as expected
return Promise.all(
["POST", "PUT", "DELETE", "OPTIONS"]
.map(function(method) {
var req = new Request(tests[2], {method: method});
return c.keys(req)
.then(function(keys) {
is(keys.length, 0, "No request should be matched without ignoreMethod");
return c.keys(req, {ignoreMethod: true});
}).then(function(keys) {
is(keys.length, 1, "One match should be found");
ok(keys[0].url.indexOf(tests[2]) >= 0, "Valid URL");
});
})
);
}).then(function() {
// But HEAD should be allowed even without ignoreMethod
return c.keys(new Request(tests[0], {method: "HEAD"}));
}).then(function(keys) {
is(keys.length, 1, "One match should be found");
ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL");
// Make sure cacheName is ignored.
return c.keys(tests[0], {cacheName: "non-existing-cache"});
}).then(function(keys) {
is(keys.length, 1, "One match should be found");
ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL");
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, "The cache should be successfully deleted");
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate calling matchAll with a Request object</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_matchAll_request.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,183 @@
var request1 = new Request("//mochi.test:8888/?1&" + context + "#fragment");
var request2 = new Request("//mochi.test:8888/?2&" + context);
var request3 = new Request("//mochi.test:8888/?3&" + context);
var requestWithAltQS = new Request("//mochi.test:8888/?queryString");
var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context);
var response1, response3;
var c;
var response1Text, response3Text;
var name = "matchAll-request" + context;
function checkResponse(r, response, responseText) {
ok(r !== response, "The objects should not be the same");
is(r.url, response.url.replace("#fragment", ""),
"The URLs should be the same");
is(r.status, response.status, "The status codes should be the same");
is(r.type, response.type, "The response types should be the same");
is(r.ok, response.ok, "Both responses should have succeeded");
is(r.statusText, response.statusText,
"Both responses should have the same status text");
return r.text().then(function(text) {
// Avoid dumping out the large response text to the log if they're equal.
if (text !== responseText) {
is(text, responseText, "The response body should be correct");
}
});
}
fetch(new Request(request1)).then(function(r) {
response1 = r;
return response1.text();
}).then(function(text) {
response1Text = text;
return fetch(new Request(request3));
}).then(function(r) {
response3 = r;
return response3.text();
}).then(function(text) {
response3Text = text;
return testRequest(request1, request2, request3, unknownRequest,
requestWithAltQS,
request1.url.replace("#fragment", "#other"));
}).then(function() {
return testRequest(request1.url, request2.url, request3.url,
unknownRequest.url, requestWithAltQS.url,
request1.url.replace("#fragment", "#other"));
}).then(function() {
testDone();
});
// The request arguments can either be a URL string, or a Request object.
function testRequest(request1, request2, request3, unknownRequest,
requestWithAlternateQueryString,
requestWithDifferentFragment) {
return caches.open(name).then(function(cache) {
c = cache;
return c.add(request1);
}).then(function() {
return c.add(request3);
}).then(function() {
return Promise.all(
["HEAD", "POST", "PUT", "DELETE", "OPTIONS"]
.map(function(method) {
var r = new Request(request1, {method: method});
return c.add(r)
.then(function() {
ok(false, "Promise should be rejected");
}, function(err) {
is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail");
});
})
);
}).then(function() {
return c.matchAll(request1);
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
}).then(function() {
return c.matchAll(new Request(request1, {method: "HEAD"}));
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, "");
}).then(function() {
return c.matchAll(new Request(request1, {method: "HEAD"}), {ignoreMethod: true});
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
}).then(function() {
return Promise.all(
["POST", "PUT", "DELETE", "OPTIONS"]
.map(function(method) {
var req = new Request(request1, {method: method});
return c.matchAll(req)
.then(function(r) {
is(r.length, 0, "Searching for a request with a non-GET/HEAD method should not succeed");
return c.matchAll(req, {ignoreMethod: true});
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
});
})
);
}).then(function() {
return c.matchAll(requestWithDifferentFragment);
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
}).then(function() {
return c.matchAll(requestWithAlternateQueryString,
{ignoreSearch: true});
}).then(function(r) {
is(r.length, 2, "Should find 2 items");
return Promise.all([
checkResponse(r[0], response1, response1Text),
checkResponse(r[1], response3, response3Text)
]);
}).then(function() {
return c.matchAll(request3);
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response3, response3Text);
}).then(function() {
return c.matchAll();
}).then(function(r) {
is(r.length, 2, "Should find 2 items");
return Promise.all([
checkResponse(r[0], response1, response1Text),
checkResponse(r[1], response3, response3Text)
]);
}).then(function() {
return caches.match(request1, {cacheName: name + "mambojambo"})
.then(function() {
is(typeof r, "undefined", 'Searching in the wrong cache should resolve to undefined');
return caches.has(name + "mambojambo");
}).then(function(hasCache) {
ok(!hasCache, 'The wrong cache should still not exist');
});
}).then(function() {
return c.matchAll(unknownRequest);
}).then(function(r) {
is(r.length, 0, "Searching for an unknown request should not succeed");
return caches.match(unknownRequest, {cacheName: name});
}).then(function(r) {
is(typeof r, "undefined", "Searching for an unknown request should not succeed");
// Make sure that cacheName is ignored on Cache
return c.matchAll(request1, {cacheName: name + "mambojambo"});
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
}).then(function() {
return caches.delete(name);
}).then(function(success) {
ok(success, "We should be able to delete the cache successfully");
// Make sure that the cache is still usable after deletion.
return c.matchAll(request1);
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response1, response1Text);
}).then(function() {
return c.matchAll(request3);
}).then(function(r) {
is(r.length, 1, "Should only find 1 item");
return checkResponse(r[0], response3, response3Text);
}).then(function() {
return c.matchAll();
}).then(function(r) {
is(r.length, 2, "Should find 2 items");
return Promise.all([
checkResponse(r[0], response1, response1Text),
checkResponse(r[1], response3, response3Text)
]);
}).then(function() {
// Now, drop the cache, reopen and verify that we can't find the request any more.
c = null;
return caches.open(name);
}).then(function(cache) {
return cache.matchAll();
}).then(function(r) {
is(r.length, 0, "Searching in the cache after deletion should not succeed");
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, "The cache should be deleted successfully");
});
}

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate calling match with a Request object</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_match_request.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,145 @@
var request = new Request("//mochi.test:8888/?" + context + "#fragment");
var requestWithAltQS = new Request("//mochi.test:8888/?queryString");
var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context);
var response;
var c;
var responseText;
var name = "match-request" + context;
function checkResponse(r, expectedBody) {
if (expectedBody === undefined) {
expectedBody = responseText;
}
ok(r !== response, "The objects should not be the same");
is(r.url, response.url.replace("#fragment", ""),
"The URLs should be the same");
is(r.status, response.status, "The status codes should be the same");
is(r.type, response.type, "The response types should be the same");
is(r.ok, response.ok, "Both responses should have succeeded");
is(r.statusText, response.statusText,
"Both responses should have the same status text");
return r.text().then(function(text) {
// Avoid dumping out the large response text to the log if they're equal.
if (text !== expectedBody) {
is(text, responseText, "The response body should be correct");
}
});
}
fetch(new Request(request)).then(function(r) {
response = r;
return response.text();
}).then(function(text) {
responseText = text;
return testRequest(request, unknownRequest, requestWithAltQS,
request.url.replace("#fragment", "#other"));
}).then(function() {
return testRequest(request.url, unknownRequest.url, requestWithAltQS.url,
request.url.replace("#fragment", "#other"));
}).then(function() {
testDone();
});
// The request argument can either be a URL string, or a Request object.
function testRequest(request, unknownRequest, requestWithAlternateQueryString,
requestWithDifferentFragment) {
return caches.open(name).then(function(cache) {
c = cache;
return c.add(request);
}).then(function() {
return Promise.all(
["HEAD", "POST", "PUT", "DELETE", "OPTIONS"]
.map(function(method) {
var r = new Request(request, {method: method});
return c.add(r)
.then(function() {
ok(false, "Promise should be rejected");
}, function(err) {
is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail");
});
})
);
}).then(function() {
return c.match(request);
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return c.match(new Request(request, {method: "HEAD"}));
}).then(function(r) {
return checkResponse(r, '');
}).then(function() {
return c.match(new Request(request, {method: "HEAD"}), {ignoreMethod: true});
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return Promise.all(
["POST", "PUT", "DELETE", "OPTIONS"]
.map(function(method) {
var req = new Request(request, {method: method});
return c.match(req)
.then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-GET/HEAD method should not succeed");
return c.match(req, {ignoreMethod: true});
}).then(function(r) {
return checkResponse(r);
});
})
);
}).then(function() {
return caches.match(request);
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(requestWithDifferentFragment);
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(requestWithAlternateQueryString,
{ignoreSearch: true, cacheName: name});
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(request, {cacheName: name});
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(request, {cacheName: name + "mambojambo"})
.then(function(result) {
is(typeof r, "undefined", 'Searching in the wrong cache should resolve to undefined');
return caches.has(name + "mambojambo");
}).then(function(hasCache) {
ok(!hasCache, 'The wrong cache should still not exist');
});
}).then(function() {
// Make sure that cacheName is ignored on Cache
return c.match(request, {cacheName: name + "mambojambo"});
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return c.match(unknownRequest);
}).then(function(r) {
is(typeof r, "undefined", "Searching for an unknown request should not succeed");
return caches.match(unknownRequest);
}).then(function(r) {
is(typeof r, "undefined", "Searching for an unknown request should not succeed");
return caches.match(unknownRequest, {cacheName: name});
}).then(function(r) {
is(typeof r, "undefined", "Searching for an unknown request should not succeed");
return caches.delete(name);
}).then(function(success) {
ok(success, "We should be able to delete the cache successfully");
// Make sure that the cache is still usable after deletion.
return c.match(request);
}).then(function(r) {
return checkResponse(r);
}).then(function() {
// Now, drop the cache, reopen and verify that we can't find the request any more.
c = null;
return caches.open(name);
}).then(function(cache) {
return cache.match(request);
}).then(function(r) {
is(typeof r, "undefined", "Searching in the cache after deletion should not succeed");
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, "The cache should be deleted successfully");
});
}

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate calling match with requests involving the Vary header</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_match_vary.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,334 @@
var requestURL = "//mochi.test:8888/tests/dom/cache/test/mochitest/vary.sjs?" + context;
var name = "match-vary" + context;
function checkResponse(r, response, responseText) {
ok(r !== response, "The objects should not be the same");
is(r.url, response.url.replace("#fragment", ""),
"The URLs should be the same");
is(r.status, response.status, "The status codes should be the same");
is(r.type, response.type, "The response types should be the same");
is(r.ok, response.ok, "Both responses should have succeeded");
is(r.statusText, response.statusText,
"Both responses should have the same status text");
is(r.headers.get("Vary"), response.headers.get("Vary"),
"Both responses should have the same Vary header");
return r.text().then(function(text) {
is(text, responseText, "The response body should be correct");
});
}
// Returns a Promise that will be resolved to an object with the following
// properties:
// * cache: A Cache object that contains one entry fetched with headers.
// * response: A Response object which is the result of fetching a request
// with the specified headers.
// * responseText: The body of the above response object.
function setupTest(headers) {
return setupTestMultipleEntries([headers]).then(function(test) {
return {response: test.response[0],
responseText: test.responseText[0],
cache: test.cache};
});
}
function setupTestMultipleEntries(headers) {
ok(Array.isArray(headers), "headers should be an array");
return new Promise(function(resolve, reject) {
var response, responseText, cache;
Promise.all(headers.map(function(h) {
return fetch(requestURL, {headers: h});
})).then(function(r) {
response = r;
return Promise.all(response.map(function(r) {
return r.text();
}));
}).then(function(text) {
responseText = text;
return caches.open(name);
}).then(function(c) {
cache = c;
return Promise.all(headers.map(function(h) {
return c.add(new Request(requestURL, {headers: h}));
}));
}).then(function() {
resolve({response: response, responseText: responseText, cache: cache});
}).catch(function(err) {
reject(err);
});
});
}
function testBasics() {
var test;
return setupTest({"WhatToVary": "Custom"})
.then(function(t) {
test = t;
// Ensure that searching without specifying a Custom header succeeds.
return test.cache.match(requestURL);
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that searching with a non-matching value for the Custom header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom": "foo=bar"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with an unknown Vary header should not succeed");
// Ensure that searching with a non-matching value for the Custom header but with ignoreVary set succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom": "foo=bar"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
});
}
function testBasicKeys() {
function checkRequest(reqs) {
is(reqs.length, 1, "One request expected");
ok(reqs[0].url.indexOf(requestURL) >= 0, "The correct request expected");
ok(reqs[0].headers.get("WhatToVary"), "Custom", "The correct request headers expected");
}
var test;
return setupTest({"WhatToVary": "Custom"})
.then(function(t) {
test = t;
// Ensure that searching without specifying a Custom header succeeds.
return test.cache.keys(requestURL);
}).then(function(r) {
return checkRequest(r);
}).then(function() {
// Ensure that searching with a non-matching value for the Custom header fails.
return test.cache.keys(new Request(requestURL, {headers: {"Custom": "foo=bar"}}));
}).then(function(r) {
is(r.length, 0, "Searching for a request with an unknown Vary header should not succeed");
// Ensure that searching with a non-matching value for the Custom header but with ignoreVary set succeeds.
return test.cache.keys(new Request(requestURL, {headers: {"Custom": "foo=bar"}}),
{ignoreVary: true});
}).then(function(r) {
return checkRequest(r);
});
}
function testStar() {
function ensurePromiseRejected(promise) {
return promise
.then(function() {
ok(false, "Promise should be rejected");
}, function(err) {
is(err.name, "TypeError", "Attempting to store a Response with a Vary:* header must fail");
});
}
var test;
return new Promise(function(resolve, reject) {
var cache;
caches.open(name).then(function(c) {
cache = c;
Promise.all([
ensurePromiseRejected(
cache.add(new Request(requestURL + "1", {headers: {"WhatToVary": "*"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "2", {headers: {"WhatToVary": "*"}}),
requestURL + "3",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "4", {headers: {"WhatToVary": "*"}}))
.then(function(response) {
return cache.put(requestURL + "4", response);
})),
ensurePromiseRejected(
cache.add(new Request(requestURL + "5", {headers: {"WhatToVary": "*,User-Agent"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "6", {headers: {"WhatToVary": "*,User-Agent"}}),
requestURL + "7",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "8", {headers: {"WhatToVary": "*,User-Agent"}}))
.then(function(response) {
return cache.put(requestURL + "8", response);
})),
ensurePromiseRejected(
cache.add(new Request(requestURL + "9", {headers: {"WhatToVary": "User-Agent,*"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "10", {headers: {"WhatToVary": "User-Agent,*"}}),
requestURL + "10",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "11", {headers: {"WhatToVary": "User-Agent,*"}}))
.then(function(response) {
return cache.put(requestURL + "11", response);
})),
]).then(reject, resolve);
});
});
}
function testMatch() {
var test;
return setupTest({"WhatToVary": "Custom", "Custom": "foo=bar"})
.then(function(t) {
test = t;
// Ensure that searching with a different Custom header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom": "bar=baz"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching Custom header should not succeed");
// Ensure that searching with the same Custom header succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom": "foo=bar"}}));
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
});
}
function testInvalidHeaderName() {
var test;
return setupTest({"WhatToVary": "Foo/Bar, Custom-User-Agent"})
.then(function(t) {
test = t;
// Ensure that searching with a different User-Agent header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom-User-Agent": "MyUA"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching Custom-User-Agent header should not succeed");
// Ensure that searching with a different Custom-User-Agent header but with ignoreVary succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-User-Agent": "MyUA"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that we do not mistakenly recognize the tokens in the invalid header name.
return test.cache.match(new Request(requestURL, {headers: {"Foo": "foobar"}}));
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
});
}
function testMultipleHeaders() {
var test;
return setupTest({"WhatToVary": "Custom-Referer,\tCustom-Accept-Encoding"})
.then(function(t) {
test = t;
// Ensure that searching with a different Referer header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Referer": "https://somesite.com/"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching Custom-Referer header should not succeed");
// Ensure that searching with a different Custom-Referer header but with ignoreVary succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Referer": "https://somesite.com/"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that searching with a different Custom-Accept-Encoding header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Accept-Encoding": "myencoding"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching Custom-Accept-Encoding header should not succeed");
// Ensure that searching with a different Custom-Accept-Encoding header but with ignoreVary succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Accept-Encoding": "myencoding"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that searching with an empty Custom-Referer header succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Referer": ""}}));
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that searching with an empty Custom-Accept-Encoding header succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Accept-Encoding": ""}}));
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
}).then(function() {
// Ensure that searching with an empty Custom-Referer header but with a different Custom-Accept-Encoding header fails.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Referer": "",
"Custom-Accept-Encoding": "myencoding"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching Custom-Accept-Encoding header should not succeed");
// Ensure that searching with an empty Custom-Referer header but with a different Custom-Accept-Encoding header and ignoreVary succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Custom-Referer": "",
"Custom-Accept-Encoding": "myencoding"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
});
}
function testMultipleCacheEntries() {
var test;
return setupTestMultipleEntries([
{"WhatToVary": "Accept-Language", "Accept-Language": "en-US"},
{"WhatToVary": "Accept-Language", "Accept-Language": "en-US, fa-IR"},
]).then(function(t) {
test = t;
return test.cache.matchAll();
}).then(function (r) {
is(r.length, 2, "Two cache entries should be stored in the DB");
// Ensure that searching without specifying an Accept-Language header fails.
return test.cache.matchAll(requestURL);
}).then(function(r) {
is(r.length, 0, "Searching for a request without specifying an Accept-Language header should not succeed");
// Ensure that searching without specifying an Accept-Language header but with ignoreVary succeeds.
return test.cache.matchAll(requestURL, {ignoreVary: true});
}).then(function(r) {
return Promise.all([
checkResponse(r[0], test.response[0], test.responseText[0]),
checkResponse(r[1], test.response[1], test.responseText[1]),
]);
}).then(function() {
// Ensure that searching with Accept-Language: en-US succeeds.
return test.cache.matchAll(new Request(requestURL, {headers: {"Accept-Language": "en-US"}}));
}).then(function(r) {
is(r.length, 1, "One cache entry should be found");
return checkResponse(r[0], test.response[0], test.responseText[0]);
}).then(function() {
// Ensure that searching with Accept-Language: en-US,fa-IR succeeds.
return test.cache.matchAll(new Request(requestURL, {headers: {"Accept-Language": "en-US, fa-IR"}}));
}).then(function(r) {
is(r.length, 1, "One cache entry should be found");
return checkResponse(r[0], test.response[1], test.responseText[1]);
}).then(function() {
// Ensure that searching with a valid Accept-Language header but with ignoreVary returns both entries.
return test.cache.matchAll(new Request(requestURL, {headers: {"Accept-Language": "en-US"}}),
{ignoreVary: true});
}).then(function(r) {
return Promise.all([
checkResponse(r[0], test.response[0], test.responseText[0]),
checkResponse(r[1], test.response[1], test.responseText[1]),
]);
}).then(function() {
// Ensure that searching with Accept-Language: fa-IR fails.
return test.cache.matchAll(new Request(requestURL, {headers: {"Accept-Language": "fa-IR"}}));
}).then(function(r) {
is(r.length, 0, "Searching for a request with a different Accept-Language header should not succeed");
// Ensure that searching with Accept-Language: fa-IR but with ignoreVary should succeed.
return test.cache.matchAll(new Request(requestURL, {headers: {"Accept-Language": "fa-IR"}}),
{ignoreVary: true});
}).then(function(r) {
is(r.length, 2, "Two cache entries should be found");
return Promise.all([
checkResponse(r[0], test.response[0], test.responseText[0]),
checkResponse(r[1], test.response[1], test.responseText[1]),
]);
});
}
// Make sure to clean up after each test step.
function step(testPromise) {
return testPromise.then(function() {
caches.delete(name);
}, function() {
caches.delete(name);
});
}
step(testBasics()).then(function() {
return step(testBasicKeys());
}).then(function() {
return step(testStar());
}).then(function() {
return step(testMatch());
}).then(function() {
return step(testInvalidHeaderName());
}).then(function() {
return step(testMultipleHeaders());
}).then(function() {
return step(testMultipleCacheEntries());
}).then(function() {
testDone();
});

View file

@ -0,0 +1,235 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test Cache with QuotaManager Restart</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="large_url_list.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
function setupTestIframe() {
return new Promise(function(resolve) {
var iframe = document.createElement("iframe");
iframe.src = "empty.html";
iframe.onload = function() {
window.caches = iframe.contentWindow.caches;
resolve();
};
document.body.appendChild(iframe);
});
}
function clearStorage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var principal = SpecialPowers.wrap(document).nodePrincipal;
var request = qms.clearStoragesForPrincipal(principal);
var cb = SpecialPowers.wrapCallback(resolve);
request.callback = cb;
});
}
function storageUsage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var principal = SpecialPowers.wrap(document).nodePrincipal;
var cb = SpecialPowers.wrapCallback(function(request) {
var result = request.result;
resolve(result.usage, result.fileUsage);
});
qms.getUsageForPrincipal(principal, cb);
});
}
function groupUsage() {
return new Promise(function(resolve, reject) {
navigator.storage.estimate().then(storageEstimation => {
resolve(storageEstimation.usage, 0);
});
});
}
function workerGroupUsage() {
return new Promise(function(resolve, reject) {
function workerScript() {
navigator.storage.estimate().then(storageEstimation => {
postMessage(storageEstimation.usage);
});
}
let url =
URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
let worker = new Worker(url);
worker.onmessage = function (e) {
resolve(e.data, 0);
};
});
}
function resetStorage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var request = qms.reset();
var cb = SpecialPowers.wrapCallback(resolve);
request.callback = cb;
});
}
function gc() {
return new Promise(function(resolve, reject) {
SpecialPowers.exactGC(resolve);
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true],
["dom.storageManager.enabled", true]],
}, function() {
var name = 'orphanedBodyOwner';
var cache = null;
var response = null;
var initialUsage = 0;
var fullUsage = 0;
var resetUsage = 0;
var endUsage = 0;
var url = 'test_cache_add.js';
// start from a fresh origin directory so other tests do not influence our
// results
setupTestIframe().then(function() {
return clearStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
is(0, usage, 'disk usage should be zero to start');
})
// Initialize and populate an initial cache to get the base sqlite pages
// and directory structure allocated.
.then(function() {
return caches.open(name);
}).then(function(c) {
return c.add(url);
}).then(function() {
return gc();
}).then(function() {
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, 'cache should be deleted');
// This is a bit superfluous, but its necessary to make sure the Cache is
// fully deleted before we proceed. The deletion actually takes place in
// two async steps. We don't want to resetStorage() until the second step
// has taken place. This extra Cache operation ensure that all the
// runnables have been flushed through the threads, etc.
return caches.has(name);
})
// Now measure initial disk usage
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
initialUsage = usage;
})
// Now re-populate the Cache object
.then(function() {
return caches.open(name);
}).then(function(c) {
cache = c;
return cache.add(url);
})
// Get a reference to the body we've stored in the Cache.
.then(function() {
return cache.match(url);
}).then(function(r) {
response = r;
return cache.delete(url);
}).then(function(result) {
ok(result, "Cache entry should be deleted");
})
// Reset the quota dir while the cache entry is deleted, but still referenced
// from the DOM. This forces the body to be orphaned.
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
fullUsage = usage;
ok(fullUsage > initialUsage, 'disk usage should have grown');
})
// Test groupUsage()
.then(function() {
return resetStorage();
}).then(function() {
return groupUsage();
}).then(function(usage) {
fullUsage = usage;
ok(fullUsage > initialUsage, 'disk group usage should have grown');
})
// Test workerGroupUsage()
.then(function() {
return resetStorage();
}).then(function() {
return workerGroupUsage();
}).then(function(usage) {
fullUsage = usage;
ok(fullUsage > initialUsage, 'disk group usage on worker should have grown');
})
// Now perform a new Cache operation that will reopen the origin. This
// should clean up the orphaned body.
.then(function() {
return caches.match(url);
}).then(function(r) {
ok(!r, 'response should not exist in storage');
})
// Finally, verify orphaned data was cleaned up by re-checking the disk
// usage. Reset the storage first to ensure any WAL transaction files
// are flushed before measuring the usage.
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
endUsage = usage;
dump("### ### initial:" + initialUsage + ", full:" + fullUsage +
", end:" + endUsage + "\n");
ok(endUsage < fullUsage, 'disk usage should have shrank');
is(endUsage, initialUsage, 'disk usage should return to original');
})
// Verify that the stale, orphaned response cannot be put back into
// the cache.
.then(function() {
ok(!response.bodyUsed, 'response body should not be considered used');
return cache.put(url, response).then(function() {
ok(false, 'Should not be able to store stale orphaned body.');
}).catch(function(e) {
is(e.name, 'TypeError', 'storing a stale orphaned body should throw TypeError');
});
}).then(function() {
ok(response.bodyUsed, 'attempting to store response should mark body used');
})
.then(function() {
SimpleTest.finish();
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,165 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test Cache with QuotaManager Restart</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="large_url_list.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
function setupTestIframe() {
return new Promise(function(resolve) {
var iframe = document.createElement("iframe");
iframe.src = "empty.html";
iframe.onload = function() {
window.caches = iframe.contentWindow.caches;
resolve();
};
document.body.appendChild(iframe);
});
}
function clearStorage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var principal = SpecialPowers.wrap(document).nodePrincipal;
var request = qms.clearStoragesForPrincipal(principal);
var cb = SpecialPowers.wrapCallback(resolve);
request.callback = cb;
});
}
function storageUsage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var principal = SpecialPowers.wrap(document).nodePrincipal;
var cb = SpecialPowers.wrapCallback(function(request) {
var result = request.result;
resolve(result.usage, result.fileUsage);
});
qms.getUsageForPrincipal(principal, cb);
});
}
function resetStorage() {
return new Promise(function(resolve, reject) {
var qms = SpecialPowers.Services.qms;
var request = qms.reset();
var cb = SpecialPowers.wrapCallback(resolve);
request.callback = cb;
});
}
function gc() {
return new Promise(function(resolve, reject) {
SpecialPowers.exactGC(resolve);
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true]],
}, function() {
var name = 'toBeOrphaned';
var cache = null;
var initialUsage = 0;
var fullUsage = 0;
var resetUsage = 0;
var endUsage = 0;
var url = 'test_cache_add.js';
// start from a fresh origin directory so other tests do not influence our
// results
setupTestIframe().then(function() {
return clearStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
is(0, usage, 'disk usage should be zero to start');
})
// Initialize and populate an initial cache to get the base sqlite pages
// and directory structure allocated.
.then(function() {
return caches.open(name);
}).then(function(c) {
return c.add(url);
}).then(function() {
return gc();
}).then(function() {
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, 'cache should be deleted');
// This is a bit superfluous, but its necessary to make sure the Cache is
// fully deleted before we proceed. The deletion actually takes place in
// two async steps. We don't want to resetStorage() until the second step
// has taken place. This extra Cache operation ensure that all the
// runnables have been flushed through the threads, etc.
return caches.has(name);
})
// Now measure initial disk usage
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
initialUsage = usage;
})
// Now re-populate the Cache object
.then(function() {
return caches.open(name);
}).then(function(c) {
cache = c;
return cache.add(url);
}).then(function() {
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, 'cache should be deleted');
})
// Reset the quota dir while the cache is deleted, but still referenced
// from the DOM. This forces it to be orphaned.
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
fullUsage = usage;
ok(fullUsage > initialUsage, 'disk usage should have grown');
})
// Now perform a new Cache operation that will reopen the origin. This
// should clean up the orphaned Cache data.
.then(function() {
return caches.has(name);
}).then(function(result) {
ok(!result, 'cache should not exist in storage');
})
// Finally, verify orphaned data was cleaned up by re-checking the disk
// usage. Reset the storage first to ensure any WAL transaction files
// are flushed before measuring the usage.
.then(function() {
return resetStorage();
}).then(function() {
return storageUsage();
}).then(function(usage) {
endUsage = usage;
dump("### ### initial:" + initialUsage + ", full:" + fullUsage +
", end:" + endUsage + "\n");
ok(endUsage < fullUsage, 'disk usage should have shrank');
is(endUsage, initialUsage, 'disk usage should return to original');
SimpleTest.finish();
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test what happens when you overwrite a cache entry</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_overwrite.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,47 @@
var requestURL = "//mochi.test:8888/tests/dom/cache/test/mochitest/mirror.sjs?" + context;
var response;
var c;
var responseText;
var name = "match-mirror" + context;
function checkResponse(r) {
ok(r !== response, "The objects should not be the same");
is(r.url, response.url.replace("#fragment", ""),
"The URLs should be the same");
is(r.status, response.status, "The status codes should be the same");
is(r.type, response.type, "The response types should be the same");
is(r.ok, response.ok, "Both responses should have succeeded");
is(r.statusText, response.statusText,
"Both responses should have the same status text");
is(r.headers.get("Mirrored"), response.headers.get("Mirrored"),
"Both responses should have the same Mirrored header");
return r.text().then(function(text) {
is(text, responseText, "The response body should be correct");
});
}
fetch(new Request(requestURL, {headers: {"Mirror": "bar"}})).then(function(r) {
is(r.headers.get("Mirrored"), "bar", "The server should give back the correct header");
response = r;
return response.text();
}).then(function(text) {
responseText = text;
return caches.open(name);
}).then(function(cache) {
c = cache;
return c.add(new Request(requestURL, {headers: {"Mirror": "foo"}}));
}).then(function() {
// Overwrite the request, to replace the entry stored in response_headers
// with a different value.
return c.add(new Request(requestURL, {headers: {"Mirror": "bar"}}));
}).then(function() {
return c.matchAll();
}).then(function(r) {
is(r.length, 1, "Only one request should be in the cache");
return checkResponse(r[0]);
}).then(function() {
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, "The cache should be deleted successfully");
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Interfaces Exposed to Workers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_put.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,50 @@
var url = 'test_cache.js';
var cache;
var fetchResponse;
Promise.all([fetch(url),
caches.open('putter' + context)]).then(function(results) {
fetchResponse = results[0];
cache = results[1];
return cache.put(url, fetchResponse.clone());
}).then(function(result) {
is(undefined, result, 'Successful put() should resolve undefined');
return cache.match(url);
}).then(function(response) {
ok(response, 'match() should find resppnse that was previously put()');
ok(response.url.endsWith(url), 'matched response should match original url');
return Promise.all([fetchResponse.text(),
response.text()]);
}).then(function(results) {
// suppress large assert spam unless it's relevent
if (results[0] !== results[1]) {
is(results[0], results[1], 'stored response body should match original');
}
// Now, try to overwrite the request with a different response object.
return cache.put(url, new Response("overwritten"));
}).then(function() {
return cache.matchAll(url);
}).then(function(result) {
is(result.length, 1, "Only one entry should exist");
return result[0].text();
}).then(function(body) {
is(body, "overwritten", "The cache entry should be successfully overwritten");
// Now, try to write a URL with a fragment
return cache.put(url + "#fragment", new Response("more overwritten"));
}).then(function() {
return cache.matchAll(url + "#differentFragment");
}).then(function(result) {
is(result.length, 1, "Only one entry should exist");
return result[0].text();
}).then(function(body) {
is(body, "more overwritten", "The cache entry should be successfully overwritten");
// TODO: Verify that trying to store a response with an error raises a TypeError
// when bug 1147178 is fixed.
return caches.delete('putter' + context);
}).then(function(deleted) {
ok(deleted, "The cache should be deleted successfully");
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Ensure that using Cache.put to overwrite an entry will change its insertion order</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_put_reorder.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
var name = "putreorder" + context;
var c;
var reqs = [
"//mochi.test:8888/?foo" + context,
"//mochi.test:8888/?bar" + context,
"//mochi.test:8888/?baz" + context,
];
caches.open(name).then(function(cache) {
c = cache;
return c.addAll(reqs);
}).then(function() {
return c.put(reqs[1], new Response("overwritten"));
}).then(function() {
return c.keys();
}).then(function(keys) {
is(keys.length, 3, "Correct number of entries expected");
ok(keys[0].url.indexOf(reqs[0]) >= 0, "The first entry should be untouched");
ok(keys[2].url.indexOf(reqs[1]) >= 0, "The second entry should be moved to the end");
ok(keys[1].url.indexOf(reqs[2]) >= 0, "The third entry should now be the second one");
return c.match(reqs[1]);
}).then(function(r) {
return r.text();
}).then(function(body) {
is(body, "overwritten", "The body should be overwritten");
return caches.delete(name);
}).then(function(deleted) {
ok(deleted, "The cache should be deleted successfully");
testDone();
});

View file

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Cache storage of redirect responses</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_redirect.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more