Files
palemoon27/dom/cache/FetchPut.cpp
T
roytam1 56aad8a83e import change from rmottola/Arctic-Fox:
- Bug 1149987 - Part 2: Make ErrorResult unassignable; r=bzbarsky (32661559b)
- Bug 1149987 - Part 3: Give ErrorResult a move constructor and a move assignment operator; (27f4c6125)
- Bug 1149987 - Part 4: Do not attempt to delete ErrorResult::mMessage when deserializing the object from IPDL; r=bzbarsky (0f9dcc603)
- Bug 1110485 P0 Add an ErrorResult constructor that takes nsresult. (72a779666)
- Bug 1110485 P1 Refactor Cache IPC requests to use a separate actor. (a7e4c1959)
- Bug 1127914 - Part 1 - Duplicate keyed histograms for double submission. (78673277f)
- Bug 1127914 - Part 2 - Duplicate normal histograms for double submission. (55c302057)
- Bug 1127914 - Part 3 - Submit duplicate histogram data for 'non-classic' telemetry sessions. r=vladan (bb3e49c43)
- Bug 1120362 - Part 1 - Enable snapshotting and clearing subsession histograms. (14378a6e5)
- Bug 1120362 - Part 2 - Enable snapshotting and clearing keyed subsession histograms. r=vladan (c0e0bfb3e)
- partial apply of Bug 1119281 - Fix missing telemetry client id (ae0dc0194)
- Bug 1122047 - Part 1 - Sketch out Telemetry environment module. (0419391b0)
- Bug 1122047 - Part 2 - Make TelemetryPing shutdown properly on delayed initialization (0102cef09)
- Bug 1122061 - Give TelemetryPing a common API for sending pings. (999cb825d)
- Bug 1122061 - Move TelemetrySession tests out of test_telemetryPing.js. (2d5b61de1)
- Bug 1120362 - Part 3 - Reset subsession histograms on telemetry payload collections. r=vladan (0d3f04df1)
- Bug 1120362 - Part 4 - Start new telemetry subsessions on local midnight. r=vladan (93eb9ca21)
- Bug 1120363 - Break up Telemetry sessions on environment changes. (a7c8d70c7)
- Bug 1122052 - Remove duplicated data from TelemetrySession. (bb905d602)
- Bug 1122050 - Remove persona and experiment data from TelemetrySession. (40ca59a9e)
- Bug 1134268 - Part 1 - Fix and order Telemetry shutdown for TelemetryPing and TelemetrySession. r=yoric (30d0f0656)
- Bug 1134268 - Part 2 - Fixup TelemetryEnvironment shutdown if the module wasnt initialized. r=vladan (ec2875fea)
- Bug 1135076 - Missing histograms in childPayloads. r=vladan (9f317cf9d)
- Bug 1134279 - Make TelemetryPing and TelemetrySession code use the "FHR enabled" & "Telemetry enabled" prefs properly. r=vladan (4050d7f24)
- Bug 1128768: Part 1 - Modify IPC to allow retrieval of topmost routing id on the stack; (cd2e8a2f0)
- Bug 1129249 - Add a "restyle" feature to profiler and split the style label in Cleopatra based on the restyleSource, r=dholbert,mstange (b37df94d1)
- Bug 1150684: Remove XPCOM.h from IOInterposer.h (5b7e1cef3)
- Bug 1093934 - Create a XPCOM library that can be used to support standalone WebRTC. (9ec8a819f)
- Merge branch 'master' of https://github.com/rmottola/Arctic-Fox (d0f05eea4)
- Bug 1128768: Part 2,3,4 - Refactor hang annotation code; (f5086aba9) (with xpcom/threads/ fixes for my tele-removed tree)
- Bug 1128768: Part 5 - Update plugin code to retrieve SWF file for hang annotations; (774a47aec)
- Bug 1110485 P2 Remove 'P' prefix from non-protocol IPC types in Cache API. r=baku (ea29a10cf)
- Bug 1110485 P3 Move Fetch IPC PHeaderEntry type to Cache. Rename HeadesEntry. (9eba0aca0)
- Bug 1110485 P4 Keep Cache Actors alive during async operations. (eb75f2316)
- Bug 1110485 P5 Replace useless DBSchema class type with namespace. (159b902db)
- Bug 1110485 P6 Remove useless cache::FileUtils type (1bdf00fc3)
- Bug 1110485 P7 Rename DeleteCache() to DeleteCacheId() better distinguish it from CacheDelete(). (5199f9d6f)
- Bug 1110485 P8 Correctly set the Feature on the stream control child actor. (c8673cb13)
- Bug 1150691 Fix Cache API race with storage invalidation. (2723dff50)
- Bug 1151892 Refactor Cache Manager Context usage to be more sane and fix shutdown assert. r=ehsan (ea96381cf)
- Bug 1136331 - OdinMonkey: allow stdlib calls in heap expressions (2fc5e2bfd)
- Bug 1141439 - Exit with an error code instead of falling through the REMOTE_NOT_FOUND code path when the X-remote returns an explicit command line handler error. (afcf9b1aa)
- Bug 1135825: Add missing MOZ_OVERRIDE annotation in RTCIdentityProviderRegistrar.h (e8beec4e8)
- (Bug 1135138 is not merged due to broken build)
2019-07-25 21:33:11 +08:00

