Bug 497003 - Support delivery of OnDataAvailable off the main thread r=bz r=jduell

This commit is contained in:
Steve Workman 2013-06-13 10:42:48 -07:00
Родитель 5e38907e9d
Коммит 1f8d40499c
18 изменённых файлов: 586 добавлений и 23 удалений

Просмотреть файл

@ -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'