1714 lines
52 KiB
C++
1714 lines
52 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et 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 "InterceptedHttpChannel.h"
|
|
#include "NetworkMarker.h"
|
|
#include "nsContentSecurityManager.h"
|
|
#include "nsEscape.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/dom/ChannelInfo.h"
|
|
#include "mozilla/dom/PerformanceStorage.h"
|
|
#include "mozilla/glean/DomServiceworkersMetrics.h"
|
|
#include "nsHttpChannel.h"
|
|
#include "nsIHttpHeaderVisitor.h"
|
|
#include "nsIRedirectResultListener.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsQueryObject.h"
|
|
#include "mozilla/Logging.h"
|
|
|
|
namespace mozilla::net {
|
|
|
|
mozilla::LazyLogModule gInterceptedLog("Intercepted");
|
|
|
|
#define INTERCEPTED_LOG(args) MOZ_LOG(gInterceptedLog, LogLevel::Debug, args)
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel,
|
|
nsIInterceptedChannel, nsICacheInfoChannel,
|
|
nsIAsyncVerifyRedirectCallback, nsIRequestObserver,
|
|
nsIStreamListener, nsIThreadRetargetableRequest,
|
|
nsIThreadRetargetableStreamListener,
|
|
nsIClassOfService)
|
|
|
|
InterceptedHttpChannel::InterceptedHttpChannel(
|
|
PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
|
|
const TimeStamp& aAsyncOpenTimestamp)
|
|
: HttpAsyncAborter<InterceptedHttpChannel>(this),
|
|
mProgress(0),
|
|
mProgressReported(0),
|
|
mSynthesizedStreamLength(-1),
|
|
mResumeStartPos(0),
|
|
mCallingStatusAndProgress(false) {
|
|
// Pre-set the creation and AsyncOpen times based on the original channel
|
|
// we are intercepting. We don't want our extra internal redirect to mask
|
|
// any time spent processing the channel.
|
|
INTERCEPTED_LOG(("Creating InterceptedHttpChannel [%p]", this));
|
|
mChannelCreationTime = aCreationTime;
|
|
mChannelCreationTimestamp = aCreationTimestamp;
|
|
mInterceptedChannelCreationTimestamp = TimeStamp::Now();
|
|
mAsyncOpenTime = aAsyncOpenTimestamp;
|
|
}
|
|
|
|
void InterceptedHttpChannel::ReleaseListeners() {
|
|
if (mLoadGroup) {
|
|
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
|
|
}
|
|
HttpBaseChannel::ReleaseListeners();
|
|
mSynthesizedResponseHead.reset();
|
|
mRedirectChannel = nullptr;
|
|
mBodyReader = nullptr;
|
|
mReleaseHandle = nullptr;
|
|
mProgressSink = nullptr;
|
|
mBodyCallback = nullptr;
|
|
mPump = nullptr;
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!LoadIsPending());
|
|
}
|
|
|
|
nsresult InterceptedHttpChannel::SetupReplacementChannel(
|
|
nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
|
|
uint32_t aRedirectFlags) {
|
|
INTERCEPTED_LOG(
|
|
("InterceptedHttpChannel::SetupReplacementChannel [%p] flag: %u", this,
|
|
aRedirectFlags));
|
|
nsresult rv = HttpBaseChannel::SetupReplacementChannel(
|
|
aURI, aChannel, aPreserveMethod, aRedirectFlags);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CheckRedirectLimit(aURI, aRedirectFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// While we can't resume an synthetic response, we can still propagate
|
|
// the resume params across redirects for other channels to handle.
|
|
if (mResumeStartPos > 0) {
|
|
nsCOMPtr<nsIResumableChannel> resumable = do_QueryInterface(aChannel);
|
|
if (!resumable) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
resumable->ResumeAt(mResumeStartPos, mResumeEntityId);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void InterceptedHttpChannel::AsyncOpenInternal() {
|
|
// We save this timestamp from outside of the if block in case we enable the
|
|
// profiler after AsyncOpen().
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpenInternal [%p]", this));
|
|
mLastStatusReported = TimeStamp::Now();
|
|
if (profiler_thread_is_being_profiled_for_markers()) {
|
|
nsAutoCString requestMethod;
|
|
GetRequestMethod(requestMethod);
|
|
|
|
profiler_add_network_marker(
|
|
mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
|
|
mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
|
|
mLoadInfo->GetInnerWindowID(),
|
|
mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(),
|
|
mClassOfService.Flags(), mStatus);
|
|
}
|
|
|
|
// If an error occurs in this file we must ensure mListener callbacks are
|
|
// invoked in some way. We either Cancel() or ResetInterception below
|
|
// depending on which path we take.
|
|
nsresult rv = NS_OK;
|
|
|
|
// Start the interception, record the start time.
|
|
mTimeStamps.Init(this);
|
|
mTimeStamps.RecordTime();
|
|
|
|
// We should have pre-set the AsyncOpen time based on the original channel if
|
|
// timings are enabled.
|
|
MOZ_DIAGNOSTIC_ASSERT(!mAsyncOpenTime.IsNull());
|
|
|
|
StoreIsPending(true);
|
|
StoreResponseCouldBeSynthesized(true);
|
|
|
|
if (mLoadGroup) {
|
|
mLoadGroup->AddRequest(this, nullptr);
|
|
}
|
|
|
|
// If we already have a synthesized body then we are pre-synthesized.
|
|
// This can happen for two reasons:
|
|
// 1. We have a pre-synthesized redirect in e10s mode. In this case
|
|
// we should follow the redirect.
|
|
// 2. We are handling a "fake" redirect for an opaque response. Here
|
|
// we should just process the synthetic body.
|
|
if (mBodyReader) {
|
|
// If we fail in this path, then cancel the channel. We don't want
|
|
// to ResetInterception() after a synthetic result has already been
|
|
// produced by the ServiceWorker.
|
|
auto autoCancel = MakeScopeExit([&] {
|
|
if (NS_FAILED(rv)) {
|
|
Cancel(rv);
|
|
}
|
|
});
|
|
|
|
// The fetch event will not be dispatched, record current time for
|
|
// FetchHandlerStart and FetchHandlerFinish.
|
|
SetFetchHandlerStart(TimeStamp::Now());
|
|
SetFetchHandlerFinish(TimeStamp::Now());
|
|
|
|
if (ShouldRedirect()) {
|
|
rv = FollowSyntheticRedirect();
|
|
return;
|
|
}
|
|
|
|
rv = StartPump();
|
|
return;
|
|
}
|
|
|
|
// If we fail the initial interception, then attempt to ResetInterception
|
|
// to fall back to network. We only cancel if the reset fails.
|
|
auto autoReset = MakeScopeExit([&] {
|
|
if (NS_FAILED(rv)) {
|
|
rv = ResetInterception(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Cancel(rv);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Otherwise we need to trigger a FetchEvent in a ServiceWorker.
|
|
nsCOMPtr<nsINetworkInterceptController> controller;
|
|
GetCallback(controller);
|
|
|
|
if (NS_WARN_IF(!controller)) {
|
|
rv = NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
return;
|
|
}
|
|
|
|
rv = controller->ChannelIntercepted(this);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
}
|
|
|
|
bool InterceptedHttpChannel::ShouldRedirect() const {
|
|
// Determine if the synthetic response requires us to perform a real redirect.
|
|
return nsHttpChannel::WillRedirect(*mResponseHead) &&
|
|
!mLoadInfo->GetDontFollowRedirects();
|
|
}
|
|
|
|
nsresult InterceptedHttpChannel::FollowSyntheticRedirect() {
|
|
// Perform a real redirect based on the synthetic response.
|
|
|
|
nsCOMPtr<nsIIOService> ioService;
|
|
nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString location;
|
|
rv = mResponseHead->GetHeader(nsHttp::Location, location);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
|
|
|
// make sure non-ASCII characters in the location header are escaped.
|
|
nsAutoCString locationBuf;
|
|
if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
|
|
locationBuf)) {
|
|
location = locationBuf;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> redirectURI;
|
|
rv = ioService->NewURI(nsDependentCString(location.get()), nullptr, mURI,
|
|
getter_AddRefs(redirectURI));
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_CORRUPTED_CONTENT);
|
|
|
|
uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
|
|
if (nsHttp::IsPermanentRedirect(mResponseHead->Status())) {
|
|
redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
|
|
}
|
|
|
|
PropagateReferenceIfNeeded(mURI, redirectURI);
|
|
|
|
bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(),
|
|
mRequestHead.ParsedMethod());
|
|
|
|
nsCOMPtr<nsIChannel> newChannel;
|
|
nsCOMPtr<nsILoadInfo> redirectLoadInfo =
|
|
CloneLoadInfoForRedirect(redirectURI, redirectFlags);
|
|
rv = NS_NewChannelInternal(getter_AddRefs(newChannel), redirectURI,
|
|
redirectLoadInfo,
|
|
nullptr, // PerformanceStorage
|
|
nullptr, // aLoadGroup
|
|
nullptr, // aCallbacks
|
|
mLoadFlags, ioService);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
|
|
redirectFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mRedirectChannel = std::move(newChannel);
|
|
|
|
rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel,
|
|
redirectFlags);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
OnRedirectVerifyCallback(rv);
|
|
} else {
|
|
// Redirect success, record the finish time and the final status.
|
|
mTimeStamps.RecordTime(InterceptionTimeStamps::Redirected);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult InterceptedHttpChannel::RedirectForResponseURL(
|
|
nsIURI* aResponseURI, bool aResponseRedirected) {
|
|
// Perform a service worker redirect to another InterceptedHttpChannel using
|
|
// the given response URL. It allows content to see the final URL where
|
|
// appropriate and also helps us enforce cross-origin restrictions. The
|
|
// resulting channel will then process the synthetic response as normal. This
|
|
// extra redirect is performed so that listeners treat the result as unsafe
|
|
// cross-origin data.
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
// We want to pass ownership of the body callback to the new synthesized
|
|
// channel. We need to hold a reference to the callbacks on the stack
|
|
// as well, though, so we can call them if a failure occurs.
|
|
nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = std::move(mBodyCallback);
|
|
|
|
RefPtr<InterceptedHttpChannel> newChannel = CreateForSynthesis(
|
|
mResponseHead.get(), mBodyReader, bodyCallback, mChannelCreationTime,
|
|
mChannelCreationTimestamp, mAsyncOpenTime);
|
|
|
|
// If the response has been redirected, propagate all the URLs to content.
|
|
// Thus, the exact value of the redirect flag does not matter as long as it's
|
|
// not REDIRECT_INTERNAL.
|
|
uint32_t flags = aResponseRedirected ? nsIChannelEventSink::REDIRECT_TEMPORARY
|
|
: nsIChannelEventSink::REDIRECT_INTERNAL;
|
|
|
|
nsCOMPtr<nsILoadInfo> redirectLoadInfo =
|
|
CloneLoadInfoForRedirect(aResponseURI, flags);
|
|
|
|
ExtContentPolicyType contentPolicyType =
|
|
redirectLoadInfo->GetExternalContentPolicyType();
|
|
|
|
rv = newChannel->Init(aResponseURI, mCaps,
|
|
static_cast<nsProxyInfo*>(mProxyInfo.get()),
|
|
mProxyResolveFlags, mProxyURI, mChannelId,
|
|
contentPolicyType, redirectLoadInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Normally we don't propagate the LoadInfo's service worker tainting
|
|
// synthesis flag on redirect. A real redirect normally will want to allow
|
|
// normal tainting to proceed from its starting taint. For this particular
|
|
// redirect, though, we are performing a redirect to communicate the URL of
|
|
// the service worker synthetic response itself. This redirect still
|
|
// represents the synthetic response, so we must preserve the flag.
|
|
if (redirectLoadInfo && mLoadInfo &&
|
|
mLoadInfo->GetServiceWorkerTaintingSynthesized()) {
|
|
redirectLoadInfo->SynthesizeServiceWorkerTainting(mLoadInfo->GetTainting());
|
|
}
|
|
|
|
rv = SetupReplacementChannel(aResponseURI, newChannel, true, flags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mRedirectChannel = newChannel;
|
|
|
|
MOZ_ASSERT(mBodyReader);
|
|
MOZ_ASSERT(!LoadApplyConversion());
|
|
newChannel->SetApplyConversion(false);
|
|
|
|
rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
// Make sure to call the body callback since we took ownership
|
|
// above. Neither the new channel or our standard
|
|
// OnRedirectVerifyCallback() code will invoke the callback. Do it here.
|
|
bodyCallback->BodyComplete(rv);
|
|
|
|
OnRedirectVerifyCallback(rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult InterceptedHttpChannel::StartPump() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mPump);
|
|
MOZ_DIAGNOSTIC_ASSERT(mBodyReader);
|
|
|
|
// We don't support resuming an intercepted channel. We can't guarantee the
|
|
// ServiceWorker will always return the same data and we can't rely on the
|
|
// http cache code to detect changes. For now, just force the channel to
|
|
// NS_ERROR_NOT_RESUMABLE which should cause the front-end to recreate the
|
|
// channel without calling ResumeAt().
|
|
//
|
|
// It would also be possible to convert this information to a range request,
|
|
// but its unclear if we should do that for ServiceWorker FetchEvents. See:
|
|
//
|
|
// https://github.com/w3c/ServiceWorker/issues/1201
|
|
if (mResumeStartPos > 0) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
// For progress we trust the content-length for the "maximum" size.
|
|
// We can't determine the full size from the stream itself since
|
|
// we may only receive the data incrementally. We can't trust
|
|
// Available() here.
|
|
// TODO: We could implement an nsIFixedLengthInputStream interface and
|
|
// QI to it here. This would let us determine the total length
|
|
// for streams that support it. See bug 1388774.
|
|
Unused << GetContentLength(&mSynthesizedStreamLength);
|
|
|
|
nsresult rv =
|
|
nsInputStreamPump::Create(getter_AddRefs(mPump), mBodyReader, 0, 0, true);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mPump->AsyncRead(this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t suspendCount = mSuspendCount;
|
|
while (suspendCount--) {
|
|
mPump->Suspend();
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult InterceptedHttpChannel::OpenRedirectChannel() {
|
|
INTERCEPTED_LOG(
|
|
("InterceptedHttpChannel::OpenRedirectChannel [%p], mRedirectChannel: %p",
|
|
this, mRedirectChannel.get()));
|
|
nsresult rv = NS_OK;
|
|
|
|
if (NS_FAILED(mStatus)) {
|
|
return mStatus;
|
|
}
|
|
|
|
if (!mRedirectChannel) {
|
|
return NS_ERROR_DOM_ABORT_ERR;
|
|
}
|
|
|
|
// Make sure to do this after we received redirect veto answer,
|
|
// i.e. after all sinks had been notified
|
|
mRedirectChannel->SetOriginalURI(mOriginalURI);
|
|
|
|
// open new channel
|
|
rv = mRedirectChannel->AsyncOpen(mListener);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mStatus = NS_BINDING_REDIRECTED;
|
|
|
|
return rv;
|
|
}
|
|
|
|
void InterceptedHttpChannel::MaybeCallStatusAndProgress() {
|
|
// OnStatus() and OnProgress() must only be called on the main thread. If
|
|
// we are on a separate thread, then we maybe need to schedule a runnable
|
|
// to call them asynchronousnly.
|
|
if (!NS_IsMainThread()) {
|
|
// Check to see if we are already trying to call OnStatus/OnProgress
|
|
// asynchronously. If we are, then don't queue up another runnable.
|
|
// We don't want to flood the main thread.
|
|
if (mCallingStatusAndProgress) {
|
|
return;
|
|
}
|
|
mCallingStatusAndProgress = true;
|
|
|
|
nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
|
|
"InterceptedHttpChannel::MaybeCallStatusAndProgress", this,
|
|
&InterceptedHttpChannel::MaybeCallStatusAndProgress);
|
|
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
|
|
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// We are about to capture out progress position. Clear the flag we use
|
|
// to de-duplicate progress report runnables. We want any further progress
|
|
// updates to trigger another runnable. We do this before capture the
|
|
// progress value since we're using atomics and not a mutex lock.
|
|
mCallingStatusAndProgress = false;
|
|
|
|
// Capture the current status from our atomic count.
|
|
int64_t progress = mProgress;
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(progress >= mProgressReported);
|
|
|
|
// Do nothing if we've already made the calls for this amount of progress
|
|
// or if the channel is not configured for these calls. Note, the check
|
|
// for mProgressSink here means we will not fire any spurious late calls
|
|
// after ReleaseListeners() is executed.
|
|
if (progress <= mProgressReported || mCanceled || !mProgressSink ||
|
|
(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
|
|
return;
|
|
}
|
|
|
|
// Capture the host name on the first set of calls to avoid doing this
|
|
// string processing repeatedly.
|
|
if (mProgressReported == 0) {
|
|
nsAutoCString host;
|
|
MOZ_ALWAYS_SUCCEEDS(mURI->GetHost(host));
|
|
CopyUTF8toUTF16(host, mStatusHost);
|
|
}
|
|
|
|
mProgressSink->OnStatus(this, NS_NET_STATUS_READING, mStatusHost.get());
|
|
|
|
mProgressSink->OnProgress(this, progress, mSynthesizedStreamLength);
|
|
|
|
mProgressReported = progress;
|
|
}
|
|
|
|
void InterceptedHttpChannel::MaybeCallBodyCallback() {
|
|
nsCOMPtr<nsIInterceptedBodyCallback> callback = std::move(mBodyCallback);
|
|
if (callback) {
|
|
callback->BodyComplete(mStatus);
|
|
}
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<InterceptedHttpChannel>
|
|
InterceptedHttpChannel::CreateForInterception(
|
|
PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
|
|
const TimeStamp& aAsyncOpenTimestamp) {
|
|
// Create an InterceptedHttpChannel that will trigger a FetchEvent
|
|
// in a ServiceWorker when opened.
|
|
RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
|
|
aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
|
|
|
|
return ref.forget();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<InterceptedHttpChannel>
|
|
InterceptedHttpChannel::CreateForSynthesis(
|
|
const nsHttpResponseHead* aHead, nsIInputStream* aBody,
|
|
nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
|
|
const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aHead);
|
|
MOZ_DIAGNOSTIC_ASSERT(aBody);
|
|
|
|
// Create an InterceptedHttpChannel that already has a synthesized response.
|
|
// The synthetic response will be processed when opened. A FetchEvent
|
|
// will not be triggered.
|
|
RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
|
|
aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
|
|
|
|
ref->mResponseHead = MakeUnique<nsHttpResponseHead>(*aHead);
|
|
ref->mBodyReader = aBody;
|
|
ref->mBodyCallback = aBodyCallback;
|
|
|
|
return ref.forget();
|
|
}
|
|
|
|
NS_IMETHODIMP InterceptedHttpChannel::SetCanceledReason(
|
|
const nsACString& aReason) {
|
|
return SetCanceledReasonImpl(aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP InterceptedHttpChannel::GetCanceledReason(nsACString& aReason) {
|
|
return GetCanceledReasonImpl(aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::CancelWithReason(nsresult aStatus,
|
|
const nsACString& aReason) {
|
|
return CancelWithReasonImpl(aStatus, aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::Cancel(nsresult aStatus) {
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::Cancel [%p]", this));
|
|
// Note: This class has been designed to send all error results through
|
|
// Cancel(). Don't add calls directly to AsyncAbort() or
|
|
// DoNotifyListener(). Instead call Cancel().
|
|
|
|
if (mCanceled) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// The interception is canceled, record the finish time stamp and the final
|
|
// status
|
|
mTimeStamps.RecordTime(InterceptionTimeStamps::Canceled);
|
|
|
|
mCanceled = true;
|
|
|
|
if (mLastStatusReported && profiler_thread_is_being_profiled_for_markers()) {
|
|
// These do allocations/frees/etc; avoid if not active
|
|
// mLastStatusReported can be null if Cancel is called before we added the
|
|
// start marker.
|
|
nsAutoCString requestMethod;
|
|
GetRequestMethod(requestMethod);
|
|
|
|
int32_t priority = PRIORITY_NORMAL;
|
|
GetPriority(&priority);
|
|
|
|
uint64_t size = 0;
|
|
GetEncodedBodySize(&size);
|
|
|
|
profiler_add_network_marker(
|
|
mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
|
|
mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
|
|
mLoadInfo->GetInnerWindowID(),
|
|
mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(),
|
|
mClassOfService.Flags(), mStatus, &mTransactionTimings,
|
|
std::move(mSource));
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus));
|
|
if (NS_SUCCEEDED(mStatus)) {
|
|
mStatus = aStatus;
|
|
}
|
|
|
|
if (mPump) {
|
|
return mPump->Cancel(mStatus);
|
|
}
|
|
|
|
return AsyncAbort(mStatus);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::Suspend(void) {
|
|
++mSuspendCount;
|
|
if (mPump) {
|
|
return mPump->Suspend();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::Resume(void) {
|
|
--mSuspendCount;
|
|
if (mPump) {
|
|
return mPump->Resume();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetSecurityInfo(
|
|
nsITransportSecurityInfo** aSecurityInfo) {
|
|
nsCOMPtr<nsITransportSecurityInfo> ref(mSecurityInfo);
|
|
ref.forget(aSecurityInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpen [%p], listener: %p", this,
|
|
aListener));
|
|
nsCOMPtr<nsIStreamListener> listener(aListener);
|
|
|
|
nsresult rv =
|
|
nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Cancel(rv);
|
|
return rv;
|
|
}
|
|
if (mCanceled) {
|
|
return mStatus;
|
|
}
|
|
|
|
// After this point we should try to return NS_OK and notify the listener
|
|
// of the result.
|
|
mListener = aListener;
|
|
|
|
AsyncOpenInternal();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
|
|
const nsACString& aCategory,
|
|
bool aIsWarning) {
|
|
// Synthetic responses should not trigger CORS blocking.
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
|
|
bool aWarning,
|
|
const nsAString& aURL,
|
|
const nsAString& aContentType) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetPriority(int32_t aPriority) {
|
|
mPriority = std::clamp<int32_t>(aPriority, INT16_MIN, INT16_MAX);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) {
|
|
mClassOfService.SetFlags(aClassFlags);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) {
|
|
mClassOfService.SetFlags(~aClassFlags & mClassOfService.Flags());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) {
|
|
mClassOfService.SetFlags(aClassFlags | mClassOfService.Flags());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetClassOfService(ClassOfService cos) {
|
|
mClassOfService = cos;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetIncremental(bool incremental) {
|
|
mClassOfService.SetIncremental(incremental);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::ResumeAt(uint64_t aStartPos,
|
|
const nsACString& aEntityId) {
|
|
// We don't support resuming synthesized responses, but we do track this
|
|
// information so it can be passed on to the resulting nsHttpChannel if
|
|
// ResetInterception is called.
|
|
mResumeStartPos = aStartPos;
|
|
mResumeEntityId = aEntityId;
|
|
return NS_OK;
|
|
}
|
|
|
|
void InterceptedHttpChannel::DoNotifyListenerCleanup() {
|
|
// Prefer to cleanup in ReleaseListeners() as it seems to be called
|
|
// more consistently in necko.
|
|
}
|
|
|
|
void InterceptedHttpChannel::DoAsyncAbort(nsresult aStatus) {
|
|
Unused << AsyncAbort(aStatus);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ResetInterceptionHeaderVisitor final : public nsIHttpHeaderVisitor {
|
|
nsCOMPtr<nsIHttpChannel> mTarget;
|
|
|
|
~ResetInterceptionHeaderVisitor() = default;
|
|
|
|
NS_IMETHOD
|
|
VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
|
|
// We skip Cookie header here, since it will be added during
|
|
// nsHttpChannel::AsyncOpen.
|
|
if (aHeader.Equals(nsHttp::Cookie.val())) {
|
|
return NS_OK;
|
|
}
|
|
if (aValue.IsEmpty()) {
|
|
return mTarget->SetEmptyRequestHeader(aHeader);
|
|
}
|
|
return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
|
|
}
|
|
|
|
public:
|
|
explicit ResetInterceptionHeaderVisitor(nsIHttpChannel* aTarget)
|
|
: mTarget(aTarget) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mTarget);
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::ResetInterception(bool aBypass) {
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s",
|
|
this, aBypass ? "true" : "false"));
|
|
if (mCanceled) {
|
|
return mStatus;
|
|
}
|
|
|
|
mInterceptionReset = true;
|
|
|
|
uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
|
|
|
|
nsCOMPtr<nsIChannel> newChannel;
|
|
nsCOMPtr<nsILoadInfo> redirectLoadInfo =
|
|
CloneLoadInfoForRedirect(mURI, flags);
|
|
|
|
if (aBypass) {
|
|
redirectLoadInfo->ClearController();
|
|
// TODO: Audit whether we should also be calling
|
|
// ServiceWorkerManager::StopControllingClient for maximum correctness.
|
|
}
|
|
|
|
nsresult rv =
|
|
NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo,
|
|
nullptr, // PerformanceStorage
|
|
nullptr, // aLoadGroup
|
|
nullptr, // aCallbacks
|
|
mLoadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (profiler_thread_is_being_profiled_for_markers()) {
|
|
nsAutoCString requestMethod;
|
|
GetRequestMethod(requestMethod);
|
|
|
|
int32_t priority = PRIORITY_NORMAL;
|
|
GetPriority(&priority);
|
|
|
|
uint64_t size = 0;
|
|
GetEncodedBodySize(&size);
|
|
|
|
nsAutoCString contentType;
|
|
mozilla::Maybe<mozilla::net::HttpVersion> httpVersion = Nothing();
|
|
mozilla::Maybe<uint32_t> responseStatus = Nothing();
|
|
if (mResponseHead) {
|
|
mResponseHead->ContentType(contentType);
|
|
httpVersion = Some(mResponseHead->Version());
|
|
responseStatus = Some(mResponseHead->Status());
|
|
}
|
|
|
|
RefPtr<HttpBaseChannel> newBaseChannel = do_QueryObject(newChannel);
|
|
MOZ_ASSERT(newBaseChannel,
|
|
"The redirect channel should be a base channel.");
|
|
profiler_add_network_marker(
|
|
mURI, requestMethod, priority, mChannelId,
|
|
NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
|
|
size, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
|
|
mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(),
|
|
mClassOfService.Flags(), mStatus, &mTransactionTimings,
|
|
std::move(mSource), httpVersion, responseStatus,
|
|
Some(nsDependentCString(contentType.get())), mURI, flags,
|
|
newBaseChannel->ChannelId());
|
|
}
|
|
|
|
rv = SetupReplacementChannel(mURI, newChannel, true, flags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Restore the non-default headers for fallback channel.
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
|
|
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
|
|
new ResetInterceptionHeaderVisitor(httpChannel);
|
|
rv = VisitNonDefaultRequestHeaders(visitor);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsITimedChannel> newTimedChannel = do_QueryInterface(newChannel);
|
|
if (newTimedChannel) {
|
|
if (!mAsyncOpenTime.IsNull()) {
|
|
newTimedChannel->SetAsyncOpen(mAsyncOpenTime);
|
|
}
|
|
if (!mChannelCreationTimestamp.IsNull()) {
|
|
newTimedChannel->SetChannelCreation(mChannelCreationTimestamp);
|
|
}
|
|
}
|
|
|
|
if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL) {
|
|
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
|
|
rv = newChannel->GetLoadFlags(&loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
|
|
rv = newChannel->SetLoadFlags(loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
mRedirectChannel = std::move(newChannel);
|
|
|
|
rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
OnRedirectVerifyCallback(rv);
|
|
} else {
|
|
// ResetInterception success, record the finish time stamps and the final
|
|
// status.
|
|
mTimeStamps.RecordTime(InterceptionTimeStamps::Reset);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus,
|
|
const nsACString& aReason) {
|
|
if (mCanceled) {
|
|
return mStatus;
|
|
}
|
|
|
|
if (!mSynthesizedResponseHead) {
|
|
mSynthesizedResponseHead.reset(new nsHttpResponseHead());
|
|
}
|
|
|
|
nsAutoCString statusLine;
|
|
statusLine.AppendLiteral("HTTP/1.1 ");
|
|
statusLine.AppendInt(aStatus);
|
|
statusLine.AppendLiteral(" ");
|
|
statusLine.Append(aReason);
|
|
|
|
NS_ENSURE_SUCCESS(mSynthesizedResponseHead->ParseStatusLine(statusLine),
|
|
NS_ERROR_FAILURE);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName,
|
|
const nsACString& aValue) {
|
|
if (mCanceled) {
|
|
return mStatus;
|
|
}
|
|
|
|
if (!mSynthesizedResponseHead) {
|
|
mSynthesizedResponseHead.reset(new nsHttpResponseHead());
|
|
}
|
|
|
|
nsAutoCString header = aName + ": "_ns + aValue;
|
|
// Overwrite any existing header.
|
|
nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::StartSynthesizedResponse(
|
|
nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback,
|
|
nsICacheInfoChannel* aSynthesizedCacheInfo, const nsACString& aFinalURLSpec,
|
|
bool aResponseRedirected) {
|
|
nsresult rv = NS_OK;
|
|
|
|
auto autoCleanup = MakeScopeExit([&] {
|
|
// Auto-cancel on failure. Do this first to get mStatus set, if necessary.
|
|
if (NS_FAILED(rv)) {
|
|
Cancel(rv);
|
|
}
|
|
|
|
// If we early exit before taking ownership of the body, then automatically
|
|
// invoke the callback. This could be due to an error or because we're not
|
|
// going to consume it due to a redirect, etc.
|
|
if (aBodyCallback) {
|
|
aBodyCallback->BodyComplete(mStatus);
|
|
}
|
|
});
|
|
|
|
if (NS_FAILED(mStatus)) {
|
|
// Return NS_OK. The channel should fire callbacks with an error code
|
|
// if it was cancelled before this point.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Take ownership of the body callbacks If a failure occurs we will
|
|
// automatically Cancel() the channel. This will then invoke OnStopRequest()
|
|
// which will invoke the correct callback. In the case of an opaque response
|
|
// redirect we pass ownership of the callback to the new channel.
|
|
mBodyCallback = aBodyCallback;
|
|
aBodyCallback = nullptr;
|
|
|
|
mSynthesizedCacheInfo = aSynthesizedCacheInfo;
|
|
|
|
if (!mSynthesizedResponseHead) {
|
|
mSynthesizedResponseHead.reset(new nsHttpResponseHead());
|
|
}
|
|
|
|
mResponseHead = std::move(mSynthesizedResponseHead);
|
|
|
|
if (ShouldRedirect()) {
|
|
rv = FollowSyntheticRedirect();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Intercepted responses should already be decoded.
|
|
SetApplyConversion(false);
|
|
|
|
// Errors and redirects may not have a body. Synthesize an empty string
|
|
// stream here so later code can be simpler.
|
|
mBodyReader = aBody;
|
|
if (!mBodyReader) {
|
|
rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), ""_ns);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> responseURI;
|
|
if (!aFinalURLSpec.IsEmpty()) {
|
|
rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
responseURI = mURI;
|
|
}
|
|
|
|
bool equal = false;
|
|
Unused << mURI->Equals(responseURI, &equal);
|
|
if (!equal) {
|
|
rv = RedirectForResponseURL(responseURI, aResponseRedirected);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = StartPump();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::FinishSynthesizedResponse() {
|
|
if (mCanceled) {
|
|
// Return NS_OK. The channel should fire callbacks with an error code
|
|
// if it was cancelled before this point.
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::CancelInterception(nsresult aStatus) {
|
|
return Cancel(aStatus);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) {
|
|
nsCOMPtr<nsIChannel> ref(this);
|
|
ref.forget(aChannel);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetSecureUpgradedChannelURI(
|
|
nsIURI** aSecureUpgradedChannelURI) {
|
|
nsCOMPtr<nsIURI> ref(mURI);
|
|
ref.forget(aSecureUpgradedChannelURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetChannelInfo(
|
|
mozilla::dom::ChannelInfo* aChannelInfo) {
|
|
return aChannelInfo->ResurrectInfoOnChannel(this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetInternalContentPolicyType(
|
|
nsContentPolicyType* aPolicyType) {
|
|
if (mLoadInfo) {
|
|
*aPolicyType = mLoadInfo->InternalContentPolicyType();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetConsoleReportCollector(
|
|
nsIConsoleReportCollector** aConsoleReportCollector) {
|
|
nsCOMPtr<nsIConsoleReportCollector> ref(this);
|
|
ref.forget(aConsoleReportCollector);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetFetchHandlerStart(TimeStamp aTimeStamp) {
|
|
mTimeStamps.RecordTime(std::move(aTimeStamp));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetFetchHandlerFinish(TimeStamp aTimeStamp) {
|
|
mTimeStamps.RecordTime(std::move(aTimeStamp));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetRemoteWorkerLaunchStart(TimeStamp aTimeStamp) {
|
|
mServiceWorkerLaunchStart = aTimeStamp > mTimeStamps.mInterceptionStart
|
|
? aTimeStamp
|
|
: mTimeStamps.mInterceptionStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetRemoteWorkerLaunchEnd(TimeStamp aTimeStamp) {
|
|
mServiceWorkerLaunchEnd = aTimeStamp > mTimeStamps.mInterceptionStart
|
|
? aTimeStamp
|
|
: mTimeStamps.mInterceptionStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
|
|
mServiceWorkerLaunchStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetLaunchServiceWorkerStart(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mServiceWorkerLaunchStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
|
|
mServiceWorkerLaunchEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mServiceWorkerLaunchEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetDispatchFetchEventStart(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mTimeStamps.mInterceptionStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetDispatchFetchEventEnd(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mTimeStamps.mFetchHandlerStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetHandleFetchEventStart(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mTimeStamps.mFetchHandlerStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetHandleFetchEventEnd(TimeStamp* aRetVal) {
|
|
MOZ_ASSERT(aRetVal);
|
|
*aRetVal = mTimeStamps.mFetchHandlerFinish;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetIsReset(bool* aResult) {
|
|
*aResult = mInterceptionReset;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetReleaseHandle(nsISupports* aHandle) {
|
|
mReleaseHandle = aHandle;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = OpenRedirectChannel();
|
|
}
|
|
|
|
nsCOMPtr<nsIRedirectResultListener> hook;
|
|
GetCallback(hook);
|
|
if (hook) {
|
|
hook->OnRedirectResult(rv);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
Cancel(rv);
|
|
}
|
|
|
|
MaybeCallBodyCallback();
|
|
|
|
StoreIsPending(false);
|
|
// We can only release listeners after the redirected channel really owns
|
|
// mListener. Otherwise, the OnStart/OnStopRequest functions of mListener will
|
|
// not be called.
|
|
if (NS_SUCCEEDED(rv)) {
|
|
ReleaseListeners();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::OnStartRequest [%p]", this));
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mProgressSink) {
|
|
GetCallback(mProgressSink);
|
|
}
|
|
|
|
MOZ_ASSERT_IF(!mLoadInfo->GetServiceWorkerTaintingSynthesized(),
|
|
mLoadInfo->GetLoadingPrincipal());
|
|
// No need to do ORB checks if these conditions hold.
|
|
MOZ_DIAGNOSTIC_ASSERT(mLoadInfo->GetServiceWorkerTaintingSynthesized() ||
|
|
mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal());
|
|
|
|
if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
|
|
mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
|
|
}
|
|
|
|
nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
|
|
if (NS_FAILED(rv)) {
|
|
mStatus = NS_ERROR_BLOCKED_BY_POLICY;
|
|
Cancel(mStatus);
|
|
}
|
|
|
|
rv = ProcessCrossOriginResourcePolicyHeader();
|
|
if (NS_FAILED(rv)) {
|
|
mStatus = NS_ERROR_DOM_CORP_FAILED;
|
|
Cancel(mStatus);
|
|
}
|
|
|
|
rv = ComputeCrossOriginOpenerPolicyMismatch();
|
|
if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
|
|
mStatus = NS_ERROR_BLOCKED_BY_POLICY;
|
|
Cancel(mStatus);
|
|
}
|
|
|
|
rv = ValidateMIMEType();
|
|
if (NS_FAILED(rv)) {
|
|
mStatus = rv;
|
|
Cancel(mStatus);
|
|
}
|
|
|
|
StoreOnStartRequestCalled(true);
|
|
if (mListener) {
|
|
return mListener->OnStartRequest(this);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
|
|
INTERCEPTED_LOG(("InterceptedHttpChannel::OnStopRequest [%p]", this));
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_SUCCEEDED(mStatus)) {
|
|
mStatus = aStatus;
|
|
}
|
|
|
|
MaybeCallBodyCallback();
|
|
|
|
mTimeStamps.RecordTime(InterceptionTimeStamps::Synthesized);
|
|
|
|
// Its possible that we have any async runnable queued to report some
|
|
// progress when OnStopRequest() is triggered. Report any left over
|
|
// progress immediately. The extra runnable will then do nothing thanks
|
|
// to the ReleaseListeners() call below.
|
|
MaybeCallStatusAndProgress();
|
|
|
|
StoreIsPending(false);
|
|
|
|
// Register entry to the PerformanceStorage resource timing
|
|
MaybeReportTimingData();
|
|
|
|
if (profiler_thread_is_being_profiled_for_markers()) {
|
|
// These do allocations/frees/etc; avoid if not active
|
|
nsAutoCString requestMethod;
|
|
GetRequestMethod(requestMethod);
|
|
|
|
int32_t priority = PRIORITY_NORMAL;
|
|
GetPriority(&priority);
|
|
|
|
uint64_t size = 0;
|
|
GetEncodedBodySize(&size);
|
|
|
|
nsAutoCString contentType;
|
|
mozilla::Maybe<mozilla::net::HttpVersion> httpVersion = Nothing();
|
|
mozilla::Maybe<uint32_t> responseStatus = Nothing();
|
|
if (mResponseHead) {
|
|
mResponseHead->ContentType(contentType);
|
|
httpVersion = Some(mResponseHead->Version());
|
|
responseStatus = Some(mResponseHead->Status());
|
|
}
|
|
profiler_add_network_marker(
|
|
mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
|
|
mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
|
|
mLoadInfo->GetInnerWindowID(),
|
|
mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(),
|
|
mClassOfService.Flags(), mStatus, &mTransactionTimings,
|
|
std::move(mSource), httpVersion, responseStatus,
|
|
Some(nsDependentCString(contentType.get())));
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
if (mListener) {
|
|
rv = mListener->OnStopRequest(this, mStatus);
|
|
}
|
|
|
|
gHttpHandler->OnStopRequest(this);
|
|
|
|
ReleaseListeners();
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OnDataAvailable(nsIRequest* aRequest,
|
|
nsIInputStream* aInputStream,
|
|
uint64_t aOffset, uint32_t aCount) {
|
|
// Any thread if the channel has been retargeted.
|
|
|
|
if (mCanceled || !mListener) {
|
|
// If there is no listener, we still need to drain the stream in order
|
|
// maintain necko invariants.
|
|
uint32_t unused = 0;
|
|
aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &unused);
|
|
return mStatus;
|
|
}
|
|
if (mProgressSink) {
|
|
if (!(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
|
|
mProgress = aOffset + aCount;
|
|
MaybeCallStatusAndProgress();
|
|
}
|
|
}
|
|
|
|
return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OnDataFinished(nsresult aStatus) {
|
|
if (mCanceled || !mListener) {
|
|
return aStatus;
|
|
}
|
|
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
|
do_QueryInterface(mListener);
|
|
if (retargetableListener) {
|
|
return retargetableListener->OnDataFinished(aStatus);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
NS_ENSURE_ARG(aNewTarget);
|
|
|
|
// If retargeting to the main thread, do nothing.
|
|
if (aNewTarget->IsOnCurrentThread()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Retargeting is only valid during OnStartRequest for nsIChannels. So
|
|
// we should only be called if we have a pump.
|
|
if (!mPump) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mPump->RetargetDeliveryTo(aNewTarget);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
|
|
if (!mPump) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return mPump->GetDeliveryTarget(aEventTarget);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::CheckListenerChain() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsresult rv = NS_OK;
|
|
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
|
do_QueryInterface(mListener, &rv);
|
|
if (retargetableListener) {
|
|
rv = retargetableListener->CheckListenerChain();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// InterceptedHttpChannel::nsICacheInfoChannel
|
|
//-----------------------------------------------------------------------------
|
|
// InterceptedHttpChannel does not really implement the nsICacheInfoChannel
|
|
// interface, we tranfers parameters to the saved
|
|
// nsICacheInfoChannel(mSynthesizedCacheInfo) from StartSynthesizedResponse. And
|
|
// we return false in IsFromCache and NS_ERROR_NOT_AVAILABLE for all other
|
|
// methods while the saved mSynthesizedCacheInfo does not exist.
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::IsFromCache(bool* value) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->IsFromCache(value);
|
|
}
|
|
*value = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::IsRacing(bool* value) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->IsRacing(value);
|
|
}
|
|
*value = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetCacheTokenFetchCount(_retval);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetCacheTokenExpirationTime(_retval);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetAllowStaleCacheContent(
|
|
bool aAllowStaleCacheContent) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->SetAllowStaleCacheContent(
|
|
aAllowStaleCacheContent);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetAllowStaleCacheContent(
|
|
bool* aAllowStaleCacheContent) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetAllowStaleCacheContent(
|
|
aAllowStaleCacheContent);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetForceValidateCacheContent(
|
|
bool aForceValidateCacheContent) {
|
|
// We store aForceValidateCacheContent locally because
|
|
// mSynthesizedCacheInfo isn't present until a response
|
|
// is actually synthesized, which is too late for the value
|
|
// to be forwarded during the redirect to the intercepted
|
|
// channel.
|
|
StoreForceValidateCacheContent(aForceValidateCacheContent);
|
|
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->SetForceValidateCacheContent(
|
|
aForceValidateCacheContent);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetForceValidateCacheContent(
|
|
bool* aForceValidateCacheContent) {
|
|
*aForceValidateCacheContent = LoadForceValidateCacheContent();
|
|
#ifdef DEBUG
|
|
if (mSynthesizedCacheInfo) {
|
|
bool synthesizedForceValidateCacheContent;
|
|
mSynthesizedCacheInfo->GetForceValidateCacheContent(
|
|
&synthesizedForceValidateCacheContent);
|
|
MOZ_ASSERT(*aForceValidateCacheContent ==
|
|
synthesizedForceValidateCacheContent);
|
|
}
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetPreferCacheLoadOverBypass(
|
|
bool* aPreferCacheLoadOverBypass) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetPreferCacheLoadOverBypass(
|
|
aPreferCacheLoadOverBypass);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetPreferCacheLoadOverBypass(
|
|
bool aPreferCacheLoadOverBypass) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->SetPreferCacheLoadOverBypass(
|
|
aPreferCacheLoadOverBypass);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::PreferAlternativeDataType(
|
|
const nsACString& aType, const nsACString& aContentType,
|
|
PreferredAlternativeDataDeliveryType aDeliverAltData) {
|
|
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
|
mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
|
|
nsCString(aType), nsCString(aContentType), aDeliverAltData));
|
|
return NS_OK;
|
|
}
|
|
|
|
const nsTArray<PreferredAlternativeDataTypeParams>&
|
|
InterceptedHttpChannel::PreferredAlternativeDataTypes() {
|
|
return mPreferredCachedAltDataTypes;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::OpenAlternativeOutputStream(
|
|
const nsACString& type, int64_t predictedSize,
|
|
nsIAsyncOutputStream** _retval) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->OpenAlternativeOutputStream(
|
|
type, predictedSize, _retval);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetOriginalInputStream(
|
|
nsIInputStreamReceiver* aReceiver) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetAlternativeDataInputStream(
|
|
nsIInputStream** aInputStream) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetAlternativeDataInputStream(aInputStream);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::GetCacheKey(uint32_t* key) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->GetCacheKey(key);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
InterceptedHttpChannel::SetCacheKey(uint32_t key) {
|
|
if (mSynthesizedCacheInfo) {
|
|
return mSynthesizedCacheInfo->SetCacheKey(key);
|
|
}
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// InterceptionTimeStamps implementation
|
|
InterceptedHttpChannel::InterceptionTimeStamps::InterceptionTimeStamps()
|
|
: mStage(InterceptedHttpChannel::InterceptionTimeStamps::InterceptionStart),
|
|
mStatus(InterceptedHttpChannel::InterceptionTimeStamps::Created) {}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::Init(
|
|
nsIChannel* aChannel) {
|
|
MOZ_ASSERT(aChannel);
|
|
MOZ_ASSERT(mStatus == Created);
|
|
|
|
mStatus = Initialized;
|
|
|
|
mIsNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(aChannel);
|
|
mKey = mIsNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
|
|
nsCOMPtr<nsIInterceptedChannel> interceptedChannel =
|
|
do_QueryInterface(aChannel);
|
|
// It must be a InterceptedHttpChannel
|
|
MOZ_ASSERT(interceptedChannel);
|
|
if (!mIsNonSubresourceRequest) {
|
|
interceptedChannel->GetSubresourceTimeStampKey(aChannel, mSubresourceKey);
|
|
}
|
|
}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
|
|
InterceptedHttpChannel::InterceptionTimeStamps::Status&& aStatus,
|
|
TimeStamp&& aTimeStamp) {
|
|
// Only allow passing Synthesized, Reset, Redirected, and Canceled in this
|
|
// method.
|
|
MOZ_ASSERT(aStatus == Synthesized || aStatus == Reset ||
|
|
aStatus == Canceled || aStatus == Redirected);
|
|
if (mStatus == Canceled) {
|
|
return;
|
|
}
|
|
|
|
// If current status is not Initialized, only Canceled can be recorded.
|
|
// That means it is canceled after other operation is done, ex. synthesized.
|
|
MOZ_ASSERT(mStatus == Initialized || aStatus == Canceled);
|
|
|
|
switch (mStatus) {
|
|
case Initialized:
|
|
mStatus = aStatus;
|
|
break;
|
|
case Synthesized:
|
|
mStatus = CanceledAfterSynthesized;
|
|
break;
|
|
case Reset:
|
|
mStatus = CanceledAfterReset;
|
|
break;
|
|
case Redirected:
|
|
mStatus = CanceledAfterRedirected;
|
|
break;
|
|
// Channel is cancelled before calling AsyncOpenInternal(), no need to
|
|
// record the cancel time stamp.
|
|
case Created:
|
|
return;
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
RecordTimeInternal(std::move(aTimeStamp));
|
|
}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
|
|
TimeStamp&& aTimeStamp) {
|
|
MOZ_ASSERT(mStatus == Initialized || mStatus == Canceled);
|
|
if (mStatus == Canceled) {
|
|
return;
|
|
}
|
|
RecordTimeInternal(std::move(aTimeStamp));
|
|
}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::RecordTimeInternal(
|
|
TimeStamp&& aTimeStamp) {
|
|
MOZ_ASSERT(mStatus != Created);
|
|
|
|
if (mStatus == Canceled && mStage != InterceptionFinish) {
|
|
mFetchHandlerStart = aTimeStamp;
|
|
mFetchHandlerFinish = aTimeStamp;
|
|
mStage = InterceptionFinish;
|
|
}
|
|
|
|
switch (mStage) {
|
|
case InterceptionStart: {
|
|
MOZ_ASSERT(mInterceptionStart.IsNull());
|
|
mInterceptionStart = aTimeStamp;
|
|
mStage = FetchHandlerStart;
|
|
break;
|
|
}
|
|
case (FetchHandlerStart): {
|
|
MOZ_ASSERT(mFetchHandlerStart.IsNull());
|
|
mFetchHandlerStart = aTimeStamp;
|
|
mStage = FetchHandlerFinish;
|
|
break;
|
|
}
|
|
case (FetchHandlerFinish): {
|
|
MOZ_ASSERT(mFetchHandlerFinish.IsNull());
|
|
mFetchHandlerFinish = aTimeStamp;
|
|
mStage = InterceptionFinish;
|
|
break;
|
|
}
|
|
case InterceptionFinish: {
|
|
mInterceptionFinish = aTimeStamp;
|
|
SaveTimeStamps();
|
|
return;
|
|
}
|
|
default: {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::GenKeysWithStatus(
|
|
nsCString& aKey, nsCString& aSubresourceKey) {
|
|
nsAutoCString statusString;
|
|
switch (mStatus) {
|
|
case Synthesized:
|
|
statusString = "synthesized"_ns;
|
|
break;
|
|
case Reset:
|
|
statusString = "reset"_ns;
|
|
break;
|
|
case Redirected:
|
|
statusString = "redirected"_ns;
|
|
break;
|
|
case Canceled:
|
|
statusString = "canceled"_ns;
|
|
break;
|
|
case CanceledAfterSynthesized:
|
|
statusString = "canceled-after-synthesized"_ns;
|
|
break;
|
|
case CanceledAfterReset:
|
|
statusString = "canceled-after-reset"_ns;
|
|
break;
|
|
case CanceledAfterRedirected:
|
|
statusString = "canceled-after-redirected"_ns;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
aKey = mKey;
|
|
aSubresourceKey = mSubresourceKey;
|
|
aKey.AppendLiteral("_");
|
|
aSubresourceKey.AppendLiteral("_");
|
|
aKey.Append(statusString);
|
|
aSubresourceKey.Append(statusString);
|
|
}
|
|
|
|
void InterceptedHttpChannel::InterceptionTimeStamps::SaveTimeStamps() {
|
|
MOZ_ASSERT(mStatus != Initialized && mStatus != Created);
|
|
|
|
if (mStatus == Reset) {
|
|
glean::service_worker::fetch_event_channel_reset.Get(mKey)
|
|
.AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish);
|
|
if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
|
|
glean::service_worker::fetch_event_channel_reset.Get(mSubresourceKey)
|
|
.AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish);
|
|
}
|
|
} else if (mStatus == Synthesized) {
|
|
glean::service_worker::fetch_event_finish_synthesized_response.Get(mKey)
|
|
.AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish);
|
|
if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
|
|
glean::service_worker::fetch_event_finish_synthesized_response
|
|
.Get(mSubresourceKey)
|
|
.AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish);
|
|
}
|
|
}
|
|
|
|
if (!mFetchHandlerStart.IsNull()) {
|
|
glean::service_worker::fetch_event_dispatch.Get(mKey).AccumulateRawDuration(
|
|
mFetchHandlerStart - mInterceptionStart);
|
|
|
|
if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
|
|
glean::service_worker::fetch_event_dispatch.Get(mSubresourceKey)
|
|
.AccumulateRawDuration(mFetchHandlerStart - mInterceptionStart);
|
|
}
|
|
}
|
|
|
|
nsAutoCString key, subresourceKey;
|
|
GenKeysWithStatus(key, subresourceKey);
|
|
|
|
glean::service_worker::fetch_interception_duration.Get(key)
|
|
.AccumulateRawDuration(mInterceptionFinish - mInterceptionStart);
|
|
if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
|
|
glean::service_worker::fetch_interception_duration.Get(subresourceKey)
|
|
.AccumulateRawDuration(mInterceptionFinish - mInterceptionStart);
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla::net
|