483 lines
14 KiB
C++

/* -*- 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/FetchPut.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FetchDriver.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/cache/ManagerId.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "nsCRT.h"
#include "nsHttp.h"
namespace mozilla {
namespace dom {
namespace cache {
class FetchPut::Runnable final : public nsRunnable
{
public:
explicit Runnable(FetchPut* aFetchPut)
: mFetchPut(aFetchPut)
{
MOZ_ASSERT(mFetchPut);
}
NS_IMETHOD Run() override
{
if (NS_IsMainThread())
{
mFetchPut->DoFetchOnMainThread();
return NS_OK;
}
MOZ_ASSERT(mFetchPut->mInitiatingThread == NS_GetCurrentThread());
mFetchPut->DoPutOnWorkerThread();
// The FetchPut object must ultimately be freed on the worker thread,
// so make sure we release our reference here. The runnable may end
// up getting deleted on the main thread.
mFetchPut = nullptr;
return NS_OK;
}
private:
nsRefPtr<FetchPut> mFetchPut;
};
class FetchPut::FetchObserver final : public FetchDriverObserver
{
public:
explicit FetchObserver(FetchPut* aFetchPut)
: mFetchPut(aFetchPut)
{
}
virtual void OnResponseAvailable(InternalResponse* aResponse) override
{
MOZ_ASSERT(!mInternalResponse);
mInternalResponse = aResponse;
}
virtual void OnResponseEnd() override
{
mFetchPut->FetchComplete(this, mInternalResponse);
if (mFetchPut->mInitiatingThread == NS_GetCurrentThread()) {
mFetchPut = nullptr;
} else {
nsCOMPtr<nsIThread> initiatingThread(mFetchPut->mInitiatingThread);
nsCOMPtr<nsIRunnable> runnable =
NS_NewNonOwningRunnableMethod(mFetchPut.forget().take(), &FetchPut::Release);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
initiatingThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
}
}
protected:
virtual ~FetchObserver() { }
private:
nsRefPtr<FetchPut> mFetchPut;
nsRefPtr<InternalResponse> mInternalResponse;
};
// static
nsresult
FetchPut::Create(Listener* aListener, Manager* aManager, CacheId aCacheId,
const nsTArray<CacheRequest>& aRequests,
const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams,
FetchPut** aFetchPutOut)
{
MOZ_ASSERT(aRequests.Length() == aRequestStreams.Length());
// The FetchDriver requires that all requests have a referrer already set.
#ifdef DEBUG
for (uint32_t i = 0; i < aRequests.Length(); ++i) {
if (aRequests[i].referrer() == EmptyString()) {
return NS_ERROR_UNEXPECTED;
}
}
#endif
nsRefPtr<FetchPut> ref = new FetchPut(aListener, aManager, aCacheId,
aRequests, aRequestStreams);
nsresult rv = ref->DispatchToMainThread();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
ref.forget(aFetchPutOut);
return NS_OK;
}
void
FetchPut::ClearListener()
{
MOZ_ASSERT(mListener);
mListener = nullptr;
}
FetchPut::FetchPut(Listener* aListener, Manager* aManager, CacheId aCacheId,
const nsTArray<CacheRequest>& aRequests,
const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams)
: mListener(aListener)
, mManager(aManager)
, mCacheId(aCacheId)
, mInitiatingThread(NS_GetCurrentThread())
, mStateList(aRequests.Length())
, mPendingCount(0)
{
MOZ_ASSERT(mListener);
MOZ_ASSERT(mManager);
MOZ_ASSERT(aRequests.Length() == aRequestStreams.Length());
for (uint32_t i = 0; i < aRequests.Length(); ++i) {
State* s = mStateList.AppendElement();
s->mCacheRequest = aRequests[i];
s->mRequestStream = aRequestStreams[i];
}
mManager->AddRefCacheId(mCacheId);
}
FetchPut::~FetchPut()
{
MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
MOZ_ASSERT(!mListener);
mManager->RemoveListener(this);
mManager->ReleaseCacheId(mCacheId);
mResult.ClearMessage(); // This may contain a TypeError.
}
nsresult
FetchPut::DispatchToMainThread()
{
MOZ_ASSERT(!mRunnable);
nsCOMPtr<nsIRunnable> runnable = new Runnable(this);
nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!mRunnable);
mRunnable = runnable.forget();
return NS_OK;
}
void
FetchPut::DispatchToInitiatingThread()
{
MOZ_ASSERT(mRunnable);
nsresult rv = mInitiatingThread->Dispatch(mRunnable,
nsIThread::DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
MOZ_CRASH("Failed to dispatch to worker thread after fetch completion.");
}
mRunnable = nullptr;
}
void
FetchPut::DoFetchOnMainThread()
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<ManagerId> managerId = mManager->GetManagerId();
nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
mPendingCount = mStateList.Length();
nsCOMPtr<nsILoadGroup> loadGroup;
nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal);
if (NS_WARN_IF(NS_FAILED(rv))) {
MaybeSetError(ErrorResult(rv));
MaybeCompleteOnMainThread();
return;
}
for (uint32_t i = 0; i < mStateList.Length(); ++i) {
nsRefPtr<InternalRequest> internalRequest =
ToInternalRequest(mStateList[i].mCacheRequest);
// If there is a stream we must clone it so that its still available
// to store in the cache later;
if (mStateList[i].mRequestStream) {
internalRequest->SetBody(mStateList[i].mRequestStream);
nsRefPtr<InternalRequest> clone = internalRequest->Clone();
// The copy construction clone above can change the source stream,
// so get it back out to use when we put this in the cache.
internalRequest->GetBody(getter_AddRefs(mStateList[i].mRequestStream));
internalRequest = clone;
}
nsRefPtr<FetchDriver> fetchDriver = new FetchDriver(internalRequest,
principal,
loadGroup);
mStateList[i].mFetchObserver = new FetchObserver(this);
rv = fetchDriver->Fetch(mStateList[i].mFetchObserver);
if (NS_WARN_IF(NS_FAILED(rv))) {
MaybeSetError(ErrorResult(rv));
mStateList[i].mFetchObserver = nullptr;
mPendingCount -= 1;
continue;
}
}
// If they all failed, then we might need to complete main thread immediately
MaybeCompleteOnMainThread();
}
void
FetchPut::FetchComplete(FetchObserver* aObserver,
InternalResponse* aInternalResponse)
{
MOZ_ASSERT(NS_IsMainThread());
if (aInternalResponse->IsError() && !mResult.Failed()) {
MaybeSetError(ErrorResult(NS_ERROR_FAILURE));
}
for (uint32_t i = 0; i < mStateList.Length(); ++i) {
if (mStateList[i].mFetchObserver == aObserver) {
ErrorResult rv;
ToCacheResponseWithoutBody(mStateList[i].mCacheResponse,
*aInternalResponse, rv);
if (rv.Failed()) {
MaybeSetError(Move(rv));
} else {
aInternalResponse->GetBody(getter_AddRefs(mStateList[i].mResponseStream));
}
mStateList[i].mFetchObserver = nullptr;
MOZ_ASSERT(mPendingCount > 0);
mPendingCount -= 1;
MaybeCompleteOnMainThread();
return;
}
}
MOZ_ASSERT_UNREACHABLE("Should never get called by unknown fetch observer.");
}
void
FetchPut::MaybeCompleteOnMainThread()
{
MOZ_ASSERT(NS_IsMainThread());
if (mPendingCount > 0) {
return;
}
DispatchToInitiatingThread();
}
void
FetchPut::DoPutOnWorkerThread()
{
MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
if (mResult.Failed()) {
MaybeNotifyListener();
return;
}
// These allocate ~4.5k combined on the stack
nsAutoTArray<CacheRequestResponse, 16> putList;
nsAutoTArray<nsCOMPtr<nsIInputStream>, 16> requestStreamList;
nsAutoTArray<nsCOMPtr<nsIInputStream>, 16> responseStreamList;
putList.SetCapacity(mStateList.Length());
requestStreamList.SetCapacity(mStateList.Length());
responseStreamList.SetCapacity(mStateList.Length());
for (uint32_t i = 0; i < mStateList.Length(); ++i) {
// The spec requires us to catch if content tries to insert a set of
// requests that would overwrite each other.
if (MatchInPutList(mStateList[i].mCacheRequest, putList)) {
MaybeSetError(ErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
MaybeNotifyListener();
return;
}
CacheRequestResponse* entry = putList.AppendElement();
entry->request() = mStateList[i].mCacheRequest;
entry->response() = mStateList[i].mCacheResponse;
requestStreamList.AppendElement(mStateList[i].mRequestStream.forget());
responseStreamList.AppendElement(mStateList[i].mResponseStream.forget());
}
mStateList.Clear();
mManager->ExecutePutAll(this, mCacheId, putList, requestStreamList,
responseStreamList);
}
// static
bool
FetchPut::MatchInPutList(const CacheRequest& aRequest,
const nsTArray<CacheRequestResponse>& aPutList)
{
// 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.
if (!aRequest.method().LowerCaseEqualsLiteral("get") &&
!aRequest.method().LowerCaseEqualsLiteral("head")) {
return false;
}
nsRefPtr<InternalHeaders> requestHeaders =
ToInternalHeaders(aRequest.headers());
for (uint32_t i = 0; i < aPutList.Length(); ++i) {
const CacheRequest& cachedRequest = aPutList[i].request();
const CacheResponse& cachedResponse = aPutList[i].response();
// If the URLs don't match, then just skip to the next entry.
if (aRequest.url() != cachedRequest.url()) {
continue;
}
nsRefPtr<InternalHeaders> cachedRequestHeaders =
ToInternalHeaders(cachedRequest.headers());
nsRefPtr<InternalHeaders> cachedResponseHeaders =
ToInternalHeaders(cachedResponse.headers());
nsAutoTArray<nsCString, 16> varyHeaders;
ErrorResult rv;
cachedResponseHeaders->GetAll(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
// Assume the vary headers match until we find a conflict
bool varyHeadersMatch = true;
for (uint32_t j = 0; j < varyHeaders.Length(); ++j) {
// Extract the header names inside the Vary header value.
nsAutoCString varyValue(varyHeaders[j]);
char* rawBuffer = varyValue.BeginWriting();
char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer);
bool bailOut = false;
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
MOZ_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.ClearMessage();
MOZ_ASSERT(value.IsEmpty());
}
nsAutoCString cachedValue;
cachedRequestHeaders->Get(header, value, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.ClearMessage();
MOZ_ASSERT(cachedValue.IsEmpty());
}
if (value != cachedValue) {
varyHeadersMatch = false;
bailOut = true;
break;
}
}
if (bailOut) {
break;
}
}
// URL was equal and all vary headers match!
if (varyHeadersMatch) {
return true;
}
}
return false;
}
void
FetchPut::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult,
CacheId aOpenedCacheId,
const nsTArray<SavedResponse>& aSavedResponseList,
const nsTArray<SavedRequest>& aSavedRequestList,
StreamList* aStreamList)
{
MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
MOZ_ASSERT(aResult.type() == CacheOpResult::TCachePutAllResult);
MaybeSetError(Move(aRv));
MaybeNotifyListener();
}
void
FetchPut::MaybeSetError(ErrorResult&& aRv)
{
if (mResult.Failed() || !aRv.Failed()) {
return;
}
mResult = Move(aRv);
}
void
FetchPut::MaybeNotifyListener()
{
MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
if (!mListener) {
return;
}
// CacheParent::OnFetchPut can lead to the destruction of |this| when the
// object is removed from CacheParent::mFetchPutList, so make sure that
// doesn't happen until this method returns.
nsRefPtr<FetchPut> kungFuDeathGrip(this);
mListener->OnFetchPut(this, Move(mResult));
}
nsIGlobalObject*
FetchPut::GetGlobalObject() const
{
MOZ_CRASH("No global object in parent-size FetchPut operation!");
}
#ifdef DEBUG
void
FetchPut::AssertOwningThread() const
{
MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
}
#endif
CachePushStreamChild*
FetchPut::CreatePushStream(nsIAsyncInputStream* aStream)
{
MOZ_CRASH("FetchPut should never create a push stream!");
}
} // namespace cache
} // namespace dom
} // namespace mozilla