/* -*- 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/. */
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;
}
// 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;
}
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);
// 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());
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;
}
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.
// 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;
}
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);
// 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;
// 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());
}
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);
// 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);
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;
// 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);
}
// 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);
// 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);
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);
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.
}
if (aBypass) {
redirectLoadInfo->ClearController(); // TODO: Audit whether we should also be calling // ServiceWorkerManager::StopControllingClient for maximum correctness.
}
if (NS_FAILED(rv)) {
OnRedirectVerifyCallback(rv);
} else { // ResetInterception success, record the finish time stamps and the final // status.
mTimeStamps.RecordTime(InterceptionTimeStamps::Reset);
}
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());
}
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);
}
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;
}
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();
}
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));
}
// 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);
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();
}
}
// 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;
}
//----------------------------------------------------------------------------- // 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::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);
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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.