michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nspr.h" michael@0: #include "prlog.h" michael@0: michael@0: #include "nsDocLoader.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIWebProgressListener2.h" michael@0: michael@0: #include "nsIServiceManager.h" michael@0: #include "nsXPIDLString.h" michael@0: michael@0: #include "nsIURL.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nscore.h" michael@0: #include "nsWeakPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: michael@0: #include "nsIDOMWindow.h" michael@0: michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: michael@0: #include "nsITransport.h" michael@0: #include "nsISocketTransport.h" michael@0: michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: michael@0: static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for nsIDocumentLoader logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=DocLoader:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: PRLogModuleInfo* gDocLoaderLog = nullptr; michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: michael@0: #if defined(DEBUG) michael@0: void GetURIStringFromRequest(nsIRequest* request, nsACString &name) michael@0: { michael@0: if (request) michael@0: request->GetName(name); michael@0: else michael@0: name.AssignLiteral("???"); michael@0: } michael@0: #endif /* DEBUG */ michael@0: michael@0: michael@0: michael@0: bool michael@0: nsDocLoader::RequestInfoHashInitEntry(PLDHashTable* table, michael@0: PLDHashEntryHdr* entry, michael@0: const void* key) michael@0: { michael@0: // Initialize the entry with placement new michael@0: new (entry) nsRequestInfo(key); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table, michael@0: PLDHashEntryHdr* entry) michael@0: { michael@0: nsRequestInfo* info = static_cast(entry); michael@0: info->~nsRequestInfo(); michael@0: } michael@0: michael@0: struct nsListenerInfo { michael@0: nsListenerInfo(nsIWeakReference *aListener, unsigned long aNotifyMask) michael@0: : mWeakListener(aListener), michael@0: mNotifyMask(aNotifyMask) michael@0: { michael@0: } michael@0: michael@0: // Weak pointer for the nsIWebProgressListener... michael@0: nsWeakPtr mWeakListener; michael@0: michael@0: // Mask indicating which notifications the listener wants to receive. michael@0: unsigned long mNotifyMask; michael@0: }; michael@0: michael@0: michael@0: nsDocLoader::nsDocLoader() michael@0: : mParent(nullptr), michael@0: mListenerInfoList(8), michael@0: mCurrentSelfProgress(0), michael@0: mMaxSelfProgress(0), michael@0: mCurrentTotalProgress(0), michael@0: mMaxTotalProgress(0), michael@0: mCompletedTotalProgress(0), michael@0: mIsLoadingDocument(false), michael@0: mIsRestoringDocument(false), michael@0: mDontFlushLayout(false), michael@0: mIsFlushingLayout(false) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (nullptr == gDocLoaderLog) { michael@0: gDocLoaderLog = PR_NewLogModule("DocLoader"); michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: static const PLDHashTableOps hash_table_ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: PL_DHashVoidPtrKeyStub, michael@0: PL_DHashMatchEntryStub, michael@0: PL_DHashMoveEntryStub, michael@0: RequestInfoHashClearEntry, michael@0: PL_DHashFinalizeStub, michael@0: RequestInfoHashInitEntry michael@0: }; michael@0: michael@0: PL_DHashTableInit(&mRequestInfoHash, &hash_table_ops, nullptr, michael@0: sizeof(nsRequestInfo), 16); michael@0: michael@0: ClearInternalProgress(); michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: created.\n", this)); michael@0: } michael@0: michael@0: nsresult michael@0: nsDocLoader::SetDocLoaderParent(nsDocLoader *aParent) michael@0: { michael@0: mParent = aParent; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocLoader::Init() michael@0: { michael@0: if (!mRequestInfoHash.ops) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: load group %x.\n", this, mLoadGroup.get())); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsDocLoader::~nsDocLoader() michael@0: { michael@0: /* michael@0: |ClearWeakReferences()| here is intended to prevent people holding weak references michael@0: from re-entering this destructor since |QueryReferent()| will |AddRef()| me, and the michael@0: subsequent |Release()| will try to destroy me. At this point there should be only michael@0: weak references remaining (otherwise, we wouldn't be getting destroyed). michael@0: michael@0: An alternative would be incrementing our refcount (consider it a compressed flag michael@0: saying "Don't re-destroy."). I haven't yet decided which is better. [scc] michael@0: */ michael@0: // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is michael@0: // this needed? michael@0: ClearWeakReferences(); michael@0: michael@0: Destroy(); michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: deleted.\n", this)); michael@0: michael@0: if (mRequestInfoHash.ops) { michael@0: PL_DHashTableFinish(&mRequestInfoHash); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Implementation of ISupports methods... michael@0: */ michael@0: NS_IMPL_ADDREF(nsDocLoader) michael@0: NS_IMPL_RELEASE(nsDocLoader) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsDocLoader) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIWebProgress) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsISecurityEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) michael@0: if (aIID.Equals(kThisImplCID)) michael@0: foundInterface = static_cast(this); michael@0: else michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: /* michael@0: * Implementation of nsIInterfaceRequestor methods... michael@0: */ michael@0: NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink) michael@0: { michael@0: nsresult rv = NS_ERROR_NO_INTERFACE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aSink); michael@0: michael@0: if(aIID.Equals(NS_GET_IID(nsILoadGroup))) { michael@0: *aSink = mLoadGroup; michael@0: NS_IF_ADDREF((nsISupports*)*aSink); michael@0: rv = NS_OK; michael@0: } else { michael@0: rv = QueryInterface(aIID, aSink); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* static */ michael@0: already_AddRefed michael@0: nsDocLoader::GetAsDocLoader(nsISupports* aSupports) michael@0: { michael@0: nsRefPtr ret = do_QueryObject(aSupports); michael@0: return ret.forget(); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr docLoaderService = michael@0: do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr rootDocLoader = GetAsDocLoader(docLoaderService); michael@0: NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED); michael@0: michael@0: return rootDocLoader->AddChildLoader(aDocLoader); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::Stop(void) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: Stop() called\n", this)); michael@0: michael@0: NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, Stop, ()); michael@0: michael@0: if (mLoadGroup) michael@0: rv = mLoadGroup->Cancel(NS_BINDING_ABORTED); michael@0: michael@0: // Don't report that we're flushing layout so IsBusy returns false after a michael@0: // Stop call. michael@0: mIsFlushingLayout = false; michael@0: michael@0: // Clear out mChildrenInOnload. We want to make sure to fire our michael@0: // onload at this point, and there's no issue with mChildrenInOnload michael@0: // after this, since mDocumentRequest will be null after the michael@0: // DocLoaderIsEmpty() call. michael@0: mChildrenInOnload.Clear(); michael@0: michael@0: // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest, michael@0: // etc, as needed. We could be getting into here from a subframe onload, in michael@0: // which case the call to DocLoaderIsEmpty() is coming but hasn't quite michael@0: // happened yet, Canceling the loadgroup did nothing (because it was already michael@0: // empty), and we're about to start a new load (which is what triggered this michael@0: // Stop() call). michael@0: michael@0: // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect michael@0: // we wouldn't need the call here.... michael@0: michael@0: NS_ASSERTION(!IsBusy(), "Shouldn't be busy here"); michael@0: DocLoaderIsEmpty(false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsDocLoader::IsBusy() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // michael@0: // A document loader is busy if either: michael@0: // michael@0: // 1. One of its children is in the middle of an onload handler. Note that michael@0: // the handler may have already removed this child from mChildList! michael@0: // 2. It is currently loading a document and either has parts of it still michael@0: // loading, or has a busy child docloader. michael@0: // 3. It's currently flushing layout in DocLoaderIsEmpty(). michael@0: // michael@0: michael@0: if (mChildrenInOnload.Count() || mIsFlushingLayout) { michael@0: return true; michael@0: } michael@0: michael@0: /* Is this document loader busy? */ michael@0: if (!mIsLoadingDocument) { michael@0: return false; michael@0: } michael@0: michael@0: bool busy; michael@0: rv = mLoadGroup->IsPending(&busy); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: if (busy) { michael@0: return true; michael@0: } michael@0: michael@0: /* check its child document loaders... */ michael@0: uint32_t count = mChildList.Length(); michael@0: for (uint32_t i=0; i < count; i++) { michael@0: nsIDocumentLoader* loader = ChildAt(i); michael@0: michael@0: // This is a safe cast, because we only put nsDocLoader objects into the michael@0: // array michael@0: if (loader && static_cast(loader)->IsBusy()) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetContainer(nsISupports** aResult) michael@0: { michael@0: NS_ADDREF(*aResult = static_cast(this)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetLoadGroup(nsILoadGroup** aResult) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (nullptr == aResult) { michael@0: rv = NS_ERROR_NULL_POINTER; michael@0: } else { michael@0: *aResult = mLoadGroup; michael@0: NS_IF_ADDREF(*aResult); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsDocLoader::Destroy() michael@0: { michael@0: Stop(); michael@0: michael@0: // Remove the document loader from the parent list of loaders... michael@0: if (mParent) michael@0: { michael@0: mParent->RemoveChildLoader(this); michael@0: } michael@0: michael@0: // Release all the information about network requests... michael@0: ClearRequestInfoHash(); michael@0: michael@0: // Release all the information about registered listeners... michael@0: int32_t count = mListenerInfoList.Count(); michael@0: for(int32_t i = 0; i < count; i++) { michael@0: nsListenerInfo *info = michael@0: static_cast(mListenerInfoList.ElementAt(i)); michael@0: michael@0: delete info; michael@0: } michael@0: michael@0: mListenerInfoList.Clear(); michael@0: mListenerInfoList.Compact(); michael@0: michael@0: mDocumentRequest = 0; michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->SetGroupObserver(nullptr); michael@0: michael@0: DestroyChildren(); michael@0: } michael@0: michael@0: void michael@0: nsDocLoader::DestroyChildren() michael@0: { michael@0: uint32_t count = mChildList.Length(); michael@0: // if the doc loader still has children...we need to enumerate the michael@0: // children and make them null out their back ptr to the parent doc michael@0: // loader michael@0: for (uint32_t i=0; i < count; i++) michael@0: { michael@0: nsIDocumentLoader* loader = ChildAt(i); michael@0: michael@0: if (loader) { michael@0: // This is a safe cast, as we only put nsDocLoader objects into the michael@0: // array michael@0: static_cast(loader)->SetDocLoaderParent(nullptr); michael@0: } michael@0: } michael@0: mChildList.Clear(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt) michael@0: { michael@0: // called each time a request is added to the group. michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gDocLoaderLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString name; michael@0: request->GetName(name); michael@0: michael@0: uint32_t count = 0; michael@0: if (mLoadGroup) michael@0: mLoadGroup->GetActiveCount(&count); michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u active URLs", michael@0: this, request, name.get(), michael@0: (mIsLoadingDocument ? "true" : "false"), michael@0: count)); michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: bool bJustStartedLoading = false; michael@0: michael@0: nsLoadFlags loadFlags = 0; michael@0: request->GetLoadFlags(&loadFlags); michael@0: michael@0: if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { michael@0: bJustStartedLoading = true; michael@0: mIsLoadingDocument = true; michael@0: ClearInternalProgress(); // only clear our progress if we are starting a new load.... michael@0: } michael@0: michael@0: // michael@0: // Create a new nsRequestInfo for the request that is starting to michael@0: // load... michael@0: // michael@0: AddRequestInfo(request); michael@0: michael@0: // michael@0: // Only fire a doStartDocumentLoad(...) if the document loader michael@0: // has initiated a load... Otherwise, this notification has michael@0: // resulted from a request being added to the load group. michael@0: // michael@0: if (mIsLoadingDocument) { michael@0: if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { michael@0: // michael@0: // Make sure that the document channel is null at this point... michael@0: // (unless its been redirected) michael@0: // michael@0: NS_ASSERTION((loadFlags & nsIChannel::LOAD_REPLACE) || michael@0: !(mDocumentRequest.get()), michael@0: "Overwriting an existing document channel!"); michael@0: michael@0: // This request is associated with the entire document... michael@0: mDocumentRequest = request; michael@0: mLoadGroup->SetDefaultLoadRequest(request); michael@0: michael@0: // Only fire the start document load notification for the first michael@0: // document URI... Do not fire it again for redirections michael@0: // michael@0: if (bJustStartedLoading) { michael@0: // Update the progress status state michael@0: mProgressStateFlags = nsIWebProgressListener::STATE_START; michael@0: michael@0: // Fire the start document load notification michael@0: doStartDocumentLoad(); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest, michael@0: "mDocumentRequest MUST be set for the duration of a page load!"); michael@0: michael@0: doStartURLLoad(request); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gDocLoaderLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString name; michael@0: aRequest->GetName(name); michael@0: michael@0: uint32_t count = 0; michael@0: if (mLoadGroup) michael@0: mLoadGroup->GetActiveCount(&count); michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: OnStopRequest[%p](%s) status=%x mIsLoadingDocument=%s, %u active URLs", michael@0: this, aRequest, name.get(), michael@0: aStatus, (mIsLoadingDocument ? "true" : "false"), michael@0: count)); michael@0: } michael@0: #endif michael@0: michael@0: bool bFireTransferring = false; michael@0: michael@0: // michael@0: // Set the Maximum progress to the same value as the current progress. michael@0: // Since the URI has finished loading, all the data is there. Also, michael@0: // this will allow a more accurate estimation of the max progress (in case michael@0: // the old value was unknown ie. -1) michael@0: // michael@0: nsRequestInfo *info = GetRequestInfo(aRequest); michael@0: if (info) { michael@0: // Null out mLastStatus now so we don't find it when looking for michael@0: // status from now on. This destroys the nsStatusInfo and hence michael@0: // removes it from our list. michael@0: info->mLastStatus = nullptr; michael@0: michael@0: int64_t oldMax = info->mMaxProgress; michael@0: michael@0: info->mMaxProgress = info->mCurrentProgress; michael@0: michael@0: // michael@0: // If a request whose content-length was previously unknown has just michael@0: // finished loading, then use this new data to try to calculate a michael@0: // mMaxSelfProgress... michael@0: // michael@0: if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) { michael@0: mMaxSelfProgress = CalculateMaxProgress(); michael@0: } michael@0: michael@0: // As we know the total progress of this request now, save it to be part michael@0: // of CalculateMaxProgress() result. We need to remove the info from the michael@0: // hash, see bug 480713. michael@0: mCompletedTotalProgress += info->mMaxProgress; michael@0: michael@0: // michael@0: // Determine whether a STATE_TRANSFERRING notification should be michael@0: // 'synthesized'. michael@0: // michael@0: // If nsRequestInfo::mMaxProgress (as stored in oldMax) and michael@0: // nsRequestInfo::mCurrentProgress are both 0, then the michael@0: // STATE_TRANSFERRING notification has not been fired yet... michael@0: // michael@0: if ((oldMax == 0) && (info->mCurrentProgress == 0)) { michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: michael@0: // Only fire a TRANSFERRING notification if the request is also a michael@0: // channel -- data transfer requires a nsIChannel! michael@0: // michael@0: if (channel) { michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: bFireTransferring = true; michael@0: } michael@0: // michael@0: // If the request failed (for any reason other than being michael@0: // redirected or retargeted), the TRANSFERRING notification can michael@0: // still be fired if a HTTP connection was established to a server. michael@0: // michael@0: else if (aStatus != NS_BINDING_REDIRECTED && michael@0: aStatus != NS_BINDING_RETARGETED) { michael@0: // michael@0: // Only if the load has been targeted (see bug 268483)... michael@0: // michael@0: uint32_t lf; michael@0: channel->GetLoadFlags(&lf); michael@0: if (lf & nsIChannel::LOAD_TARGETED) { michael@0: nsCOMPtr httpChannel(do_QueryInterface(aRequest)); michael@0: if (httpChannel) { michael@0: uint32_t responseCode; michael@0: rv = httpChannel->GetResponseStatus(&responseCode); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // michael@0: // A valid server status indicates that a connection was michael@0: // established to the server... So, fire the notification michael@0: // even though a failure occurred later... michael@0: // michael@0: bFireTransferring = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (bFireTransferring) { michael@0: // Send a STATE_TRANSFERRING notification for the request. michael@0: int32_t flags; michael@0: michael@0: flags = nsIWebProgressListener::STATE_TRANSFERRING | michael@0: nsIWebProgressListener::STATE_IS_REQUEST; michael@0: // michael@0: // Move the WebProgress into the STATE_TRANSFERRING state if necessary... michael@0: // michael@0: if (mProgressStateFlags & nsIWebProgressListener::STATE_START) { michael@0: mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING; michael@0: michael@0: // Send STATE_TRANSFERRING for the document too... michael@0: flags |= nsIWebProgressListener::STATE_IS_DOCUMENT; michael@0: } michael@0: michael@0: FireOnStateChange(this, aRequest, flags, NS_OK); michael@0: } michael@0: michael@0: // michael@0: // Fire the OnStateChange(...) notification for stop request michael@0: // michael@0: doStopURLLoad(aRequest, aStatus); michael@0: michael@0: // Clear this request out of the hash to avoid bypass of FireOnStateChange michael@0: // when address of the request is reused. michael@0: RemoveRequestInfo(aRequest); michael@0: michael@0: // michael@0: // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a michael@0: // load. This will handle removing the request from our hashtable as needed. michael@0: // michael@0: if (mIsLoadingDocument) { michael@0: DocLoaderIsEmpty(true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild) michael@0: { michael@0: nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: aChild->SetDocLoaderParent(nullptr); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild) michael@0: { michael@0: nsresult rv = mChildList.AppendElement(aChild) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: aChild->SetDocLoaderParent(this); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel) michael@0: { michael@0: if (!mDocumentRequest) { michael@0: *aChannel = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return CallQueryInterface(mDocumentRequest, aChannel); michael@0: } michael@0: michael@0: michael@0: void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) michael@0: { michael@0: if (mIsLoadingDocument) { michael@0: /* In the unimagineably rude circumstance that onload event handlers michael@0: triggered by this function actually kill the window ... ok, it's michael@0: not unimagineable; it's happened ... this deathgrip keeps this object michael@0: alive long enough to survive this function call. */ michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: // Don't flush layout if we're still busy. michael@0: if (IsBusy()) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up"); michael@0: michael@0: // The load group for this DocumentLoader is idle. Flush if we need to. michael@0: if (aFlushLayout && !mDontFlushLayout) { michael@0: nsCOMPtr domDoc = do_GetInterface(GetAsSupports(this)); michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: if (doc) { michael@0: // We start loads from style resolution, so we need to flush out style michael@0: // no matter what. If we have user fonts, we also need to flush layout, michael@0: // since the reflow is what starts font loads. michael@0: mozFlushType flushType = Flush_Style; michael@0: nsIPresShell* shell = doc->GetShell(); michael@0: if (shell) { michael@0: // Be safe in case this presshell is in teardown now michael@0: nsPresContext* presContext = shell->GetPresContext(); michael@0: if (presContext && presContext->GetUserFontSet()) { michael@0: flushType = Flush_Layout; michael@0: } michael@0: } michael@0: mDontFlushLayout = mIsFlushingLayout = true; michael@0: doc->FlushPendingNotifications(flushType); michael@0: mDontFlushLayout = mIsFlushingLayout = false; michael@0: } michael@0: } michael@0: michael@0: // And now check whether we're really busy; that might have changed with michael@0: // the layout flush. michael@0: if (!IsBusy()) { michael@0: // Clear out our request info hash, now that our load really is done and michael@0: // we don't need it anymore to CalculateMaxProgress(). michael@0: ClearInternalProgress(); michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: Is now idle...\n", this)); michael@0: michael@0: nsCOMPtr docRequest = mDocumentRequest; michael@0: michael@0: NS_ASSERTION(mDocumentRequest, "No Document Request!"); michael@0: mDocumentRequest = 0; michael@0: mIsLoadingDocument = false; michael@0: michael@0: // Update the progress status state - the document is done michael@0: mProgressStateFlags = nsIWebProgressListener::STATE_STOP; michael@0: michael@0: michael@0: nsresult loadGroupStatus = NS_OK; michael@0: mLoadGroup->GetStatus(&loadGroupStatus); michael@0: michael@0: // michael@0: // New code to break the circular reference between michael@0: // the load group and the docloader... michael@0: // michael@0: mLoadGroup->SetDefaultLoadRequest(nullptr); michael@0: michael@0: // Take a ref to our parent now so that we can call DocLoaderIsEmpty() on michael@0: // it even if our onload handler removes us from the docloader tree. michael@0: nsRefPtr parent = mParent; michael@0: michael@0: // Note that if calling ChildEnteringOnload() on the parent returns false michael@0: // then calling our onload handler is not safe. That can only happen on michael@0: // OOM, so that's ok. michael@0: if (!parent || parent->ChildEnteringOnload(this)) { michael@0: // Do nothing with our state after firing the michael@0: // OnEndDocumentLoad(...). The document loader may be loading a *new* michael@0: // document - if LoadDocument() was called from a handler! michael@0: // michael@0: doStopDocumentLoad(docRequest, loadGroupStatus); michael@0: michael@0: if (parent) { michael@0: parent->ChildDoneWithOnload(this); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void nsDocLoader::doStartDocumentLoad(void) michael@0: { michael@0: michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(mDocumentRequest, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)." michael@0: "\tURI: %s \n", michael@0: this, buffer.get())); michael@0: #endif /* DEBUG */ michael@0: michael@0: // Fire an OnStatus(...) notification STATE_START. This indicates michael@0: // that the document represented by mDocumentRequest has started to michael@0: // load... michael@0: FireOnStateChange(this, michael@0: mDocumentRequest, michael@0: nsIWebProgressListener::STATE_START | michael@0: nsIWebProgressListener::STATE_IS_DOCUMENT | michael@0: nsIWebProgressListener::STATE_IS_REQUEST | michael@0: nsIWebProgressListener::STATE_IS_WINDOW | michael@0: nsIWebProgressListener::STATE_IS_NETWORK, michael@0: NS_OK); michael@0: } michael@0: michael@0: void nsDocLoader::doStartURLLoad(nsIRequest *request) michael@0: { michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(request, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: ++ Firing OnStateChange start url load (...)." michael@0: "\tURI: %s\n", michael@0: this, buffer.get())); michael@0: #endif /* DEBUG */ michael@0: michael@0: FireOnStateChange(this, michael@0: request, michael@0: nsIWebProgressListener::STATE_START | michael@0: nsIWebProgressListener::STATE_IS_REQUEST, michael@0: NS_OK); michael@0: } michael@0: michael@0: void nsDocLoader::doStopURLLoad(nsIRequest *request, nsresult aStatus) michael@0: { michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(request, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)." michael@0: "\tURI: %s status=%x\n", michael@0: this, buffer.get(), aStatus)); michael@0: #endif /* DEBUG */ michael@0: michael@0: FireOnStateChange(this, michael@0: request, michael@0: nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_REQUEST, michael@0: aStatus); michael@0: michael@0: // Fire a status change message for the most recent unfinished michael@0: // request to make sure that the displayed status is not outdated. michael@0: if (!mStatusInfoList.isEmpty()) { michael@0: nsStatusInfo* statusInfo = mStatusInfoList.getFirst(); michael@0: FireOnStatusChange(this, statusInfo->mRequest, michael@0: statusInfo->mStatusCode, michael@0: statusInfo->mStatusMessage.get()); michael@0: } michael@0: } michael@0: michael@0: void nsDocLoader::doStopDocumentLoad(nsIRequest *request, michael@0: nsresult aStatus) michael@0: { michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(request, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)." michael@0: "\tURI: %s Status=%x\n", michael@0: this, buffer.get(), aStatus)); michael@0: #endif /* DEBUG */ michael@0: michael@0: // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers. michael@0: // Grab our parent chain before doing that so we can still dispatch michael@0: // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if michael@0: // the onload handlers rearrange the docshell tree. michael@0: WebProgressList list; michael@0: GatherAncestorWebProgresses(list); michael@0: michael@0: // michael@0: // Fire an OnStateChange(...) notification indicating the the michael@0: // current document has finished loading... michael@0: // michael@0: int32_t flags = nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_DOCUMENT; michael@0: for (uint32_t i = 0; i < list.Length(); ++i) { michael@0: list[i]->DoFireOnStateChange(this, request, flags, aStatus); michael@0: } michael@0: michael@0: // michael@0: // Fire a final OnStateChange(...) notification indicating the the michael@0: // current document has finished loading... michael@0: // michael@0: flags = nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_WINDOW | michael@0: nsIWebProgressListener::STATE_IS_NETWORK; michael@0: for (uint32_t i = 0; i < list.Length(); ++i) { michael@0: list[i]->DoFireOnStateChange(this, request, flags, aStatus); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////////// michael@0: // The following section contains support for nsIWebProgress and related stuff michael@0: //////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::AddProgressListener(nsIWebProgressListener *aListener, michael@0: uint32_t aNotifyMask) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsListenerInfo* info = GetListenerInfo(aListener); michael@0: if (info) { michael@0: // The listener is already registered! michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsWeakPtr listener = do_GetWeakReference(aListener); michael@0: if (!listener) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: info = new nsListenerInfo(listener, aNotifyMask); michael@0: if (!info) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: rv = mListenerInfoList.AppendElement(info) ? NS_OK : NS_ERROR_FAILURE; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::RemoveProgressListener(nsIWebProgressListener *aListener) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsListenerInfo* info = GetListenerInfo(aListener); michael@0: if (info) { michael@0: rv = mListenerInfoList.RemoveElement(info) ? NS_OK : NS_ERROR_FAILURE; michael@0: delete info; michael@0: } else { michael@0: // The listener is not registered! michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetDOMWindow(nsIDOMWindow **aResult) michael@0: { michael@0: return CallGetInterface(this, aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetDOMWindowID(uint64_t *aResult) michael@0: { michael@0: *aResult = 0; michael@0: michael@0: nsCOMPtr window; michael@0: nsresult rv = GetDOMWindow(getter_AddRefs(window)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr piwindow = do_QueryInterface(window); michael@0: NS_ENSURE_STATE(piwindow); michael@0: michael@0: MOZ_ASSERT(piwindow->IsOuterWindow()); michael@0: *aResult = piwindow->WindowID(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetIsTopLevel(bool *aResult) michael@0: { michael@0: *aResult = false; michael@0: michael@0: nsCOMPtr window; michael@0: GetDOMWindow(getter_AddRefs(window)); michael@0: if (window) { michael@0: nsCOMPtr piwindow = do_QueryInterface(window); michael@0: NS_ENSURE_STATE(piwindow); michael@0: michael@0: nsCOMPtr topWindow; michael@0: nsresult rv = piwindow->GetTop(getter_AddRefs(topWindow)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aResult = piwindow == topWindow; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetIsLoadingDocument(bool *aIsLoadingDocument) michael@0: { michael@0: *aIsLoadingDocument = mIsLoadingDocument; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocLoader::GetLoadType(uint32_t *aLoadType) michael@0: { michael@0: *aLoadType = 0; michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: int64_t nsDocLoader::GetMaxTotalProgress() michael@0: { michael@0: int64_t newMaxTotal = 0; michael@0: michael@0: uint32_t count = mChildList.Length(); michael@0: nsCOMPtr webProgress; michael@0: for (uint32_t i=0; i < count; i++) michael@0: { michael@0: int64_t individualProgress = 0; michael@0: nsIDocumentLoader* docloader = ChildAt(i); michael@0: if (docloader) michael@0: { michael@0: // Cast is safe since all children are nsDocLoader too michael@0: individualProgress = ((nsDocLoader *) docloader)->GetMaxTotalProgress(); michael@0: } michael@0: if (individualProgress < int64_t(0)) // if one of the elements doesn't know it's size michael@0: // then none of them do michael@0: { michael@0: newMaxTotal = int64_t(-1); michael@0: break; michael@0: } michael@0: else michael@0: newMaxTotal += individualProgress; michael@0: } michael@0: michael@0: int64_t progress = -1; michael@0: if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0)) michael@0: progress = newMaxTotal + mMaxSelfProgress; michael@0: michael@0: return progress; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////////// michael@0: // The following section contains support for nsIProgressEventSink which is used to michael@0: // pass progress and status between the actual request and the doc loader. The doc loader michael@0: // then turns around and makes the right web progress calls based on this information. michael@0: //////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest *aRequest, nsISupports* ctxt, michael@0: uint64_t aProgress, uint64_t aProgressMax) michael@0: { michael@0: int64_t progressDelta = 0; michael@0: michael@0: // michael@0: // Update the RequestInfo entry with the new progress data michael@0: // michael@0: if (nsRequestInfo* info = GetRequestInfo(aRequest)) { michael@0: // Update info->mCurrentProgress before we call FireOnStateChange, michael@0: // since that can make the "info" pointer invalid. michael@0: int64_t oldCurrentProgress = info->mCurrentProgress; michael@0: progressDelta = int64_t(aProgress) - oldCurrentProgress; michael@0: info->mCurrentProgress = int64_t(aProgress); michael@0: michael@0: // suppress sending STATE_TRANSFERRING if this is upload progress (see bug 240053) michael@0: if (!info->mUploading && (int64_t(0) == oldCurrentProgress) && (int64_t(0) == info->mMaxProgress)) { michael@0: // michael@0: // If we receive an OnProgress event from a toplevel channel that the URI Loader michael@0: // has not yet targeted, then we must suppress the event. This is necessary to michael@0: // ensure that webprogresslisteners do not get confused when the channel is michael@0: // finally targeted. See bug 257308. michael@0: // michael@0: nsLoadFlags lf = 0; michael@0: aRequest->GetLoadFlags(&lf); michael@0: if ((lf & nsIChannel::LOAD_DOCUMENT_URI) && !(lf & nsIChannel::LOAD_TARGETED)) { michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p Ignoring OnProgress while load is not targeted\n", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // This is the first progress notification for the entry. If michael@0: // (aMaxProgress > 0) then the content-length of the data is known, michael@0: // so update mMaxSelfProgress... Otherwise, set it to -1 to indicate michael@0: // that the content-length is no longer known. michael@0: // michael@0: if (uint64_t(aProgressMax) != UINT64_MAX) { michael@0: mMaxSelfProgress += int64_t(aProgressMax); michael@0: info->mMaxProgress = int64_t(aProgressMax); michael@0: } else { michael@0: mMaxSelfProgress = int64_t(-1); michael@0: info->mMaxProgress = int64_t(-1); michael@0: } michael@0: michael@0: // Send a STATE_TRANSFERRING notification for the request. michael@0: int32_t flags; michael@0: michael@0: flags = nsIWebProgressListener::STATE_TRANSFERRING | michael@0: nsIWebProgressListener::STATE_IS_REQUEST; michael@0: // michael@0: // Move the WebProgress into the STATE_TRANSFERRING state if necessary... michael@0: // michael@0: if (mProgressStateFlags & nsIWebProgressListener::STATE_START) { michael@0: mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING; michael@0: michael@0: // Send STATE_TRANSFERRING for the document too... michael@0: flags |= nsIWebProgressListener::STATE_IS_DOCUMENT; michael@0: } michael@0: michael@0: FireOnStateChange(this, aRequest, flags, NS_OK); michael@0: } michael@0: michael@0: // Update our overall current progress count. michael@0: mCurrentSelfProgress += progressDelta; michael@0: } michael@0: // michael@0: // The request is not part of the load group, so ignore its progress michael@0: // information... michael@0: // michael@0: else { michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(aRequest, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p OOPS - No Request Info for: %s\n", michael@0: this, buffer.get())); michael@0: #endif /* DEBUG */ michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Fire progress notifications out to any registered nsIWebProgressListeners michael@0: // michael@0: FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta, michael@0: mCurrentTotalProgress, mMaxTotalProgress); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsISupports* ctxt, michael@0: nsresult aStatus, const char16_t* aStatusArg) michael@0: { michael@0: // michael@0: // Fire progress notifications out to any registered nsIWebProgressListeners michael@0: // michael@0: if (aStatus != NS_OK) { michael@0: // Remember the current status for this request michael@0: nsRequestInfo *info; michael@0: info = GetRequestInfo(aRequest); michael@0: if (info) { michael@0: bool uploading = (aStatus == NS_NET_STATUS_WRITING || michael@0: aStatus == NS_NET_STATUS_SENDING_TO); michael@0: // If switching from uploading to downloading (or vice versa), then we michael@0: // need to reset our progress counts. This is designed with HTTP form michael@0: // submission in mind, where an upload is performed followed by download michael@0: // of possibly several documents. michael@0: if (info->mUploading != uploading) { michael@0: mCurrentSelfProgress = mMaxSelfProgress = 0; michael@0: mCurrentTotalProgress = mMaxTotalProgress = 0; michael@0: mCompletedTotalProgress = 0; michael@0: info->mUploading = uploading; michael@0: info->mCurrentProgress = 0; michael@0: info->mMaxProgress = 0; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr sbs = michael@0: mozilla::services::GetStringBundleService(); michael@0: if (!sbs) michael@0: return NS_ERROR_FAILURE; michael@0: nsXPIDLString msg; michael@0: nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg, michael@0: getter_Copies(msg)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Keep around the message. In case a request finishes, we need to make sure michael@0: // to send the status message of another request to our user to that we michael@0: // don't display, for example, "Transferring" messages for requests that are michael@0: // already done. michael@0: if (info) { michael@0: if (!info->mLastStatus) { michael@0: info->mLastStatus = new nsStatusInfo(aRequest); michael@0: } else { michael@0: // We're going to move it to the front of the list, so remove michael@0: // it from wherever it is now. michael@0: info->mLastStatus->remove(); michael@0: } michael@0: info->mLastStatus->mStatusMessage = msg; michael@0: info->mLastStatus->mStatusCode = aStatus; michael@0: // Put the info at the front of the list michael@0: mStatusInfoList.insertFront(info->mLastStatus); michael@0: } michael@0: FireOnStatusChange(this, aRequest, aStatus, msg); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsDocLoader::ClearInternalProgress() michael@0: { michael@0: ClearRequestInfoHash(); michael@0: michael@0: mCurrentSelfProgress = mMaxSelfProgress = 0; michael@0: mCurrentTotalProgress = mMaxTotalProgress = 0; michael@0: mCompletedTotalProgress = 0; michael@0: michael@0: mProgressStateFlags = nsIWebProgressListener::STATE_STOP; michael@0: } michael@0: michael@0: michael@0: void nsDocLoader::FireOnProgressChange(nsDocLoader *aLoadInitiator, michael@0: nsIRequest *request, michael@0: int64_t aProgress, michael@0: int64_t aProgressMax, michael@0: int64_t aProgressDelta, michael@0: int64_t aTotalProgress, michael@0: int64_t aMaxTotalProgress) michael@0: { michael@0: if (mIsLoadingDocument) { michael@0: mCurrentTotalProgress += aProgressDelta; michael@0: mMaxTotalProgress = GetMaxTotalProgress(); michael@0: michael@0: aTotalProgress = mCurrentTotalProgress; michael@0: aMaxTotalProgress = mMaxTotalProgress; michael@0: } michael@0: michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(request, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: Progress (%s): curSelf: %d maxSelf: %d curTotal: %d maxTotal %d\n", michael@0: this, buffer.get(), aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress)); michael@0: #endif /* DEBUG */ michael@0: michael@0: /* michael@0: * First notify any listeners of the new progress info... michael@0: * michael@0: * Operate the elements from back to front so that if items get michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: nsCOMPtr listener; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_PROGRESS)) { michael@0: continue; michael@0: } michael@0: michael@0: listener = do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: // XXX truncates 64-bit to 32-bit michael@0: listener->OnProgressChange(aLoadInitiator,request, michael@0: int32_t(aProgress), int32_t(aProgressMax), michael@0: int32_t(aTotalProgress), int32_t(aMaxTotalProgress)); michael@0: } michael@0: michael@0: mListenerInfoList.Compact(); michael@0: michael@0: // Pass the notification up to the parent... michael@0: if (mParent) { michael@0: mParent->FireOnProgressChange(aLoadInitiator, request, michael@0: aProgress, aProgressMax, michael@0: aProgressDelta, michael@0: aTotalProgress, aMaxTotalProgress); michael@0: } michael@0: } michael@0: michael@0: void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList) michael@0: { michael@0: for (nsDocLoader* loader = this; loader; loader = loader->mParent) { michael@0: aList.AppendElement(loader); michael@0: } michael@0: } michael@0: michael@0: void nsDocLoader::FireOnStateChange(nsIWebProgress *aProgress, michael@0: nsIRequest *aRequest, michael@0: int32_t aStateFlags, michael@0: nsresult aStatus) michael@0: { michael@0: WebProgressList list; michael@0: GatherAncestorWebProgresses(list); michael@0: for (uint32_t i = 0; i < list.Length(); ++i) { michael@0: list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus); michael@0: } michael@0: } michael@0: michael@0: void nsDocLoader::DoFireOnStateChange(nsIWebProgress * const aProgress, michael@0: nsIRequest * const aRequest, michael@0: int32_t &aStateFlags, michael@0: const nsresult aStatus) michael@0: { michael@0: // michael@0: // Remove the STATE_IS_NETWORK bit if necessary. michael@0: // michael@0: // The rule is to remove this bit, if the notification has been passed michael@0: // up from a child WebProgress, and the current WebProgress is already michael@0: // active... michael@0: // michael@0: if (mIsLoadingDocument && michael@0: (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) && michael@0: (this != aProgress)) { michael@0: aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK; michael@0: } michael@0: michael@0: // Add the STATE_RESTORING bit if necessary. michael@0: if (mIsRestoringDocument) michael@0: aStateFlags |= nsIWebProgressListener::STATE_RESTORING; michael@0: michael@0: #if defined(DEBUG) michael@0: nsAutoCString buffer; michael@0: michael@0: GetURIStringFromRequest(aRequest, buffer); michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: Status (%s): code: %x\n", michael@0: this, buffer.get(), aStateFlags)); michael@0: #endif /* DEBUG */ michael@0: michael@0: NS_ASSERTION(aRequest, "Firing OnStateChange(...) notification with a NULL request!"); michael@0: michael@0: /* michael@0: * First notify any listeners of the new state info... michael@0: * michael@0: * Operate the elements from back to front so that if items get michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: nsCOMPtr listener; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: int32_t notifyMask = (aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL; michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & notifyMask)) { michael@0: continue; michael@0: } michael@0: michael@0: listener = do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus); michael@0: } michael@0: michael@0: mListenerInfoList.Compact(); michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsIURI *aUri, michael@0: uint32_t aFlags) michael@0: { michael@0: /* michael@0: * First notify any listeners of the new state info... michael@0: * michael@0: * Operate the elements from back to front so that if items get michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: nsCOMPtr listener; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_LOCATION)) { michael@0: continue; michael@0: } michael@0: michael@0: listener = do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, ("DocLoader [%p] calling %p->OnLocationChange", this, listener.get())); michael@0: listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags); michael@0: } michael@0: michael@0: mListenerInfoList.Compact(); michael@0: michael@0: // Pass the notification up to the parent... michael@0: if (mParent) { michael@0: mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsresult aStatus, michael@0: const char16_t* aMessage) michael@0: { michael@0: /* michael@0: * First notify any listeners of the new state info... michael@0: * michael@0: * Operate the elements from back to front so that if items get michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: nsCOMPtr listener; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_STATUS)) { michael@0: continue; michael@0: } michael@0: michael@0: listener = do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); michael@0: } michael@0: mListenerInfoList.Compact(); michael@0: michael@0: // Pass the notification up to the parent... michael@0: if (mParent) { michael@0: mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress, michael@0: nsIURI *aURI, michael@0: int32_t aDelay, michael@0: bool aSameURI) michael@0: { michael@0: /* michael@0: * Returns true if the refresh may proceed, michael@0: * false if the refresh should be blocked. michael@0: * michael@0: * First notify any listeners of the refresh attempt... michael@0: * michael@0: * Iterate the elements from back to front so that if items michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: bool allowRefresh = true; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_REFRESH)) { michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr listener = michael@0: do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr listener2 = michael@0: do_QueryReferent(info->mWeakListener); michael@0: if (!listener2) michael@0: continue; michael@0: michael@0: bool listenerAllowedRefresh; michael@0: nsresult listenerRV = listener2->OnRefreshAttempted( michael@0: aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh); michael@0: if (NS_FAILED(listenerRV)) michael@0: continue; michael@0: michael@0: allowRefresh = allowRefresh && listenerAllowedRefresh; michael@0: } michael@0: michael@0: mListenerInfoList.Compact(); michael@0: michael@0: // Pass the notification up to the parent... michael@0: if (mParent) { michael@0: allowRefresh = allowRefresh && michael@0: mParent->RefreshAttempted(aWebProgress, aURI, aDelay, aSameURI); michael@0: } michael@0: michael@0: return allowRefresh; michael@0: } michael@0: michael@0: nsListenerInfo * michael@0: nsDocLoader::GetListenerInfo(nsIWebProgressListener *aListener) michael@0: { michael@0: int32_t i, count; michael@0: nsListenerInfo *info; michael@0: michael@0: nsCOMPtr listener1 = do_QueryInterface(aListener); michael@0: count = mListenerInfoList.Count(); michael@0: for (i=0; i(mListenerInfoList.SafeElementAt(i)); michael@0: michael@0: NS_ASSERTION(info, "There should NEVER be a null listener in the list"); michael@0: if (info) { michael@0: nsCOMPtr listener2 = do_QueryReferent(info->mWeakListener); michael@0: if (listener1 == listener2) michael@0: return info; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult nsDocLoader::AddRequestInfo(nsIRequest *aRequest) michael@0: { michael@0: if (!PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_ADD)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsDocLoader::RemoveRequestInfo(nsIRequest *aRequest) michael@0: { michael@0: PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_REMOVE); michael@0: } michael@0: michael@0: nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(nsIRequest* aRequest) michael@0: { michael@0: nsRequestInfo* info = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mRequestInfoHash, aRequest, michael@0: PL_DHASH_LOOKUP)); michael@0: michael@0: if (PL_DHASH_ENTRY_IS_FREE(info)) { michael@0: // Nothing found in the hash, return null. michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: // Return what we found in the hash... michael@0: michael@0: return info; michael@0: } michael@0: michael@0: // PLDHashTable enumeration callback that just removes every entry michael@0: // from the hash. michael@0: static PLDHashOperator michael@0: RemoveInfoCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number, michael@0: void *arg) michael@0: { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void nsDocLoader::ClearRequestInfoHash(void) michael@0: { michael@0: if (!mRequestInfoHash.ops || !mRequestInfoHash.entryCount) { michael@0: // No hash, or the hash is empty, nothing to do here then... michael@0: michael@0: return; michael@0: } michael@0: michael@0: PL_DHashTableEnumerate(&mRequestInfoHash, RemoveInfoCallback, nullptr); michael@0: } michael@0: michael@0: // PLDHashTable enumeration callback that calculates the max progress. michael@0: PLDHashOperator michael@0: nsDocLoader::CalcMaxProgressCallback(PLDHashTable* table, PLDHashEntryHdr* hdr, michael@0: uint32_t number, void* arg) michael@0: { michael@0: const nsRequestInfo* info = static_cast(hdr); michael@0: int64_t* max = static_cast(arg); michael@0: michael@0: if (info->mMaxProgress < info->mCurrentProgress) { michael@0: *max = int64_t(-1); michael@0: michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: *max += info->mMaxProgress; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: int64_t nsDocLoader::CalculateMaxProgress() michael@0: { michael@0: int64_t max = mCompletedTotalProgress; michael@0: PL_DHashTableEnumerate(&mRequestInfoHash, CalcMaxProgressCallback, &max); michael@0: return max; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *cb) michael@0: { michael@0: if (aOldChannel) michael@0: { michael@0: nsLoadFlags loadFlags = 0; michael@0: int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING | michael@0: nsIWebProgressListener::STATE_IS_REQUEST; michael@0: michael@0: aOldChannel->GetLoadFlags(&loadFlags); michael@0: // If the document channel is being redirected, then indicate that the michael@0: // document is being redirected in the notification... michael@0: if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT; michael@0: michael@0: #if defined(DEBUG) michael@0: nsCOMPtr request(do_QueryInterface(aOldChannel)); michael@0: NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel"); michael@0: #endif /* DEBUG */ michael@0: } michael@0: michael@0: OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags); michael@0: FireOnStateChange(this, aOldChannel, stateFlags, NS_OK); michael@0: } michael@0: michael@0: cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* michael@0: * Implementation of nsISecurityEventSink method... michael@0: */ michael@0: michael@0: NS_IMETHODIMP nsDocLoader::OnSecurityChange(nsISupports * aContext, michael@0: uint32_t aState) michael@0: { michael@0: // michael@0: // Fire progress notifications out to any registered nsIWebProgressListeners. michael@0: // michael@0: michael@0: nsCOMPtr request = do_QueryInterface(aContext); michael@0: nsIWebProgress* webProgress = static_cast(this); michael@0: michael@0: /* michael@0: * First notify any listeners of the new state info... michael@0: * michael@0: * Operate the elements from back to front so that if items get michael@0: * get removed from the list it won't affect our iteration michael@0: */ michael@0: nsCOMPtr listener; michael@0: int32_t count = mListenerInfoList.Count(); michael@0: michael@0: while (--count >= 0) { michael@0: nsListenerInfo *info; michael@0: michael@0: info = static_cast(mListenerInfoList.SafeElementAt(count)); michael@0: if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_SECURITY)) { michael@0: continue; michael@0: } michael@0: michael@0: listener = do_QueryReferent(info->mWeakListener); michael@0: if (!listener) { michael@0: // the listener went away. gracefully pull it out of the list. michael@0: mListenerInfoList.RemoveElementAt(count); michael@0: delete info; michael@0: continue; michael@0: } michael@0: michael@0: listener->OnSecurityChange(webProgress, request, aState); michael@0: } michael@0: michael@0: mListenerInfoList.Compact(); michael@0: michael@0: // Pass the notification up to the parent... michael@0: if (mParent) { michael@0: mParent->OnSecurityChange(aContext, aState); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* michael@0: * Implementation of nsISupportsPriority methods... michael@0: * michael@0: * The priority of the DocLoader _is_ the priority of its LoadGroup. michael@0: * michael@0: * XXX(darin): Once we start storing loadgroups in loadgroups, this code will michael@0: * go away. michael@0: */ michael@0: michael@0: NS_IMETHODIMP nsDocLoader::GetPriority(int32_t *aPriority) michael@0: { michael@0: nsCOMPtr p = do_QueryInterface(mLoadGroup); michael@0: if (p) michael@0: return p->GetPriority(aPriority); michael@0: michael@0: *aPriority = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority) michael@0: { michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority)); michael@0: michael@0: nsCOMPtr p = do_QueryInterface(mLoadGroup); michael@0: if (p) michael@0: p->SetPriority(aPriority); michael@0: michael@0: NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, michael@0: SetPriority, (aPriority)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta) michael@0: { michael@0: PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, michael@0: ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta)); michael@0: michael@0: nsCOMPtr p = do_QueryInterface(mLoadGroup); michael@0: if (p) michael@0: p->AdjustPriority(aDelta); michael@0: michael@0: NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, michael@0: AdjustPriority, (aDelta)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: michael@0: #if 0 michael@0: void nsDocLoader::DumpChannelInfo() michael@0: { michael@0: nsChannelInfo *info; michael@0: int32_t i, count; michael@0: int32_t current=0, max=0; michael@0: michael@0: michael@0: printf("==== DocLoader=%x\n", this); michael@0: michael@0: count = mChannelInfoList.Count(); michael@0: for(i=0; imURI) { michael@0: rv = info->mURI->GetSpec(buffer); michael@0: } michael@0: michael@0: printf(" [%d] current=%d max=%d [%s]\n", i, michael@0: info->mCurrentProgress, michael@0: info->mMaxProgress, buffer.get()); michael@0: #endif /* DEBUG */ michael@0: michael@0: current += info->mCurrentProgress; michael@0: if (max >= 0) { michael@0: if (info->mMaxProgress < info->mCurrentProgress) { michael@0: max = -1; michael@0: } else { michael@0: max += info->mMaxProgress; michael@0: } michael@0: } michael@0: } michael@0: michael@0: printf("\nCurrent=%d Total=%d\n====\n", current, max); michael@0: } michael@0: #endif /* 0 */