uriloader/base/nsDocLoader.cpp
author Emilio Cobos Álvarez <emilio@crisal.io>
Fri, 08 Feb 2019 13:09:00 +0200
changeset 515842 bfdf856d1c9a2d717f33bddf1dabad8776167805
parent 515808 f8aadf41dcd052cf78272d82fd262094a8e26567
permissions -rw-r--r--
Back out bugs 1519918, 1524246 and 1523181 from beta. a=lizzard

/* -*- 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/Logging.h"
#include "mozilla/IntegerPrintfMacros.h"

#include "nsDocLoader.h"
#include "nsCURILoader.h"
#include "nsNetUtil.h"
#include "nsIHttpChannel.h"
#include "nsIWebNavigation.h"
#include "nsIWebProgressListener2.h"

#include "nsIServiceManager.h"
#include "nsString.h"

#include "nsIURL.h"
#include "nsCOMPtr.h"
#include "nscore.h"
#include "nsIWeakReferenceUtils.h"
#include "nsAutoPtr.h"
#include "nsQueryObject.h"

#include "nsIDOMWindow.h"

#include "nsIStringBundle.h"
#include "nsIScriptSecurityManager.h"

#include "nsITransport.h"
#include "nsISocketTransport.h"
#include "nsIDocShell.h"
#include "mozilla/dom/Document.h"
#include "nsPresContext.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsILoadURIDelegate.h"
#include "nsIBrowserDOMWindow.h"

using mozilla::DebugOnly;
using mozilla::LogLevel;

//
// 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()
    : 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) {
  ClearInternalProgress();

  MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: created.\n", this));
}

nsresult nsDocLoader::SetDocLoaderParent(nsDocLoader* aParent) {
  mParent = aParent;
  return NS_OK;
}

nsresult nsDocLoader::Init() {
  nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this);
  if (NS_FAILED(rv)) return rv;

  MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
          ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get()));

  return NS_OK;
}

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(nsISecurityEventSink)
  NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(nsDocLoader)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION(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) {
  nsresult rv;
  nsCOMPtr<nsIDocumentLoader> docLoaderService =
      do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
  NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);

  return rootDocLoader->AddChildLoader(aDocLoader);
}

NS_IMETHODIMP
nsDocLoader::Stop(void) {
  nsresult rv = NS_OK;

  MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
          ("DocLoader:%p: Stop() called\n", this));

  NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, Stop, ());

  if (mLoadGroup) rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);

  // Don't report that we're flushing layout so IsBusy returns false after a
  // Stop call.
  mIsFlushingLayout = false;

  // Clear out mChildrenInOnload.  We want to make sure to fire our
  // onload at this point, and there's no issue with mChildrenInOnload
  // after this, since mDocumentRequest will be null after the
  // DocLoaderIsEmpty() call.
  mChildrenInOnload.Clear();

  // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
  // etc, as needed.  We could be getting into here from a subframe onload, in
  // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
  // happened yet, Canceling the loadgroup did nothing (because it was already
  // empty), and we're about to start a new load (which is what triggered this
  // Stop() call).

  // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
  // we wouldn't need the call here....

  NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
  DocLoaderIsEmpty(false);

  return rv;
}

bool nsDocLoader::IsBusy() {
  nsresult rv;

  //
  // A document loader is busy if either:
  //
  //   1. One of its children is in the middle of an onload handler.  Note that
  //      the handler may have already removed this child from mChildList!
  //   2. It is currently loading a document and either has parts of it still
  //      loading, or has a busy child docloader.
  //   3. It's currently flushing layout in DocLoaderIsEmpty().
  //

  if (mChildrenInOnload.Count() || mIsFlushingLayout) {
    return true;
  }

  /* Is this document loader busy? */
  if (!mIsLoadingDocument) {
    return false;
  }

  bool busy;
  rv = mLoadGroup->IsPending(&busy);
  if (NS_FAILED(rv)) {
    return false;
  }
  if (busy) {
    return true;
  }

  /* check its child document loaders... */
  uint32_t count = mChildList.Length();
  for (uint32_t i = 0; i < count; i++) {
    nsIDocumentLoader* loader = ChildAt(i);

    // This is a safe cast, because we only put nsDocLoader objects into the
    // array
    if (loader && static_cast<nsDocLoader*>(loader)->IsBusy()) return true;
  }

  return false;
}

