зеркало из https://github.com/mozilla/gecko-dev.git
Bug 497003 - Support delivery of OnDataAvailable off the main thread r=bz r=jduell
This commit is contained in:
Родитель
5e38907e9d
Коммит
1f8d40499c
|
@ -95,6 +95,8 @@ XPIDL_SOURCES += [
|
|||
'nsIStrictTransportSecurityService.idl',
|
||||
'nsISyncStreamListener.idl',
|
||||
'nsISystemProxySettings.idl',
|
||||
'nsIThreadRetargetableRequest.idl',
|
||||
'nsIThreadRetargetableStreamListener.idl',
|
||||
'nsITimedChannel.idl',
|
||||
'nsITraceableChannel.idl',
|
||||
'nsITransport.idl',
|
||||
|
|
|
@ -11,10 +11,11 @@ interface nsIStreamListener;
|
|||
* nsIInputStreamPump
|
||||
*
|
||||
* This interface provides a means to configure and use a input stream pump
|
||||
* instance. The input stream pump will asynchronously read from a input
|
||||
* stream, and push data to a nsIStreamListener instance. It utilizes the
|
||||
* instance. The input stream pump will asynchronously read from an input
|
||||
* stream, and push data to an nsIStreamListener instance. It utilizes the
|
||||
* current thread's nsIEventTarget in order to make reading from the stream
|
||||
* asynchronous.
|
||||
* asynchronous. A different thread can be used if the pump also implements
|
||||
* nsIThreadRetargetableRequest.
|
||||
*
|
||||
* If the given stream supports nsIAsyncInputStream, then the stream pump will
|
||||
* call the stream's AsyncWait method to drive the stream listener. Otherwise,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- 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 "nsISupports.idl"
|
||||
#include "nsIEventTarget.idl"
|
||||
|
||||
/**
|
||||
* nsIThreadRetargetableRequest
|
||||
*
|
||||
* Should be implemented by requests that support retargeting delivery of
|
||||
* OnDataAvailable and OnStopRequest off the main thread.
|
||||
*/
|
||||
[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
|
||||
interface nsIThreadRetargetableRequest : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called to retarget delivery of OnDataAvailable and OnStopRequest to
|
||||
* another thread. Should only be called within the context of OnStartRequest
|
||||
* on the main thread.
|
||||
*
|
||||
* @param aNewTarget New event target, e.g. thread or threadpool.
|
||||
*
|
||||
* Note: no return value is given. If the retargeting cannot be handled,
|
||||
* normal delivery to the main thread will continue. As such, listeners
|
||||
* should be ready to deal with OnDataAvailable and OnStopRequest on
|
||||
* either the main thread or the new target thread.
|
||||
*/
|
||||
void retargetDeliveryTo(in nsIEventTarget aNewTarget);
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* nsIThreadRetargetableListener
|
||||
*
|
||||
* To be used by classes which implement nsIStreamListener and whose
|
||||
* OnDataAvailable and OnStopRequest may be retargeted for delivery off the
|
||||
* main thread.
|
||||
*/
|
||||
[uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)]
|
||||
interface nsIThreadRetargetableStreamListener : nsISupports
|
||||
{
|
||||
/**
|
||||
* Checks this listener and any next listeners it may have to verify that
|
||||
* they can receive OnDataAvailable and OnStopRequest off the main thread.
|
||||
* It is the responsibility of the implementing class to decide on the
|
||||
* criteria to determine if retargeted delivery of these methods is
|
||||
* possible, but it must check any and all nsIStreamListener objects that
|
||||
* might be called in the listener chain.
|
||||
*
|
||||
* An exception should be thrown if a listener in the chain does not
|
||||
* support retargeted delivery, i.e. if the next listener does not implement
|
||||
* nsIThreadRetargetableStreamListener, or a call to its checkListenerChain()
|
||||
* fails.
|
||||
*/
|
||||
void checkListenerChain();
|
||||
};
|
||||
|
|
@ -8,11 +8,13 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIRequestObserver.h"
|
||||
#include "nsIThreadRetargetableStreamListener.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
// Wrapper class to make replacement of nsHttpChannel's listener
|
||||
// from JavaScript possible. It is workaround for bug 433711 and 682305.
|
||||
class nsStreamListenerWrapper MOZ_FINAL : public nsIStreamListener
|
||||
, public nsIThreadRetargetableStreamListener
|
||||
{
|
||||
public:
|
||||
nsStreamListenerWrapper(nsIStreamListener *listener)
|
||||
|
@ -24,6 +26,7 @@ public:
|
|||
NS_DECL_ISUPPORTS
|
||||
NS_FORWARD_NSIREQUESTOBSERVER(mListener->)
|
||||
NS_FORWARD_NSISTREAMLISTENER(mListener->)
|
||||
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
|
||||
|
||||
private:
|
||||
~nsStreamListenerWrapper() {}
|
||||
|
|
|
@ -11,10 +11,13 @@
|
|||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsITransport.h"
|
||||
#include "nsIThreadRetargetableStreamListener.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "prlog.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -41,6 +44,7 @@ nsInputStreamPump::nsInputStreamPump()
|
|||
, mLoadFlags(LOAD_NORMAL)
|
||||
, mWaiting(false)
|
||||
, mCloseWhenDone(false)
|
||||
, mRetargeting(false)
|
||||
{
|
||||
#if defined(PR_LOGGING)
|
||||
if (!gStreamPumpLog)
|
||||
|
@ -119,14 +123,18 @@ nsresult
|
|||
nsInputStreamPump::EnsureWaiting()
|
||||
{
|
||||
// no need to worry about multiple threads... an input stream pump lives
|
||||
// on only one thread.
|
||||
|
||||
// on only one thread at a time.
|
||||
MOZ_ASSERT(mAsyncStream);
|
||||
if (!mWaiting) {
|
||||
MOZ_ASSERT(mTargetThread);
|
||||
nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("AsyncWait failed");
|
||||
return rv;
|
||||
}
|
||||
// Any retargeting during STATE_START or START_TRANSFER is complete
|
||||
// after the call to AsyncWait; next callback wil be on mTargetThread.
|
||||
mRetargeting = false;
|
||||
mWaiting = true;
|
||||
}
|
||||
return NS_OK;
|
||||
|
@ -139,8 +147,9 @@ nsInputStreamPump::EnsureWaiting()
|
|||
// although this class can only be accessed from one thread at a time, we do
|
||||
// allow its ownership to move from thread to thread, assuming the consumer
|
||||
// understands the limitations of this.
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS3(nsInputStreamPump,
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS4(nsInputStreamPump,
|
||||
nsIRequest,
|
||||
nsIThreadRetargetableRequest,
|
||||
nsIInputStreamCallback,
|
||||
nsIInputStreamPump)
|
||||
|
||||
|
@ -380,15 +389,30 @@ nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)
|
|||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (mState == nextState && !mSuspendCount) {
|
||||
NS_ASSERTION(mState == STATE_TRANSFER, "unexpected state");
|
||||
NS_ASSERTION(NS_SUCCEEDED(mStatus), "unexpected status");
|
||||
bool stillTransferring = (mState == STATE_TRANSFER &&
|
||||
nextState == STATE_TRANSFER);
|
||||
if (stillTransferring) {
|
||||
NS_ASSERTION(NS_SUCCEEDED(mStatus),
|
||||
"Should not have failed status for ongoing transfer");
|
||||
} else {
|
||||
NS_ASSERTION(mState != nextState,
|
||||
"Only OnStateTransfer can be called more than once.");
|
||||
}
|
||||
if (mRetargeting) {
|
||||
NS_ASSERTION(mState != STATE_STOP,
|
||||
"Retargeting should not happen during OnStateStop.");
|
||||
}
|
||||
|
||||
// Wait asynchronously if there is still data to transfer, or if
|
||||
// delivery of data has been requested on another thread.
|
||||
if (!mSuspendCount && (stillTransferring || mRetargeting)) {
|
||||
mState = nextState;
|
||||
mWaiting = false;
|
||||
mStatus = EnsureWaiting();
|
||||
if (NS_SUCCEEDED(mStatus))
|
||||
break;
|
||||
|
||||
// Failure to start asynchronous wait: stop transfer.
|
||||
nextState = STATE_STOP;
|
||||
}
|
||||
|
||||
|
@ -551,6 +575,7 @@ nsInputStreamPump::OnStateStop()
|
|||
mAsyncStream = 0;
|
||||
mTargetThread = 0;
|
||||
mIsPending = false;
|
||||
mRetargeting = false;
|
||||
|
||||
mListener->OnStopRequest(this, mListenerContext, mStatus);
|
||||
mListener = 0;
|
||||
|
@ -561,3 +586,37 @@ nsInputStreamPump::OnStateStop()
|
|||
|
||||
return STATE_IDLE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsIThreadRetargetableRequest
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsInputStreamPump::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
|
||||
{
|
||||
NS_ENSURE_ARG(aNewTarget);
|
||||
if (aNewTarget == mTargetThread) {
|
||||
NS_WARNING("Retargeting delivery to same thread");
|
||||
return NS_OK;
|
||||
}
|
||||
NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER,
|
||||
NS_ERROR_UNEXPECTED);
|
||||
|
||||
// Ensure that |mListener| and any subsequent listeners can be retargeted
|
||||
// to another thread.
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
||||
do_QueryInterface(mListener, &rv);
|
||||
if (NS_SUCCEEDED(rv) && retargetableListener) {
|
||||
rv = retargetableListener->CheckListenerChain();
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mTargetThread = aNewTarget;
|
||||
mRetargeting = true;
|
||||
}
|
||||
}
|
||||
LOG(("nsInputStreamPump::RetargetDeliveryTo [this=%x aNewTarget=%p] "
|
||||
"%s listener [%p] rv[%x]",
|
||||
this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"),
|
||||
(nsIStreamListener*)mListener, rv));
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -15,17 +15,20 @@
|
|||
#include "nsIProgressEventSink.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsIThreadRetargetableRequest.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
class nsInputStreamPump MOZ_FINAL : public nsIInputStreamPump
|
||||
, public nsIInputStreamCallback
|
||||
, public nsIThreadRetargetableRequest
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUEST
|
||||
NS_DECL_NSIINPUTSTREAMPUMP
|
||||
NS_DECL_NSIINPUTSTREAMCALLBACK
|
||||
NS_DECL_NSITHREADRETARGETABLEREQUEST
|
||||
|
||||
nsInputStreamPump();
|
||||
~nsInputStreamPump();
|
||||
|
@ -71,7 +74,7 @@ protected:
|
|||
nsCOMPtr<nsILoadGroup> mLoadGroup;
|
||||
nsCOMPtr<nsIStreamListener> mListener;
|
||||
nsCOMPtr<nsISupports> mListenerContext;
|
||||
nsCOMPtr<nsIThread> mTargetThread;
|
||||
nsCOMPtr<nsIEventTarget> mTargetThread;
|
||||
nsCOMPtr<nsIInputStream> mStream;
|
||||
nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
|
||||
uint64_t mStreamOffset;
|
||||
|
@ -84,6 +87,7 @@ protected:
|
|||
bool mIsPending;
|
||||
bool mWaiting; // true if waiting on async source
|
||||
bool mCloseWhenDone;
|
||||
bool mRetargeting;
|
||||
};
|
||||
|
||||
#endif // !nsInputStreamChannel_h__
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
#include "nsStreamListenerTee.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS3(nsStreamListenerTee,
|
||||
nsIStreamListener,
|
||||
nsIRequestObserver,
|
||||
nsIStreamListenerTee)
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS4(nsStreamListenerTee,
|
||||
nsIStreamListener,
|
||||
nsIRequestObserver,
|
||||
nsIStreamListenerTee,
|
||||
nsIThreadRetargetableStreamListener)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStreamListenerTee::OnStartRequest(nsIRequest *request,
|
||||
|
@ -92,6 +93,19 @@ nsStreamListenerTee::OnDataAvailable(nsIRequest *request,
|
|||
return mListener->OnDataAvailable(request, context, tee, offset, count);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStreamListenerTee::CheckListenerChain()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
||||
do_QueryInterface(mListener, &rv);
|
||||
if (retargetableListener) {
|
||||
rv = retargetableListener->CheckListenerChain();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStreamListenerTee::Init(nsIStreamListener *listener,
|
||||
nsIOutputStream *sink,
|
||||
|
|
|
@ -6,17 +6,20 @@
|
|||
#define nsStreamListenerTee_h__
|
||||
|
||||
#include "nsIStreamListenerTee.h"
|
||||
#include "nsIThreadRetargetableStreamListener.h"
|
||||
#include "nsIInputStreamTee.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIEventTarget.h"
|
||||
|
||||
class nsStreamListenerTee : public nsIStreamListenerTee
|
||||
, public nsIThreadRetargetableStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
|
||||
NS_DECL_NSISTREAMLISTENERTEE
|
||||
|
||||
nsStreamListenerTee() { }
|
||||
|
|
|
@ -3,8 +3,22 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsStreamListenerWrapper.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS2(nsStreamListenerWrapper,
|
||||
NS_IMPL_ISUPPORTS3(nsStreamListenerWrapper,
|
||||
nsIStreamListener,
|
||||
nsIRequestObserver)
|
||||
nsIRequestObserver,
|
||||
nsIThreadRetargetableStreamListener)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStreamListenerWrapper::CheckListenerChain()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
||||
do_QueryInterface(mListener, &rv);
|
||||
if (retargetableListener) {
|
||||
rv = retargetableListener->CheckListenerChain();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "nsIRedirectResultListener.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsError.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsAlgorithm.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "nsIConsoleService.h"
|
||||
|
@ -4252,6 +4253,8 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
|
|||
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
|
||||
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -4943,6 +4946,42 @@ nsHttpChannel::ContinueOnStartRequest3(nsresult result)
|
|||
return CallOnStartRequest();
|
||||
}
|
||||
|
||||
class OnStopRequestCleanupEvent : public nsRunnable
|
||||
{
|
||||
public:
|
||||
OnStopRequestCleanupEvent(nsHttpChannel *aHttpChannel,
|
||||
nsresult aStatus)
|
||||
: mHttpChannel(aHttpChannel)
|
||||
, mStatus(aStatus)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
|
||||
NS_ASSERTION(aHttpChannel, "aHttpChannel should not be null");
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
|
||||
if (mHttpChannel) {
|
||||
mHttpChannel->OnStopRequestCleanup(mStatus);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsRefPtr<nsHttpChannel> mHttpChannel;
|
||||
nsresult mStatus;
|
||||
};
|
||||
|
||||
nsresult
|
||||
nsHttpChannel::OnStopRequestCleanup(nsresult aStatus)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread");
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup->RemoveRequest(this, nullptr, aStatus);
|
||||
}
|
||||
ReleaseListeners();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
|
||||
{
|
||||
|
@ -5072,14 +5111,17 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
|||
if (mOfflineCacheEntry)
|
||||
CloseOfflineCacheEntry();
|
||||
|
||||
if (mLoadGroup)
|
||||
mLoadGroup->RemoveRequest(this, nullptr, status);
|
||||
if (NS_IsMainThread()) {
|
||||
OnStopRequestCleanup(status);
|
||||
} else {
|
||||
nsresult rv = NS_DispatchToMainThread(
|
||||
new OnStopRequestCleanupEvent(this, status));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// We don't need this info anymore
|
||||
CleanRedirectCacheChainIfNecessary();
|
||||
|
||||
ReleaseListeners();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -5087,6 +5129,37 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
|||
// nsHttpChannel::nsIStreamListener
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class OnTransportStatusAsyncEvent : public nsRunnable
|
||||
{
|
||||
public:
|
||||
OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
|
||||
nsresult aTransportStatus,
|
||||
uint64_t aProgress,
|
||||
uint64_t aProgressMax)
|
||||
: mEventSink(aEventSink)
|
||||
, mTransportStatus(aTransportStatus)
|
||||
, mProgress(aProgress)
|
||||
, mProgressMax(aProgressMax)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
|
||||
if (mEventSink) {
|
||||
mEventSink->OnTransportStatus(nullptr, mTransportStatus,
|
||||
mProgress, mProgressMax);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsCOMPtr<nsITransportEventSink> mEventSink;
|
||||
nsresult mTransportStatus;
|
||||
uint64_t mProgress;
|
||||
uint64_t mProgressMax;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
|
||||
nsIInputStream *input,
|
||||
|
@ -5131,7 +5204,14 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
|
|||
uint64_t progress = mLogicalOffset + uint64_t(count);
|
||||
MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
|
||||
|
||||
OnTransportStatus(nullptr, transportStatus, progress, progressMax);
|
||||
if (NS_IsMainThread()) {
|
||||
OnTransportStatus(nullptr, transportStatus, progress, progressMax);
|
||||
} else {
|
||||
nsresult rv = NS_DispatchToMainThread(
|
||||
new OnTransportStatusAsyncEvent(this, transportStatus,
|
||||
progress, progressMax));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
//
|
||||
// we have to manually keep the logical offset of the stream up-to-date.
|
||||
|
@ -5155,6 +5235,71 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
|
|||
return NS_ERROR_ABORT;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpChannel::nsIThreadRetargetableRequest
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
|
||||
|
||||
NS_ENSURE_ARG(aNewTarget);
|
||||
if (aNewTarget == NS_GetCurrentThread()) {
|
||||
NS_WARNING("Retargeting delivery to same thread");
|
||||
return NS_OK;
|
||||
}
|
||||
NS_ENSURE_TRUE(mTransactionPump || mCachePump, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
// If both cache pump and transaction pump exist, we're probably dealing
|
||||
// with partially cached content. So, we must be able to retarget both.
|
||||
nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
|
||||
nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
|
||||
if (mCachePump) {
|
||||
// Direct call to QueryInterface to avoid multiple inheritance issues.
|
||||
rv = mCachePump->QueryInterface(NS_GET_IID(nsIThreadRetargetableRequest),
|
||||
getter_AddRefs(retargetableCachePump));
|
||||
// nsInputStreamPump should implement this interface.
|
||||
MOZ_ASSERT(retargetableCachePump);
|
||||
rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
|
||||
}
|
||||
if (NS_SUCCEEDED(rv) && mTransactionPump) {
|
||||
// Direct call to QueryInterface to avoid multiple inheritance issues.
|
||||
rv = mTransactionPump->QueryInterface(NS_GET_IID(nsIThreadRetargetableRequest),
|
||||
getter_AddRefs(retargetableTransactionPump));
|
||||
// nsInputStreamPump should implement this interface.
|
||||
MOZ_ASSERT(retargetableTransactionPump);
|
||||
rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
|
||||
|
||||
// If retarget fails for transaction pump, we must restore mCachePump.
|
||||
if (NS_FAILED(rv) && retargetableCachePump) {
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
rv = NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = retargetableCachePump->RetargetDeliveryTo(mainThread);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpChannel::nsThreadRetargetableStreamListener
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::CheckListenerChain()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
|
||||
do_QueryInterface(mListener, &rv);
|
||||
if (retargetableListener) {
|
||||
rv = retargetableListener->CheckListenerChain();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpChannel::nsITransportEventSink
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||
#include "nsITimedChannel.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIThreadRetargetableRequest.h"
|
||||
#include "nsIThreadRetargetableStreamListener.h"
|
||||
#include "nsDNSPrefetch.h"
|
||||
#include "TimingStruct.h"
|
||||
#include "AutoClose.h"
|
||||
|
@ -54,11 +56,14 @@ class nsHttpChannel : public HttpBaseChannel
|
|||
, public nsIApplicationCacheChannel
|
||||
, public nsIAsyncVerifyRedirectCallback
|
||||
, public nsITimedChannel
|
||||
, public nsIThreadRetargetableRequest
|
||||
, public nsIThreadRetargetableStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
|
||||
NS_DECL_NSICACHEINFOCHANNEL
|
||||
NS_DECL_NSICACHINGCHANNEL
|
||||
NS_DECL_NSICACHELISTENER
|
||||
|
@ -69,6 +74,7 @@ public:
|
|||
NS_DECL_NSIAPPLICATIONCACHECHANNEL
|
||||
NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
|
||||
NS_DECL_NSITIMEDCHANNEL
|
||||
NS_DECL_NSITHREADRETARGETABLEREQUEST
|
||||
|
||||
// nsIHttpAuthenticableChannel. We can't use
|
||||
// NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
|
||||
|
@ -150,6 +156,8 @@ public: /* internal necko use only */
|
|||
|
||||
OfflineCacheEntryAsForeignMarker* GetOfflineCacheEntryAsForeignMarker();
|
||||
|
||||
nsresult OnStopRequestCleanup(nsresult aStatus);
|
||||
|
||||
private:
|
||||
typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
|
||||
|
||||
|
|
|
@ -3974,7 +3974,10 @@ Response.prototype =
|
|||
var avail = bodyStream ? bodyStream.available() : 0;
|
||||
|
||||
// XXX assumes stream will always report the full amount of data available
|
||||
headers.setHeader("Content-Length", "" + avail, false);
|
||||
// Set "Content-Length" if not already set by request handler.
|
||||
if (!headers.hasHeader("Content-Length")) {
|
||||
headers.setHeader("Content-Length", "" + avail, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# -*- Mode: Makefile; 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
FAIL_ON_WARNINGS := 1
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_FILES = \
|
||||
partial_content.sjs \
|
||||
test_partially_cached_content.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
|
@ -0,0 +1,7 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; 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/.
|
||||
|
||||
MODULE = 'test_necko'
|
|
@ -0,0 +1,133 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// HTTP Version from request, used for response.
|
||||
var gHttpVersion;
|
||||
|
||||
/* Debug and Error wrapper functions for dump().
|
||||
*/
|
||||
function ERR(response, responseCode, responseCodeStr, msg)
|
||||
{
|
||||
// Dump to console log and send to client in response.
|
||||
dump("SERVER ERROR: " + msg + "\n");
|
||||
response.setStatusLine(gHttpVersion, responseCode, responseCodeStr);
|
||||
response.write(msg);
|
||||
}
|
||||
|
||||
function DBG(msg)
|
||||
{
|
||||
// Dump to console only.
|
||||
dump("SERVER DEBUG: " + msg + "\n");
|
||||
}
|
||||
|
||||
/* Delivers content in parts to test partially cached content: requires two
|
||||
* requests for the same resource.
|
||||
*
|
||||
* First call will respond with partial content, but a 200 header and
|
||||
* Content-Length equal to the full content length. No Range or If-Range
|
||||
* headers are allowed in the request.
|
||||
*
|
||||
* Second call will require Range and If-Range in the request headers, and
|
||||
* will respond with the range requested.
|
||||
*/
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
// Set http version for error responses.
|
||||
gHttpVersion = request.httpVersion;
|
||||
|
||||
// All responses, inc. errors, are text/html.
|
||||
response.setHeader("Content-Type", "text/html; charset=UTF-8", false);
|
||||
|
||||
// Get state var to determine if this is the first or second request.
|
||||
var expectedRequestType;
|
||||
if (getState("expectedRequestType") === "") {
|
||||
DBG("First call: Should be requesting full content.");
|
||||
expectedRequestType = "fullRequest";
|
||||
// Set state var for second request.
|
||||
setState("expectedRequestType", "partialRequest");
|
||||
} else if (getState("expectedRequestType") === "partialRequest") {
|
||||
DBG("Second call: Should be requesting undelivered content.");
|
||||
expectedRequestType = "partialRequest";
|
||||
// Reset state var for first request.
|
||||
setState("expectedRequestType", "");
|
||||
} else {
|
||||
ERR(response, 500, "Internal Server Error",
|
||||
"Invalid expectedRequestType \"" + expectedRequestType + "\"in " +
|
||||
"server state db.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for Range and If-Range
|
||||
var range = request.hasHeader("Range") ? request.getHeader("Range") : "";
|
||||
var ifRange = request.hasHeader("If-Range") ? request.getHeader("If-Range") : "";
|
||||
|
||||
if (expectedRequestType === "fullRequest") {
|
||||
// Should not have Range or If-Range in first request.
|
||||
if (range && range.length > 0) {
|
||||
ERR(response, 400, "Bad Request",
|
||||
"Should not receive \"Range: " + range + "\" for first, full request.");
|
||||
return;
|
||||
}
|
||||
if (ifRange && ifRange.length > 0) {
|
||||
ERR(response, 400, "Bad Request",
|
||||
"Should not receive \"Range: " + range + "\" for first, full request.");
|
||||
return;
|
||||
}
|
||||
} else if (expectedRequestType === "partialRequest") {
|
||||
// Range AND If-Range should both be present in second request.
|
||||
if (!range) {
|
||||
ERR(response, 400, "Bad Request",
|
||||
"Should receive \"Range: \" for second, partial request.");
|
||||
return;
|
||||
}
|
||||
if (!ifRange) {
|
||||
ERR(response, 400, "Bad Request",
|
||||
"Should receive \"If-Range: \" for second, partial request.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Somewhat redundant, but a check for errors in this test code.
|
||||
ERR(response, 500, "Internal Server Error",
|
||||
"expectedRequestType not set correctly: \"" + expectedRequestType + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare content in two parts for responses.
|
||||
var partialContent = "<html><head></head><body><p id=\"firstResponse\">" +
|
||||
"First response</p>";
|
||||
var remainderContent = "<p id=\"secondResponse\">Second response</p>" +
|
||||
"</body></html>";
|
||||
var totalLength = partialContent.length + remainderContent.length;
|
||||
|
||||
DBG("totalLength: " + totalLength);
|
||||
|
||||
// Prepare common headers for the two responses.
|
||||
response.setHeader("ETag", "abcd0123", false);
|
||||
response.setHeader("Accept-Ranges", "bytes", false);
|
||||
|
||||
// Prepare specific headers and content for first and second responses.
|
||||
if (expectedRequestType === "fullRequest") {
|
||||
DBG("First response: Sending partial content with a full header");
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.write(partialContent, partialContent.length);
|
||||
// Set Content-Length to full length of resource.
|
||||
response.setHeader("Content-Length", "" + totalLength, false);
|
||||
} else if (expectedRequestType === "partialRequest") {
|
||||
DBG("Second response: Sending remaining content with a range header");
|
||||
response.setStatusLine(request.httpVersion, 206, "Partial Content");
|
||||
response.setHeader("Content-Range", "bytes " + partialContent.length + "-" +
|
||||
(totalLength - 1) + "/" + totalLength);
|
||||
response.write(remainderContent);
|
||||
// Set Content-Length to length of bytes transmitted.
|
||||
response.setHeader("Content-Length", "" + remainderContent.length, false);
|
||||
} else {
|
||||
// Somewhat redundant, but a check for errors in this test code.
|
||||
ERR(response, 500, "Internal Server Error",
|
||||
"Something very bad happened here: expectedRequestType is invalid " +
|
||||
"towards the end of handleRequest! - \"" + expectedRequestType + "\"");
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=497003
|
||||
|
||||
This test verifies that partially cached content is read from the cache first
|
||||
and then from the network. It is written in the mochitest framework to take
|
||||
thread retargeting into consideration of nsIStreamListener callbacks (inc.
|
||||
nsIRequestObserver). E.g. HTML5 Stream Parser requesting retargeting of
|
||||
nsIStreamListener callbacks to the parser thread.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test for Bug 497003: support sending OnDataAvailable() to other threads</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=497003">Mozilla Bug 497003: support sending OnDataAvailable() to other threads</a></p>
|
||||
<p><iframe id="contentFrame" src="partial_content.sjs"></iframe></p>
|
||||
|
||||
<pre id="test">
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
/* Check that the iframe has initial content only after the first load.
|
||||
*/
|
||||
function expectInitialContent(e) {
|
||||
info("expectInitialContent",
|
||||
"First response received: should have partial content");
|
||||
var frameWindow = document.getElementById('contentFrame').contentWindow;
|
||||
|
||||
// Expect "First response" in received HTML.
|
||||
var firstResponse = frameWindow.document.getElementById('firstResponse');
|
||||
is(firstResponse.innerHTML, "First response", "firstResponse");
|
||||
|
||||
// Expect NOT to get any second response element.
|
||||
var secondResponse = frameWindow.document.getElementById('secondResponse');
|
||||
ok(!secondResponse, "Should not get text for second response in first.");
|
||||
|
||||
// Set up listener for second load.
|
||||
e.target.removeEventListener("load", expectInitialContent, false);
|
||||
e.target.addEventListener("load", expectFullContent, false);
|
||||
|
||||
// Reload.
|
||||
e.target.src="partial_content.sjs";
|
||||
}
|
||||
|
||||
/* Check that the iframe has all the content after the second load.
|
||||
*/
|
||||
function expectFullContent(e)
|
||||
{
|
||||
info("expectFullContent",
|
||||
"Second response received: should complete content from first load");
|
||||
var frameWindow = document.getElementById('contentFrame').contentWindow;
|
||||
|
||||
// Expect "First response" to still be there
|
||||
var firstResponse = frameWindow.document.getElementById('firstResponse');
|
||||
is(firstResponse.innerHTML, "First response", "firstResponse");
|
||||
|
||||
// Expect "Second response" to be there also.
|
||||
var secondResponse = frameWindow.document.getElementById('secondResponse');
|
||||
is(secondResponse.innerHTML, "Second response", "secondResponse");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
// Set listener for first load to expect partial content.
|
||||
document.getElementById('contentFrame')
|
||||
.addEventListener("load", expectInitialContent, false);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -4,7 +4,7 @@
|
|||
# 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/.
|
||||
|
||||
TEST_DIRS += ['httpserver', 'browser']
|
||||
TEST_DIRS += ['httpserver', 'browser', 'mochitests']
|
||||
|
||||
MODULE = 'test_necko'
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче