diff options
Diffstat (limited to 'uriloader/base')
-rw-r--r-- | uriloader/base/moz.build | 43 | ||||
-rw-r--r-- | uriloader/base/nsCURILoader.idl | 18 | ||||
-rw-r--r-- | uriloader/base/nsDocLoader.cpp | 1588 | ||||
-rw-r--r-- | uriloader/base/nsDocLoader.h | 373 | ||||
-rw-r--r-- | uriloader/base/nsIContentHandler.idl | 35 | ||||
-rw-r--r-- | uriloader/base/nsIDocumentLoader.idl | 35 | ||||
-rw-r--r-- | uriloader/base/nsITransfer.idl | 156 | ||||
-rw-r--r-- | uriloader/base/nsIURIContentListener.idl | 123 | ||||
-rw-r--r-- | uriloader/base/nsIURILoader.idl | 139 | ||||
-rw-r--r-- | uriloader/base/nsIWebProgress.idl | 185 | ||||
-rw-r--r-- | uriloader/base/nsIWebProgressListener.idl | 597 | ||||
-rw-r--r-- | uriloader/base/nsIWebProgressListener2.idl | 69 | ||||
-rw-r--r-- | uriloader/base/nsURILoader.cpp | 846 | ||||
-rw-r--r-- | uriloader/base/nsURILoader.h | 218 |
14 files changed, 4425 insertions, 0 deletions
diff --git a/uriloader/base/moz.build b/uriloader/base/moz.build new file mode 100644 index 0000000000..a206c29385 --- /dev/null +++ b/uriloader/base/moz.build @@ -0,0 +1,43 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +include("/ipc/chromium/chromium-config.mozbuild") + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +with Files("nsITransfer.idl"): + BUG_COMPONENT = ("Firefox", "File Handling") + +XPIDL_SOURCES += [ + "nsCURILoader.idl", + "nsIContentHandler.idl", + "nsIDocumentLoader.idl", + "nsITransfer.idl", + "nsIURIContentListener.idl", + "nsIURILoader.idl", + "nsIWebProgress.idl", + "nsIWebProgressListener.idl", + "nsIWebProgressListener2.idl", +] + +XPIDL_MODULE = "uriloader" + +EXPORTS += [ + "nsDocLoader.h", + "nsURILoader.h", +] + +UNIFIED_SOURCES += [ + "nsDocLoader.cpp", + "nsURILoader.cpp", +] + +LOCAL_INCLUDES += [ + "/netwerk/base", +] + +FINAL_LIBRARY = "xul" diff --git a/uriloader/base/nsCURILoader.idl b/uriloader/base/nsCURILoader.idl new file mode 100644 index 0000000000..82f17fc83e --- /dev/null +++ b/uriloader/base/nsCURILoader.idl @@ -0,0 +1,18 @@ +/* -*- Mode: IDL; tab-width: 3; 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 "nsIURILoader.idl" + +/* +nsCURILoader implements: +------------------------- +nsIURILoader +*/ + +%{ C++ +#define NS_CONTENT_HANDLER_CONTRACTID "@mozilla.org/uriloader/content-handler;1" +#define NS_CONTENT_HANDLER_CONTRACTID_PREFIX NS_CONTENT_HANDLER_CONTRACTID "?type=" +%} diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp new file mode 100644 index 0000000000..fe72a2715d --- /dev/null +++ b/uriloader/base/nsDocLoader.cpp @@ -0,0 +1,1588 @@ +/* -*- 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 "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 "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<nsRequestInfo*>(entry); + info->~nsRequestInfo(); +} + +// this is used for mListenerInfoList.Contains() +template <> +class nsDefaultComparator<nsDocLoader::nsListenerInfo, + nsIWebProgressListener*> { + public: + bool Equals(const nsDocLoader::nsListenerInfo& aInfo, + nsIWebProgressListener* const& aListener) const { + nsCOMPtr<nsIWebProgressListener> 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(bool aNotifyAboutBackgroundRequests) + : 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), + mDocumentOpenedButNotLoaded(false), + mNotifyAboutBackgroundRequests(aNotifyAboutBackgroundRequests) { + ClearInternalProgress(); + + MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: created.\n", this)); +} + +nsresult nsDocLoader::SetDocLoaderParent(nsDocLoader* aParent) { + mParent = aParent; + return NS_OK; +} + +nsresult nsDocLoader::Init() { + RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup(); + nsresult rv = loadGroup->Init(); + if (NS_FAILED(rv)) return rv; + + loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests); + + mLoadGroup = loadGroup; + + MOZ_LOG(gDocLoaderLog, LogLevel::Debug, + ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get())); + + return NS_OK; +} + +nsresult nsDocLoader::InitWithBrowsingContext( + BrowsingContext* aBrowsingContext) { + RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup(); + if (!aBrowsingContext->GetRequestContextId()) { + return NS_ERROR_NOT_AVAILABLE; + } + nsresult rv = loadGroup->InitWithRequestContextId( + aBrowsingContext->GetRequestContextId()); + if (NS_FAILED(rv)) return rv; + + loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests); + + 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> nsDocLoader::GetAsDocLoader( + nsISupports* aSupports) { + RefPtr<nsDocLoader> ret = do_QueryObject(aSupports); + return ret.forget(); +} + +/* static */ +nsresult nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader) { + nsCOMPtr<nsIDocumentLoader> docLoaderService = + components::DocLoader::Service(); + NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED); + + RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService); + NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED); + + return rootDocLoader->AddChildLoader(aDocLoader); +} + +// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) +MOZ_CAN_RUN_SCRIPT_BOUNDARY 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->CancelWithReason(NS_BINDING_ABORTED, + "nsDocLoader::Stop"_ns); + } + + // 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(); + nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this)); + Document* doc = ds ? ds->GetExtantDocument() : nullptr; + if (doc) { + doc->ClearOOPChildrenLoading(); + } + + // 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::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(). + // + + nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this)); + Document* doc = ds ? ds->GetExtantDocument() : nullptr; + if (!mChildrenInOnload.IsEmpty() || (doc && doc->HasOOPChildrenLoading()) || + 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); + // This is a safe cast, because we only put nsDocLoader objects into the + // array + if (loader && static_cast<nsDocLoader*>(loader)->IsBusy()) { + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsDocLoader::GetContainer(nsISupports** aResult) { + NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(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<nsresult> 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<nsresult> rv = + static_cast<nsDocLoader*>(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. + + // Some docloaders deal with background requests in their OnStartRequest + // override, but here we don't want to do anything with them, so return early. + nsLoadFlags loadFlags = 0; + request->GetLoadFlags(&loadFlags); + if (loadFlags & nsIRequest::LOAD_BACKGROUND) { + return NS_OK; + } + + 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 justStartedLoading = false; + + if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { + justStartedLoading = 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 (justStartedLoading) { + // 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 && !justStartedLoading && + (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) && + (loadFlags & nsIChannel::LOAD_REPLACE)) { + extraFlags = nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT; + } + doStartURLLoad(request, extraFlags); + + return NS_OK; +} + +// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { + // Some docloaders deal with background requests in their OnStopRequest + // override, but here we don't want to do anything with them, so return early. + nsLoadFlags lf = 0; + aRequest->GetLoadFlags(&lf); + if (lf & nsIRequest::LOAD_BACKGROUND) { + return NS_OK; + } + + 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<uint32_t>(aStatus), + (mIsLoadingDocument ? "true" : "false"), + (mDocumentOpenedButNotLoaded ? "true" : "false"), count)); + } + + bool fireTransferring = 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<nsIChannel> 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)) { + fireTransferring = 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)... + // + if (lf & nsIChannel::LOAD_TARGETED) { + nsCOMPtr<nsIHttpChannel> 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... + // + fireTransferring = true; + } + } + } + } + } + } + } + + if (fireTransferring) { + // 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); + + // For the special case where the current document is an initial about:blank + // document, we may still have subframes loading, and keeping the DocLoader + // busy. In that case, if we have an error, we won't show it until those + // frames finish loading, which is nonsensical. So stop any subframe loads + // now. + if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED && + aStatus != NS_BINDING_REDIRECTED && aStatus != NS_BINDING_RETARGETED) { + if (RefPtr<Document> doc = do_GetInterface(GetAsSupports(this))) { + if (doc->IsInitialDocument()) { + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ()); + } + } + } + + // + // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload. + // + if (IsBlockingLoadEvent()) { + nsCOMPtr<nsIDocShell> ds = + do_QueryInterface(static_cast<nsIRequestObserver*>(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<nsresult>& 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<nsIDocumentLoader> 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<Document> 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<nsIRequest> 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<nsDocLoader> 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<nsDocLoader> parent = mParent; + if (!parent || parent->ChildEnteringOnload(this)) { + nsresult loadGroupStatus = NS_OK; + mLoadGroup->GetStatus(&loadGroupStatus); + // 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) { + // Can "doc" or "window" ever come back null here? Our state machine + // is complicated enough I wouldn't bet against it... + if (nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this))) { + doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE, + /* updateTimingInformation = */ false); + doc->StopDocumentLoad(); + + nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow(); + if (window && !doc->SkipLoadEventAfterClose()) { + 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); + // MOZ_KnownLive due to bug 1506441 + EventDispatcher::Dispatch( + MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)), nullptr, + &event, nullptr, &unused); + doc->SetLoadEventFiring(false); + + // Now unsuppress painting on the presshell, if we + // haven't done that yet. + RefPtr<PresShell> presShell = doc->GetPresShell(); + if (presShell && !presShell->IsDestroying()) { + presShell->UnsuppressPainting(); + + if (!presShell->IsDestroying()) { + presShell->LoadComplete(); + } + } + } + } + } + NotifyDoneWithOnload(parent); + } + } + } +} + +void nsDocLoader::NotifyDoneWithOnload(nsDocLoader* aParent) { + if (aParent) { + // In-process parent: + aParent->ChildDoneWithOnload(this); + } + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(this); + if (!docShell) { + return; + } + BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext(); + if (bc->IsContentSubframe() && !bc->GetParentWindowContext()->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<uint32_t>(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<uint32_t>(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::GetBrowsingContextXPCOM(BrowsingContext** aResult) { + *aResult = nullptr; + return NS_OK; +} + +BrowsingContext* nsDocLoader::GetBrowsingContext() { return nullptr; } + +NS_IMETHODIMP +nsDocLoader::GetDOMWindow(mozIDOMWindowProxy** aResult) { + return CallGetInterface(this, aResult); +} + +NS_IMETHODIMP +nsDocLoader::GetIsTopLevel(bool* aResult) { + nsCOMPtr<nsIDocShell> 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<nsIEventTarget> target = GetMainThreadSerialEventTarget(); + 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<nsIStringBundleService> sbs = + mozilla::components::StringBundle::Service(); + 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<nsStatusInfo>(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<nsIWebProgressListener> 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, + uint32_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<nsIWebProgressListener2> 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<nsRequestInfo*>(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<const nsRequestInfo*>(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<nsIRequest> 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<nsIRequest> request = do_QueryInterface(aContext); + nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(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<nsISupportsPriority> 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<nsISupportsPriority> 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<nsISupportsPriority> p = do_QueryInterface(mLoadGroup); + if (p) p->AdjustPriority(aDelta); + + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, AdjustPriority, + (aDelta)); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocLoader::GetDocumentRequest(nsIRequest** aRequest) { + NS_IF_ADDREF(*aRequest = mDocumentRequest); + 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; i<count; i++) { + info = (nsChannelInfo *)mChannelInfoList.ElementAt(i); + +# if defined(DEBUG) + nsAutoCString buffer; + nsresult rv = NS_OK; + if (info->mURI) { + 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 */ diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h new file mode 100644 index 0000000000..e2ef3b4e77 --- /dev/null +++ b/uriloader/base/nsDocLoader.h @@ -0,0 +1,373 @@ +/* -*- 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/. */ + +#ifndef nsDocLoader_h__ +#define nsDocLoader_h__ + +#include "nsIDocumentLoader.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIRequestObserver.h" +#include "nsWeakReference.h" +#include "nsILoadGroup.h" +#include "nsCOMArray.h" +#include "nsTObserverArray.h" +#include "nsString.h" +#include "nsIChannel.h" +#include "nsIProgressEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIChannelEventSink.h" +#include "nsISupportsPriority.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include "nsCycleCollectionParticipant.h" + +#include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace dom { +class BrowsingContext; +} // namespace dom +} // namespace mozilla + +/**************************************************************************** + * nsDocLoader implementation... + ****************************************************************************/ + +#define NS_THIS_DOCLOADER_IMPL_CID \ + { /* b4ec8387-98aa-4c08-93b6-6d23069c06f2 */ \ + 0xb4ec8387, 0x98aa, 0x4c08, { \ + 0x93, 0xb6, 0x6d, 0x23, 0x06, 0x9c, 0x06, 0xf2 \ + } \ + } + +class nsDocLoader : public nsIDocumentLoader, + public nsIRequestObserver, + public nsSupportsWeakReference, + public nsIProgressEventSink, + public nsIWebProgress, + public nsIInterfaceRequestor, + public nsIChannelEventSink, + public nsISupportsPriority { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_DOCLOADER_IMPL_CID) + + nsDocLoader() : nsDocLoader(false) {} + + [[nodiscard]] virtual nsresult Init(); + [[nodiscard]] nsresult InitWithBrowsingContext( + mozilla::dom::BrowsingContext* aBrowsingContext); + + static already_AddRefed<nsDocLoader> GetAsDocLoader(nsISupports* aSupports); + // Needed to deal with ambiguous inheritance from nsISupports... + static nsISupports* GetAsSupports(nsDocLoader* aDocLoader) { + return static_cast<nsIDocumentLoader*>(aDocLoader); + } + + // Add aDocLoader as a child to the docloader service. + [[nodiscard]] static nsresult AddDocLoaderAsChildOfRoot( + nsDocLoader* aDocLoader); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDocLoader, nsIDocumentLoader) + + NS_DECL_NSIDOCUMENTLOADER + + // nsIProgressEventSink + NS_DECL_NSIPROGRESSEVENTSINK + + // nsIRequestObserver methods: (for observing the load group) + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIWEBPROGRESS + + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSISUPPORTSPRIORITY; // semicolon for clang-format bug 1629756 + + // Implementation specific methods... + + // Remove aChild from our childlist. This nulls out the child's mParent + // pointer. + [[nodiscard]] nsresult RemoveChildLoader(nsDocLoader* aChild); + + // Add aChild to our child list. This will set aChild's mParent pointer to + // |this|. + [[nodiscard]] nsresult AddChildLoader(nsDocLoader* aChild); + nsDocLoader* GetParent() const { return mParent; } + + struct nsListenerInfo { + nsListenerInfo(nsIWeakReference* aListener, unsigned long aNotifyMask) + : mWeakListener(aListener), mNotifyMask(aNotifyMask) {} + + // Weak pointer for the nsIWebProgressListener... + nsWeakPtr mWeakListener; + + // Mask indicating which notifications the listener wants to receive. + unsigned long mNotifyMask; + }; + + /** + * Fired when a security change occurs due to page transitions, + * or end document load. This interface should be called by + * a security package (eg Netscape Personal Security Manager) + * to notify nsIWebProgressListeners that security state has + * changed. State flags are in nsIWebProgressListener.idl + */ + void OnSecurityChange(nsISupports* aContext, uint32_t aState); + + void SetDocumentOpenedButNotLoaded() { mDocumentOpenedButNotLoaded = true; } + + uint32_t ChildCount() const { return mChildList.Length(); } + + // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + MOZ_CAN_RUN_SCRIPT_BOUNDARY void OOPChildrenLoadingIsEmpty() { + DocLoaderIsEmpty(true); + } + + protected: + explicit nsDocLoader(bool aNotifyAboutBackgroundRequests); + virtual ~nsDocLoader(); + + [[nodiscard]] virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader); + + bool IsBusy(); + + void SetBackgroundLoadIframe(); + + void Destroy(); + virtual void DestroyChildren(); + + nsIDocumentLoader* ChildAt(int32_t i) { + return mChildList.SafeElementAt(i, nullptr); + } + + void FireOnProgressChange(nsDocLoader* aLoadInitiator, nsIRequest* request, + int64_t aProgress, int64_t aProgressMax, + int64_t aProgressDelta, int64_t aTotalProgress, + int64_t aMaxTotalProgress); + + // This should be at least 2 long since we'll generally always + // have the current page and the global docloader on the ancestor + // list. But to deal with frames it's better to make it a bit + // longer, and it's always a stack temporary so there's no real + // reason not to. + typedef AutoTArray<RefPtr<nsDocLoader>, 8> WebProgressList; + void GatherAncestorWebProgresses(WebProgressList& aList); + + void FireOnStateChange(nsIWebProgress* aProgress, nsIRequest* request, + int32_t aStateFlags, nsresult aStatus); + + // The guts of FireOnStateChange, but does not call itself on our ancestors. + // The arguments that are const are const so that we can detect cases when + // DoFireOnStateChange wants to propagate changes to the next web progress + // at compile time. The ones that are not, are references so that such + // changes can be propagated. + void DoFireOnStateChange(nsIWebProgress* const aProgress, + nsIRequest* const request, int32_t& aStateFlags, + const nsresult aStatus); + + void FireOnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage); + + void FireOnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsIURI* aUri, uint32_t aFlags); + + [[nodiscard]] bool RefreshAttempted(nsIWebProgress* aWebProgress, + nsIURI* aURI, uint32_t aDelay, + bool aSameURI); + + // this function is overridden by the docshell, it is provided so that we + // can pass more information about redirect state (the normal OnStateChange + // doesn't get the new channel). + // @param aRedirectFlags The flags being sent to OnStateChange that + // indicate the type of redirect. + // @param aStateFlags The channel flags normally sent to OnStateChange. + virtual void OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) {} + + void doStartDocumentLoad(); + void doStartURLLoad(nsIRequest* request, int32_t aExtraFlags); + void doStopURLLoad(nsIRequest* request, nsresult aStatus); + void doStopDocumentLoad(nsIRequest* request, nsresult aStatus); + + void NotifyDoneWithOnload(nsDocLoader* aParent); + + // Inform a parent docloader that aChild is about to call its onload + // handler. + [[nodiscard]] bool ChildEnteringOnload(nsIDocumentLoader* aChild) { + // It's ok if we're already in the list -- we'll just be in there twice + // and then the RemoveObject calls from ChildDoneWithOnload will remove + // us. + return mChildrenInOnload.AppendObject(aChild); + } + + // Inform a parent docloader that aChild is done calling its onload + // handler. + // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + MOZ_CAN_RUN_SCRIPT_BOUNDARY void ChildDoneWithOnload( + nsIDocumentLoader* aChild) { + mChildrenInOnload.RemoveObject(aChild); + DocLoaderIsEmpty(true); + } + + // DocLoaderIsEmpty should be called whenever the docloader may be empty. + // This method is idempotent and does nothing if the docloader is not in + // fact empty. This method _does_ make sure that layout is flushed if our + // loadgroup has no active requests before checking for "real" emptiness if + // aFlushLayout is true. + // @param aOverrideStatus An optional status to use when notifying listeners + // of the completed load, instead of using the load group's status. + MOZ_CAN_RUN_SCRIPT void DocLoaderIsEmpty( + bool aFlushLayout, + const mozilla::Maybe<nsresult>& aOverrideStatus = mozilla::Nothing()); + + protected: + struct nsStatusInfo : public mozilla::LinkedListElement<nsStatusInfo> { + nsString mStatusMessage; + nsresult mStatusCode; + // Weak mRequest is ok; we'll be told if it decides to go away. + nsIRequest* const mRequest; + + explicit nsStatusInfo(nsIRequest* aRequest) + : mStatusCode(NS_ERROR_NOT_INITIALIZED), mRequest(aRequest) { + MOZ_COUNT_CTOR(nsStatusInfo); + } + MOZ_COUNTED_DTOR(nsStatusInfo) + }; + + struct nsRequestInfo : public PLDHashEntryHdr { + explicit nsRequestInfo(const void* key) + : mKey(key), + mCurrentProgress(0), + mMaxProgress(0), + mUploading(false), + mLastStatus(nullptr) { + MOZ_COUNT_CTOR(nsRequestInfo); + } + + MOZ_COUNTED_DTOR(nsRequestInfo) + + nsIRequest* Request() { + return static_cast<nsIRequest*>(const_cast<void*>(mKey)); + } + + const void* mKey; // Must be first for the PLDHashTable stubs to work + int64_t mCurrentProgress; + int64_t mMaxProgress; + bool mUploading; + + mozilla::UniquePtr<nsStatusInfo> mLastStatus; + }; + + static void RequestInfoHashInitEntry(PLDHashEntryHdr* entry, const void* key); + static void RequestInfoHashClearEntry(PLDHashTable* table, + PLDHashEntryHdr* entry); + + // IMPORTANT: The ownership implicit in the following member + // variables has been explicitly checked and set using nsCOMPtr + // for owning pointers and raw COM interface pointers for weak + // (ie, non owning) references. If you add any members to this + // class, please make the ownership explicit (pinkerton, scc). + + nsCOMPtr<nsIRequest> mDocumentRequest; // [OWNER] ???compare with document + + nsDocLoader* mParent; // [WEAK] + + typedef nsAutoTObserverArray<nsListenerInfo, 8> ListenerArray; + ListenerArray mListenerInfoList; + + nsCOMPtr<nsILoadGroup> mLoadGroup; + // We hold weak refs to all our kids + nsTObserverArray<nsDocLoader*> mChildList; + + // The following member variables are related to the new nsIWebProgress + // feedback interfaces that travis cooked up. + int32_t mProgressStateFlags; + + int64_t mCurrentSelfProgress; + int64_t mMaxSelfProgress; + + int64_t mCurrentTotalProgress; + int64_t mMaxTotalProgress; + + PLDHashTable mRequestInfoHash; + int64_t mCompletedTotalProgress; + + mozilla::LinkedList<nsStatusInfo> mStatusInfoList; + + /* + * This flag indicates that the loader is loading a document. It is set + * from the call to LoadDocument(...) until the OnConnectionsComplete(...) + * notification is fired... + */ + bool mIsLoadingDocument; + + /* Flag to indicate that we're in the process of restoring a document. */ + bool mIsRestoringDocument; + + /* Flag to indicate that we're in the process of flushing layout + under DocLoaderIsEmpty() and should not do another flush. */ + bool mDontFlushLayout; + + /* Flag to indicate whether we should consider ourselves as currently + flushing layout for the purposes of IsBusy. For example, if Stop has + been called then IsBusy should return false even if we are still + flushing. */ + bool mIsFlushingLayout; + + private: + /** + * This flag indicates that the loader is waiting for completion of + * a document.open-triggered "document load". This is set when + * document.open() happens and sets up a new parser and cleared out + * when we go to fire our load event or end up with a new document + * channel. + */ + bool mDocumentOpenedButNotLoaded; + + bool mNotifyAboutBackgroundRequests; + + static const PLDHashTableOps sRequestInfoHashOps; + + // A list of kids that are in the middle of their onload calls and will let + // us know once they're done. We don't want to fire onload for "normal" + // DocLoaderIsEmpty calls (those coming from requests finishing in our + // loadgroup) unless this is empty. + nsCOMArray<nsIDocumentLoader> mChildrenInOnload; + + int64_t GetMaxTotalProgress(); + + nsresult AddRequestInfo(nsIRequest* aRequest); + void RemoveRequestInfo(nsIRequest* aRequest); + nsRequestInfo* GetRequestInfo(nsIRequest* aRequest) const; + void ClearRequestInfoHash(); + int64_t CalculateMaxProgress(); + /// void DumpChannelInfo(void); + + // used to clear our internal progress state between loads... + void ClearInternalProgress(); + + /** + * Used to test whether we might need to fire a load event. This + * can happen when we have a document load going on, or when we've + * had document.open() called and haven't fired the corresponding + * load event yet. + */ + bool IsBlockingLoadEvent() const { + return mIsLoadingDocument || mDocumentOpenedButNotLoaded; + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID) + +static inline nsISupports* ToSupports(nsDocLoader* aDocLoader) { + return static_cast<nsIDocumentLoader*>(aDocLoader); +} + +#endif /* nsDocLoader_h__ */ diff --git a/uriloader/base/nsIContentHandler.idl b/uriloader/base/nsIContentHandler.idl new file mode 100644 index 0000000000..31ef87a8ba --- /dev/null +++ b/uriloader/base/nsIContentHandler.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIRequest; +interface nsIInterfaceRequestor; + +[scriptable, uuid(49439df2-b3d2-441c-bf62-866bdaf56fd2)] +interface nsIContentHandler : nsISupports +{ + /** + * Tells the content handler to take over handling the content. If this + * function succeeds, the URI Loader will leave this request alone, ignoring + * progress notifications. Failure of this method will cause the request to be + * cancelled, unless the error code is NS_ERROR_WONT_HANDLE_CONTENT (see + * below). + * + * @param aWindowContext + * Window context, used to get things like the current nsIDOMWindow + * for this request. May be null. + * @param aContentType + * The content type of aRequest + * @param aRequest + * A request whose content type is already known. + * + * @throw NS_ERROR_WONT_HANDLE_CONTENT Indicates that this handler does not + * want to handle this content. A different way for handling this + * content should be tried. + */ + void handleContent(in string aContentType, + in nsIInterfaceRequestor aWindowContext, + in nsIRequest aRequest); +}; diff --git a/uriloader/base/nsIDocumentLoader.idl b/uriloader/base/nsIDocumentLoader.idl new file mode 100644 index 0000000000..ae02a827e2 --- /dev/null +++ b/uriloader/base/nsIDocumentLoader.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsILoadGroup; +interface nsIChannel; +interface nsIURI; +interface nsIWebProgress; +interface nsIRequest; + +/** + * An nsIDocumentLoader is an interface responsible for tracking groups of + * loads that belong together (images, external scripts, etc) and subdocuments + * (<iframe>, <frame>, etc). It is also responsible for sending + * nsIWebProgressListener notifications. + * XXXbz this interface should go away, we think... + */ +[scriptable, uuid(bbe961ee-59e9-42bb-be50-0331979bb79f)] +interface nsIDocumentLoader : nsISupports +{ + // Stop all loads in the loadgroup of this docloader + void stop(); + + // XXXbz is this needed? For embedding? What this does is does is not + // defined by this interface! + readonly attribute nsISupports container; + + // The loadgroup associated with this docloader + readonly attribute nsILoadGroup loadGroup; + + // The defaultLoadRequest of the loadgroup associated with this docloader + readonly attribute nsIChannel documentChannel; +}; diff --git a/uriloader/base/nsITransfer.idl b/uriloader/base/nsITransfer.idl new file mode 100644 index 0000000000..3d828b7f12 --- /dev/null +++ b/uriloader/base/nsITransfer.idl @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIWebProgressListener2.idl" + +interface nsIArray; +interface nsIURI; +interface nsICancelable; +interface nsIMIMEInfo; +interface nsIFile; +interface nsIReferrerInfo; +interface nsIHttpChannel; +webidl BrowsingContext; + +[scriptable, uuid(37ec75d3-97ad-4da8-afaa-eabe5b4afd73)] +interface nsITransfer : nsIWebProgressListener2 { + + const unsigned long DOWNLOAD_ACCEPTABLE = 0; + const unsigned long DOWNLOAD_FORBIDDEN = 1; + const unsigned long DOWNLOAD_POTENTIALLY_UNSAFE = 2; + + /** + * Initializes the transfer with certain properties. This function must + * be called prior to accessing any properties on this interface. + * + * @param aSource The source URI of the transfer. Must not be null. + * + * @param aSourceOriginalURI The original URI of the transfer in case + * aSource is a blob URL. Can be null. + * + * @param aTarget The target URI of the transfer. Must not be null. + * + * @param aDisplayName The user-readable description of the transfer. + * Can be empty. + * + * @param aMIMEInfo The MIME info associated with the target, + * including MIME type and helper app when appropriate. + * This parameter is optional. + * + * @param startTime Time when the download started (ie, when the first + * response from the server was received) + * XXX presumably wbp and exthandler do this differently + * + * @param aTempFile The location of a temporary file; i.e. a file in which + * the received data will be stored, but which is not + * equal to the target file. (will be moved to the real + * target by the caller, when the download is finished) + * May be null. + * + * @param aCancelable An object that can be used to abort the download. + * Must not be null. + * Implementations are expected to hold a strong + * reference to this object until the download is + * finished, at which point they should release the + * reference. + * + * @param aIsPrivate Used to determine the privacy status of the new transfer. + * If true, indicates that the transfer was initiated from + * a source that desires privacy. + * + * @param aDownloadClassification Indicates wheter the download is unwanted, + * should be considered dangerous or insecure. + * + * @param aReferrerInfo The Referrer this download is started with + * + * @param aOpenDownloadsListOnStart true (default) - Open downloads panel. + * false - Only show an icon indicator. + * This parameter is optional. + */ + void init(in nsIURI aSource, + in nsIURI aSourceOriginalURI, + in nsIURI aTarget, + in AString aDisplayName, + in nsIMIMEInfo aMIMEInfo, + in PRTime startTime, + in nsIFile aTempFile, + in nsICancelable aCancelable, + in boolean aIsPrivate, + in long aDownloadClassification, + in nsIReferrerInfo aReferrerInfo, + [optional] in boolean aOpenDownloadsListOnStart); + + /** + * Same as init, but allows for passing the browsingContext + * which will allow for opening the download with the same + * userContextId and auth header. + * + * @param aBrowsingContext BrowsingContext of the initiating document. + * + * @param aHandleInternally Set to true if the download should be opened within + * the browser. + * @param aHttpChannel Channel of the initiating document. + */ + void initWithBrowsingContext(in nsIURI aSource, + in nsIURI aTarget, + in AString aDisplayName, + in nsIMIMEInfo aMIMEInfo, + in PRTime startTime, + in nsIFile aTempFile, + in nsICancelable aCancelable, + in boolean aIsPrivate, + in long aDownloadClassification, + in nsIReferrerInfo aReferrerInfo, + in boolean aOpenDownloadsListOnStart, + in BrowsingContext aBrowsingContext, + in boolean aHandleInternally, + in nsIHttpChannel aHttpChannel); + + + /* + * Used to notify the transfer object of the hash of the downloaded file. + * Must be called on the main thread, only after the download has finished + * successfully. + * @param aHash The SHA-256 hash in raw bytes of the downloaded file. + */ + void setSha256Hash(in ACString aHash); + + /* + * Used to notify the transfer object of the signature of the downloaded + * file. Must be called on the main thread, only after the download has + * finished successfully. + * @param aSignatureInfo The Array of Array of Array of bytes + * certificates of the downloaded file. + */ + void setSignatureInfo(in Array<Array<Array<uint8_t> > > aSignatureInfo); + + /* + * Used to notify the transfer object of the redirects associated with the + * channel that terminated in the downloaded file. Must be called on the + * main thread, only after the download has finished successfully. + * @param aRedirects The nsIArray of nsIPrincipal of redirected URIs + * associated with the downloaded file. + */ + void setRedirects(in nsIArray aRedirects); +}; + +%{C++ +/** + * A component with this contract ID will be created each time a download is + * started, and nsITransfer::Init will be called on it and an observer will be set. + * + * Notifications of the download progress will happen via + * nsIWebProgressListener/nsIWebProgressListener2. + * + * INTERFACES THAT MUST BE IMPLEMENTED: + * nsITransfer + * nsIWebProgressListener + * nsIWebProgressListener2 + * + * XXX move this to nsEmbedCID.h once the interfaces (and the contract ID) are + * frozen. + */ +#define NS_TRANSFER_CONTRACTID "@mozilla.org/transfer;1" +%} diff --git a/uriloader/base/nsIURIContentListener.idl b/uriloader/base/nsIURIContentListener.idl new file mode 100644 index 0000000000..000b878273 --- /dev/null +++ b/uriloader/base/nsIURIContentListener.idl @@ -0,0 +1,123 @@ +/* -*- Mode: IDL; 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 "nsISupports.idl" + +interface nsIRequest; +interface nsIStreamListener; +interface nsIURI; + +/** + * nsIURIContentListener is an interface used by components which + * want to know (and have a chance to handle) a particular content type. + * Typical usage scenarios will include running applications which register + * a nsIURIContentListener for each of its content windows with the uri + * dispatcher service. + */ +[scriptable, uuid(10a28f38-32e8-4c63-8aa1-12eaaebc369a)] +interface nsIURIContentListener : nsISupports +{ + /** + * Notifies the content listener to hook up an nsIStreamListener capable of + * consuming the data stream. + * + * @param aContentType Content type of the data. + * @param aIsContentPreferred Indicates whether the content should be + * preferred by this listener. + * @param aRequest Request that is providing the data. + * @param aContentHandler nsIStreamListener that will consume the data. + * This should be set to <code>nullptr</code> if + * this content listener can't handle the content + * type; in this case, doContent should also fail + * (i.e., return failure nsresult). + * + * @return <code>true</code> if the load should + * be aborted and consumer wants to + * handle the load completely by itself. This + * causes the URI Loader do nothing else... + * <code>false</code> if the URI Loader should + * continue handling the load and call the + * returned streamlistener's methods. + */ + boolean doContent(in ACString aContentType, + in boolean aIsContentPreferred, + in nsIRequest aRequest, + out nsIStreamListener aContentHandler); + + /** + * When given a uri to dispatch, if the URI is specified as 'preferred + * content' then the uri loader tries to find a preferred content handler + * for the content type. The thought is that many content listeners may + * be able to handle the same content type if they have to. i.e. the mail + * content window can handle text/html just like a browser window content + * listener. However, if the user clicks on a link with text/html content, + * then the browser window should handle that content and not the mail + * window where the user may have clicked the link. This is the difference + * between isPreferred and canHandleContent. + * + * @param aContentType Content type of the data. + * @param aDesiredContentType Indicates that aContentType must be converted + * to aDesiredContentType before processing the + * data. This causes a stream converted to be + * inserted into the nsIStreamListener chain. + * This argument can be <code>nullptr</code> if + * the content should be consumed directly as + * aContentType. + * + * @return <code>true</code> if this is a preferred + * content handler for aContentType; + * <code>false<code> otherwise. + */ + boolean isPreferred(in string aContentType, out string aDesiredContentType); + + /** + * When given a uri to dispatch, if the URI is not specified as 'preferred + * content' then the uri loader calls canHandleContent to see if the content + * listener is capable of handling the content. + * + * @param aContentType Content type of the data. + * @param aIsContentPreferred Indicates whether the content should be + * preferred by this listener. + * @param aDesiredContentType Indicates that aContentType must be converted + * to aDesiredContentType before processing the + * data. This causes a stream converted to be + * inserted into the nsIStreamListener chain. + * This argument can be <code>nullptr</code> if + * the content should be consumed directly as + * aContentType. + * + * @return <code>true</code> if the data can be consumed. + * <code>false</code> otherwise. + * + * Note: I really envision canHandleContent as a method implemented + * by the docshell as the implementation is generic to all doc + * shells. The isPreferred decision is a decision made by a top level + * application content listener that sits at the top of the docshell + * hierarchy. + */ + boolean canHandleContent(in string aContentType, + in boolean aIsContentPreferred, + out string aDesiredContentType); + + /** + * The load context associated with a particular content listener. + * The URI Loader stores and accesses this value as needed. + */ + attribute nsISupports loadCookie; + + /** + * The parent content listener if this particular listener is part of a chain + * of content listeners (i.e. a docshell!) + * + * @note If this attribute is set to an object that implements + * nsISupportsWeakReference, the implementation should get the + * nsIWeakReference and hold that. Otherwise, the implementation + * should not refcount this interface; it should assume that a non + * null value is always valid. In that case, the caller is + * responsible for explicitly setting this value back to null if the + * parent content listener is destroyed. + */ + attribute nsIURIContentListener parentContentListener; +}; diff --git a/uriloader/base/nsIURILoader.idl b/uriloader/base/nsIURILoader.idl new file mode 100644 index 0000000000..527092c5a6 --- /dev/null +++ b/uriloader/base/nsIURILoader.idl @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURIContentListener; +interface nsIURI; +interface nsILoadGroup; +interface nsIProgressEventSink; +interface nsIChannel; +interface nsIRequest; +interface nsIStreamListener; +interface nsIInputStream; +interface nsIInterfaceRequestor; + +/** + * The uri dispatcher is responsible for taking uri's, determining + * the content and routing the opened url to the correct content + * handler. + * + * When you encounter a url you want to open, you typically call + * openURI, passing it the content listener for the window the uri is + * originating from. The uri dispatcher opens the url to discover the + * content type. It then gives the content listener first crack at + * handling the content. If it doesn't want it, the dispatcher tries + * to hand it off one of the registered content listeners. This allows + * running applications the chance to jump in and handle the content. + * + * If that also fails, then the uri dispatcher goes to the registry + * looking for the preferred content handler for the content type + * of the uri. The content handler may create an app instance + * or it may hand the contents off to a platform specific plugin + * or helper app. Or it may hand the url off to an OS registered + * application. + */ +[scriptable, uuid(8762c4e7-be35-4958-9b81-a05685bb516d)] +interface nsIURILoader : nsISupports +{ + /** + * @name Flags for opening URIs. + */ + /* @{ */ + /** + * Should the content be displayed in a container that prefers the + * content-type, or will any container do. + */ + const unsigned long IS_CONTENT_PREFERRED = 1 << 0; + /** + * If this flag is set, only the listener of the specified window context will + * be considered for content handling; if it refuses the load, an error will + * be indicated. + */ + const unsigned long DONT_RETARGET = 1 << 1; + /* @} */ + + /** + * As applications such as messenger and the browser are instantiated, + * they register content listener's with the uri dispatcher corresponding + * to content windows within that application. + * + * Note to self: we may want to optimize things a bit more by requiring + * the content types the registered content listener cares about. + * + * @param aContentListener + * The listener to register. This listener must implement + * nsISupportsWeakReference. + * + * @see the nsIURILoader class description + */ + void registerContentListener (in nsIURIContentListener aContentListener); + void unRegisterContentListener (in nsIURIContentListener aContentListener); + + /** + * OpenURI requires the following parameters..... + * @param aChannel + * The channel that should be opened. This must not be asyncOpen'd yet! + * If a loadgroup is set on the channel, it will get replaced with a + * different one. + * @param aFlags + * Combination (bitwise OR) of the flags specified above. 0 indicates + * default handling. + * @param aWindowContext + * If you are running the url from a doc shell or a web shell, this is + * your window context. If you have a content listener you want to + * give first crack to, the uri loader needs to be able to get it + * from the window context. We will also be using the window context + * to get at the progress event sink interface. + * <b>Must not be null!</b> + */ + void openURI(in nsIChannel aChannel, + in unsigned long aFlags, + in nsIInterfaceRequestor aWindowContext); + + /** + * Loads data from a channel. This differs from openURI in that the channel + * may already be opened, and that it returns a stream listener into which the + * caller should pump data. The caller is responsible for opening the channel + * and pumping the channel's data into the returned stream listener. + * + * Note: If the channel already has a loadgroup, it will be replaced with the + * window context's load group, or null if the context doesn't have one. + * + * If the window context's nsIURIContentListener refuses the load immediately + * (e.g. in nsIURIContentListener::onStartURIOpen), this method will return + * NS_ERROR_WONT_HANDLE_CONTENT. At that point, the caller should probably + * cancel the channel if it's already open (this method will not cancel the + * channel). + * + * If flags include DONT_RETARGET, and the content listener refuses the load + * during onStartRequest (e.g. in canHandleContent/isPreferred), then the + * returned stream listener's onStartRequest method will return + * NS_ERROR_WONT_HANDLE_CONTENT. + * + * @param aChannel + * The channel that should be loaded. The channel may already be + * opened. It must not be closed (i.e. this must be called before the + * channel calls onStopRequest on its stream listener). + * @param aFlags + * Combination (bitwise OR) of the flags specified above. 0 indicates + * default handling. + * @param aWindowContext + * If you are running the url from a doc shell or a web shell, this is + * your window context. If you have a content listener you want to + * give first crack to, the uri loader needs to be able to get it + * from the window context. We will also be using the window context + * to get at the progress event sink interface. + * <b>Must not be null!</b> + */ + nsIStreamListener openChannel(in nsIChannel aChannel, + in unsigned long aFlags, + in nsIInterfaceRequestor aWindowContext); + + /** + * Stops an in progress load + */ + void stop(in nsISupports aLoadCookie); +}; diff --git a/uriloader/base/nsIWebProgress.idl b/uriloader/base/nsIWebProgress.idl new file mode 100644 index 0000000000..10d6593381 --- /dev/null +++ b/uriloader/base/nsIWebProgress.idl @@ -0,0 +1,185 @@ +/* -*- Mode: IDL; tab-width: 4; 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 "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIEventTarget; +interface nsIRequest; +interface nsIWebProgressListener; +webidl BrowsingContext; + +/** + * The nsIWebProgress interface is used to add or remove nsIWebProgressListener + * instances to observe the loading of asynchronous requests (usually in the + * context of a DOM window). + * + * nsIWebProgress instances may be arranged in a parent-child configuration, + * corresponding to the parent-child configuration of their respective DOM + * windows. However, in some cases a nsIWebProgress instance may not have an + * associated DOM window. The parent-child relationship of nsIWebProgress + * instances is not made explicit by this interface, but the relationship may + * exist in some implementations. + * + * A nsIWebProgressListener instance receives notifications for the + * nsIWebProgress instance to which it added itself, and it may also receive + * notifications from any nsIWebProgress instances that are children of that + * nsIWebProgress instance. + */ +[scriptable, builtinclass, uuid(c4d64640-b332-4db6-a2a5-e08566000dc9)] +interface nsIWebProgress : nsISupports +{ + /** + * The following flags may be combined to form the aNotifyMask parameter for + * the addProgressListener method. They limit the set of events that are + * delivered to an nsIWebProgressListener instance. + */ + + /** + * These flags indicate the state transistions to observe, corresponding to + * nsIWebProgressListener::onStateChange. + * + * NOTIFY_STATE_REQUEST + * Only receive the onStateChange event if the aStateFlags parameter + * includes nsIWebProgressListener::STATE_IS_REQUEST. + * + * NOTIFY_STATE_DOCUMENT + * Only receive the onStateChange event if the aStateFlags parameter + * includes nsIWebProgressListener::STATE_IS_DOCUMENT. + * + * NOTIFY_STATE_NETWORK + * Only receive the onStateChange event if the aStateFlags parameter + * includes nsIWebProgressListener::STATE_IS_NETWORK. + * + * NOTIFY_STATE_WINDOW + * Only receive the onStateChange event if the aStateFlags parameter + * includes nsIWebProgressListener::STATE_IS_WINDOW. + * + * NOTIFY_STATE_ALL + * Receive all onStateChange events. + */ + const unsigned long NOTIFY_STATE_REQUEST = 0x00000001; + const unsigned long NOTIFY_STATE_DOCUMENT = 0x00000002; + const unsigned long NOTIFY_STATE_NETWORK = 0x00000004; + const unsigned long NOTIFY_STATE_WINDOW = 0x00000008; + const unsigned long NOTIFY_STATE_ALL = 0x0000000f; + + /** + * These flags indicate the other events to observe, corresponding to the + * other four methods defined on nsIWebProgressListener. + * + * NOTIFY_PROGRESS + * Receive onProgressChange events. + * + * NOTIFY_STATUS + * Receive onStatusChange events. + * + * NOTIFY_SECURITY + * Receive onSecurityChange events. + * + * NOTIFY_LOCATION + * Receive onLocationChange events. + * + * NOTIFY_CONTENT_BLOCKING + * Receive onContentBlockingEvent events. + * + * NOTIFY_REFRESH + * Receive onRefreshAttempted events. + * This is defined on nsIWebProgressListener2. + */ + const unsigned long NOTIFY_PROGRESS = 0x00000010; + const unsigned long NOTIFY_STATUS = 0x00000020; + const unsigned long NOTIFY_SECURITY = 0x00000040; + const unsigned long NOTIFY_LOCATION = 0x00000080; + const unsigned long NOTIFY_REFRESH = 0x00000100; + const unsigned long NOTIFY_CONTENT_BLOCKING = 0x00000200; + + /** + * This flag enables all notifications. + */ + const unsigned long NOTIFY_ALL = 0x000003ff; + + /** + * Registers a listener to receive web progress events. + * + * @param aListener + * The listener interface to be called when a progress event occurs. + * This object must also implement nsISupportsWeakReference. + * @param aNotifyMask + * The types of notifications to receive. + * + * @throw NS_ERROR_INVALID_ARG + * Indicates that aListener was either null or that it does not + * support weak references. + * @throw NS_ERROR_FAILURE + * Indicates that aListener was already registered. + */ + void addProgressListener(in nsIWebProgressListener aListener, + in unsigned long aNotifyMask); + + /** + * Removes a previously registered listener of progress events. + * + * @param aListener + * The listener interface previously registered with a call to + * addProgressListener. + * + * @throw NS_ERROR_FAILURE + * Indicates that aListener was not registered. + */ + void removeProgressListener(in nsIWebProgressListener aListener); + + /** + * BrowsingContext associated with this nsIWebProgress instance, or `null` if + * there is no BrowsingContext. + */ + [binaryname(BrowsingContextXPCOM)] + readonly attribute BrowsingContext browsingContext; + + [noscript,notxpcom,nostdcall] BrowsingContext getBrowsingContext(); + + /** + * The DOM window associated with this nsIWebProgress instance. + * + * @throw NS_ERROR_FAILURE + * Indicates that there is no associated DOM window. + */ + readonly attribute mozIDOMWindowProxy DOMWindow; + + /** + * Indicates whether DOMWindow.top == DOMWindow. + */ + readonly attribute boolean isTopLevel; + + /** + * Indicates whether or not a document is currently being loaded + * in the context of this nsIWebProgress instance. + */ + readonly attribute boolean isLoadingDocument; + + /** + * Contains a load type as specified by the load* constants in + * nsIDocShell:LoadCommand. + */ + readonly attribute unsigned long loadType; + + /** + * Main thread event target to which progress updates should be + * dispatched. This typically will be a SchedulerEventTarget + * corresponding to the tab requesting updates. + */ + attribute nsIEventTarget target; + + /** + * The request for the currently loading document. It is null if + * isLoadingDocument is false. + * Note, the request may not be the actual nsIChannel instance used for + * loading, but a dummy RemoteWebProgressRequest. And since redirects are + * hidden from the child processes, this may not reflect the complete + * redirect state of the load. + */ + readonly attribute nsIRequest documentRequest; +}; diff --git a/uriloader/base/nsIWebProgressListener.idl b/uriloader/base/nsIWebProgressListener.idl new file mode 100644 index 0000000000..43fa055eac --- /dev/null +++ b/uriloader/base/nsIWebProgressListener.idl @@ -0,0 +1,597 @@ +/* -*- Mode: IDL; tab-width: 4; 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 "nsISupports.idl" + +interface nsIWebProgress; +interface nsIRequest; +interface nsIURI; + +/** + * The nsIWebProgressListener interface is implemented by clients wishing to + * listen in on the progress associated with the loading of asynchronous + * requests in the context of a nsIWebProgress instance as well as any child + * nsIWebProgress instances. nsIWebProgress.idl describes the parent-child + * relationship of nsIWebProgress instances. + */ +[scriptable, uuid(a9df523b-efe2-421e-9d8e-3d7f807dda4c)] +interface nsIWebProgressListener : nsISupports +{ + /** + * State Transition Flags + * + * These flags indicate the various states that requests may transition + * through as they are being loaded. These flags are mutually exclusive. + * + * For any given request, onStateChange is called once with the STATE_START + * flag, zero or more times with the STATE_TRANSFERRING flag or once with the + * STATE_REDIRECTING flag, and then finally once with the STATE_STOP flag. + * NOTE: For document requests, a second STATE_STOP is generated (see the + * description of STATE_IS_WINDOW for more details). + * + * STATE_START + * This flag indicates the start of a request. This flag is set when a + * request is initiated. The request is complete when onStateChange is + * called for the same request with the STATE_STOP flag set. + * + * STATE_REDIRECTING + * This flag indicates that a request is being redirected. The request + * passed to onStateChange is the request that is being redirected. When a + * redirect occurs, a new request is generated automatically to process the + * new request. Expect a corresponding STATE_START event for the new + * request, and a STATE_STOP for the redirected request. + * + * STATE_TRANSFERRING + * This flag indicates that data for a request is being transferred to an + * end consumer. This flag indicates that the request has been targeted, + * and that the user may start seeing content corresponding to the request. + * + * STATE_NEGOTIATING + * This flag is not used. + * + * STATE_STOP + * This flag indicates the completion of a request. The aStatus parameter + * to onStateChange indicates the final status of the request. + */ + const unsigned long STATE_START = 0x00000001; + const unsigned long STATE_REDIRECTING = 0x00000002; + const unsigned long STATE_TRANSFERRING = 0x00000004; + const unsigned long STATE_NEGOTIATING = 0x00000008; + const unsigned long STATE_STOP = 0x00000010; + + + /** + * State Type Flags + * + * These flags further describe the entity for which the state transition is + * occuring. These flags are NOT mutually exclusive (i.e., an onStateChange + * event may indicate some combination of these flags). + * + * STATE_IS_REQUEST + * This flag indicates that the state transition is for a request, which + * includes but is not limited to document requests. (See below for a + * description of document requests.) Other types of requests, such as + * requests for inline content (e.g., images and stylesheets) are + * considered normal requests. + * + * STATE_IS_DOCUMENT + * This flag indicates that the state transition is for a document request. + * This flag is set in addition to STATE_IS_REQUEST. A document request + * supports the nsIChannel interface and its loadFlags attribute includes + * the nsIChannel::LOAD_DOCUMENT_URI flag. + * + * A document request does not complete until all requests associated with + * the loading of its corresponding document have completed. This includes + * other document requests (e.g., corresponding to HTML <iframe> elements). + * The document corresponding to a document request is available via the + * DOMWindow attribute of onStateChange's aWebProgress parameter. + * + * STATE_IS_NETWORK + * This flag indicates that the state transition corresponds to the start + * or stop of activity in the indicated nsIWebProgress instance. This flag + * is accompanied by either STATE_START or STATE_STOP, and it may be + * combined with other State Type Flags. + * + * Unlike STATE_IS_WINDOW, this flag is only set when activity within the + * nsIWebProgress instance being observed starts or stops. If activity + * only occurs in a child nsIWebProgress instance, then this flag will be + * set to indicate the start and stop of that activity. + * + * For example, in the case of navigation within a single frame of a HTML + * frameset, a nsIWebProgressListener instance attached to the + * nsIWebProgress of the frameset window will receive onStateChange calls + * with the STATE_IS_NETWORK flag set to indicate the start and stop of + * said navigation. In other words, an observer of an outer window can + * determine when activity, that may be constrained to a child window or + * set of child windows, starts and stops. + * + * STATE_IS_WINDOW + * This flag indicates that the state transition corresponds to the start + * or stop of activity in the indicated nsIWebProgress instance. This flag + * is accompanied by either STATE_START or STATE_STOP, and it may be + * combined with other State Type Flags. + * + * This flag is similar to STATE_IS_DOCUMENT. However, when a document + * request completes, two onStateChange calls with STATE_STOP are + * generated. The document request is passed as aRequest to both calls. + * The first has STATE_IS_REQUEST and STATE_IS_DOCUMENT set, and the second + * has the STATE_IS_WINDOW flag set (and possibly the STATE_IS_NETWORK flag + * set as well -- see above for a description of when the STATE_IS_NETWORK + * flag may be set). This second STATE_STOP event may be useful as a way + * to partition the work that occurs when a document request completes. + * + * STATE_IS_REDIRECTED_DOCUMENT + * Same as STATE_IS_DOCUMENT, but sent only after a redirect has occured. + * Introduced in order not to confuse existing code with extra state change + * events. See |nsDocLoader::OnStartRequest| for more info. + */ + const unsigned long STATE_IS_REQUEST = 0x00010000; + const unsigned long STATE_IS_DOCUMENT = 0x00020000; + const unsigned long STATE_IS_NETWORK = 0x00040000; + const unsigned long STATE_IS_WINDOW = 0x00080000; + const unsigned long STATE_IS_REDIRECTED_DOCUMENT = 0x00100000; + + /** + * State Modifier Flags + * + * These flags further describe the transition which is occuring. These + * flags are NOT mutually exclusive (i.e., an onStateChange event may + * indicate some combination of these flags). + * + * STATE_RESTORING + * This flag indicates that the state transition corresponds to the start + * or stop of activity for restoring a previously-rendered presentation. + * As such, there is no actual network activity associated with this + * request, and any modifications made to the document or presentation + * when it was originally loaded will still be present. + */ + const unsigned long STATE_RESTORING = 0x01000000; + + /** + * State Security Flags + * + * These flags describe the security state reported by a call to the + * onSecurityChange method. These flags are mutually exclusive. + * + * STATE_IS_INSECURE + * This flag indicates that the data corresponding to the request + * was received over an insecure channel. + * + * STATE_IS_BROKEN + * This flag indicates an unknown security state. This may mean that the + * request is being loaded as part of a page in which some content was + * received over an insecure channel. + * + * STATE_IS_SECURE + * This flag indicates that the data corresponding to the request was + * received over a secure channel. The degree of security is expressed by + * STATE_SECURE_HIGH, STATE_SECURE_MED, or STATE_SECURE_LOW. + */ + const unsigned long STATE_IS_INSECURE = 0x00000004; + const unsigned long STATE_IS_BROKEN = 0x00000001; + const unsigned long STATE_IS_SECURE = 0x00000002; + + /** + * Mixed active content flags + * + * NOTE: IF YOU ARE ADDING MORE OF THESE FLAGS, MAKE SURE TO EDIT + * nsSecureBrowserUIImpl::CheckForBlockedContent(). + * + * May be set in addition to the State Security Flags, to indicate that + * mixed active content has been encountered. + * + * STATE_BLOCKED_MIXED_ACTIVE_CONTENT + * Mixed active content has been blocked from loading. + * + * STATE_LOADED_MIXED_ACTIVE_CONTENT + * Mixed active content has been loaded. State should be STATE_IS_BROKEN. + */ + const unsigned long STATE_BLOCKED_MIXED_ACTIVE_CONTENT = 0x00000010; + const unsigned long STATE_LOADED_MIXED_ACTIVE_CONTENT = 0x00000020; + + /** + * Mixed display content flags + * + * NOTE: IF YOU ARE ADDING MORE OF THESE FLAGS, MAKE SURE TO EDIT + * nsSecureBrowserUIImpl::CheckForBlockedContent(). + * + * May be set in addition to the State Security Flags, to indicate that + * mixed display content has been encountered. + * + * STATE_BLOCKED_MIXED_DISPLAY_CONTENT + * Mixed display content has been blocked from loading. + * + * STATE_LOADED_MIXED_DISPLAY_CONTENT + * Mixed display content has been loaded. State should be STATE_IS_BROKEN. + */ + const unsigned long STATE_BLOCKED_MIXED_DISPLAY_CONTENT = 0x00000100; + const unsigned long STATE_LOADED_MIXED_DISPLAY_CONTENT = 0x00000200; + + /** + * State bits for EV == Extended Validation == High Assurance + * + * These flags describe the level of identity verification + * in a call to the onSecurityChange method. + * + * STATE_IDENTITY_EV_TOPLEVEL + * The topmost document uses an EV cert. + * NOTE: Available since Gecko 1.9 + */ + + const unsigned long STATE_IDENTITY_EV_TOPLEVEL = 0x00100000; + + /*** + * Associated state flag + * + * This flag is used to if a page is inheriting its security context. + * + * STATE_IDENTITY_ASSOCIATED + * This document doesn't have an identity of its own, and instead + * uses its predecessor's identity. + */ + const unsigned long STATE_IDENTITY_ASSOCIATED = 0x00200000; + + /** + * Broken state flags + * + * These flags describe the reason of the broken state. + * + * STATE_USES_SSL_3 + * The topmost document uses SSL 3.0. + * + * STATE_USES_WEAK_CRYPTO + * The topmost document uses a weak cipher suite such as RC4. + * + * STATE_CERT_USER_OVERRIDDEN + * The user has added a security exception for the site. + */ + const unsigned long STATE_USES_SSL_3 = 0x01000000; + const unsigned long STATE_USES_WEAK_CRYPTO = 0x02000000; + const unsigned long STATE_CERT_USER_OVERRIDDEN = 0x04000000; + + /** + * Content Blocking Event flags + * + * NOTE: IF YOU ARE ADDING MORE OF THESE FLAGS, MAKE SURE TO EDIT + * nsSecureBrowserUIImpl::CheckForBlockedContent() AND UPDATE THE + * CORRESPONDING LIST IN ContentBlockingController.java + * + * These flags describe the reason of cookie jar rejection. + * + * STATE_BLOCKED_TRACKING_CONTENT + * Tracking content has been blocked from loading. + * + * STATE_LOADED_LEVEL_1_TRACKING_CONTENT + * Tracking content from the Disconnect Level 1 list has been loaded. + * + * STATE_LOADED_LEVEL_2_TRACKING_CONTENT + * Tracking content from the Disconnect Level 2 list has been loaded. + * + * STATE_BLOCKED_FINGERPRINTING_CONTENT + * Fingerprinting content has been blocked from loading. + * + * STATE_LOADED_FINGERPRINTING_CONTENT + * Fingerprinting content has been loaded. + * + * STATE_REPLACED_FINGERPRINTING_CONTENT + * Fingerprinting content which should be blocked from loading was replaced with a + * shim. + * + * STATE_ALLOWED_FINGERPRINTING_CONTENT + * Fingerprinting content which should be blocked from loading was allowed. + * + * STATE_BLOCKED_CRYPTOMINING_CONTENT + * Cryptomining content has been blocked from loading. + * + * STATE_LOADED_CRYPTOMINING_CONTENT + * Cryptomining content has been loaded. + * + * STATE_BLOCKED_UNSAFE_CONTENT + * Content which againts SafeBrowsing list has been blocked from loading. + * + * STATE_COOKIES_LOADED + * Performed a storage access check, which usually means something like a + * cookie or a storage item was loaded/stored on the current tab. + * Alternatively this could indicate that something in the current tab + * attempted to communicate with its same-origin counterparts in other + * tabs. + * + * STATE_COOKIES_LOADED_TRACKER + * Similar to STATE_COOKIES_LOADED, but only sent if the subject of the + * action was a third-party tracker when the active cookie policy imposes + * restrictions on such content. + * + * STATE_COOKIES_LOADED_SOCIALTRACKER + * Similar to STATE_COOKIES_LOADED, but only sent if the subject of the + * action was a third-party social tracker when the active cookie policy + * imposes restrictions on such content. + * + * STATE_COOKIES_BLOCKED_BY_PERMISSION + * Rejected for custom site permission. + * + * STATE_COOKIES_BLOCKED_TRACKER + * Rejected because the resource is a tracker and cookie policy doesn't + * allow its loading. + * + * STATE_COOKIES_BLOCKED_SOCIALTRACKER + * Rejected because the resource is a tracker from a social origin and + * cookie policy doesn't allow its loading. + * + * STATE_COOKIES_PARTITIONED_FOREIGN + * Rejected because the resource is a third-party and cookie policy forces + * third-party resources to be partitioned. + * + * STATE_COOKIES_BLOCKED_ALL + * Rejected because cookie policy blocks all cookies. + * + * STATE_COOKIES_BLOCKED_FOREIGN + * Rejected because cookie policy blocks 3rd party cookies. + * + * STATE_BLOCKED_SOCIALTRACKING_CONTENT + * SocialTracking content has been blocked from loading. + * + * STATE_LOADED_SOCIALTRACKING_CONTENT + * SocialTracking content has been loaded. + * + * STATE_REPLACED_TRACKING_CONTENT + * Tracking content which should be blocked from loading was replaced with a + * shim. + * + * STATE_ALLOWED_TRACKING_CONTENT + * Tracking content which should be blocked from loading was allowed. + * + * STATE_BLOCKED_EMAILTRACING_CONTENT + * EmailTracking content has been blocked from loading. + * + * STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT + * EmailTracking content from the Disconnect level 1 has been loaded. + * + * STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT + * EmailTracking content from the Disconnect level 2 has been loaded. + * + * STATE_ALLOWED_CANVAS_FINGERPRINTING + * A potential attempt to fingerprint using the canvas API was observed. + * + * STATE_ALLOWED_FONT_FINGERPRINTING + * A potential attempt to fingerprint by checking the exposed fonts + * was observed. + * + * STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING + * Suspicious fingerprinting activity has been blocked by the fingerprinting + * protection. + */ + const unsigned long STATE_BLOCKED_TRACKING_CONTENT = 0x00001000; + const unsigned long STATE_LOADED_LEVEL_1_TRACKING_CONTENT = 0x00002000; + const unsigned long STATE_LOADED_LEVEL_2_TRACKING_CONTENT = 0x00100000; + const unsigned long STATE_BLOCKED_FINGERPRINTING_CONTENT = 0x00000040; + const unsigned long STATE_LOADED_FINGERPRINTING_CONTENT = 0x00000400; + const unsigned long STATE_REPLACED_FINGERPRINTING_CONTENT = 0x08000000; + const unsigned long STATE_ALLOWED_FINGERPRINTING_CONTENT = 0x00000200; + const unsigned long STATE_BLOCKED_CRYPTOMINING_CONTENT = 0x00000800; + const unsigned long STATE_LOADED_CRYPTOMINING_CONTENT = 0x00200000; + const unsigned long STATE_BLOCKED_UNSAFE_CONTENT = 0x00004000; + const unsigned long STATE_COOKIES_LOADED = 0x00008000; + const unsigned long STATE_COOKIES_LOADED_TRACKER = 0x00040000; + const unsigned long STATE_COOKIES_LOADED_SOCIALTRACKER = 0x00080000; + const unsigned long STATE_COOKIES_BLOCKED_BY_PERMISSION = 0x10000000; + const unsigned long STATE_COOKIES_BLOCKED_TRACKER = 0x20000000; + const unsigned long STATE_COOKIES_BLOCKED_SOCIALTRACKER = 0x01000000; + const unsigned long STATE_COOKIES_BLOCKED_ALL = 0x40000000; + const unsigned long STATE_COOKIES_PARTITIONED_FOREIGN = 0x80000000; + const unsigned long STATE_COOKIES_BLOCKED_FOREIGN = 0x00000080; + const unsigned long STATE_BLOCKED_SOCIALTRACKING_CONTENT = 0x00010000; + const unsigned long STATE_LOADED_SOCIALTRACKING_CONTENT = 0x00020000; + const unsigned long STATE_REPLACED_TRACKING_CONTENT = 0x00000010; + const unsigned long STATE_ALLOWED_TRACKING_CONTENT = 0x00000020; + const unsigned long STATE_BLOCKED_EMAILTRACKING_CONTENT = 0x00400000; + const unsigned long STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT = 0x00800000; + const unsigned long STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT = 0x00000100; + const unsigned long STATE_ALLOWED_CANVAS_FINGERPRINTING = 0x02000000; + const unsigned long STATE_ALLOWED_FONT_FINGERPRINTING = 0x04000000; + const unsigned long STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING = 0x00000004; + + /** + * Flags for HTTPS-Only and HTTPS-First Mode upgrades + * + * STATE_HTTPS_ONLY_MODE_UPGRADED + * When a request has been upgraded by HTTPS-Only Mode + * + * STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED + * When an upgraded request failed. + * + * STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST + * When a request has been upgraded by HTTPS-First Mode + */ + const unsigned long STATE_HTTPS_ONLY_MODE_UPGRADED = 0x00400000; + const unsigned long STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED = 0x00800000; + const unsigned long STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST = 0x08000000; + + /** + * Notification indicating the state has changed for one of the requests + * associated with aWebProgress. + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification + * @param aRequest + * The nsIRequest that has changed state. + * @param aStateFlags + * Flags indicating the new state. This value is a combination of one + * of the State Transition Flags and one or more of the State Type + * Flags defined above. Any undefined bits are reserved for future + * use. + * @param aStatus + * Error status code associated with the state change. This parameter + * should be ignored unless aStateFlags includes the STATE_STOP bit. + * The status code indicates success or failure of the request + * associated with the state change. NOTE: aStatus may be a success + * code even for server generated errors, such as the HTTP 404 error. + * In such cases, the request itself should be queried for extended + * error information (e.g., for HTTP requests see nsIHttpChannel). + */ + void onStateChange(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in unsigned long aStateFlags, + in nsresult aStatus); + + /** + * Notification that the progress has changed for one of the requests + * associated with aWebProgress. Progress totals are reset to zero when all + * requests in aWebProgress complete (corresponding to onStateChange being + * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW + * flags). + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The nsIRequest that has new progress. + * @param aCurSelfProgress + * The current progress for aRequest. + * @param aMaxSelfProgress + * The maximum progress for aRequest. + * @param aCurTotalProgress + * The current progress for all requests associated with aWebProgress. + * @param aMaxTotalProgress + * The total progress for all requests associated with aWebProgress. + * + * NOTE: If any progress value is unknown, or if its value would exceed the + * maximum value of type long, then its value is replaced with -1. + * + * NOTE: If the object also implements nsIWebProgressListener2 and the caller + * knows about that interface, this function will not be called. Instead, + * nsIWebProgressListener2::onProgressChange64 will be called. + */ + void onProgressChange(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in long aCurSelfProgress, + in long aMaxSelfProgress, + in long aCurTotalProgress, + in long aMaxTotalProgress); + + /** + * Flags for onLocationChange + * + * LOCATION_CHANGE_SAME_DOCUMENT + * This flag is on when |aWebProgress| did not load a new document. + * For example, the location change is due to an anchor scroll or a + * pushState/popState/replaceState. + * + * LOCATION_CHANGE_ERROR_PAGE + * This flag is on when |aWebProgress| redirected from the requested + * contents to an internal page to show error status, such as + * <about:neterror>, <about:certerror> and so on. + * + * Generally speaking, |aURI| and |aRequest| are the original data. DOM + * |window.location.href| is also the original location, while + * |document.documentURI| is the redirected location. Sometimes |aURI| is + * <about:blank> and |aRequest| is null when the original data does not + + remain. + * + * |aWebProgress| does NOT set this flag when it did not try to load a new + * document. In this case, it should set LOCATION_CHANGE_SAME_DOCUMENT. + * + * LOCATION_CHANGE_RELOAD + * This flag is on when reloading the current page, either from + * location.reload() or the browser UI. + * + * LOCATION_CHANGE_HASHCHANGE + * This flag is on for same-document location changes where only the URI's + * reference fragment has changed. This flag implies + * LOCATION_CHANGE_SAME_DOCUMENT. + * + * LOCATION_CHANGE_SESSION_STORE + * This flag is on for location changes that are the result of the session + * store updating the URI of aWebProgress without actually navigating. + */ + const unsigned long LOCATION_CHANGE_SAME_DOCUMENT = 0x00000001; + const unsigned long LOCATION_CHANGE_ERROR_PAGE = 0x00000002; + const unsigned long LOCATION_CHANGE_RELOAD = 0x00000004; + const unsigned long LOCATION_CHANGE_HASHCHANGE = 0x00000008; + const unsigned long LOCATION_CHANGE_SESSION_STORE = 0x00000010; + + /** + * Called when the location of the window being watched changes. This is not + * when a load is requested, but rather once it is verified that the load is + * going to occur in the given window. For instance, a load that starts in a + * window might send progress and status messages for the new site, but it + * will not send the onLocationChange until we are sure that we are loading + * this new page here. + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The associated nsIRequest. This may be null in some cases. + * @param aLocation + * The URI of the location that is being loaded. + * @param aFlags + * This is a value which explains the situation or the reason why + * the location has changed. + */ + void onLocationChange(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in nsIURI aLocation, + [optional] in unsigned long aFlags); + + /** + * Notification that the status of a request has changed. The status message + * is intended to be displayed to the user (e.g., in the status bar of the + * browser). + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The nsIRequest that has new status. + * @param aStatus + * This value is not an error code. Instead, it is a numeric value + * that indicates the current status of the request. This interface + * does not define the set of possible status codes. NOTE: Some + * status values are defined by nsITransport and nsISocketTransport. + * @param aMessage + * Localized text corresponding to aStatus. + */ + void onStatusChange(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in nsresult aStatus, + in wstring aMessage); + + /** + * Notification called for security progress. This method will be called on + * security transitions (eg HTTP -> HTTPS, HTTPS -> HTTP, FOO -> HTTPS) and + * after document load completion. It might also be called if an error + * occurs during network loading. + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The nsIRequest that has new security state. + * @param aState + * A value composed of the Security State Flags and the Security + * Strength Flags listed above. Any undefined bits are reserved for + * future use. + * + * NOTE: These notifications will only occur if a security package is + * installed. + */ + void onSecurityChange(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in unsigned long aState); + + /** + * Notification called for content blocking events. This method will be + * called when content gets allowed/blocked for various reasons per the + * Content Blocking rules. + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The nsIRequest that has new security state. + * @param aEvent + * A value composed of the Content Blocking Event Flags listed above. + * Any undefined bits are reserved for future use. + */ + void onContentBlockingEvent(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in unsigned long aEvent); +}; diff --git a/uriloader/base/nsIWebProgressListener2.idl b/uriloader/base/nsIWebProgressListener2.idl new file mode 100644 index 0000000000..a40d9538fb --- /dev/null +++ b/uriloader/base/nsIWebProgressListener2.idl @@ -0,0 +1,69 @@ +/* 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 "nsIWebProgressListener.idl" + +/** + * An extended version of nsIWebProgressListener. + */ +[scriptable, uuid(dde39de0-e4e0-11da-8ad9-0800200c9a66)] +interface nsIWebProgressListener2 : nsIWebProgressListener { + /** + * Notification that the progress has changed for one of the requests + * associated with aWebProgress. Progress totals are reset to zero when all + * requests in aWebProgress complete (corresponding to onStateChange being + * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW + * flags). + * + * This function is identical to nsIWebProgressListener::onProgressChange, + * except that this function supports 64-bit values. + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRequest + * The nsIRequest that has new progress. + * @param aCurSelfProgress + * The current progress for aRequest. + * @param aMaxSelfProgress + * The maximum progress for aRequest. + * @param aCurTotalProgress + * The current progress for all requests associated with aWebProgress. + * @param aMaxTotalProgress + * The total progress for all requests associated with aWebProgress. + * + * NOTE: If any progress value is unknown, then its value is replaced with -1. + * + * @see nsIWebProgressListener2::onProgressChange64 + */ + void onProgressChange64(in nsIWebProgress aWebProgress, + in nsIRequest aRequest, + in long long aCurSelfProgress, + in long long aMaxSelfProgress, + in long long aCurTotalProgress, + in long long aMaxTotalProgress); + + /** + * Notification that a refresh or redirect has been requested in aWebProgress + * For example, via a <meta http-equiv="refresh"> or an HTTP Refresh: header + * + * @param aWebProgress + * The nsIWebProgress instance that fired the notification. + * @param aRefreshURI + * The new URI that aWebProgress has requested redirecting to. + * @param aMillis + * The delay (in milliseconds) before refresh. + * @param aSameURI + * True if aWebProgress is requesting a refresh of the + * current URI. + * False if aWebProgress is requesting a redirection to + * a different URI. + * + * @return True if the refresh may proceed. + * False if the refresh should be aborted. + */ + boolean onRefreshAttempted(in nsIWebProgress aWebProgress, + in nsIURI aRefreshURI, + in unsigned long aMillis, + in boolean aSameURI); +}; diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp new file mode 100644 index 0000000000..0acd451c9d --- /dev/null +++ b/uriloader/base/nsURILoader.cpp @@ -0,0 +1,846 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* 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 "nsURILoader.h" +#include "nsComponentManagerUtils.h" +#include "nsIURIContentListener.h" +#include "nsIContentHandler.h" +#include "nsILoadGroup.h" +#include "nsIDocumentLoader.h" +#include "nsIStreamListener.h" +#include "nsIURL.h" +#include "nsIChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInputStream.h" +#include "nsIStreamConverterService.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIHttpChannel.h" +#include "netCore.h" +#include "nsCRT.h" +#include "nsIDocShell.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIChildChannel.h" +#include "nsExternalHelperAppService.h" + +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsError.h" + +#include "nsICategoryManager.h" +#include "nsCExternalHandlerService.h" + +#include "nsNetCID.h" + +#include "nsMimeTypes.h" + +#include "nsDocLoader.h" +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_general.h" +#include "nsContentUtils.h" + +mozilla::LazyLogModule nsURILoader::mLog("URILoader"); + +#define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args) +#define LOG_ERROR(args) \ + MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args) +#define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug) + +NS_IMPL_ADDREF(nsDocumentOpenInfo) +NS_IMPL_RELEASE(nsDocumentOpenInfo) + +NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) +NS_INTERFACE_MAP_END + +nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, + uint32_t aFlags, nsURILoader* aURILoader) + : m_originalContext(aWindowContext), + mFlags(aFlags), + mURILoader(aURILoader), + mDataConversionDepthLimit( + mozilla::StaticPrefs:: + general_document_open_conversion_depth_limit()) {} + +nsDocumentOpenInfo::nsDocumentOpenInfo(uint32_t aFlags, + bool aAllowListenerConversions) + : m_originalContext(nullptr), + mFlags(aFlags), + mURILoader(nullptr), + mDataConversionDepthLimit( + mozilla::StaticPrefs::general_document_open_conversion_depth_limit()), + mAllowListenerConversions(aAllowListenerConversions) {} + +nsDocumentOpenInfo::~nsDocumentOpenInfo() {} + +nsresult nsDocumentOpenInfo::Prepare() { + LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this)); + + nsresult rv; + + // ask our window context if it has a uri content listener... + m_contentListener = do_GetInterface(m_originalContext, &rv); + return rv; +} + +NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest* request) { + LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this)); + MOZ_ASSERT(request); + if (!request) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + + // + // Deal with "special" HTTP responses: + // + // - In the case of a 204 (No Content) or 205 (Reset Content) response, do + // not try to find a content handler. Return NS_BINDING_ABORTED to cancel + // the request. This has the effect of ensuring that the DocLoader does + // not try to interpret this as a real request. + // + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv)); + + if (NS_SUCCEEDED(rv)) { + uint32_t responseCode = 0; + + rv = httpChannel->GetResponseStatus(&responseCode); + + if (NS_FAILED(rv)) { + LOG_ERROR((" Failed to get HTTP response status")); + + // behave as in the canceled case + return NS_OK; + } + + LOG((" HTTP response status: %d", responseCode)); + + if (204 == responseCode || 205 == responseCode) { + return NS_BINDING_ABORTED; + } + } + + // + // Make sure that the transaction has succeeded, so far... + // + nsresult status; + + rv = request->GetStatus(&status); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!"); + if (NS_FAILED(rv)) return rv; + + if (NS_FAILED(status)) { + LOG_ERROR((" Request failed, status: 0x%08" PRIX32, + static_cast<uint32_t>(status))); + + // + // The transaction has already reported an error - so it will be torn + // down. Therefore, it is not necessary to return an error code... + // + return NS_OK; + } + + rv = DispatchContent(request); + + LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32, + m_targetStreamListener.get(), static_cast<uint32_t>(rv))); + + NS_ASSERTION( + NS_SUCCEEDED(rv) || !m_targetStreamListener, + "Must not have an m_targetStreamListener with a failure return!"); + + NS_ENSURE_SUCCESS(rv, rv); + + if (m_targetStreamListener) + rv = m_targetStreamListener->OnStartRequest(request); + + LOG((" OnStartRequest returning: 0x%08" PRIX32, static_cast<uint32_t>(rv))); + + return rv; +} + +NS_IMETHODIMP +nsDocumentOpenInfo::CheckListenerChain() { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(m_targetStreamListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + LOG( + ("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv " + "%" PRIx32, + this, (NS_SUCCEEDED(rv) ? "success" : "failure"), + (nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv))); + return rv; +} + +NS_IMETHODIMP +nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + // if we have retarged to the end stream listener, then forward the call.... + // otherwise, don't do anything + + nsresult rv = NS_OK; + + if (m_targetStreamListener) + rv = m_targetStreamListener->OnDataAvailable(request, inStr, sourceOffset, + count); + return rv; +} + +NS_IMETHODIMP +nsDocumentOpenInfo::OnDataFinished(nsresult aStatus) { + if (!m_targetStreamListener) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(m_targetStreamListener); + if (retargetableListener) { + return retargetableListener->OnDataFinished(aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this)); + + if (m_targetStreamListener) { + nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener); + + // If this is a multipart stream, we could get another + // OnStartRequest after this... reset state. + m_targetStreamListener = nullptr; + mContentType.Truncate(); + listener->OnStopRequest(request, aStatus); + } + mUsedContentHandler = false; + + // Remember... + // In the case of multiplexed streams (such as multipart/x-mixed-replace) + // these stream listener methods could be called again :-) + // + return NS_OK; +} + +nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) { + LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, + mContentType.get())); + + MOZ_ASSERT(!m_targetStreamListener, + "Why do we already have a target stream listener?"); + + nsresult rv; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) { + LOG_ERROR((" Request is not a channel. Bailing.")); + return NS_ERROR_FAILURE; + } + + constexpr auto anyType = "*/*"_ns; + if (mContentType.IsEmpty() || mContentType == anyType) { + rv = aChannel->GetContentType(mContentType); + if (NS_FAILED(rv)) return rv; + LOG((" Got type from channel: '%s'", mContentType.get())); + } + + bool isGuessFromExt = + mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT); + if (isGuessFromExt) { + // Reset to application/octet-stream for now; no one other than the + // external helper app service should see APPLICATION_GUESS_FROM_EXT. + mContentType = APPLICATION_OCTET_STREAM; + aChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM)); + } + + // Check whether the data should be forced to be handled externally. This + // could happen because the Content-Disposition header is set so, or, in the + // future, because the user has specified external handling for the MIME + // type. + bool forceExternalHandling = false; + uint32_t disposition; + rv = aChannel->GetContentDisposition(&disposition); + + if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) { + forceExternalHandling = true; + } + + LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no")); + + if (forceExternalHandling && + mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline()) { + // Check if this is a PDF which should be opened internally. We also handle + // octet-streams that look like they might be PDFs based on their extension. + bool isPDF = mContentType.LowerCaseEqualsASCII(APPLICATION_PDF); + if (!isPDF && + (mContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) || + mContentType.IsEmpty())) { + nsAutoString flname; + aChannel->GetContentDispositionFilename(flname); + isPDF = StringEndsWith(flname, u".pdf"_ns); + if (!isPDF) { + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); + if (url) { + nsAutoCString ext; + url->GetFileExtension(ext); + isPDF = ext.EqualsLiteral("pdf"); + } + } + } + + // For a PDF, check if the preference is set that forces attachments to be + // opened inline. If so, treat it as a non-attachment by clearing + // 'forceExternalHandling' again. This allows it open a PDF directly + // instead of downloading it first. It may still end up being handled by + // a helper app depending anyway on the later checks. + if (isPDF) { + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + + nsCOMPtr<nsIMIMEInfo> mimeInfo; + + nsCOMPtr<nsIMIMEService> mimeSvc( + do_GetService(NS_MIMESERVICE_CONTRACTID)); + NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE); + mimeSvc->GetFromTypeAndExtension(nsLiteralCString(APPLICATION_PDF), ""_ns, + getter_AddRefs(mimeInfo)); + + if (mimeInfo) { + int32_t action = nsIMIMEInfo::saveToDisk; + mimeInfo->GetPreferredAction(&action); + + bool alwaysAsk = true; + mimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); + forceExternalHandling = + alwaysAsk || action != nsIMIMEInfo::handleInternally; + } + } + } + + if (!forceExternalHandling) { + // + // First step: See whether m_contentListener wants to handle this + // content type. + // + if (TryDefaultContentListener(aChannel)) { + LOG((" Success! Our default listener likes this type")); + // All done here + return NS_OK; + } + + // If we aren't allowed to try other listeners, just skip through to + // trying to convert the data. + if (!(mFlags & nsIURILoader::DONT_RETARGET)) { + // + // Second step: See whether some other registered listener wants + // to handle this content type. + // + int32_t count = mURILoader ? mURILoader->m_listeners.Count() : 0; + nsCOMPtr<nsIURIContentListener> listener; + for (int32_t i = 0; i < count; i++) { + listener = do_QueryReferent(mURILoader->m_listeners[i]); + if (listener) { + if (TryContentListener(listener, aChannel)) { + LOG((" Found listener registered on the URILoader")); + return NS_OK; + } + } else { + // remove from the listener list, reset i and update count + mURILoader->m_listeners.RemoveObjectAt(i--); + --count; + } + } + + // + // Third step: Try to find an nsIContentHandler for our type. + // + nsAutoCString handlerContractID(NS_CONTENT_HANDLER_CONTRACTID_PREFIX); + handlerContractID += mContentType; + + nsCOMPtr<nsIContentHandler> contentHandler = + do_CreateInstance(handlerContractID.get()); + if (contentHandler) { + LOG((" Content handler found")); + // Note that m_originalContext can be nullptr when running this in + // the parent process on behalf on a docshell in the content process, + // and in that case we only support content handlers that don't need + // the context. + rv = contentHandler->HandleContent(mContentType.get(), + m_originalContext, request); + // XXXbz returning an error code to represent handling the + // content is just bizarre! + if (rv != NS_ERROR_WONT_HANDLE_CONTENT) { + if (NS_FAILED(rv)) { + // The content handler has unexpectedly failed. Cancel the request + // just in case the handler didn't... + LOG((" Content handler failed. Aborting load")); + request->Cancel(rv); + } else { + LOG((" Content handler taking over load")); + mUsedContentHandler = true; + } + + return rv; + } + } + } else { + LOG( + (" DONT_RETARGET flag set, so skipped over random other content " + "listeners and content handlers")); + } + + // + // Fourth step: If no listener prefers this type, see if any stream + // converters exist to transform this content type into + // some other. + // + // Don't do this if the server sent us a MIME type of "*/*" because they saw + // it in our Accept header and got confused. + // XXXbz have to be careful here; may end up in some sort of bizarre + // infinite decoding loop. + if (mContentType != anyType) { + rv = TryStreamConversion(aChannel); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + } + } + + NS_ASSERTION(!m_targetStreamListener, + "If we found a listener, why are we not using it?"); + + if (mFlags & nsIURILoader::DONT_RETARGET) { + LOG( + (" External handling forced or (listener not interested and no " + "stream converter exists), and retargeting disallowed -> aborting")); + return NS_ERROR_WONT_HANDLE_CONTENT; + } + + // Before dispatching to the external helper app service, check for an HTTP + // error page. If we got one, we don't want to handle it with a helper app, + // really. + // The WPT a-download-click-404.html requires us to silently handle this + // without displaying an error page, so we just return early here. + // See bug 1604308 for discussion around what the ideal behaviour is. + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request)); + if (httpChannel) { + bool requestSucceeded; + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); + if (NS_FAILED(rv) || !requestSucceeded) { + return NS_OK; + } + } + + // Fifth step: + // + // All attempts to dispatch this content have failed. Just pass it off to + // the helper app service. + // + + nsCOMPtr<nsIExternalHelperAppService> helperAppService = + do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); + if (helperAppService) { + LOG((" Passing load off to helper app service")); + + // Set these flags to indicate that the channel has been targeted and that + // we are not using the original consumer. + nsLoadFlags loadFlags = 0; + request->GetLoadFlags(&loadFlags); + request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI | + nsIChannel::LOAD_TARGETED); + + if (isGuessFromExt || mContentType.IsEmpty()) { + mContentType = APPLICATION_GUESS_FROM_EXT; + aChannel->SetContentType(nsLiteralCString(APPLICATION_GUESS_FROM_EXT)); + } + + rv = TryExternalHelperApp(helperAppService, aChannel); + if (NS_FAILED(rv)) { + request->SetLoadFlags(loadFlags); + m_targetStreamListener = nullptr; + } + } + + NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv), + "There is no way we should be successful at this point without " + "a m_targetStreamListener"); + return rv; +} + +nsresult nsDocumentOpenInfo::TryExternalHelperApp( + nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel) { + return aHelperAppService->DoContent(mContentType, aChannel, m_originalContext, + false, nullptr, + getter_AddRefs(m_targetStreamListener)); +} + +nsresult nsDocumentOpenInfo::ConvertData(nsIRequest* request, + nsIURIContentListener* aListener, + const nsACString& aSrcContentType, + const nsACString& aOutContentType) { + LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this, + PromiseFlatCString(aSrcContentType).get(), + PromiseFlatCString(aOutContentType).get())); + + if (mDataConversionDepthLimit == 0) { + LOG( + ("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion " + "limit!", + this)); + // This will fall back to external helper app handling. + return NS_ERROR_ABORT; + } + + MOZ_ASSERT(aSrcContentType != aOutContentType, + "ConvertData called when the two types are the same!"); + + nsresult rv = NS_OK; + + nsCOMPtr<nsIStreamConverterService> StreamConvService = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + LOG((" Got converter service")); + + // When applying stream decoders, it is necessary to "insert" an + // intermediate nsDocumentOpenInfo instance to handle the targeting of + // the "final" stream or streams. + // + // For certain content types (ie. multi-part/x-mixed-replace) the input + // stream is split up into multiple destination streams. This + // intermediate instance is used to target these "decoded" streams... + // + RefPtr<nsDocumentOpenInfo> nextLink = Clone(); + + LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get())); + + // Decrease the conversion recursion limit by one to prevent infinite loops. + nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1; + + // Make sure nextLink starts with the contentListener that said it wanted + // the results of this decode. + nextLink->m_contentListener = aListener; + // Also make sure it has to look for a stream listener to pump data into. + nextLink->m_targetStreamListener = nullptr; + + // Make sure that nextLink treats the data as aOutContentType when + // dispatching; that way even if the stream converters don't change the type + // on the channel we will still do the right thing. If aOutContentType is + // */*, that's OK -- that will just indicate to nextLink that it should get + // the type off the channel. + nextLink->mContentType = aOutContentType; + + // The following call sets m_targetStreamListener to the input end of the + // stream converter and sets the output end of the stream converter to + // nextLink. As we pump data into m_targetStreamListener the stream + // converter will convert it and pass the converted data to nextLink. + return StreamConvService->AsyncConvertData( + PromiseFlatCString(aSrcContentType).get(), + PromiseFlatCString(aOutContentType).get(), nextLink, request, + getter_AddRefs(m_targetStreamListener)); +} + +nsresult nsDocumentOpenInfo::TryStreamConversion(nsIChannel* aChannel) { + constexpr auto anyType = "*/*"_ns; + + // A empty content type should be treated like the unknown content type. + nsCString srcContentType(mContentType); + if (srcContentType.IsEmpty()) { + srcContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); + } + + nsresult rv = + ConvertData(aChannel, m_contentListener, srcContentType, anyType); + if (NS_FAILED(rv)) { + m_targetStreamListener = nullptr; + } else if (m_targetStreamListener) { + // We found a converter for this MIME type. We'll just pump data into + // it and let the downstream nsDocumentOpenInfo handle things. + LOG((" Converter taking over now")); + } + return rv; +} + +bool nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener, + nsIChannel* aChannel) { + LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x", this, + mFlags)); + + MOZ_ASSERT(aListener, "Must have a non-null listener"); + MOZ_ASSERT(aChannel, "Must have a channel"); + + bool listenerWantsContent = false; + nsCString typeToUse; + + if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) { + aListener->IsPreferred(mContentType.get(), getter_Copies(typeToUse), + &listenerWantsContent); + } else { + aListener->CanHandleContent(mContentType.get(), false, + getter_Copies(typeToUse), + &listenerWantsContent); + } + if (!listenerWantsContent) { + LOG((" Listener is not interested")); + return false; + } + + if (!typeToUse.IsEmpty() && typeToUse != mContentType) { + // Need to do a conversion here. + + nsresult rv = NS_ERROR_NOT_AVAILABLE; + if (mAllowListenerConversions) { + rv = ConvertData(aChannel, aListener, mContentType, typeToUse); + } + + if (NS_FAILED(rv)) { + // No conversion path -- we don't want this listener, if we got one + m_targetStreamListener = nullptr; + } + + LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no")); + + // m_targetStreamListener is now the input end of the converter, and we can + // just pump the data in there, if it exists. If it does not, we need to + // try other nsIURIContentListeners. + return m_targetStreamListener != nullptr; + } + + // At this point, aListener wants data of type mContentType. Let 'em have + // it. But first, if we are retargeting, set an appropriate flag on the + // channel + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + + // Set this flag to indicate that the channel has been targeted at a final + // consumer. This load flag is tested in nsDocLoader::OnProgress. + nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED; + + nsCOMPtr<nsIURIContentListener> originalListener = + do_GetInterface(m_originalContext); + if (originalListener != aListener) { + newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI; + } + aChannel->SetLoadFlags(loadFlags | newLoadFlags); + + bool abort = false; + bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0; + nsresult rv = + aListener->DoContent(mContentType, isPreferred, aChannel, + getter_AddRefs(m_targetStreamListener), &abort); + + if (NS_FAILED(rv)) { + LOG_ERROR((" DoContent failed")); + + // Unset the RETARGETED_DOCUMENT_URI flag if we set it... + aChannel->SetLoadFlags(loadFlags); + m_targetStreamListener = nullptr; + return false; + } + + if (abort) { + // Nothing else to do here -- aListener is handling it all. Make + // sure m_targetStreamListener is null so we don't do anything + // after this point. + LOG((" Listener has aborted the load")); + m_targetStreamListener = nullptr; + } + + NS_ASSERTION(abort || m_targetStreamListener, + "DoContent returned no listener?"); + + // aListener is handling the load from this point on. + return true; +} + +bool nsDocumentOpenInfo::TryDefaultContentListener(nsIChannel* aChannel) { + if (m_contentListener) { + return TryContentListener(m_contentListener, aChannel); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of nsURILoader +/////////////////////////////////////////////////////////////////////////////////////////////// + +nsURILoader::nsURILoader() {} + +nsURILoader::~nsURILoader() {} + +NS_IMPL_ADDREF(nsURILoader) +NS_IMPL_RELEASE(nsURILoader) + +NS_INTERFACE_MAP_BEGIN(nsURILoader) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader) + NS_INTERFACE_MAP_ENTRY(nsIURILoader) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP nsURILoader::RegisterContentListener( + nsIURIContentListener* aContentListener) { + nsresult rv = NS_OK; + + nsWeakPtr weakListener = do_GetWeakReference(aContentListener); + NS_ASSERTION(weakListener, + "your URIContentListener must support weak refs!\n"); + + if (weakListener) m_listeners.AppendObject(weakListener); + + return rv; +} + +NS_IMETHODIMP nsURILoader::UnRegisterContentListener( + nsIURIContentListener* aContentListener) { + nsWeakPtr weakListener = do_GetWeakReference(aContentListener); + if (weakListener) m_listeners.RemoveObject(weakListener); + + return NS_OK; +} + +NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext) { + NS_ENSURE_ARG_POINTER(channel); + + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsURILoader::OpenURI for %s", spec.get())); + } + + nsCOMPtr<nsIStreamListener> loader; + nsresult rv = OpenChannel(channel, aFlags, aWindowContext, false, + getter_AddRefs(loader)); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_WONT_HANDLE_CONTENT) { + // Not really an error, from this method's point of view + return NS_OK; + } + } + + // This method is not complete. Eventually, we should first go + // to the content listener and ask them for a protocol handler... + // if they don't give us one, we need to go to the registry and get + // the preferred protocol handler. + + // But for now, I'm going to let necko do the work for us.... + rv = channel->AsyncOpen(loader); + + // no content from this load - that's OK. + if (rv == NS_ERROR_NO_CONTENT) { + LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing")); + return NS_OK; + } + return rv; +} + +nsresult nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext, + bool aChannelIsOpen, + nsIStreamListener** aListener) { + NS_ASSERTION(channel, "Trying to open a null channel!"); + NS_ASSERTION(aWindowContext, "Window context must not be null"); + + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsURILoader::OpenChannel for %s", spec.get())); + } + + // we need to create a DocumentOpenInfo object which will go ahead and open + // the url and discover the content type.... + RefPtr<nsDocumentOpenInfo> loader = + new nsDocumentOpenInfo(aWindowContext, aFlags, this); + + // Set the correct loadgroup on the channel + nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext)); + + if (!loadGroup) { + // XXXbz This context is violating what we'd like to be the new uriloader + // api.... Set up a nsDocLoader to handle the loadgroup for this context. + // This really needs to go away! + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext)); + if (listener) { + nsCOMPtr<nsISupports> cookie; + listener->GetLoadCookie(getter_AddRefs(cookie)); + if (!cookie) { + RefPtr<nsDocLoader> newDocLoader = new nsDocLoader(); + nsresult rv = newDocLoader->Init(); + if (NS_FAILED(rv)) return rv; + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader); + if (NS_FAILED(rv)) return rv; + cookie = nsDocLoader::GetAsSupports(newDocLoader); + listener->SetLoadCookie(cookie); + } + loadGroup = do_GetInterface(cookie); + } + } + + // If the channel is pending, then we need to remove it from its current + // loadgroup + nsCOMPtr<nsILoadGroup> oldGroup; + channel->GetLoadGroup(getter_AddRefs(oldGroup)); + if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) { + // It is important to add the channel to the new group before + // removing it from the old one, so that the load isn't considered + // done as soon as the request is removed. + loadGroup->AddRequest(channel, nullptr); + + if (oldGroup) { + oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED); + } + } + + channel->SetLoadGroup(loadGroup); + + // prepare the loader for receiving data + nsresult rv = loader->Prepare(); + if (NS_SUCCEEDED(rv)) NS_ADDREF(*aListener = loader); + return rv; +} + +NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext, + nsIStreamListener** aListener) { + bool pending; + if (NS_FAILED(channel->IsPending(&pending))) { + pending = false; + } + + return OpenChannel(channel, aFlags, aWindowContext, pending, aListener); +} + +NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie) { + nsresult rv; + nsCOMPtr<nsIDocumentLoader> docLoader; + + NS_ENSURE_ARG_POINTER(aLoadCookie); + + docLoader = do_GetInterface(aLoadCookie, &rv); + if (docLoader) { + rv = docLoader->Stop(); + } + return rv; +} diff --git a/uriloader/base/nsURILoader.h b/uriloader/base/nsURILoader.h new file mode 100644 index 0000000000..68fd398520 --- /dev/null +++ b/uriloader/base/nsURILoader.h @@ -0,0 +1,218 @@ +/* -*- 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/. */ + +#ifndef nsURILoader_h__ +#define nsURILoader_h__ + +#include "nsCURILoader.h" +#include "nsISupportsUtils.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsString.h" +#include "nsIWeakReference.h" +#include "mozilla/Attributes.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIExternalHelperAppService.h" + +#include "mozilla/Logging.h" + +class nsDocumentOpenInfo; + +class nsURILoader final : public nsIURILoader { + public: + NS_DECL_NSIURILOADER + NS_DECL_ISUPPORTS + + nsURILoader(); + + protected: + ~nsURILoader(); + + /** + * Equivalent to nsIURILoader::openChannel, but allows specifying whether the + * channel is opened already. + */ + [[nodiscard]] nsresult OpenChannel(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext, + bool aChannelOpen, + nsIStreamListener** aListener); + + /** + * we shouldn't need to have an owning ref count on registered + * content listeners because they are supposed to unregister themselves + * when they go away. This array stores weak references + */ + nsCOMArray<nsIWeakReference> m_listeners; + + /** + * Logging. The module is called "URILoader" + */ + static mozilla::LazyLogModule mLog; + + friend class nsDocumentOpenInfo; +}; + +/** + * The nsDocumentOpenInfo contains the state required when a single + * document is being opened in order to discover the content type... + * Each instance remains alive until its target URL has been loaded + * (or aborted). + */ +class nsDocumentOpenInfo : public nsIThreadRetargetableStreamListener { + public: + // Real constructor + // aFlags is a combination of the flags on nsIURILoader + nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, uint32_t aFlags, + nsURILoader* aURILoader); + nsDocumentOpenInfo(uint32_t aFlags, bool aAllowListenerConversions); + + NS_DECL_THREADSAFE_ISUPPORTS + + /** + * Prepares this object for receiving data. The stream + * listener methods of this class must not be called before calling this + * method. + */ + nsresult Prepare(); + + // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to + // take the data off our hands. + nsresult DispatchContent(nsIRequest* request); + + // Call this if we need to insert a stream converter from aSrcContentType to + // aOutContentType into the StreamListener chain. DO NOT call it if the two + // types are the same, since no conversion is needed in that case. + nsresult ConvertData(nsIRequest* request, nsIURIContentListener* aListener, + const nsACString& aSrcContentType, + const nsACString& aOutContentType); + + /** + * Function to attempt to use aListener to handle the load. If + * true is returned, nothing else needs to be done; if false + * is returned, then a different way of handling the load should be + * tried. + */ + bool TryContentListener(nsIURIContentListener* aListener, + nsIChannel* aChannel); + + /** + * Virtual helper functions for content that we expect to be + * overriden when running in the parent process on behalf of + * a content process docshell. + * We also expect nsIStreamListener functions to be overriden + * to add functionality. + */ + + /** + * Attempt to create a steam converter converting from the + * current mContentType into something else. + * Sets m_targetStreamListener if it succeeds. + */ + virtual nsresult TryStreamConversion(nsIChannel* aChannel); + + /** + * Attempt to use the default content listener as our stream + * listener. + * Sets m_targetStreamListener if it succeeds. + */ + virtual bool TryDefaultContentListener(nsIChannel* aChannel); + + /** + * Attempt to pass aChannel onto the external helper app service. + * Sets m_targetStreamListener if it succeeds. + */ + virtual nsresult TryExternalHelperApp( + nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel); + + /** + * Create another nsDocumentOpenInfo like this one, so that we can chain + * them together when we use a stream converter and don't know what the + * converted content type is until the converter outputs OnStartRequest. + */ + virtual nsDocumentOpenInfo* Clone() { + return new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader); + } + + // nsIRequestObserver methods: + NS_DECL_NSIREQUESTOBSERVER + + // nsIStreamListener methods: + NS_DECL_NSISTREAMLISTENER + + // nsIThreadRetargetableStreamListener + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + protected: + virtual ~nsDocumentOpenInfo(); + + protected: + /** + * The first content listener to try dispatching data to. Typically + * the listener associated with the entity that originated the load. + * This can be nullptr when running in the parent process for a content + * process docshell. + */ + nsCOMPtr<nsIURIContentListener> m_contentListener; + + /** + * The stream listener to forward nsIStreamListener notifications + * to. This is set once the load is dispatched. + */ + nsCOMPtr<nsIStreamListener> m_targetStreamListener; + + /** + * A pointer to the entity that originated the load. We depend on getting + * things like nsIURIContentListeners, nsIDOMWindows, etc off of it. + * This can be nullptr when running in the parent process for a content + * process docshell. + */ + nsCOMPtr<nsIInterfaceRequestor> m_originalContext; + + /** + * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent + * (also determines whether we use CanHandleContent or IsPreferred). + * DONT_RETARGET means that we will only try m_originalContext, no other + * listeners. + */ + uint32_t mFlags; + + /** + * The type of the data we will be trying to dispatch. + */ + nsCString mContentType; + + /** + * Reference to the URILoader service so we can access its list of + * nsIURIContentListeners. + * This can be nullptr when running in the parent process for a content + * process docshell. + */ + RefPtr<nsURILoader> mURILoader; + + /** + * Limit of data conversion depth to prevent infinite conversion loops + */ + uint32_t mDataConversionDepthLimit; + + /** + * Set to true if OnStartRequest handles the content using an + * nsIContentHandler, and the content is consumed despite + * m_targetStreamListener being nullptr. + */ + bool mUsedContentHandler = false; + + /** + * True if we allow nsIURIContentListeners to return a requested + * input typeToUse, and attempt to create a matching stream converter. + * This is false when running in the parent process for a content process + * docshell + */ + bool mAllowListenerConversions = true; +}; + +#endif /* nsURILoader_h__ */ |