NS_IMETHODIMP
nsDocLoader::GetContainer(nsISupports** aResult) {
  NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));

  return NS_OK;
}

NS_IMETHODIMP
nsDocLoader::GetLoadGroup(nsILoadGroup** aResult) {
  nsresult rv = NS_OK;

  if (nullptr == aResult) {
    rv = NS_ERROR_NULL_POINTER;
  } else {
    *aResult = mLoadGroup;
    NS_IF_ADDREF(*aResult);
  }
  return rv;
}

void nsDocLoader::Destroy() {
  Stop();

  // Remove the document loader from the parent list of loaders...
  if (mParent) {
    DebugOnly<nsresult> rv = mParent->RemoveChildLoader(this);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed");
  }

  // Release all the information about network requests...
  ClearRequestInfoHash();

  mListenerInfoList.Clear();
  mListenerInfoList.Compact();

  mDocumentRequest = nullptr;

  if (mLoadGroup) mLoadGroup->SetGroupObserver(nullptr);

  DestroyChildren();
}

void nsDocLoader::DestroyChildren() {
  uint32_t count = mChildList.Length();
  // if the doc loader still has children...we need to enumerate the
  // children and make them null out their back ptr to the parent doc
  // loader
  for (uint32_t i = 0; i < count; i++) {
    nsIDocumentLoader* loader = ChildAt(i);

    if (loader) {
      // This is a safe cast, as we only put nsDocLoader objects into the
      // array
      DebugOnly<nsresult> rv =
          static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed");
    }
  }
  mChildList.Clear();
}

NS_IMETHODIMP
nsDocLoader::OnStartRequest(nsIRequest* request, nsISupports* aCtxt) {
  // called each time a request is added to the group.

  if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
    nsAutoCString name;
    request->GetName(name);

    uint32_t count = 0;
    if (mLoadGroup) mLoadGroup->GetActiveCount(&count);

    MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
            ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u "
             "active URLs",
             this, request, name.get(), (mIsLoadingDocument ? "true" : "false"),
             count));
  }

  bool bJustStartedLoading = false;

  nsLoadFlags loadFlags = 0;
  request->GetLoadFlags(&loadFlags);

  if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
    bJustStartedLoading = true;
    mIsLoadingDocument = true;
    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) {
      // If we have a document request channel, and this is not a redirect, we
      // must abort it and replace it with the new one.
      if (!(loadFlags & nsIChannel::LOAD_REPLACE) && mDocumentRequest) {
        mDocumentRequest->Cancel(NS_ERROR_ABORT);
        mDocumentRequest = nullptr;
      }

      // This request is associated with the entire document...
      mDocumentRequest = request;
      mLoadGroup->SetDefaultLoadRequest(request);

      // Only fire the start document load notification for the first
      // document URI...  Do not fire it again for redirections
      //
      if (bJustStartedLoading) {
        // Update the progress status state
        mProgressStateFlags = nsIWebProgressListener::STATE_START;

        // Fire the start document load notification
        doStartDocumentLoad();
        return NS_OK;
      }
    }
  }

  NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
               "mDocumentRequest MUST be set for the duration of a page load!");

  // This is the only way to catch document request start event after a redirect
  // has occured without changing inherited Firefox behaviour significantly.
  // Problem description:
  // The combination of |STATE_START + STATE_IS_DOCUMENT| is only sent for
  // initial request (see |doStartDocumentLoad| call above).
  // And |STATE_REDIRECTING + STATE_IS_DOCUMENT| is sent with old channel, which
  // makes it impossible to filter by destination URL (see
  // |AsyncOnChannelRedirect| implementation).
  // Fixing any of those bugs may cause unpredictable consequences in any part
  // of the browser, so we just add a custom flag for this exact situation.
  int32_t extraFlags = 0;
  if (mIsLoadingDocument && !bJustStartedLoading &&
      (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) &&
      (loadFlags & nsIChannel::LOAD_REPLACE)) {
    extraFlags = nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
  }
  doStartURLLoad(request, extraFlags);

  return NS_OK;
}

