Mozilla/mozilla/uriloader/base/nsDocLoader.cpp
dougt%netscape.com 175245e2de Relanding Necko Changes.
Revising nsIChannel to allow for overlapped i/o. This consists of three parts:

1. Factoring nsIChannel into a protocol specific part, the nsIChannel, and a socket specific, the nsITransport.
2. Derive the nsIChannel from a nsIRequest.
2. Changes the notification system from necko and the URILoader to pass the nsIRequest interface instead of nsIChannel interface.

This goal stems from wanting to be able to have active AsyncRead and AsyncWrite operations on nsSocketTransport.
This is desired because it would greatly simplify the task of maintaining persistent/reusable socket connections
for FTP, HTTP, and Imap (and potentially other protocols). The problem with the existing nsIChannel interface is
that it does not allow one to selectively suspend just one of the read or write operations while keeping the other active.

r=darin@netscape.com
sr=rpotts@netscape.com


git-svn-id: svn://10.0.0.236/trunk@87587 18797224-902f-48f8-a5cc-f745e15eee43
2001-02-21 20:38:08 +00:00

1582 lines
43 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
#include "nspr.h"
#include "prlog.h"
#include "nsIDocumentLoaderObserver.h"
#include "nsDocLoader.h"
#include "nsCURILoader.h"
#include "nsNetUtil.h"
#include "nsIServiceManager.h"
#include "nsXPIDLString.h"
#include "nsIURL.h"
#include "nsCOMPtr.h"
#include "nsCom.h"
#include "nsWeakPtr.h"
#include "nsIDOMWindow.h"
// XXX ick ick ick
#include "nsIContentViewerContainer.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"
#include "nsIPresContext.h"
#include "nsIStringBundle.h"
static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);
#if defined(PR_LOGGING)
//
// Log module for nsIDocumentLoader logging...
//
// To enable logging (see prlog.h for full details):
//
// set NSPR_LOG_MODULES=DocLoader:5
// set NSPR_LOG_FILE=nspr.log
//
// this enables PR_LOG_DEBUG level information and places all output in
// the file nspr.log
//
PRLogModuleInfo* gDocLoaderLog = nsnull;
#endif /* PR_LOGGING */
#if defined(DEBUG)
void GetURIStringFromRequest(nsIRequest* request, nsXPIDLCString &aStr)
{
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (!channel) {
aStr = "???";
return;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = channel->GetURI(getter_AddRefs(uri));
if (NS_SUCCEEDED(rv) && uri)
rv = uri->GetSpec(getter_Copies(aStr));
else
aStr = "???";
}
#endif /* DEBUG */
/* Define IIDs... */
static NS_DEFINE_IID(kIDocumentIID, NS_IDOCUMENT_IID);
static NS_DEFINE_IID(kIContentViewerContainerIID, NS_ICONTENTVIEWERCONTAINER_IID);
struct nsChannelInfo {
nsChannelInfo(nsIChannel *key) : mKey(key),
mCurrentProgress(0),
mMaxProgress(0)
{
key->GetURI(getter_AddRefs(mURI));
}
void* mKey;
nsCOMPtr<nsIURI> mURI;
PRInt32 mCurrentProgress;
PRInt32 mMaxProgress;
};
nsDocLoaderImpl::nsDocLoaderImpl()
{
NS_INIT_REFCNT();
#if defined(PR_LOGGING)
if (nsnull == gDocLoaderLog) {
gDocLoaderLog = PR_NewLogModule("DocLoader");
}
#endif /* PR_LOGGING */
mContainer = nsnull;
mParent = nsnull;
mIsLoadingDocument = PR_FALSE;
ClearInternalProgress();
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: created.\n", this));
}
nsresult
nsDocLoaderImpl::SetDocLoaderParent(nsDocLoaderImpl *aParent)
{
mParent = aParent;
return NS_OK;
}
nsresult
nsDocLoaderImpl::Init()
{
nsresult rv;
rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this);
if (NS_FAILED(rv)) return rv;
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: load group %x.\n", this, mLoadGroup.get()));
rv = NS_NewISupportsArray(getter_AddRefs(mChildList));
if (NS_FAILED(rv)) return rv;
return NS_NewISupportsArray(getter_AddRefs(mListenerList));
}
NS_IMETHODIMP nsDocLoaderImpl::ClearParentDocLoader()
{
SetDocLoaderParent(nsnull);
return NS_OK;
}
nsDocLoaderImpl::~nsDocLoaderImpl()
{
/*
|ClearWeakReferences()| here is intended to prevent people holding weak references
from re-entering this destructor since |QueryReferent()| will |AddRef()| me, and the
subsequent |Release()| will try to destroy me. At this point there should be only
weak references remaining (otherwise, we wouldn't be getting destroyed).
An alternative would be incrementing our refcount (consider it a compressed flag
saying "Don't re-destroy."). I haven't yet decided which is better. [scc]
*/
ClearWeakReferences();
Destroy();
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: deleted.\n", this));
PRUint32 count=0;
mChildList->Count(&count);
// if the doc loader still has children...we need to enumerate the children and make
// them null out their back ptr to the parent doc loader
if (count > 0)
{
for (PRUint32 i=0; i<count; i++)
{
nsCOMPtr<nsIDocumentLoader> loader;
loader = getter_AddRefs(NS_STATIC_CAST(nsIDocumentLoader*, mChildList->ElementAt(i)));
if (loader)
loader->ClearParentDocLoader();
}
mChildList->Clear();
}
}
/*
* Implementation of ISupports methods...
*/
NS_IMPL_THREADSAFE_ADDREF(nsDocLoaderImpl)
NS_IMPL_THREADSAFE_RELEASE(nsDocLoaderImpl)
NS_INTERFACE_MAP_BEGIN(nsDocLoaderImpl)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamObserver)
NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIHTTPEventSink)
NS_INTERFACE_MAP_ENTRY(nsISecurityEventSink)
NS_INTERFACE_MAP_END
/*
* Implementation of nsIInterfaceRequestor methods...
*/
NS_IMETHODIMP nsDocLoaderImpl::GetInterface(const nsIID& aIID, void** aSink)
{
nsresult rv = NS_ERROR_NO_INTERFACE;
NS_ENSURE_ARG_POINTER(aSink);
if(aIID.Equals(NS_GET_IID(nsILoadGroup))) {
*aSink = mLoadGroup;
NS_IF_ADDREF((nsISupports*)*aSink);
rv = NS_OK;
} else {
rv = QueryInterface(aIID, aSink);
}
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::CreateDocumentLoader(nsIDocumentLoader** anInstance)
{
nsresult rv = NS_OK;
nsDocLoaderImpl * newLoader = new nsDocLoaderImpl();
NS_ENSURE_TRUE(newLoader, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(newLoader);
newLoader->Init();
// Initialize now that we have a reference
rv = newLoader->SetDocLoaderParent(this);
if (NS_SUCCEEDED(rv)) {
//
// XXX this method incorrectly returns a bool
//
rv = mChildList->AppendElement((nsIDocumentLoader*)newLoader)
? NS_OK : NS_ERROR_FAILURE;
}
rv = newLoader->QueryInterface(NS_GET_IID(nsIDocumentLoader), (void **) anInstance);
NS_RELEASE(newLoader);
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::Stop(void)
{
nsresult rv = NS_OK;
PRUint32 count, i;
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: Stop() called\n", this));
rv = mChildList->Count(&count);
if (NS_FAILED(rv)) return rv;
for (i=0; i<count; i++) {
nsIDocumentLoader* loader;
loader = NS_STATIC_CAST(nsIDocumentLoader*, mChildList->ElementAt(i));
if (loader) {
(void) loader->Stop();
NS_RELEASE(loader);
}
}
rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::IsBusy(PRBool * aResult)
{
nsresult rv;
//
// A document loader is busy if either:
//
// 1. It is currently loading a document (ie. one or more URIs)
// 2. One of it's child document loaders is busy...
//
*aResult = PR_FALSE;
/* Is this document loader busy? */
if (mIsLoadingDocument) {
rv = mLoadGroup->IsPending(aResult);
if (NS_FAILED(rv)) return rv;
}
/* Otherwise, check its child document loaders... */
if (!*aResult) {
PRUint32 count, i;
rv = mChildList->Count(&count);
if (NS_FAILED(rv)) return rv;
for (i=0; i<count; i++) {
nsIDocumentLoader* loader;
loader = NS_STATIC_CAST(nsIDocumentLoader*, mChildList->ElementAt(i));
if (loader) {
(void) loader->IsBusy(aResult);
NS_RELEASE(loader);
if (*aResult) break;
}
}
}
return NS_OK;
}
/*
* Do not hold refs to the objects in the observer lists. Observers
* are expected to remove themselves upon their destruction if they
* have not removed themselves previously
*/
NS_IMETHODIMP
nsDocLoaderImpl::AddObserver(nsIDocumentLoaderObserver* aObserver)
{
nsresult rv;
if (mDocObservers.IndexOf(aObserver) == -1) {
//
// XXX this method incorrectly returns a bool
//
rv = mDocObservers.AppendElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
} else {
// The observer is already in the list...
rv = NS_ERROR_FAILURE;
}
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::RemoveObserver(nsIDocumentLoaderObserver* aObserver)
{
if (PR_TRUE == mDocObservers.RemoveElement(aObserver)) {
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDocLoaderImpl::SetContainer(nsISupports* aContainer)
{
// This is a weak reference...
mContainer = aContainer;
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::GetContainer(nsISupports** aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
*aResult = mContainer;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::GetLoadGroup(nsILoadGroup** aResult)
{
nsresult rv = NS_OK;
if (nsnull == aResult) {
rv = NS_ERROR_NULL_POINTER;
} else {
*aResult = mLoadGroup;
NS_IF_ADDREF(*aResult);
}
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::GetContentViewerContainer(nsISupports* aDocumentID,
nsIContentViewerContainer** aResult)
{
nsISupports* base = aDocumentID;
nsIDocument* doc;
nsresult rv;
rv = base->QueryInterface(kIDocumentIID, (void**)&doc);
if (NS_SUCCEEDED(rv)) {
nsIPresShell* pres;
pres = doc->GetShellAt(0);
if (nsnull != pres) {
nsIPresContext* presContext;
rv = pres->GetPresContext(&presContext);
if (NS_SUCCEEDED(rv) && nsnull != presContext) {
nsISupports* supp;
rv = presContext->GetContainer(&supp);
if (NS_SUCCEEDED(rv) && nsnull != supp) {
rv = supp->QueryInterface(kIContentViewerContainerIID, (void**)aResult);
NS_RELEASE(supp);
}
NS_RELEASE(presContext);
}
NS_RELEASE(pres);
}
NS_RELEASE(doc);
}
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::Destroy()
{
Stop();
// Remove the document loader from the parent list of loaders...
if (mParent)
{
mParent->RemoveChildGroup(this);
mParent = nsnull;
}
ClearChannelInfoList();
mDocumentRequest = 0;
mLoadGroup->SetGroupObserver(nsnull);
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
{
// called each time a channel is added to the group.
nsresult rv;
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
if (!mIsLoadingDocument) {
PRUint32 loadAttribs = 0;
aChannel->GetLoadAttributes(&loadAttribs);
if (loadAttribs & nsIChannel::LOAD_DOCUMENT_URI) {
mIsLoadingDocument = PR_TRUE;
ClearInternalProgress(); // only clear our progress if we are starting a new load....
}
}
//
// Only fire an OnStartDocumentLoad(...) if the document loader
// has initiated a load... Otherwise, this notification has
// resulted from a channel being added to the load group.
//
if (mIsLoadingDocument) {
PRUint32 count;
rv = mLoadGroup->GetActiveCount(&count);
if (NS_FAILED(rv)) return rv;
//
// Create a new ChannelInfo for the channel that is starting to
// load...
//
AddChannelInfo(aChannel);
if (1 == count) {
// This channel is associated with the entire document...
mDocumentRequest = do_QueryInterface(request);
mLoadGroup->SetDefaultLoadRequest(mDocumentRequest);
// Update the progress status state
mProgressStateFlags = nsIWebProgressListener::STATE_START;
doStartDocumentLoad();
FireOnStartDocumentLoad(this, request);
}
else {
doStartURLLoad(request);
FireOnStartURLLoad(this, request);
}
}
else {
ClearChannelInfoList();
doStartURLLoad(request);
FireOnStartURLLoad(this, request);
}
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::OnStopRequest(nsIRequest *request,
nsISupports *aCtxt,
nsresult aStatus,
const PRUnichar *aMsg)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
//
// Only fire the OnEndDocumentLoad(...) if the document loader
// has initiated a load...
//
if (mIsLoadingDocument) {
PRUint32 count;
//
// Set the Maximum progress to the same value as the current progress.
// Since the URI has finished loading, all the data is there. Also,
// this will allow a more accurate estimation of the max progress (in case
// the old value was unknown ie. -1)
//
nsChannelInfo *info;
info = GetChannelInfo(aChannel);
if (info) {
PRInt32 oldMax = info->mMaxProgress;
info->mMaxProgress = info->mCurrentProgress;
//
// If a channel whose content-length was previously unknown has just
// finished loading, then use this new data to try to calculate a
// mMaxSelfProgress...
//
if ((oldMax < 0) && (mMaxSelfProgress < 0)) {
CalculateMaxProgress(&mMaxSelfProgress);
}
}
//
// Fire the OnStateChange(...) notification for stop request
//
doStopURLLoad(request, aStatus);
FireOnEndURLLoad(this, request, aStatus);
rv = mLoadGroup->GetActiveCount(&count);
if (NS_FAILED(rv)) return rv;
//
// The load group for this DocumentLoader is idle...
//
if (0 == count) {
DocLoaderIsEmpty(aStatus);
}
}
else {
doStopURLLoad(request, aStatus);
FireOnEndURLLoad(this, request, aStatus);
}
return NS_OK;
}
nsresult nsDocLoaderImpl::RemoveChildGroup(nsDocLoaderImpl* aLoader)
{
nsresult rv = NS_OK;
if (NS_SUCCEEDED(rv)) {
mChildList->RemoveElement((nsIDocumentLoader*)aLoader);
}
return rv;
}
NS_IMETHODIMP nsDocLoaderImpl::GetDocumentChannel(nsIChannel ** aChannel)
{
nsCOMPtr<nsIChannel> ourChannel = do_QueryInterface(mDocumentRequest);
*aChannel = ourChannel.get();
NS_IF_ADDREF(*aChannel);
return NS_OK;
}
void nsDocLoaderImpl::DocLoaderIsEmpty(nsresult aStatus)
{
if (mIsLoadingDocument) {
PRBool busy = PR_FALSE;
/* In the unimagineably rude circumstance that onload event handlers
triggered by this function actually kill the window ... ok, it's
not unimagineable; it's happened ... this deathgrip keeps this object
alive long enough to survive this function call. */
nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
IsBusy(&busy);
if (!busy) {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: Is now idle...\n", this));
nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
mDocumentRequest = null_nsCOMPtr();
mIsLoadingDocument = PR_FALSE;
// Update the progress status state - the document is done
mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
nsresult loadGroupStatus = NS_OK;
mLoadGroup->GetStatus(&loadGroupStatus);
//
// New code to break the circular reference between
// the load group and the docloader...
//
mLoadGroup->SetDefaultLoadRequest(nsnull);
//
// Do nothing after firing the OnEndDocumentLoad(...). The document
// loader may be loading a *new* document - if LoadDocument()
// was called from a handler!
//
doStopDocumentLoad(docRequest, loadGroupStatus);
FireOnEndDocumentLoad(this, docRequest, loadGroupStatus);
if (mParent) {
mParent->DocLoaderIsEmpty(loadGroupStatus);
}
}
}
}
void nsDocLoaderImpl::doStartDocumentLoad(void)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(mDocumentRequest, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
"\tURI: %s \n",
this, (const char *) buffer));
#endif /* DEBUG */
// Fire an OnStatus(...) notification STATE_START. This indicates
// that the document represented by mDocumentRequest has started to
// load...
FireOnStateChange(this,
mDocumentRequest,
nsIWebProgressListener::STATE_START |
nsIWebProgressListener::STATE_IS_DOCUMENT |
nsIWebProgressListener::STATE_IS_REQUEST |
nsIWebProgressListener::STATE_IS_NETWORK,
NS_OK);
}
void nsDocLoaderImpl::doStartURLLoad(nsIRequest *request)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
"\tURI: %s\n",
this, (const char *) buffer));
#endif /* DEBUG */
FireOnStateChange(this,
request,
nsIWebProgressListener::STATE_START |
nsIWebProgressListener::STATE_IS_REQUEST,
NS_OK);
}
void nsDocLoaderImpl::doStopURLLoad(nsIRequest *request, nsresult aStatus)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
"\tURI: %s status=%x\n",
this, (const char *) buffer, aStatus));
#endif /* DEBUG */
FireOnStateChange(this,
request,
nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_REQUEST,
aStatus);
}
void nsDocLoaderImpl::doStopDocumentLoad(nsIRequest *request,
nsresult aStatus)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
"\tURI: %s Status=%x\n",
this, (const char *) buffer, aStatus));
#endif /* DEBUG */
//
// Fire an OnStatusChange(...) notification indicating the the
// current document has finished loading...
//
FireOnStateChange(this,
request,
nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_DOCUMENT,
aStatus);
//
// Fire a final OnStatusChange(...) notification indicating the the
// current document has finished loading...
//
FireOnStateChange(this,
request,
nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_WINDOW |
nsIWebProgressListener::STATE_IS_NETWORK,
aStatus);
}
void nsDocLoaderImpl::FireOnStartDocumentLoad(nsDocLoaderImpl* aLoadInitiator,
nsIRequest *aDocRequest)
{
PRInt32 count;
nsCOMPtr<nsIURI> uri;
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(aDocRequest);
aChannel->GetURI(getter_AddRefs(uri));
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(aDocRequest, buffer);
if (aLoadInitiator == this) {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStartDocumentLoad(...).\tURI: %s\n",
this, (const char *) buffer));
} else {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: -- Propagating OnStartDocumentLoad(...)."
"DocLoader:%p URI:%s\n",
this, aLoadInitiator, (const char *) buffer));
}
#endif /* DEBUG */
/*
* First notify any observers that the document load has begun...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
count = mDocObservers.Count();
while (count > 0) {
nsIDocumentLoaderObserver *observer;
observer = NS_STATIC_CAST(nsIDocumentLoaderObserver*, mDocObservers.ElementAt(--count));
NS_ASSERTION(observer, "NULL observer found in list.");
if (! observer) {
continue;
}
observer->OnStartDocumentLoad(aLoadInitiator, uri, mCommand);
}
/*
* Finally notify the parent...
*/
if (mParent) {
mParent->FireOnStartDocumentLoad(aLoadInitiator, aDocRequest);
}
}
void nsDocLoaderImpl::FireOnEndDocumentLoad(nsDocLoaderImpl* aLoadInitiator,
nsIRequest *aDocRequest,
nsresult aStatus)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(aDocRequest, buffer);
if (aLoadInitiator == this) {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnEndDocumentLoad(...)"
"\tURI: %s Status: %x\n",
this, (const char *) buffer, aStatus));
} else {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: -- Propagating OnEndDocumentLoad(...)."
"DocLoader:%p URI:%s\n",
this, aLoadInitiator, (const char *)buffer));
}
#endif /* DEBUG */
/*
* First notify any observers that the document load has finished...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
PRInt32 count;
count = mDocObservers.Count();
while (count > 0) {
nsIDocumentLoaderObserver *observer;
observer = NS_STATIC_CAST(nsIDocumentLoaderObserver*, mDocObservers.ElementAt(--count));
NS_ASSERTION(observer, "NULL observer found in list.");
if (! observer) {
continue;
}
observer->OnEndDocumentLoad(aLoadInitiator, aDocRequest, aStatus);
}
/*
* Next notify the parent...
*/
if (mParent) {
mParent->FireOnEndDocumentLoad(aLoadInitiator, aDocRequest, aStatus);
}
}
void nsDocLoaderImpl::FireOnStartURLLoad(nsDocLoaderImpl* aLoadInitiator,
nsIRequest* aRequest)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(aRequest, buffer);
if (aLoadInitiator == this) {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnStartURLLoad(...)"
"\tURI: %s\n",
this, (const char *) buffer));
} else {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: -- Propagating OnStartURLLoad(...)."
"DocLoader:%p URI:%s\n",
this, aLoadInitiator, (const char *) buffer));
}
#endif /* DEBUG */
PRInt32 count;
/*
* First notify any observers that the URL load has begun...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
count = mDocObservers.Count();
while (count > 0) {
nsIDocumentLoaderObserver *observer;
observer = NS_STATIC_CAST(nsIDocumentLoaderObserver*, mDocObservers.ElementAt(--count));
NS_ASSERTION(observer, "NULL observer found in list.");
if (! observer) {
continue;
}
observer->OnStartURLLoad(aLoadInitiator, aRequest);
}
/*
* Finally notify the parent...
*/
if (mParent) {
mParent->FireOnStartURLLoad(aLoadInitiator, aRequest);
}
}
void nsDocLoaderImpl::FireOnEndURLLoad(nsDocLoaderImpl* aLoadInitiator,
nsIRequest *request, nsresult aStatus)
{
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
if (aLoadInitiator == this) {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: ++ Firing OnEndURLLoad(...)"
"\tURI: %s Status: %x\n",
this, (const char *) buffer, aStatus));
} else {
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: -- Propagating OnEndURLLoad(...)."
"DocLoader:%p URI:%s\n",
this, aLoadInitiator, (const char *) buffer));
}
#endif /* DEBUG */
PRInt32 count;
/*
* First notify any observers that the URL load has completed...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
count = mDocObservers.Count();
while (count > 0) {
nsIDocumentLoaderObserver *observer;
observer = NS_STATIC_CAST(nsIDocumentLoaderObserver*, mDocObservers.ElementAt(--count));
NS_ASSERTION(observer, "NULL observer found in list.");
if (! observer) {
continue;
}
observer->OnEndURLLoad(aLoadInitiator, request, aStatus);
}
/*
* Finally notify the parent...
*/
if (mParent) {
mParent->FireOnEndURLLoad(aLoadInitiator, request, aStatus);
}
}
////////////////////////////////////////////////////////////////////////////////////
// The following section contains support for nsIWebProgress and related stuff
////////////////////////////////////////////////////////////////////////////////////
// get web progress returns our web progress listener or if
// we don't have one, it will look up the doc loader hierarchy
// to see if one of our parent doc loaders has one.
nsresult nsDocLoaderImpl::GetParentWebProgressListener(nsDocLoaderImpl * aDocLoader, nsIWebProgressListener ** aWebProgress)
{
// if we got here, there is no web progress to return
*aWebProgress = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::AddProgressListener(nsIWebProgressListener *aListener)
{
nsresult rv;
nsWeakPtr listener = getter_AddRefs(NS_GetWeakReference(aListener));
if (!listener) return NS_ERROR_INVALID_ARG;
if (mListenerList->IndexOf(listener) == -1) {
// XXX this method incorrectly returns a bool
rv = mListenerList->AppendElement(listener) ? NS_OK : NS_ERROR_FAILURE;
} else {
// The listener is already in the list...
rv = NS_ERROR_FAILURE;
}
return rv;
}
NS_IMETHODIMP
nsDocLoaderImpl::RemoveProgressListener(nsIWebProgressListener *aListener)
{
nsWeakPtr listener = getter_AddRefs(NS_GetWeakReference(aListener));
if (!listener) return NS_ERROR_INVALID_ARG;
// XXX this method incorrectly returns a bool
return mListenerList->RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDocLoaderImpl::GetDOMWindow(nsIDOMWindow **aResult)
{
nsresult rv = NS_OK;
*aResult = nsnull;
//
// The DOM Window is available from the associated container (ie DocShell)
// if one is available...
//
if (mContainer) {
nsCOMPtr<nsIDOMWindow> window(do_GetInterface(mContainer, &rv));
*aResult = window;
NS_IF_ADDREF(*aResult);
}
return rv;
}
nsresult nsDocLoaderImpl::GetProgressStatusFlags(PRInt32 *aProgressStateFlags)
{
*aProgressStateFlags = mProgressStateFlags;
return NS_OK;
}
nsresult nsDocLoaderImpl::GetCurSelfProgress(PRInt32 *aCurSelfProgress)
{
*aCurSelfProgress = mCurrentSelfProgress;
return NS_OK;
}
nsresult nsDocLoaderImpl::GetMaxSelfProgress(PRInt32 *aMaxSelfProgress)
{
*aMaxSelfProgress = mMaxSelfProgress;
return NS_OK;
}
nsresult nsDocLoaderImpl::GetCurTotalProgress(PRInt32 *aCurTotalProgress)
{
*aCurTotalProgress = mCurrentTotalProgress;
return NS_OK;
}
nsresult nsDocLoaderImpl::GetMaxTotalProgress(PRInt32 *aMaxTotalProgress)
{
PRUint32 count = 0;
nsresult rv = NS_OK;
PRInt32 invididualProgress, newMaxTotal;
newMaxTotal = 0;
rv = mChildList->Count(&count);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIWebProgress> webProgress;
nsCOMPtr<nsISupports> docloader;
for (PRUint32 i=0; i<count; i++)
{
invididualProgress = 0;
docloader = getter_AddRefs(mChildList->ElementAt(i));
if (docloader)
{
// Cast is safe since all children are nsDocLoaderImpl too
((nsDocLoaderImpl *) docloader.get())->GetMaxTotalProgress(&invididualProgress);
}
if (invididualProgress < 0) // if one of the elements doesn't know it's size
// then none of them do
{
newMaxTotal = -1;
break;
}
else
newMaxTotal += invididualProgress;
}
if (mMaxSelfProgress >= 0 && newMaxTotal >= 0) {
*aMaxTotalProgress = newMaxTotal + mMaxSelfProgress;
} else {
*aMaxTotalProgress = -1;
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////////
// The following section contains support for nsIEventProgressSink which is used to
// pass progress and status between the actual channel and the doc loader. The doc loader
// then turns around and makes the right web progress calls based on this information.
////////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP nsDocLoaderImpl::OnProgress(nsIRequest *request, nsISupports* ctxt,
PRUint32 aProgress, PRUint32 aProgressMax)
{
nsChannelInfo *info;
PRInt32 progressDelta = 0;
//
// Update the ChannelInfo entry with the new progress data
//
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
info = GetChannelInfo(aChannel);
if (info) {
if ((0 == info->mCurrentProgress) && (0 == info->mMaxProgress)) {
//
// This is the first progress notification for the entry. If
// (aMaxProgress > 0) then the content-length of the data is known,
// so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
// that the content-length is no longer known.
//
if (aProgressMax != (PRUint32)-1) {
mMaxSelfProgress += aProgressMax;
info->mMaxProgress = aProgressMax;
} else {
mMaxSelfProgress = -1;
info->mMaxProgress = -1;
}
// Send a STATE_TRANSFERRING notification for the request.
PRInt32 flags;
flags = nsIWebProgressListener::STATE_TRANSFERRING |
nsIWebProgressListener::STATE_IS_REQUEST;
//
// Move the WebProgress into the STATE_TRANSFERRING state if necessary...
//
if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
// Send STATE_TRANSFERRING for the document too...
flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
}
FireOnStateChange(this, request, flags, NS_OK);
}
// Update the current progress count...
progressDelta = aProgress - info->mCurrentProgress;
mCurrentSelfProgress += progressDelta;
info->mCurrentProgress = aProgress;
}
//
// The channel is not part of the load group, so ignore its progress
// information...
//
else {
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p OOPS - No Channel Info for: %s\n",
this, (const char *)buffer));
#endif /* DEBUG */
return NS_OK;
}
//
// Fire progress notifications out to any registered nsIWebProgressListeners
//
FireOnProgressChange(this, request, aProgress, aProgressMax, progressDelta,
mCurrentTotalProgress, mMaxTotalProgress);
return NS_OK;
}
NS_IMETHODIMP nsDocLoaderImpl::OnStatus(nsIRequest* aRequest, nsISupports* ctxt,
nsresult aStatus, const PRUnichar* aStatusArg)
{
//
// Fire progress notifications out to any registered nsIWebProgressListeners
//
if (aStatus) {
nsresult rv;
nsCOMPtr<nsIStringBundleService> sbs = do_GetService(kStringBundleServiceCID, &rv);
if (NS_FAILED(rv)) return rv;
nsXPIDLString msg;
rv = sbs->FormatStatusMessage(aStatus, aStatusArg, getter_Copies(msg));
if (NS_FAILED(rv)) return rv;
FireOnStatusChange(this, aRequest, aStatus, msg);
}
return NS_OK;
}
void nsDocLoaderImpl::ClearInternalProgress()
{
ClearChannelInfoList();
mCurrentSelfProgress = mMaxSelfProgress = 0;
mCurrentTotalProgress = mMaxTotalProgress = 0;
mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
}
void nsDocLoaderImpl::FireOnProgressChange(nsDocLoaderImpl *aLoadInitiator,
nsIRequest *request,
PRInt32 aProgress,
PRInt32 aProgressMax,
PRInt32 aProgressDelta,
PRInt32 aTotalProgress,
PRInt32 aMaxTotalProgress)
{
PRUint32 count;
if (mIsLoadingDocument) {
mCurrentTotalProgress += aProgressDelta;
GetMaxTotalProgress(&mMaxTotalProgress);
aTotalProgress = mCurrentTotalProgress;
aMaxTotalProgress = mMaxTotalProgress;
}
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(request, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: Progress (%s): curSelf: %d maxSelf: %d curTotal: %d maxTotal %d\n",
this, (const char *)buffer, aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress));
#endif /* DEBUG */
/*
* First notify any listeners of the new progress info...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
(void)mListenerList->Count(&count);
while (count > 0) {
nsCOMPtr<nsISupports> supports;
nsresult rv = mListenerList->GetElementAt(--count, getter_AddRefs(supports));
if (NS_FAILED(rv)) return;
nsWeakPtr weakPtr = do_QueryInterface(supports);
nsCOMPtr<nsIWebProgressListener> listener;
listener = do_QueryReferent(weakPtr);
if (!listener) {
// the listener went away. gracefully pull it out of the list.
mListenerList->RemoveElementAt(count);
continue;
}
listener->OnProgressChange(aLoadInitiator,request,
aProgress, aProgressMax,
aTotalProgress, aMaxTotalProgress);
}
mListenerList->Compact();
// Pass the notification up to the parent...
if (mParent) {
mParent->FireOnProgressChange(aLoadInitiator, request,
aProgress, aProgressMax,
aProgressDelta,
aTotalProgress, aMaxTotalProgress);
}
}
void nsDocLoaderImpl::FireOnStateChange(nsIWebProgress *aProgress,
nsIRequest *aRequest,
PRInt32 aStateFlags,
nsresult aStatus)
{
PRUint32 count;
//
// Remove the STATE_IS_NETWORK bit if necessary.
//
// The rule is to remove this bit, if the notification has been passed
// up from a child WebProgress, and the current WebProgress is already
// active...
//
if (mIsLoadingDocument &&
(aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) &&
(this != aProgress)) {
aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
}
#if defined(DEBUG)
nsXPIDLCString buffer;
GetURIStringFromRequest(aRequest, buffer);
PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
("DocLoader:%p: Status (%s): code: %x\n",
this, (const char *)buffer, aStateFlags));
#endif /* DEBUG */
/*
* First notify any listeners of the new state info...
*
* Operate the elements from back to front so that if items get
* get removed from the list it won't affect our iteration
*/
(void)mListenerList->Count(&count);
while (count > 0) {
nsCOMPtr<nsISupports> supports;
nsresult rv = mListenerList->GetElementAt(--count, getter_AddRefs(supports));
if (NS_FAILED(rv)) return;
nsWeakPtr weakPtr = do_QueryInterface(supports);
nsCOMPtr<nsIWebProgressListener> listener;
listener = do_QueryReferent(weakPtr);
if (!listener) {
// the listener went away, gracefully pull it out of the list.
mListenerList->RemoveElementAt(count);
continue;
}
listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus);
}
mListenerList->Compact();
// Pass the notification up to the parent...
if (mParent) {
mParent->FireOnStateChange(aProgress, aRequest, aStateFlags, aStatus);
}
}
NS_IMETHODIMP
nsDocLoaderImpl::FireOnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsIURI *aUri)
{
PRUint32 count;
(void)mListenerList->Count(&count);
while (count > 0) {
nsCOMPtr<nsISupports> supports;
nsresult rv = mListenerList->GetElementAt(--count, getter_AddRefs(supports));
if (NS_FAILED(rv)) return rv;
nsWeakPtr weakPtr = do_QueryInterface(supports);
nsCOMPtr<nsIWebProgressListener> listener;
listener = do_QueryReferent(weakPtr);
if (!listener) {
// the listener went away. gracefully pull it out of the list.
mListenerList->RemoveElementAt(count);
continue;
}
listener->OnLocationChange(aWebProgress, aRequest, aUri);
}
mListenerList->Compact();
return NS_OK;
}
NS_IMETHODIMP
nsDocLoaderImpl::FireOnStatusChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsresult aStatus,
const PRUnichar* aMessage)
{
PRUint32 count;
(void)mListenerList->Count(&count);
while (count > 0) {
nsCOMPtr<nsISupports> supports;
nsresult rv = mListenerList->GetElementAt(--count, getter_AddRefs(supports));
if (NS_FAILED(rv)) return rv;
nsWeakPtr weakPtr = do_QueryInterface(supports);
nsCOMPtr<nsIWebProgressListener> listener;
listener = do_QueryReferent(weakPtr);
if (!listener) {
// the listener went away. gracefully pull it out of the list.
mListenerList->RemoveElementAt(count);
continue;
}
listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
}
mListenerList->Compact();
return NS_OK;
}
nsresult nsDocLoaderImpl::AddChannelInfo(nsIChannel *aChannel)
{
nsresult rv;
PRUint32 loadAttribs=nsIChannel::LOAD_NORMAL;
//
// Only create a ChannelInfo entry if the channel is *not* being loaded
// in the background...
//
rv = aChannel->GetLoadAttributes(&loadAttribs);
if (!(nsIChannel::LOAD_BACKGROUND & loadAttribs)) {
nsChannelInfo *info;
info = new nsChannelInfo(aChannel);
if (info) {
// XXX this method incorrectly returns a bool
rv = mChannelInfoList.AppendElement(info) ? NS_OK : NS_ERROR_FAILURE;
} else {
rv = NS_ERROR_OUT_OF_MEMORY;
}
}
else {
rv = NS_OK;
}
return rv;
}
nsChannelInfo * nsDocLoaderImpl::GetChannelInfo(nsIChannel *aChannel)
{
nsChannelInfo *info;
PRInt32 i, count;
count = mChannelInfoList.Count();
for(i=0; i<count; i++) {
info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
if (aChannel == info->mKey) {
return info;
}
}
return nsnull;
}
nsresult nsDocLoaderImpl::ClearChannelInfoList(void)
{
nsChannelInfo *info;
PRInt32 i, count;
count = mChannelInfoList.Count();
for(i=0; i<count; i++) {
info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
delete info;
}
mChannelInfoList.Clear();
mChannelInfoList.Compact();
return NS_OK;
}
void nsDocLoaderImpl::CalculateMaxProgress(PRInt32 *aMax)
{
PRInt32 i, count;
PRInt32 current=0, max=0;
count = mChannelInfoList.Count();
for(i=0; i<count; i++) {
nsChannelInfo *info;
info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
current += info->mCurrentProgress;
if (max >= 0) {
if (info->mMaxProgress < info->mCurrentProgress) {
max = -1;
} else {
max += info->mMaxProgress;
}
}
}
*aMax = max;
}
NS_IMETHODIMP nsDocLoaderImpl::OnHeadersAvailable(nsISupports * aContext)
{
// right now I don't think we need to do anything special for this case...
return NS_OK;
}
NS_IMETHODIMP nsDocLoaderImpl::OnRedirect(nsISupports * aContext, nsIURI * aNewLocation)
{
// we have a problem in that this method doesn't give us enough information about
// the url being redirected. We need to know if the url is the document url or some other
// part of the document (like an image). Proper implementation requires this, otherwise
// we end up setting the url bar location to a redirected image url when we didn't want to.
// for now, we'll make the implementation empty.
if (aContext)
{
PRInt32 stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
nsIWebProgressListener::STATE_IS_REQUEST |
nsIWebProgressListener::STATE_IS_NETWORK;
nsCOMPtr<nsIRequest> request (do_QueryInterface(aContext));
// if the current channel == the document channel (then we must be getting a redirect on the
// actual document and not a part in the document so be sure to set the state is document flag
// and to reset mDocumentRequest...
if (request.get() == mDocumentRequest.get())
{
stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
mDocumentRequest = request; // reset the document channel
}
FireOnStateChange(this, request, stateFlags, NS_OK);
}
return NS_OK;
}
NS_IMETHODIMP nsDocLoaderImpl::OnSecurityChange(nsISupports * aContext,
PRInt32 state)
{
//
// Fire progress notifications out to any registered nsIWebProgressListeners.
//
nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
nsIWebProgress* webProgress = NS_STATIC_CAST(nsIWebProgress*, this);
PRUint32 count;
(void)mListenerList->Count(&count);
while (count > 0) {
nsCOMPtr<nsISupports> supports;
nsresult rv = mListenerList->GetElementAt(--count, getter_AddRefs(supports));
if (NS_FAILED(rv)) return rv;
nsWeakPtr weakPtr = do_QueryInterface(supports);
nsCOMPtr<nsIWebProgressListener> listener;
listener = do_QueryReferent(weakPtr);
if (!listener) {
// the listener went away. gracefully pull it out of the list.
mListenerList->RemoveElementAt(count);
continue;
}
listener->OnSecurityChange(webProgress, request, state);
}
mListenerList->Compact();
// Pass the notification up to the parent...
if (mParent) {
mParent->OnSecurityChange(aContext, state);
}
return NS_OK;
}
#if 0
void nsDocLoaderImpl::DumpChannelInfo()
{
nsChannelInfo *info;
PRInt32 i, count;
PRInt32 current=0, max=0;
printf("==== DocLoader=%x\n", this);
count = mChannelInfoList.Count();
for(i=0; i<count; i++) {
info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
#if defined(DEBUG)
nsXPIDLCString buffer;
nsresult rv = NS_OK;
if (info->mURI) {
rv = info->mURI->GetSpec(getter_Copies(buffer));
}
printf(" [%d] current=%d max=%d [%s]\n", i,
info->mCurrentProgress,
info->mMaxProgress, (const char *)buffer);
#endif /* DEBUG */
current += info->mCurrentProgress;
if (max >= 0) {
if (info->mMaxProgress < info->mCurrentProgress) {
max = -1;
} else {
max += info->mMaxProgress;
}
}
}
printf("\nCurrent=%d Total=%d\n====\n", current, max);
}
#endif /* 0 */