summaryrefslogtreecommitdiffstats
path: root/uriloader/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /uriloader/base
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'uriloader/base')
-rw-r--r--uriloader/base/moz.build43
-rw-r--r--uriloader/base/nsCURILoader.idl18
-rw-r--r--uriloader/base/nsDocLoader.cpp1612
-rw-r--r--uriloader/base/nsDocLoader.h390
-rw-r--r--uriloader/base/nsIContentHandler.idl35
-rw-r--r--uriloader/base/nsIDocumentLoader.idl35
-rw-r--r--uriloader/base/nsITransfer.idl156
-rw-r--r--uriloader/base/nsIURIContentListener.idl123
-rw-r--r--uriloader/base/nsIURILoader.idl139
-rw-r--r--uriloader/base/nsIWebProgress.idl185
-rw-r--r--uriloader/base/nsIWebProgressListener.idl568
-rw-r--r--uriloader/base/nsIWebProgressListener2.idl69
-rw-r--r--uriloader/base/nsURILoader.cpp832
-rw-r--r--uriloader/base/nsURILoader.h219
14 files changed, 4424 insertions, 0 deletions
diff --git a/uriloader/base/moz.build b/uriloader/base/moz.build
new file mode 100644
index 0000000000..36282f6f14
--- /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", "Document 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..e1e46ccdce
--- /dev/null
+++ b/uriloader/base/nsDocLoader.cpp
@@ -0,0 +1,1612 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nspr.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Components.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Logging.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/PresShell.h"
+
+#include "nsDocLoader.h"
+#include "nsDocShell.h"
+#include "nsLoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsIHttpChannel.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgressListener2.h"
+
+#include "nsString.h"
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsQueryObject.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindow.h"
+
+#include "nsIStringBundle.h"
+
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocGroup.h"
+#include "nsPresContext.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/ThrottledEventQueue.h"
+using namespace mozilla;
+using mozilla::DebugOnly;
+using mozilla::eLoad;
+using mozilla::EventDispatcher;
+using mozilla::LogLevel;
+using mozilla::WidgetEvent;
+using mozilla::dom::BrowserChild;
+using mozilla::dom::BrowsingContext;
+using mozilla::dom::Document;
+
+//
+// Log module for nsIDocumentLoader logging...
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=DocLoader:5
+// set MOZ_LOG_FILE=debug.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file 'debug.log'.
+//
+mozilla::LazyLogModule gDocLoaderLog("DocLoader");
+
+#if defined(DEBUG)
+void GetURIStringFromRequest(nsIRequest* request, nsACString& name) {
+ if (request)
+ request->GetName(name);
+ else
+ name.AssignLiteral("???");
+}
+#endif /* DEBUG */
+
+void nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry,
+ const void* key) {
+ // Initialize the entry with placement new
+ new (entry) nsRequestInfo(key);
+}
+
+void nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
+ PLDHashEntryHdr* entry) {
+ nsRequestInfo* info = static_cast<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),
+ mTreatAsBackgroundLoad(false),
+ mHasFakeOnLoadDispatched(false),
+ mIsReadyToHandlePostMessage(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::TreatAsBackgroundLoad() { return mTreatAsBackgroundLoad; }
+
+void nsDocLoader::SetBackgroundLoadIframe() { mTreatAsBackgroundLoad = true; }
+
+bool nsDocLoader::IsBusy() {
+ nsresult rv;
+
+ //
+ // A document loader is busy if either:
+ //
+ // 1. One of its children is in the middle of an onload handler. Note that
+ // the handler may have already removed this child from mChildList!
+ // 2. It is currently loading a document and either has parts of it still
+ // loading, or has a busy child docloader.
+ // 3. It's currently flushing layout in DocLoaderIsEmpty().
+ //
+
+ 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);
+
+ // If 'dom.cross_origin_iframes_loaded_in_background' is set, the parent
+ // document treats cross domain iframes as background loading frame
+ if (loader && static_cast<nsDocLoader*>(loader)->TreatAsBackgroundLoad()) {
+ continue;
+ }
+ // 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...
+ nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this));
+ if (doc) {
+ doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE,
+ /* updateTimingInformation = */ false);
+ doc->StopDocumentLoad();
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow();
+ if (window && !doc->SkipLoadEventAfterClose()) {
+ if (!mozilla::dom::DocGroup::TryToLoadIframesInBackground() ||
+ (mozilla::dom::DocGroup::TryToLoadIframesInBackground() &&
+ !HasFakeOnLoadDispatched())) {
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Firing load event for document.open\n",
+ this));
+
+ // This is a very cut-down version of
+ // nsDocumentViewer::LoadComplete that doesn't do various things
+ // that are not relevant here because this wasn't an actual
+ // navigation.
+ WidgetEvent event(true, eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ // Dispatching to |window|, but using |document| as the target,
+ // per spec.
+ event.mTarget = doc;
+ nsEventStatus unused = nsEventStatus_eIgnore;
+ doc->SetLoadEventFiring(true);
+ EventDispatcher::Dispatch(window, nullptr, &event, nullptr,
+ &unused);
+ doc->SetLoadEventFiring(false);
+
+ // Now unsuppress painting on the presshell, if we
+ // haven't done that yet.
+ RefPtr<PresShell> 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->GetParent()->IsInProcess()) {
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
+ mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
+ dom::EmbedderElementEventType::NoEvent);
+ }
+ }
+}
+
+void nsDocLoader::doStartDocumentLoad(void) {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(mDocumentRequest, buffer);
+ MOZ_LOG(
+ gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
+ "\tURI: %s \n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ // Fire an OnStatus(...) notification STATE_START. This indicates
+ // that the document represented by mDocumentRequest has started to
+ // load...
+ FireOnStateChange(this, mDocumentRequest,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+}
+
+void nsDocLoader::doStartURLLoad(nsIRequest* request, int32_t aExtraFlags) {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
+ "\tURI: %s\n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ FireOnStateChange(this, request,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST | aExtraFlags,
+ NS_OK);
+}
+
+void nsDocLoader::doStopURLLoad(nsIRequest* request, nsresult aStatus) {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
+ "\tURI: %s status=%" PRIx32 "\n",
+ this, buffer.get(), static_cast<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<mozIDOMWindowProxy> window;
+ nsresult rv = GetDOMWindow(getter_AddRefs(window));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
+ NS_ENSURE_STATE(global);
+
+ nsCOMPtr<nsIEventTarget> target =
+ global->EventTargetFor(mozilla::TaskCategory::Other);
+ target.forget(aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::SetTarget(nsIEventTarget* aTarget) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int64_t nsDocLoader::GetMaxTotalProgress() {
+ int64_t newMaxTotal = 0;
+
+ uint32_t count = mChildList.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ int64_t individualProgress = 0;
+ nsIDocumentLoader* docloader = ChildAt(i);
+ if (docloader) {
+ // Cast is safe since all children are nsDocLoader too
+ individualProgress = ((nsDocLoader*)docloader)->GetMaxTotalProgress();
+ }
+ if (individualProgress < int64_t(0)) // if one of the elements doesn't know
+ // it's size then none of them do
+ {
+ newMaxTotal = int64_t(-1);
+ break;
+ } else
+ newMaxTotal += individualProgress;
+ }
+
+ int64_t progress = -1;
+ if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
+ progress = newMaxTotal + mMaxSelfProgress;
+
+ return progress;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// The following section contains support for nsIProgressEventSink which is used
+// to pass progress and status between the actual request and the doc loader.
+// The doc loader then turns around and makes the right web progress calls based
+// on this information.
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest* aRequest, int64_t aProgress,
+ int64_t aProgressMax) {
+ int64_t progressDelta = 0;
+
+ //
+ // Update the RequestInfo entry with the new progress data
+ //
+ if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
+ // Update info->mCurrentProgress before we call FireOnStateChange,
+ // since that can make the "info" pointer invalid.
+ int64_t oldCurrentProgress = info->mCurrentProgress;
+ progressDelta = aProgress - oldCurrentProgress;
+ info->mCurrentProgress = aProgress;
+
+ // suppress sending STATE_TRANSFERRING if this is upload progress (see bug
+ // 240053)
+ if (!info->mUploading && (int64_t(0) == oldCurrentProgress) &&
+ (int64_t(0) == info->mMaxProgress)) {
+ //
+ // If we receive an OnProgress event from a toplevel channel that the URI
+ // Loader has not yet targeted, then we must suppress the event. This is
+ // necessary to ensure that webprogresslisteners do not get confused when
+ // the channel is finally targeted. See bug 257308.
+ //
+ nsLoadFlags lf = 0;
+ aRequest->GetLoadFlags(&lf);
+ if ((lf & nsIChannel::LOAD_DOCUMENT_URI) &&
+ !(lf & nsIChannel::LOAD_TARGETED)) {
+ MOZ_LOG(
+ gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p Ignoring OnProgress while load is not targeted\n",
+ this));
+ return NS_OK;
+ }
+
+ //
+ // This is the first progress notification for the entry. If
+ // (aMaxProgress != -1) then the content-length of the data is known,
+ // so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
+ // that the content-length is no longer known.
+ //
+ if (aProgressMax != -1) {
+ mMaxSelfProgress += aProgressMax;
+ info->mMaxProgress = aProgressMax;
+ } else {
+ mMaxSelfProgress = int64_t(-1);
+ info->mMaxProgress = int64_t(-1);
+ }
+
+ // Send a STATE_TRANSFERRING notification for the request.
+ int32_t flags;
+
+ flags = nsIWebProgressListener::STATE_TRANSFERRING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ //
+ // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
+ //
+ if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
+ mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
+
+ // Send STATE_TRANSFERRING for the document too...
+ flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+ }
+
+ FireOnStateChange(this, aRequest, flags, NS_OK);
+ }
+
+ // Update our overall current progress count.
+ mCurrentSelfProgress += progressDelta;
+ }
+ //
+ // The request is not part of the load group, so ignore its progress
+ // information...
+ //
+ else {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(aRequest, buffer);
+ MOZ_LOG(
+ gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p OOPS - No Request Info for: %s\n", this, buffer.get()));
+#endif /* DEBUG */
+
+ return NS_OK;
+ }
+
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
+ mCurrentTotalProgress, mMaxTotalProgress);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aStatusArg) {
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ if (aStatus != NS_OK) {
+ // Remember the current status for this request
+ nsRequestInfo* info;
+ info = GetRequestInfo(aRequest);
+ if (info) {
+ bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
+ aStatus == NS_NET_STATUS_SENDING_TO);
+ // If switching from uploading to downloading (or vice versa), then we
+ // need to reset our progress counts. This is designed with HTTP form
+ // submission in mind, where an upload is performed followed by download
+ // of possibly several documents.
+ if (info->mUploading != uploading) {
+ mCurrentSelfProgress = mMaxSelfProgress = 0;
+ mCurrentTotalProgress = mMaxTotalProgress = 0;
+ mCompletedTotalProgress = 0;
+ info->mUploading = uploading;
+ info->mCurrentProgress = 0;
+ info->mMaxProgress = 0;
+ }
+ }
+
+ nsCOMPtr<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..e828cb8a11
--- /dev/null
+++ b/uriloader/base/nsDocLoader.h
@@ -0,0 +1,390 @@
+/* -*- 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; }
+
+ bool TreatAsBackgroundLoad();
+
+ void SetFakeOnLoadDispatched() { mHasFakeOnLoadDispatched = true; };
+
+ bool HasFakeOnLoadDispatched() { return mHasFakeOnLoadDispatched; };
+
+ void ResetToFirstLoad() {
+ mHasFakeOnLoadDispatched = false;
+ mIsReadyToHandlePostMessage = false;
+ mTreatAsBackgroundLoad = false;
+ };
+
+ 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;
+
+ bool mTreatAsBackgroundLoad;
+
+ private:
+ bool mHasFakeOnLoadDispatched;
+
+ bool mIsReadyToHandlePostMessage;
+ /**
+ * 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..54c59cd396
--- /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,
+ [optional] 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..1af549a7fc
--- /dev/null
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -0,0 +1,568 @@
+/* -*- 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;
+
+ /**
+ * 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.
+ */
+ 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;
+
+ /**
+ * Flag for HTTPS-Only 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.
+ */
+ const unsigned long STATE_HTTPS_ONLY_MODE_UPGRADED = 0x00400000;
+ const unsigned long STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED = 0x00800000;
+
+ /**
+ * 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..df286e5a73
--- /dev/null
+++ b/uriloader/base/nsURILoader.cpp
@@ -0,0 +1,832 @@
+/* -*- 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::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..2dcba44f76
--- /dev/null
+++ b/uriloader/base/nsURILoader.h
@@ -0,0 +1,219 @@
+/* -*- 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 nsIStreamListener,
+ 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__ */