NS_IMETHODIMP
nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt,
                           nsresult aStatus) {
  nsresult rv = NS_OK;

  if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
    nsAutoCString name;
    aRequest->GetName(name);

    uint32_t count = 0;
    if (mLoadGroup) mLoadGroup->GetActiveCount(&count);

    MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
            ("DocLoader:%p: OnStopRequest[%p](%s) status=%" PRIx32
             " mIsLoadingDocument=%s, %u active URLs",
             this, aRequest, name.get(), static_cast<uint32_t>(aStatus),
             (mIsLoadingDocument ? "true" : "false"), count));
  }

  bool bFireTransferring = false;

  //
  // Set the Maximum progress to the same value as the current progress.
  // Since the URI has finished loading, all the data is there.  Also,
  // this will allow a more accurate estimation of the max progress (in case
  // the old value was unknown ie. -1)
  //
  nsRequestInfo* info = GetRequestInfo(aRequest);
  if (info) {
    // Null out mLastStatus now so we don't find it when looking for
    // status from now on.  This destroys the nsStatusInfo and hence
    // removes it from our list.
    info->mLastStatus = nullptr;

    int64_t oldMax = info->mMaxProgress;

    info->mMaxProgress = info->mCurrentProgress;

    //
    // If a request whose content-length was previously unknown has just
    // finished loading, then use this new data to try to calculate a
    // mMaxSelfProgress...
    //
    if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
      mMaxSelfProgress = CalculateMaxProgress();
    }

    // As we know the total progress of this request now, save it to be part
    // of CalculateMaxProgress() result. We need to remove the info from the
    // hash, see bug 480713.
    mCompletedTotalProgress += info->mMaxProgress;

    //
    // Determine whether a STATE_TRANSFERRING notification should be
    // 'synthesized'.
    //
    // If nsRequestInfo::mMaxProgress (as stored in oldMax) and
    // nsRequestInfo::mCurrentProgress are both 0, then the
    // STATE_TRANSFERRING notification has not been fired yet...
    //
    if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
      nsCOMPtr<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)) {
          bFireTransferring = true;
        }
        //
        // If the request failed (for any reason other than being
        // redirected or retargeted), the TRANSFERRING notification can
        // still be fired if a HTTP connection was established to a server.
        //
        else if (aStatus != NS_BINDING_REDIRECTED &&
                 aStatus != NS_BINDING_RETARGETED) {
          //
          // Only if the load has been targeted (see bug 268483)...
          //
          uint32_t lf;
          channel->GetLoadFlags(&lf);
          if (lf & nsIChannel::LOAD_TARGETED) {
            nsCOMPtr<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...
                //
                bFireTransferring = true;
              }
            }
          }
        }
      }
    }
  }

  if (bFireTransferring) {
    // Send a STATE_TRANSFERRING notification for the request.
    int32_t flags;

    flags = nsIWebProgressListener::STATE_TRANSFERRING |
            nsIWebProgressListener::STATE_IS_REQUEST;
    //
    // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
    //
    if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
      mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;

      // Send STATE_TRANSFERRING for the document too...
      flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
    }

    FireOnStateChange(this, aRequest, flags, NS_OK);
  }

  //
  // Fire the OnStateChange(...) notification for stop request
  //
  doStopURLLoad(aRequest, aStatus);

  // Clear this request out of the hash to avoid bypass of FireOnStateChange
  // when address of the request is reused.
  RemoveRequestInfo(aRequest);

  //
  // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a
  // load.  This will handle removing the request from our hashtable as needed.
  //
  if (mIsLoadingDocument) {
    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) {
  if (mIsLoadingDocument) {
    /* 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");
    NS_ASSERTION(mDocumentRequest, "No Document Request!");

    // The load group for this DocumentLoader is idle.  Flush if we need to.
    if (aFlushLayout && !mDontFlushLayout) {
      nsCOMPtr<mozilla::dom::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
        nsPresContext* presContext = doc->GetPresContext();
        if (presContext && presContext->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 if the flushing above re-entered this
    // method.
    if (!IsBusy() && 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;
      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 DocLoaderIsEmpty() 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);

        if (parent) {
          parent->ChildDoneWithOnload(this);
        }
      }
    }
  }
}

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::GetDOMWindow(mozIDOMWindowProxy** aResult) {
  return CallGetInterface(this, aResult);
}

NS_IMETHODIMP
nsDocLoader::GetDOMWindowID(uint64_t* aResult) {
  *aResult = 0;

  nsCOMPtr<mozIDOMWindowProxy> window;
  nsresult rv = GetDOMWindow(getter_AddRefs(window));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
  NS_ENSURE_STATE(piwindow);

  *aResult = piwindow->WindowID();
  return NS_OK;
}

NS_IMETHODIMP
nsDocLoader::GetInnerDOMWindowID(uint64_t* aResult) {
  *aResult = 0;

  nsCOMPtr<mozIDOMWindowProxy> window;
  nsresult rv = GetDOMWindow(getter_AddRefs(window));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(window);
  NS_ENSURE_STATE(outer);

  nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
  if (!inner) {
    // If we don't have an inner window, return 0.
    return NS_OK;
  }

  *aResult = inner->WindowID();
  return NS_OK;
}

NS_IMETHODIMP
nsDocLoader::GetIsTopLevel(bool* aResult) {
  *aResult = false;

  nsCOMPtr<mozIDOMWindowProxy> window;
  GetDOMWindow(getter_AddRefs(window));
  if (window) {
    nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
    NS_ENSURE_STATE(piwindow);

    nsCOMPtr<nsPIDOMWindowOuter> topWindow = piwindow->GetTop();
    *aResult = piwindow == topWindow;
  }

  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, nsISupports* ctxt,
                                      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, nsISupports* ctxt,
                                    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::services::GetStringBundleService();
    if (!sbs) return NS_ERROR_FAILURE;
    nsAutoString msg;
    nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg, msg);
    if (NS_FAILED(rv)) return rv;

    // Keep around the message. In case a request finishes, we need to make sure
    // to send the status message of another request to our user to that we
    // don't display, for example, "Transferring" messages for requests that are
    // already done.
    if (info) {
      if (!info->mLastStatus) {
        info->mLastStatus = new 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);
    }
    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", this,
               listener.get()));
      listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags););

  // Pass the notification up to the parent...
  if (mParent) {
    mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags);
  }
}

void nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress,
                                     nsIRequest* aRequest, nsresult aStatus,
                                     const char16_t* aMessage) {
  NOTIFY_LISTENERS(
      nsIWebProgress::NOTIFY_STATUS,
      listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage););

  // Pass the notification up to the parent...
  if (mParent) {
    mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
  }
}

bool nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress, nsIURI* aURI,
                                   int32_t aDelay, bool aSameURI) {
  /*
   * Returns true if the refresh may proceed,
   * false if the refresh should be blocked.
   */
  bool allowRefresh = true;

  NOTIFY_LISTENERS(
      nsIWebProgress::NOTIFY_REFRESH,
      nsCOMPtr<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 (aFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
                nsIChannelEventSink::REDIRECT_PERMANENT)) {
    nsCOMPtr<nsIDocShell> docShell =
        do_QueryInterface(static_cast<nsIRequestObserver*>(this));

    nsCOMPtr<nsILoadURIDelegate> delegate;
    if (docShell) {
      docShell->GetLoadURIDelegate(getter_AddRefs(delegate));
    }

    nsCOMPtr<nsIURI> newURI;
    if (delegate) {
      // No point in getting the URI if we don't have a LoadURIDelegate.
      aNewChannel->GetURI(getter_AddRefs(newURI));
    }

    if (newURI) {
      const int where = nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
      bool loadURIHandled = false;
      nsresult rv = delegate->LoadURI(
          newURI, where, nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
          /* triggering principal */ nullptr, &loadURIHandled);
      if (NS_SUCCEEDED(rv) && loadURIHandled) {
        cb->OnRedirectVerifyCallback(NS_OK);
        return NS_OK;
      }
    }
  }

  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;
}

/*
 * Implementation of nsISecurityEventSink method...
 */

NS_IMETHODIMP 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);
  }
  return NS_OK;
}

NS_IMETHODIMP nsDocLoader::OnContentBlockingEvent(nsISupports* aContext,
                                                  uint32_t aEvent) {
  //
  // Fire progress notifications out to any registered nsIWebProgressListeners.
  //

  nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
  nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(this);

  NOTIFY_LISTENERS(
      nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
      listener->OnContentBlockingEvent(webProgress, request, aEvent););

  // Pass the notification up to the parent...
  if (mParent) {
    mParent->OnContentBlockingEvent(aContext, aEvent);
  }
  return NS_OK;
}

/*
 * 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, nsDocLoader, 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, nsDocLoader,
                                           AdjustPriority, (aDelta));

  return NS_OK;
}

#if 0
void nsDocLoader::DumpChannelInfo()
{
  nsChannelInfo *info;
  int32_t i, count;
  int32_t current=0, max=0;


  printf("==== DocLoader=%x\n", this);

  count = mChannelInfoList.Count();
  for(i=0; 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 */