mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-03 13:57:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
40
dom/cache/Action.cpp
vendored
Normal file
40
dom/cache/Action.cpp
vendored
Normal 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
105
dom/cache/Action.h
vendored
Normal 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
66
dom/cache/ActorChild.cpp
vendored
Normal 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
48
dom/cache/ActorChild.h
vendored
Normal 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
65
dom/cache/ActorUtils.h
vendored
Normal 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
565
dom/cache/AutoUtils.cpp
vendored
Normal 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
104
dom/cache/AutoUtils.h
vendored
Normal 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
659
dom/cache/Cache.cpp
vendored
Normal 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
120
dom/cache/Cache.h
vendored
Normal 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
185
dom/cache/CacheChild.cpp
vendored
Normal 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
111
dom/cache/CacheChild.h
vendored
Normal 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
268
dom/cache/CacheOpChild.cpp
vendored
Normal 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
84
dom/cache/CacheOpChild.h
vendored
Normal 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
230
dom/cache/CacheOpParent.cpp
vendored
Normal 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
80
dom/cache/CacheOpParent.h
vendored
Normal 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
89
dom/cache/CacheParent.cpp
vendored
Normal 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
50
dom/cache/CacheParent.h
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_cache_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
628
dom/cache/CacheStorage.cpp
vendored
Normal 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
130
dom/cache/CacheStorage.h
vendored
Normal 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
148
dom/cache/CacheStorageChild.cpp
vendored
Normal 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
81
dom/cache/CacheStorageChild.h
vendored
Normal 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
143
dom/cache/CacheStorageParent.cpp
vendored
Normal 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
60
dom/cache/CacheStorageParent.h
vendored
Normal 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
159
dom/cache/CacheStreamControlChild.cpp
vendored
Normal 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
67
dom/cache/CacheStreamControlChild.h
vendored
Normal 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
158
dom/cache/CacheStreamControlParent.cpp
vendored
Normal 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
72
dom/cache/CacheStreamControlParent.h
vendored
Normal 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
237
dom/cache/CacheTypes.ipdlh
vendored
Normal 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
109
dom/cache/CacheWorkerHolder.cpp
vendored
Normal 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
54
dom/cache/CacheWorkerHolder.h
vendored
Normal 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
286
dom/cache/Connection.cpp
vendored
Normal 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
37
dom/cache/Connection.h
vendored
Normal 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
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
235
dom/cache/Context.h
vendored
Normal 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
231
dom/cache/DBAction.cpp
vendored
Normal 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
79
dom/cache/DBAction.h
vendored
Normal 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
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
129
dom/cache/DBSchema.h
vendored
Normal 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
501
dom/cache/FileUtils.cpp
vendored
Normal 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
69
dom/cache/FileUtils.h
vendored
Normal 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
23
dom/cache/IPCUtils.h
vendored
Normal 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
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
292
dom/cache/Manager.h
vendored
Normal 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
74
dom/cache/ManagerId.cpp
vendored
Normal 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
|
||||
"aOrigin,
|
||||
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
60
dom/cache/ManagerId.h
vendored
Normal 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
33
dom/cache/PCache.ipdl
vendored
Normal 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
29
dom/cache/PCacheOp.ipdl
vendored
Normal 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
34
dom/cache/PCacheStorage.ipdl
vendored
Normal 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
28
dom/cache/PCacheStreamControl.ipdl
vendored
Normal 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
222
dom/cache/PrincipalVerifier.cpp
vendored
Normal 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
79
dom/cache/PrincipalVerifier.h
vendored
Normal 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
243
dom/cache/QuotaClient.cpp
vendored
Normal 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
24
dom/cache/QuotaClient.h
vendored
Normal 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
578
dom/cache/ReadStream.cpp
vendored
Normal 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
119
dom/cache/ReadStream.h
vendored
Normal 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
45
dom/cache/SavedTypes.h
vendored
Normal 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
105
dom/cache/StreamControl.cpp
vendored
Normal 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
92
dom/cache/StreamControl.h
vendored
Normal 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
175
dom/cache/StreamList.cpp
vendored
Normal 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
68
dom/cache/StreamList.h
vendored
Normal 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
504
dom/cache/TypeUtils.cpp
vendored
Normal 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
157
dom/cache/TypeUtils.h
vendored
Normal 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
44
dom/cache/Types.h
vendored
Normal 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
100
dom/cache/moz.build
vendored
Normal 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
1
dom/cache/test/mochitest/browser.ini
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[browser_cache_pb_window.js]
|
||||
81
dom/cache/test/mochitest/browser_cache_pb_window.js
vendored
Normal file
81
dom/cache/test/mochitest/browser_cache_pb_window.js
vendored
Normal 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
0
dom/cache/test/mochitest/chrome.ini
vendored
Normal file
132
dom/cache/test/mochitest/driver.js
vendored
Normal file
132
dom/cache/test/mochitest/driver.js
vendored
Normal 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
2
dom/cache/test/mochitest/empty.html
vendored
Normal 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
17
dom/cache/test/mochitest/frame.html
vendored
Normal 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
1002
dom/cache/test/mochitest/large_url_list.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
6
dom/cache/test/mochitest/message_receiver.html
vendored
Normal file
6
dom/cache/test/mochitest/message_receiver.html
vendored
Normal 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
5
dom/cache/test/mochitest/mirror.sjs
vendored
Normal 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
47
dom/cache/test/mochitest/mochitest.ini
vendored
Normal 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]
|
||||
44
dom/cache/test/mochitest/serviceworker_driver.js
vendored
Normal file
44
dom/cache/test/mochitest/serviceworker_driver.js
vendored
Normal 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: "."});
|
||||
});
|
||||
}
|
||||
20
dom/cache/test/mochitest/test_cache.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache.html
vendored
Normal 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
131
dom/cache/test/mochitest/test_cache.js
vendored
Normal 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();
|
||||
})
|
||||
20
dom/cache/test/mochitest/test_cache_add.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_add.html
vendored
Normal 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>
|
||||
55
dom/cache/test/mochitest/test_cache_add.js
vendored
Normal file
55
dom/cache/test/mochitest/test_cache_add.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_delete.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_delete.html
vendored
Normal 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>
|
||||
111
dom/cache/test/mochitest/test_cache_delete.js
vendored
Normal file
111
dom/cache/test/mochitest/test_cache_delete.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_https.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_https.html
vendored
Normal 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>
|
||||
31
dom/cache/test/mochitest/test_cache_https.js
vendored
Normal file
31
dom/cache/test/mochitest/test_cache_https.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_keys.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_keys.html
vendored
Normal 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>
|
||||
74
dom/cache/test/mochitest/test_cache_keys.js
vendored
Normal file
74
dom/cache/test/mochitest/test_cache_keys.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_matchAll_request.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_matchAll_request.html
vendored
Normal 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>
|
||||
183
dom/cache/test/mochitest/test_cache_matchAll_request.js
vendored
Normal file
183
dom/cache/test/mochitest/test_cache_matchAll_request.js
vendored
Normal 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");
|
||||
});
|
||||
}
|
||||
20
dom/cache/test/mochitest/test_cache_match_request.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_match_request.html
vendored
Normal 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>
|
||||
145
dom/cache/test/mochitest/test_cache_match_request.js
vendored
Normal file
145
dom/cache/test/mochitest/test_cache_match_request.js
vendored
Normal 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");
|
||||
});
|
||||
}
|
||||
20
dom/cache/test/mochitest/test_cache_match_vary.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_match_vary.html
vendored
Normal 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>
|
||||
334
dom/cache/test/mochitest/test_cache_match_vary.js
vendored
Normal file
334
dom/cache/test/mochitest/test_cache_match_vary.js
vendored
Normal 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();
|
||||
});
|
||||
235
dom/cache/test/mochitest/test_cache_orphaned_body.html
vendored
Normal file
235
dom/cache/test/mochitest/test_cache_orphaned_body.html
vendored
Normal 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>
|
||||
165
dom/cache/test/mochitest/test_cache_orphaned_cache.html
vendored
Normal file
165
dom/cache/test/mochitest/test_cache_orphaned_cache.html
vendored
Normal 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>
|
||||
20
dom/cache/test/mochitest/test_cache_overwrite.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_overwrite.html
vendored
Normal 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>
|
||||
47
dom/cache/test/mochitest/test_cache_overwrite.js
vendored
Normal file
47
dom/cache/test/mochitest/test_cache_overwrite.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_put.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_put.html
vendored
Normal 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>
|
||||
50
dom/cache/test/mochitest/test_cache_put.js
vendored
Normal file
50
dom/cache/test/mochitest/test_cache_put.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_put_reorder.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_put_reorder.html
vendored
Normal 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>
|
||||
31
dom/cache/test/mochitest/test_cache_put_reorder.js
vendored
Normal file
31
dom/cache/test/mochitest/test_cache_put_reorder.js
vendored
Normal 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();
|
||||
});
|
||||
20
dom/cache/test/mochitest/test_cache_redirect.html
vendored
Normal file
20
dom/cache/test/mochitest/test_cache_redirect.html
vendored
Normal 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
Loading…
Add table
Add a link
Reference in a new issue