/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nspr.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/Document.h" #include "mozilla/BasicEvents.h" #include "mozilla/Components.h" #include "mozilla/EventDispatcher.h" #include "mozilla/Logging.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/PresShell.h" #include "nsDocLoader.h" #include "nsDocShell.h" #include "nsLoadGroup.h" #include "nsNetUtil.h" #include "nsIHttpChannel.h" #include "nsIWebNavigation.h" #include "nsIWebProgressListener2.h" #include "nsString.h" #include "nsCOMPtr.h" #include "nscore.h" #include "nsIWeakReferenceUtils.h" #include "nsQueryObject.h" #include "nsPIDOMWindow.h" #include "nsGlobalWindow.h" #include "nsIStringBundle.h" #include "nsIDocShell.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocGroup.h" #include "nsPresContext.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIBrowserDOMWindow.h" #include "nsGlobalWindow.h" #include "mozilla/ThrottledEventQueue.h" using namespace mozilla; using mozilla::DebugOnly; using mozilla::eLoad; using mozilla::EventDispatcher; using mozilla::LogLevel; using mozilla::WidgetEvent; using mozilla::dom::BrowserChild; using mozilla::dom::BrowsingContext; using mozilla::dom::Document; // // Log module for nsIDocumentLoader logging... // // To enable logging (see mozilla/Logging.h for full details): // // set MOZ_LOG=DocLoader:5 // set MOZ_LOG_FILE=debug.log // // this enables LogLevel::Debug level information and places all output in // the file 'debug.log'. // mozilla::LazyLogModule gDocLoaderLog("DocLoader"); #if defined(DEBUG) void GetURIStringFromRequest(nsIRequest* request, nsACString& name) { if (request) request->GetName(name); else name.AssignLiteral("???"); } #endif /* DEBUG */ void nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry, const void* key) { // Initialize the entry with placement new new (entry) nsRequestInfo(key); } void nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) { nsRequestInfo* info = static_cast(entry); info->~nsRequestInfo(); } // this is used for mListenerInfoList.Contains() template <> class nsDefaultComparator { public: bool Equals(const nsDocLoader::nsListenerInfo& aInfo, nsIWebProgressListener* const& aListener) const { nsCOMPtr listener = do_QueryReferent(aInfo.mWeakListener); return aListener == listener; } }; /* static */ const PLDHashTableOps nsDocLoader::sRequestInfoHashOps = { PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, PLDHashTable::MoveEntryStub, nsDocLoader::RequestInfoHashClearEntry, nsDocLoader::RequestInfoHashInitEntry}; nsDocLoader::nsDocLoader() : mParent(nullptr), mProgressStateFlags(0), mCurrentSelfProgress(0), mMaxSelfProgress(0), mCurrentTotalProgress(0), mMaxTotalProgress(0), mRequestInfoHash(&sRequestInfoHashOps, sizeof(nsRequestInfo)), mCompletedTotalProgress(0), mIsLoadingDocument(false), mIsRestoringDocument(false), mDontFlushLayout(false), mIsFlushingLayout(false), mTreatAsBackgroundLoad(false), mHasFakeOnLoadDispatched(false), mIsReadyToHandlePostMessage(false), mDocumentOpenedButNotLoaded(false) { ClearInternalProgress(); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: created.\n", this)); } nsresult nsDocLoader::SetDocLoaderParent(nsDocLoader* aParent) { mParent = aParent; return NS_OK; } nsresult nsDocLoader::Init() { nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this); if (NS_FAILED(rv)) return rv; MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get())); return NS_OK; } nsresult nsDocLoader::InitWithBrowsingContext( BrowsingContext* aBrowsingContext) { RefPtr loadGroup = new net::nsLoadGroup(); if (!aBrowsingContext->GetRequestContextId()) { return NS_ERROR_NOT_AVAILABLE; } nsresult rv = loadGroup->InitWithRequestContextId( aBrowsingContext->GetRequestContextId()); if (NS_FAILED(rv)) return rv; rv = loadGroup->SetGroupObserver(this); if (NS_FAILED(rv)) return rv; mLoadGroup = loadGroup; MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get())); return NS_OK; } nsDocLoader::~nsDocLoader() { /* |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] */ // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is // this needed? ClearWeakReferences(); Destroy(); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: deleted.\n", this)); } /* * Implementation of ISupports methods... */ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocLoader) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocLoader) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocLoader) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentLoader) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) 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(nsIChannelEventSink) NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) NS_INTERFACE_MAP_ENTRY_CONCRETE(nsDocLoader) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_WEAK(nsDocLoader, mChildrenInOnload) /* * Implementation of nsIInterfaceRequestor methods... */ NS_IMETHODIMP nsDocLoader::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; } /* static */ already_AddRefed nsDocLoader::GetAsDocLoader( nsISupports* aSupports) { RefPtr ret = do_QueryObject(aSupports); return ret.forget(); } /* static */ nsresult nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader) { nsCOMPtr docLoaderService = components::DocLoader::Service(); NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED); RefPtr rootDocLoader = GetAsDocLoader(docLoaderService); NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED); return rootDocLoader->AddChildLoader(aDocLoader); } NS_IMETHODIMP nsDocLoader::Stop(void) { nsresult rv = NS_OK; MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: Stop() called\n", this)); NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ()); if (mLoadGroup) rv = mLoadGroup->Cancel(NS_BINDING_ABORTED); // Don't report that we're flushing layout so IsBusy returns false after a // Stop call. mIsFlushingLayout = false; // Clear out mChildrenInOnload. We're not going to fire our onload // anyway at this point, and there's no issue with mChildrenInOnload // after this, since mDocumentRequest will be null after the // DocLoaderIsEmpty() call. mChildrenInOnload.Clear(); mOOPChildrenLoading.Clear(); // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest, // etc, as needed. We could be getting into here from a subframe onload, in // which case the call to DocLoaderIsEmpty() is coming but hasn't quite // happened yet, Canceling the loadgroup did nothing (because it was already // empty), and we're about to start a new load (which is what triggered this // Stop() call). // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect // we wouldn't need the call here.... NS_ASSERTION(!IsBusy(), "Shouldn't be busy here"); // If Cancelling the load group only had pending subresource requests, then // the group status will still be success, and we would fire the load event. // We want to avoid that when we're aborting the load, so override the status // with an explicit NS_BINDING_ABORTED value. DocLoaderIsEmpty(false, Some(NS_BINDING_ABORTED)); return rv; } bool nsDocLoader::TreatAsBackgroundLoad() { return mTreatAsBackgroundLoad; } void nsDocLoader::SetBackgroundLoadIframe() { mTreatAsBackgroundLoad = true; } bool nsDocLoader::IsBusy() { nsresult rv; // // A document loader is busy if either: // // 1. One of its children is in the middle of an onload handler. Note that // the handler may have already removed this child from mChildList! // 2. It is currently loading a document and either has parts of it still // loading, or has a busy child docloader. // 3. It's currently flushing layout in DocLoaderIsEmpty(). // if (!mChildrenInOnload.IsEmpty() || !mOOPChildrenLoading.IsEmpty() || mIsFlushingLayout) { return true; } /* Is this document loader busy? */ if (!IsBlockingLoadEvent()) { return false; } // Check if any in-process sub-document is awaiting its 'load' event: bool busy; rv = mLoadGroup->IsPending(&busy); if (NS_FAILED(rv)) { return false; } if (busy) { return true; } /* check its child document loaders... */ uint32_t count = mChildList.Length(); for (uint32_t i = 0; i < count; i++) { nsIDocumentLoader* loader = ChildAt(i); // If 'dom.cross_origin_iframes_loaded_in_background' is set, the parent // document treats cross domain iframes as background loading frame if (loader && static_cast(loader)->TreatAsBackgroundLoad()) { continue; } // This is a safe cast, because we only put nsDocLoader objects into the // array if (loader && static_cast(loader)->IsBusy()) return true; } return false; } NS_IMETHODIMP nsDocLoader::GetContainer(nsISupports** aResult) { NS_ADDREF(*aResult = static_cast(this)); return NS_OK; } NS_IMETHODIMP nsDocLoader::GetLoadGroup(nsILoadGroup** aResult) { nsresult rv = NS_OK; if (nullptr == aResult) { rv = NS_ERROR_NULL_POINTER; } else { *aResult = mLoadGroup; NS_IF_ADDREF(*aResult); } return rv; } void nsDocLoader::Destroy() { Stop(); // Remove the document loader from the parent list of loaders... if (mParent) { DebugOnly rv = mParent->RemoveChildLoader(this); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed"); } // Release all the information about network requests... ClearRequestInfoHash(); mListenerInfoList.Clear(); mListenerInfoList.Compact(); mDocumentRequest = nullptr; if (mLoadGroup) mLoadGroup->SetGroupObserver(nullptr); DestroyChildren(); } void nsDocLoader::DestroyChildren() { uint32_t count = mChildList.Length(); // 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 for (uint32_t i = 0; i < count; i++) { nsIDocumentLoader* loader = ChildAt(i); if (loader) { // This is a safe cast, as we only put nsDocLoader objects into the // array DebugOnly rv = static_cast(loader)->SetDocLoaderParent(nullptr); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed"); } } mChildList.Clear(); } NS_IMETHODIMP nsDocLoader::OnStartRequest(nsIRequest* request) { // called each time a request is added to the group. if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) { nsAutoCString name; request->GetName(name); uint32_t count = 0; if (mLoadGroup) mLoadGroup->GetActiveCount(&count); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u " "active URLs", this, request, name.get(), (mIsLoadingDocument ? "true" : "false"), count)); } bool bJustStartedLoading = false; nsLoadFlags loadFlags = 0; request->GetLoadFlags(&loadFlags); if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { bJustStartedLoading = true; mIsLoadingDocument = true; mDocumentOpenedButNotLoaded = false; ClearInternalProgress(); // only clear our progress if we are starting a // new load.... } // // Create a new nsRequestInfo for the request that is starting to // load... // AddRequestInfo(request); // // Only fire a doStartDocumentLoad(...) if the document loader // has initiated a load... Otherwise, this notification has // resulted from a request being added to the load group. // if (mIsLoadingDocument) { if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { // // Make sure that the document channel is null at this point... // (unless its been redirected) // NS_ASSERTION( (loadFlags & nsIChannel::LOAD_REPLACE) || !(mDocumentRequest.get()), "Overwriting an existing document channel!"); // This request is associated with the entire document... mDocumentRequest = request; mLoadGroup->SetDefaultLoadRequest(request); // Only fire the start document load notification for the first // document URI... Do not fire it again for redirections // if (bJustStartedLoading) { // Update the progress status state mProgressStateFlags = nsIWebProgressListener::STATE_START; // Fire the start document load notification doStartDocumentLoad(); return NS_OK; } } } NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest, "mDocumentRequest MUST be set for the duration of a page load!"); // This is the only way to catch document request start event after a redirect // has occured without changing inherited Firefox behaviour significantly. // Problem description: // The combination of |STATE_START + STATE_IS_DOCUMENT| is only sent for // initial request (see |doStartDocumentLoad| call above). // And |STATE_REDIRECTING + STATE_IS_DOCUMENT| is sent with old channel, which // makes it impossible to filter by destination URL (see // |AsyncOnChannelRedirect| implementation). // Fixing any of those bugs may cause unpredictable consequences in any part // of the browser, so we just add a custom flag for this exact situation. int32_t extraFlags = 0; if (mIsLoadingDocument && !bJustStartedLoading && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) && (loadFlags & nsIChannel::LOAD_REPLACE)) { extraFlags = nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT; } doStartURLLoad(request, extraFlags); return NS_OK; } NS_IMETHODIMP nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { nsresult rv = NS_OK; if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) { nsAutoCString name; aRequest->GetName(name); uint32_t count = 0; if (mLoadGroup) mLoadGroup->GetActiveCount(&count); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: OnStopRequest[%p](%s) status=%" PRIx32 " mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s," " %u active URLs", this, aRequest, name.get(), static_cast(aStatus), (mIsLoadingDocument ? "true" : "false"), (mDocumentOpenedButNotLoaded ? "true" : "false"), count)); } bool bFireTransferring = false; // // 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) // nsRequestInfo* info = GetRequestInfo(aRequest); if (info) { // Null out mLastStatus now so we don't find it when looking for // status from now on. This destroys the nsStatusInfo and hence // removes it from our list. info->mLastStatus = nullptr; int64_t oldMax = info->mMaxProgress; info->mMaxProgress = info->mCurrentProgress; // // If a request whose content-length was previously unknown has just // finished loading, then use this new data to try to calculate a // mMaxSelfProgress... // if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) { mMaxSelfProgress = CalculateMaxProgress(); } // As we know the total progress of this request now, save it to be part // of CalculateMaxProgress() result. We need to remove the info from the // hash, see bug 480713. mCompletedTotalProgress += info->mMaxProgress; // // Determine whether a STATE_TRANSFERRING notification should be // 'synthesized'. // // If nsRequestInfo::mMaxProgress (as stored in oldMax) and // nsRequestInfo::mCurrentProgress are both 0, then the // STATE_TRANSFERRING notification has not been fired yet... // if ((oldMax == 0) && (info->mCurrentProgress == 0)) { nsCOMPtr channel(do_QueryInterface(aRequest)); // Only fire a TRANSFERRING notification if the request is also a // channel -- data transfer requires a nsIChannel! // if (channel) { if (NS_SUCCEEDED(aStatus)) { bFireTransferring = true; } // // If the request failed (for any reason other than being // redirected or retargeted), the TRANSFERRING notification can // still be fired if a HTTP connection was established to a server. // else if (aStatus != NS_BINDING_REDIRECTED && aStatus != NS_BINDING_RETARGETED) { // // Only if the load has been targeted (see bug 268483)... // uint32_t lf; channel->GetLoadFlags(&lf); if (lf & nsIChannel::LOAD_TARGETED) { nsCOMPtr httpChannel(do_QueryInterface(aRequest)); if (httpChannel) { uint32_t responseCode; rv = httpChannel->GetResponseStatus(&responseCode); if (NS_SUCCEEDED(rv)) { // // A valid server status indicates that a connection was // established to the server... So, fire the notification // even though a failure occurred later... // bFireTransferring = true; } } } } } } } if (bFireTransferring) { // Send a STATE_TRANSFERRING notification for the request. int32_t 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, aRequest, flags, NS_OK); } // // Fire the OnStateChange(...) notification for stop request // doStopURLLoad(aRequest, aStatus); // Clear this request out of the hash to avoid bypass of FireOnStateChange // when address of the request is reused. RemoveRequestInfo(aRequest); // // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload. // if (IsBlockingLoadEvent()) { nsCOMPtr ds = do_QueryInterface(static_cast(this)); bool doNotFlushLayout = false; if (ds) { // Don't do unexpected layout flushes while we're in process of restoring // a document from the bfcache. ds->GetRestoringDocument(&doNotFlushLayout); } DocLoaderIsEmpty(!doNotFlushLayout); } return NS_OK; } nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild) { nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE; if (NS_SUCCEEDED(rv)) { rv = aChild->SetDocLoaderParent(nullptr); } return rv; } nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild) { mChildList.AppendElement(aChild); return aChild->SetDocLoaderParent(this); } NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel** aChannel) { if (!mDocumentRequest) { *aChannel = nullptr; return NS_OK; } return CallQueryInterface(mDocumentRequest, aChannel); } void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout, const Maybe& aOverrideStatus) { if (IsBlockingLoadEvent()) { /* 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 kungFuDeathGrip(this); // Don't flush layout if we're still busy. if (IsBusy()) { return; } NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up"); // We may not have a document request if we are in a // document.open() situation. NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded, "No Document Request!"); // The load group for this DocumentLoader is idle. Flush if we need to. if (aFlushLayout && !mDontFlushLayout) { nsCOMPtr doc = do_GetInterface(GetAsSupports(this)); if (doc) { // We start loads from style resolution, so we need to flush out style // no matter what. If we have user fonts, we also need to flush layout, // since the reflow is what starts font loads. mozilla::FlushType flushType = mozilla::FlushType::Style; // Be safe in case this presshell is in teardown now doc->FlushUserFontSet(); if (doc->GetUserFontSet()) { flushType = mozilla::FlushType::Layout; } mDontFlushLayout = mIsFlushingLayout = true; doc->FlushPendingNotifications(flushType); mDontFlushLayout = mIsFlushingLayout = false; } } // And now check whether we're really busy; that might have changed with // the layout flush. // // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is // false if the flushing above re-entered this method. if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) { return; } if (mDocumentRequest) { // Clear out our request info hash, now that our load really is done and // we don't need it anymore to CalculateMaxProgress(). ClearInternalProgress(); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: Is now idle...\n", this)); nsCOMPtr docRequest = mDocumentRequest; mDocumentRequest = nullptr; mIsLoadingDocument = false; // Update the progress status state - the document is done mProgressStateFlags = nsIWebProgressListener::STATE_STOP; nsresult loadGroupStatus = NS_OK; if (aOverrideStatus) { loadGroupStatus = *aOverrideStatus; } else { mLoadGroup->GetStatus(&loadGroupStatus); } // // New code to break the circular reference between // the load group and the docloader... // mLoadGroup->SetDefaultLoadRequest(nullptr); // Take a ref to our parent now so that we can call ChildDoneWithOnload() // on it even if our onload handler removes us from the docloader tree. RefPtr parent = mParent; // Note that if calling ChildEnteringOnload() on the parent returns false // then calling our onload handler is not safe. That can only happen on // OOM, so that's ok. if (!parent || parent->ChildEnteringOnload(this)) { // Do nothing with our state after firing the // OnEndDocumentLoad(...). The document loader may be loading a *new* // document - if LoadDocument() was called from a handler! // doStopDocumentLoad(docRequest, loadGroupStatus); NotifyDoneWithOnload(parent); } } else { MOZ_ASSERT(mDocumentOpenedButNotLoaded); mDocumentOpenedButNotLoaded = false; // Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we // plan to skip firing our own load event, because otherwise we might // never end up firing our parent's load event. RefPtr parent = mParent; if (!parent || parent->ChildEnteringOnload(this)) { nsresult loadGroupStatus = NS_OK; mLoadGroup->GetStatus(&loadGroupStatus); // Can "doc" or "window" ever come back null here? Our state machine // is complicated enough I wouldn't bet against it... nsCOMPtr doc = do_GetInterface(GetAsSupports(this)); if (doc) { // Make sure we're not canceling the loadgroup. If we are, then just // like the normal navigation case we should not fire a load event. if (NS_SUCCEEDED(loadGroupStatus) || loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) { // The readyState change is required to pass // dom/html/test/test_bug347174_write.html doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE, /* updateTimingInformation = */ false); doc->StopDocumentLoad(); nsCOMPtr window = doc->GetWindow(); if (window && !doc->SkipLoadEventAfterClose()) { if (!mozilla::dom::DocGroup::TryToLoadIframesInBackground() || (mozilla::dom::DocGroup::TryToLoadIframesInBackground() && !HasFakeOnLoadDispatched())) { MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: Firing load event for document.open\n", this)); // This is a very cut-down version of // nsDocumentViewer::LoadComplete that doesn't do various things // that are not relevant here because this wasn't an actual // navigation. WidgetEvent event(true, eLoad); event.mFlags.mBubbles = false; event.mFlags.mCancelable = false; // Dispatching to |window|, but using |document| as the target, // per spec. event.mTarget = doc; nsEventStatus unused = nsEventStatus_eIgnore; doc->SetLoadEventFiring(true); EventDispatcher::Dispatch(window, nullptr, &event, nullptr, &unused); doc->SetLoadEventFiring(false); // Now unsuppress painting on the presshell, if we // haven't done that yet. RefPtr presShell = doc->GetPresShell(); if (presShell && !presShell->IsDestroying()) { presShell->UnsuppressPainting(); if (!presShell->IsDestroying()) { presShell->LoadComplete(); } } } } } else if (loadGroupStatus == NS_BINDING_ABORTED) { doc->NotifyAbortedLoad(); } if (doc->IsCurrentActiveDocument() && !doc->IsShowing() && loadGroupStatus != NS_BINDING_ABORTED) { nsCOMPtr docShell = do_QueryInterface(this); bool isInUnload; if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) && !isInUnload) { doc->OnPageShow(false, nullptr); } } } NotifyDoneWithOnload(parent); } } } } void nsDocLoader::NotifyDoneWithOnload(nsDocLoader* aParent) { if (aParent) { // In-process parent: aParent->ChildDoneWithOnload(this); } nsCOMPtr docShell = do_QueryInterface(this); if (!docShell) { return; } BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext(); if (bc->IsContentSubframe() && !bc->GetParent()->IsInProcess()) { if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( dom::EmbedderElementEventType::NoEvent); } } } void nsDocLoader::doStartDocumentLoad(void) { #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(mDocumentRequest, buffer); MOZ_LOG( gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)." "\tURI: %s \n", this, buffer.get())); #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_WINDOW | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); } void nsDocLoader::doStartURLLoad(nsIRequest* request, int32_t aExtraFlags) { #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(request, buffer); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: ++ Firing OnStateChange start url load (...)." "\tURI: %s\n", this, buffer.get())); #endif /* DEBUG */ FireOnStateChange(this, request, nsIWebProgressListener::STATE_START | nsIWebProgressListener::STATE_IS_REQUEST | aExtraFlags, NS_OK); } void nsDocLoader::doStopURLLoad(nsIRequest* request, nsresult aStatus) { #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(request, buffer); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)." "\tURI: %s status=%" PRIx32 "\n", this, buffer.get(), static_cast(aStatus))); #endif /* DEBUG */ FireOnStateChange(this, request, nsIWebProgressListener::STATE_STOP | nsIWebProgressListener::STATE_IS_REQUEST, aStatus); // Fire a status change message for the most recent unfinished // request to make sure that the displayed status is not outdated. if (!mStatusInfoList.isEmpty()) { nsStatusInfo* statusInfo = mStatusInfoList.getFirst(); FireOnStatusChange(this, statusInfo->mRequest, statusInfo->mStatusCode, statusInfo->mStatusMessage.get()); } } void nsDocLoader::doStopDocumentLoad(nsIRequest* request, nsresult aStatus) { #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(request, buffer); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)." "\tURI: %s Status=%" PRIx32 "\n", this, buffer.get(), static_cast(aStatus))); #endif /* DEBUG */ // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers. // Grab our parent chain before doing that so we can still dispatch // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if // the onload handlers rearrange the docshell tree. WebProgressList list; GatherAncestorWebProgresses(list); // // Fire an OnStateChange(...) notification indicating the the // current document has finished loading... // int32_t flags = nsIWebProgressListener::STATE_STOP | nsIWebProgressListener::STATE_IS_DOCUMENT; for (uint32_t i = 0; i < list.Length(); ++i) { list[i]->DoFireOnStateChange(this, request, flags, aStatus); } // // Fire a final OnStateChange(...) notification indicating the the // current document has finished loading... // flags = nsIWebProgressListener::STATE_STOP | nsIWebProgressListener::STATE_IS_WINDOW | nsIWebProgressListener::STATE_IS_NETWORK; for (uint32_t i = 0; i < list.Length(); ++i) { list[i]->DoFireOnStateChange(this, request, flags, aStatus); } } //////////////////////////////////////////////////////////////////////////////////// // The following section contains support for nsIWebProgress and related stuff //////////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsDocLoader::AddProgressListener(nsIWebProgressListener* aListener, uint32_t aNotifyMask) { if (mListenerInfoList.Contains(aListener)) { // The listener is already registered! return NS_ERROR_FAILURE; } nsWeakPtr listener = do_GetWeakReference(aListener); if (!listener) { return NS_ERROR_INVALID_ARG; } mListenerInfoList.AppendElement(nsListenerInfo(listener, aNotifyMask)); return NS_OK; } NS_IMETHODIMP nsDocLoader::RemoveProgressListener(nsIWebProgressListener* aListener) { return mListenerInfoList.RemoveElement(aListener) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocLoader::GetDOMWindow(mozIDOMWindowProxy** aResult) { return CallGetInterface(this, aResult); } NS_IMETHODIMP nsDocLoader::GetIsTopLevel(bool* aResult) { nsCOMPtr docShell = do_QueryInterface(this); *aResult = docShell && docShell->GetBrowsingContext()->IsTop(); return NS_OK; } NS_IMETHODIMP nsDocLoader::GetIsLoadingDocument(bool* aIsLoadingDocument) { *aIsLoadingDocument = mIsLoadingDocument; return NS_OK; } NS_IMETHODIMP nsDocLoader::GetLoadType(uint32_t* aLoadType) { *aLoadType = 0; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocLoader::GetTarget(nsIEventTarget** aTarget) { nsCOMPtr window; nsresult rv = GetDOMWindow(getter_AddRefs(window)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr global = do_QueryInterface(window); NS_ENSURE_STATE(global); nsCOMPtr target = global->EventTargetFor(mozilla::TaskCategory::Other); target.forget(aTarget); return NS_OK; } NS_IMETHODIMP nsDocLoader::SetTarget(nsIEventTarget* aTarget) { return NS_ERROR_NOT_IMPLEMENTED; } int64_t nsDocLoader::GetMaxTotalProgress() { int64_t newMaxTotal = 0; uint32_t count = mChildList.Length(); for (uint32_t i = 0; i < count; i++) { int64_t individualProgress = 0; nsIDocumentLoader* docloader = ChildAt(i); if (docloader) { // Cast is safe since all children are nsDocLoader too individualProgress = ((nsDocLoader*)docloader)->GetMaxTotalProgress(); } if (individualProgress < int64_t(0)) // if one of the elements doesn't know // it's size then none of them do { newMaxTotal = int64_t(-1); break; } else newMaxTotal += individualProgress; } int64_t progress = -1; if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0)) progress = newMaxTotal + mMaxSelfProgress; return progress; } //////////////////////////////////////////////////////////////////////////////////// // The following section contains support for nsIProgressEventSink which is used // to pass progress and status between the actual request and the doc loader. // The doc loader then turns around and makes the right web progress calls based // on this information. //////////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest* aRequest, int64_t aProgress, int64_t aProgressMax) { int64_t progressDelta = 0; // // Update the RequestInfo entry with the new progress data // if (nsRequestInfo* info = GetRequestInfo(aRequest)) { // Update info->mCurrentProgress before we call FireOnStateChange, // since that can make the "info" pointer invalid. int64_t oldCurrentProgress = info->mCurrentProgress; progressDelta = aProgress - oldCurrentProgress; info->mCurrentProgress = aProgress; // suppress sending STATE_TRANSFERRING if this is upload progress (see bug // 240053) if (!info->mUploading && (int64_t(0) == oldCurrentProgress) && (int64_t(0) == info->mMaxProgress)) { // // If we receive an OnProgress event from a toplevel channel that the URI // Loader has not yet targeted, then we must suppress the event. This is // necessary to ensure that webprogresslisteners do not get confused when // the channel is finally targeted. See bug 257308. // nsLoadFlags lf = 0; aRequest->GetLoadFlags(&lf); if ((lf & nsIChannel::LOAD_DOCUMENT_URI) && !(lf & nsIChannel::LOAD_TARGETED)) { MOZ_LOG( gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p Ignoring OnProgress while load is not targeted\n", this)); return NS_OK; } // // This is the first progress notification for the entry. If // (aMaxProgress != -1) 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 != -1) { mMaxSelfProgress += aProgressMax; info->mMaxProgress = aProgressMax; } else { mMaxSelfProgress = int64_t(-1); info->mMaxProgress = int64_t(-1); } // Send a STATE_TRANSFERRING notification for the request. int32_t 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, aRequest, flags, NS_OK); } // Update our overall current progress count. mCurrentSelfProgress += progressDelta; } // // The request is not part of the load group, so ignore its progress // information... // else { #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(aRequest, buffer); MOZ_LOG( gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p OOPS - No Request Info for: %s\n", this, buffer.get())); #endif /* DEBUG */ return NS_OK; } // // Fire progress notifications out to any registered nsIWebProgressListeners // FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta, mCurrentTotalProgress, mMaxTotalProgress); return NS_OK; } NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsresult aStatus, const char16_t* aStatusArg) { // // Fire progress notifications out to any registered nsIWebProgressListeners // if (aStatus != NS_OK) { // Remember the current status for this request nsRequestInfo* info; info = GetRequestInfo(aRequest); if (info) { bool uploading = (aStatus == NS_NET_STATUS_WRITING || aStatus == NS_NET_STATUS_SENDING_TO); // If switching from uploading to downloading (or vice versa), then we // need to reset our progress counts. This is designed with HTTP form // submission in mind, where an upload is performed followed by download // of possibly several documents. if (info->mUploading != uploading) { mCurrentSelfProgress = mMaxSelfProgress = 0; mCurrentTotalProgress = mMaxTotalProgress = 0; mCompletedTotalProgress = 0; info->mUploading = uploading; info->mCurrentProgress = 0; info->mMaxProgress = 0; } } nsCOMPtr sbs = mozilla::services::GetStringBundleService(); if (!sbs) return NS_ERROR_FAILURE; nsAutoString msg; nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg, msg); if (NS_FAILED(rv)) return rv; // Keep around the message. In case a request finishes, we need to make sure // to send the status message of another request to our user to that we // don't display, for example, "Transferring" messages for requests that are // already done. if (info) { if (!info->mLastStatus) { info->mLastStatus = MakeUnique(aRequest); } else { // We're going to move it to the front of the list, so remove // it from wherever it is now. info->mLastStatus->remove(); } info->mLastStatus->mStatusMessage = msg; info->mLastStatus->mStatusCode = aStatus; // Put the info at the front of the list mStatusInfoList.insertFront(info->mLastStatus.get()); } FireOnStatusChange(this, aRequest, aStatus, msg.get()); } return NS_OK; } void nsDocLoader::ClearInternalProgress() { ClearRequestInfoHash(); mCurrentSelfProgress = mMaxSelfProgress = 0; mCurrentTotalProgress = mMaxTotalProgress = 0; mCompletedTotalProgress = 0; mProgressStateFlags = nsIWebProgressListener::STATE_STOP; } /** * |_code| is executed for every listener matching |_flag| * |listener| should be used inside |_code| as the nsIWebProgressListener var. */ #define NOTIFY_LISTENERS(_flag, _code) \ PR_BEGIN_MACRO \ nsCOMPtr listener; \ ListenerArray::BackwardIterator iter(mListenerInfoList); \ while (iter.HasMore()) { \ nsListenerInfo& info = iter.GetNext(); \ if (!(info.mNotifyMask & (_flag))) { \ continue; \ } \ listener = do_QueryReferent(info.mWeakListener); \ if (!listener) { \ iter.Remove(); \ continue; \ } \ _code \ } \ mListenerInfoList.Compact(); \ PR_END_MACRO void nsDocLoader::FireOnProgressChange(nsDocLoader* aLoadInitiator, nsIRequest* request, int64_t aProgress, int64_t aProgressMax, int64_t aProgressDelta, int64_t aTotalProgress, int64_t aMaxTotalProgress) { if (mIsLoadingDocument) { mCurrentTotalProgress += aProgressDelta; mMaxTotalProgress = GetMaxTotalProgress(); aTotalProgress = mCurrentTotalProgress; aMaxTotalProgress = mMaxTotalProgress; } #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(request, buffer); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: Progress (%s): curSelf: %" PRId64 " maxSelf: %" PRId64 " curTotal: %" PRId64 " maxTotal %" PRId64 "\n", this, buffer.get(), aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress)); #endif /* DEBUG */ NOTIFY_LISTENERS( nsIWebProgress::NOTIFY_PROGRESS, // XXX truncates 64-bit to 32-bit listener->OnProgressChange(aLoadInitiator, request, int32_t(aProgress), int32_t(aProgressMax), int32_t(aTotalProgress), int32_t(aMaxTotalProgress));); // Pass the notification up to the parent... if (mParent) { mParent->FireOnProgressChange(aLoadInitiator, request, aProgress, aProgressMax, aProgressDelta, aTotalProgress, aMaxTotalProgress); } } void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList) { for (nsDocLoader* loader = this; loader; loader = loader->mParent) { aList.AppendElement(loader); } } void nsDocLoader::FireOnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, int32_t aStateFlags, nsresult aStatus) { WebProgressList list; GatherAncestorWebProgresses(list); for (uint32_t i = 0; i < list.Length(); ++i) { list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus); } } void nsDocLoader::DoFireOnStateChange(nsIWebProgress* const aProgress, nsIRequest* const aRequest, int32_t& aStateFlags, const nsresult aStatus) { // // 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; } // Add the STATE_RESTORING bit if necessary. if (mIsRestoringDocument) aStateFlags |= nsIWebProgressListener::STATE_RESTORING; #if defined(DEBUG) nsAutoCString buffer; GetURIStringFromRequest(aRequest, buffer); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: Status (%s): code: %x\n", this, buffer.get(), aStateFlags)); #endif /* DEBUG */ NS_ASSERTION(aRequest, "Firing OnStateChange(...) notification with a NULL request!"); NOTIFY_LISTENERS( ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus);); } void nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aUri, uint32_t aFlags) { NOTIFY_LISTENERS( nsIWebProgress::NOTIFY_LOCATION, MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader [%p] calling %p->OnLocationChange to %s %x", this, listener.get(), aUri->GetSpecOrDefault().get(), aFlags)); listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags);); // Pass the notification up to the parent... if (mParent) { mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags); } } void nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage) { NOTIFY_LISTENERS( nsIWebProgress::NOTIFY_STATUS, listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);); // Pass the notification up to the parent... if (mParent) { mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage); } } bool nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress, nsIURI* aURI, int32_t aDelay, bool aSameURI) { /* * Returns true if the refresh may proceed, * false if the refresh should be blocked. */ bool allowRefresh = true; NOTIFY_LISTENERS( nsIWebProgress::NOTIFY_REFRESH, nsCOMPtr listener2 = do_QueryReferent(info.mWeakListener); if (!listener2) continue; bool listenerAllowedRefresh; nsresult listenerRV = listener2->OnRefreshAttempted( aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh); if (NS_FAILED(listenerRV)) continue; allowRefresh = allowRefresh && listenerAllowedRefresh;); // Pass the notification up to the parent... if (mParent) { allowRefresh = allowRefresh && mParent->RefreshAttempted(aWebProgress, aURI, aDelay, aSameURI); } return allowRefresh; } nsresult nsDocLoader::AddRequestInfo(nsIRequest* aRequest) { if (!mRequestInfoHash.Add(aRequest, mozilla::fallible)) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } void nsDocLoader::RemoveRequestInfo(nsIRequest* aRequest) { mRequestInfoHash.Remove(aRequest); } nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo( nsIRequest* aRequest) const { return static_cast(mRequestInfoHash.Search(aRequest)); } void nsDocLoader::ClearRequestInfoHash(void) { mRequestInfoHash.Clear(); } int64_t nsDocLoader::CalculateMaxProgress() { int64_t max = mCompletedTotalProgress; for (auto iter = mRequestInfoHash.Iter(); !iter.Done(); iter.Next()) { auto info = static_cast(iter.Get()); if (info->mMaxProgress < info->mCurrentProgress) { return int64_t(-1); } max += info->mMaxProgress; } return max; } NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect( nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, nsIAsyncVerifyRedirectCallback* cb) { if (aOldChannel) { nsLoadFlags loadFlags = 0; int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING | nsIWebProgressListener::STATE_IS_REQUEST; aOldChannel->GetLoadFlags(&loadFlags); // If the document channel is being redirected, then indicate that the // document is being redirected in the notification... if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT; #if defined(DEBUG) // We only set mDocumentRequest in OnStartRequest(), but its possible // to get a redirect before that for service worker interception. if (mDocumentRequest) { nsCOMPtr request(aOldChannel); NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel"); } #endif /* DEBUG */ } OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags); FireOnStateChange(this, aOldChannel, stateFlags, NS_OK); } cb->OnRedirectVerifyCallback(NS_OK); return NS_OK; } void nsDocLoader::OnSecurityChange(nsISupports* aContext, uint32_t aState) { // // Fire progress notifications out to any registered nsIWebProgressListeners. // nsCOMPtr request = do_QueryInterface(aContext); nsIWebProgress* webProgress = static_cast(this); NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_SECURITY, listener->OnSecurityChange(webProgress, request, aState);); // Pass the notification up to the parent... if (mParent) { mParent->OnSecurityChange(aContext, aState); } } /* * Implementation of nsISupportsPriority methods... * * The priority of the DocLoader _is_ the priority of its LoadGroup. * * XXX(darin): Once we start storing loadgroups in loadgroups, this code will * go away. */ NS_IMETHODIMP nsDocLoader::GetPriority(int32_t* aPriority) { nsCOMPtr p = do_QueryInterface(mLoadGroup); if (p) return p->GetPriority(aPriority); *aPriority = 0; return NS_OK; } NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority) { MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority)); nsCOMPtr p = do_QueryInterface(mLoadGroup); if (p) p->SetPriority(aPriority); NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, SetPriority, (aPriority)); return NS_OK; } NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta) { MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta)); nsCOMPtr p = do_QueryInterface(mLoadGroup); if (p) p->AdjustPriority(aDelta); NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, AdjustPriority, (aDelta)); return NS_OK; } #if 0 void nsDocLoader::DumpChannelInfo() { nsChannelInfo *info; int32_t i, count; int32_t current=0, max=0; printf("==== DocLoader=%x\n", this); count = mChannelInfoList.Count(); for(i=0; imURI) { rv = info->mURI->GetSpec(buffer); } printf(" [%d] current=%d max=%d [%s]\n", i, info->mCurrentProgress, info->mMaxProgress, buffer.get()); # 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 */