/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsDocShell.h"

#include <algorithm>

#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Casting.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ChromeUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/dom/ScreenOrientation.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StartupTimeline.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "Navigator.h"
#include "URIUtils.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/TabGroup.h"

#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMElement.h"

#include "nsArray.h"
#include "nsArrayUtils.h"
#include "nsICaptivePortalService.h"
#include "nsIDOMStorage.h"
#include "nsIContentViewer.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsCURILoader.h"
#include "nsDocShellCID.h"
#include "nsDOMCID.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "nsRect.h"
#include "prenv.h"
#include "nsIDOMWindow.h"
#include "nsIGlobalObject.h"
#include "nsIViewSourceChannel.h"
#include "nsIWebBrowserChrome.h"
#include "nsPoint.h"
#include "nsIObserverService.h"
#include "nsIPrompt.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsIChannelEventSink.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScrollableFrame.h"
#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
#include "nsISeekableStream.h"
#include "nsAutoPtr.h"
#include "nsQueryObject.h"
#include "nsIWritablePropertyBag2.h"
#include "nsIAppShell.h"
#include "nsWidgetsCID.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsIScriptChannel.h"
#include "nsITimedChannel.h"
#include "nsIPrivacyTransitionObserver.h"
#include "nsIReflowObserver.h"
#include "nsIScrollObserver.h"
#include "nsIDocShellTreeItem.h"
#include "nsIChannel.h"
#include "IHistory.h"
#include "nsViewSourceHandler.h"
#include "nsWhitespaceTokenizer.h"
#include "nsICookieService.h"
#include "nsIConsoleReportCollector.h"
#include "nsObjectLoadingContent.h"

// we want to explore making the document own the load group
// so we can associate the document URI with the load group.
// until this point, we have an evil hack:
#include "nsIHttpChannelInternal.h"
#include "nsPILoadGroupInternal.h"

// Local Includes
#include "nsDocShellLoadInfo.h"
#include "nsCDefaultURIFixup.h"
#include "nsDocShellEnumerator.h"
#include "nsSHistory.h"
#include "nsDocShellEditorData.h"
#include "GeckoProfiler.h"
#include "timeline/JavascriptTimelineMarker.h"

// Helper Classes
#include "nsError.h"
#include "nsEscape.h"

// Interfaces Needed
#include "nsIFormPOSTActionChannel.h"
#include "nsIUploadChannel.h"
#include "nsIUploadChannel2.h"
#include "nsIWebProgress.h"
#include "nsILayoutHistoryState.h"
#include "nsITimer.h"
#include "nsISHistoryInternal.h"
#include "nsIPrincipal.h"
#include "nsNullPrincipal.h"
#include "nsISHEntry.h"
#include "nsIWindowWatcher.h"
#include "nsIPromptFactory.h"
#include "nsITransportSecurityInfo.h"
#include "nsINode.h"
#include "nsINSSErrorsService.h"
#include "nsIApplicationCacheChannel.h"
#include "nsIApplicationCacheContainer.h"
#include "nsStreamUtils.h"
#include "nsIController.h"
#include "nsPICommandUpdater.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIWebBrowserChrome3.h"
#include "nsITabChild.h"
#include "nsISiteSecurityService.h"
#include "nsStructuredCloneContainer.h"
#include "nsIStructuredCloneContainer.h"
#ifdef MOZ_PLACES
#include "nsIFaviconService.h"
#include "mozIPlacesPendingOperation.h"
#include "mozIAsyncFavicons.h"
#endif
#include "nsINetworkPredictor.h"

// Editor-related
#include "nsIEditingSession.h"

#include "nsPIDOMWindow.h"
#include "nsGlobalWindow.h"
#include "nsPIWindowRoot.h"
#include "nsICachingChannel.h"
#include "nsIMultiPartChannel.h"
#include "nsIWyciwygChannel.h"

// For reporting errors with the console service.
// These can go away if error reporting is propagated up past nsDocShell.
#include "nsIScriptError.h"

// used to dispatch urls to default protocol handlers
#include "nsCExternalHandlerService.h"
#include "nsIExternalProtocolService.h"

#include "nsFocusManager.h"

#include "nsITextToSubURI.h"

#include "nsIJARChannel.h"

#include "mozilla/Logging.h"

#include "nsISelectionDisplay.h"

#include "nsIGlobalHistory2.h"

#include "nsIFrame.h"
#include "nsSubDocumentFrame.h"

// for embedding
#include "nsIWebBrowserChromeFocus.h"

#if NS_PRINT_PREVIEW
#include "nsIDocumentViewerPrint.h"
#include "nsIWebBrowserPrint.h"
#endif

#include "nsContentUtils.h"
#include "nsIContentSecurityPolicy.h"
#include "nsILoadInfo.h"
#include "nsSandboxFlags.h"
#include "nsXULAppAPI.h"
#include "nsDOMNavigationTiming.h"
#include "nsISecurityUITelemetry.h"
#include "nsIAppsService.h"
#include "nsDSURIContentListener.h"
#include "nsDocShellLoadTypes.h"
#include "nsDocShellTransferableHooks.h"
#include "nsICommandManager.h"
#include "nsIDOMNode.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIHttpChannel.h"
#include "nsIIDNService.h"
#include "nsIInputStreamChannel.h"
#include "nsINestedURI.h"
#include "nsISHContainer.h"
#include "nsISHistory.h"
#include "nsISecureBrowserUI.h"
#include "nsISocketProvider.h"
#include "nsIStringBundle.h"
#include "nsIURIFixup.h"
#include "nsIURILoader.h"
#include "nsIURL.h"
#include "nsIWebBrowserFind.h"
#include "nsIWidget.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/PerformanceNavigation.h"
#include "mozilla/dom/ScriptSettings.h"

#ifdef MOZ_TOOLKIT_SEARCH
#include "nsIBrowserSearchService.h"
#endif

#include "mozIThirdPartyUtil.h"

static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);

#if defined(DEBUG_bryner) || defined(DEBUG_chb)
//#define DEBUG_DOCSHELL_FOCUS
#define DEBUG_PAGE_CACHE
#endif

#ifdef XP_WIN
#include <process.h>
#define getpid _getpid
#else
#include <unistd.h> // for getpid()
#endif

using namespace mozilla;
using namespace mozilla::dom;
using mozilla::dom::workers::ServiceWorkerManager;

// True means sUseErrorPages has been added to
// preferences var cache.
static bool gAddedPreferencesVarCache = false;

bool nsDocShell::sUseErrorPages = false;

// Number of documents currently loading
static int32_t gNumberOfDocumentsLoading = 0;

// Global count of existing docshells.
static int32_t gDocShellCount = 0;

// Global count of docshells with the private attribute set
static uint32_t gNumberOfPrivateDocShells = 0;

// Global reference to the URI fixup service.
nsIURIFixup* nsDocShell::sURIFixup = 0;

// True means we validate window targets to prevent frameset
// spoofing. Initialize this to a non-bolean value so we know to check
// the pref on the creation of the first docshell.
static uint32_t gValidateOrigin = 0xffffffff;

// Hint for native dispatch of events on how long to delay after
// all documents have loaded in milliseconds before favoring normal
// native event dispatch priorites over performance
// Can be overridden with docshell.event_starvation_delay_hint pref.
#define NS_EVENT_STARVATION_DELAY_HINT 2000

#ifdef DEBUG
static mozilla::LazyLogModule gDocShellLog("nsDocShell");
#endif
static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");;

const char kBrandBundleURL[]      = "chrome://branding/locale/brand.properties";
const char kAppstringsBundleURL[] = "chrome://global/locale/appstrings.properties";

static void
FavorPerformanceHint(bool aPerfOverStarvation)
{
  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
  if (appShell) {
    appShell->FavorPerformanceHint(
      aPerfOverStarvation,
      Preferences::GetUint("docshell.event_starvation_delay_hint",
                           NS_EVENT_STARVATION_DELAY_HINT));
  }
}

//*****************************************************************************
// <a ping> support
//*****************************************************************************

#define PREF_PINGS_ENABLED           "browser.send_pings"
#define PREF_PINGS_MAX_PER_LINK      "browser.send_pings.max_per_link"
#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"

// Check prefs to see if pings are enabled and if so what restrictions might
// be applied.
//
// @param maxPerLink
//   This parameter returns the number of pings that are allowed per link click
//
// @param requireSameHost
//   This parameter returns true if pings are restricted to the same host as
//   the document in which the click occurs.  If the same host restriction is
//   imposed, then we still allow for pings to cross over to different
//   protocols and ports for flexibility and because it is not possible to send
//   a ping via FTP.
//
// @returns
//   true if pings are enabled and false otherwise.
//
static bool
PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost)
{
  bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);

  *aMaxPerLink = 1;
  *aRequireSameHost = true;

  if (allow) {
    Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
    Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
  }

  return allow;
}

typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
                                    nsIURI* uri, nsIIOService* ios);

static bool
IsElementAnchor(nsIContent* aContent)
{
  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
  // or XHTML namespace.
  return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
}

static void
ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure)
{
  // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
  //       since we'd still need to parse the resulting string.  Instead, we
  //       just parse the raw attribute.  It might be nice if the content node
  //       implemented an interface that exposed an enumeration of nsIURIs.

  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
  // or XHTML namespace.
  if (!IsElementAnchor(aContent)) {
    return;
  }

  nsCOMPtr<nsIAtom> pingAtom = NS_Atomize("ping");
  if (!pingAtom) {
    return;
  }

  nsAutoString value;
  aContent->GetAttr(kNameSpaceID_None, pingAtom, value);
  if (value.IsEmpty()) {
    return;
  }

  nsCOMPtr<nsIIOService> ios = do_GetIOService();
  if (!ios) {
    return;
  }

  nsIDocument* doc = aContent->OwnerDoc();

  nsWhitespaceTokenizer tokenizer(value);

  while (tokenizer.hasMoreTokens()) {
    nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI();
    ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()),
                doc->GetDocumentCharacterSet().get(),
                baseURI, getter_AddRefs(uri));
    // if we can't generate a valid URI, then there is nothing to do
    if (!uri) {
      continue;
    }
    // Explicitly not allow loading data: URIs
    bool isDataScheme =
      (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme);

    if (!isDataScheme) {
      aCallback(aClosure, aContent, uri, ios);
    }
  }
}

//----------------------------------------------------------------------

// We wait this many milliseconds before killing the ping channel...
#define PING_TIMEOUT 10000

static void
OnPingTimeout(nsITimer* aTimer, void* aClosure)
{
  nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
  if (loadGroup) {
    loadGroup->Cancel(NS_ERROR_ABORT);
  }
}

class nsPingListener final
  : public nsIStreamListener
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER

  nsPingListener()
  {
  }

  void SetLoadGroup(nsILoadGroup* aLoadGroup) {
    mLoadGroup = aLoadGroup;
  }

  nsresult StartTimeout();

private:
  ~nsPingListener();

  nsCOMPtr<nsILoadGroup> mLoadGroup;
  nsCOMPtr<nsITimer> mTimer;
};

NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)

nsPingListener::~nsPingListener()
{
  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }
}

nsresult
nsPingListener::StartTimeout()
{
  nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);

  if (timer) {
    nsresult rv = timer->InitWithFuncCallback(OnPingTimeout, mLoadGroup,
                                              PING_TIMEOUT,
                                              nsITimer::TYPE_ONE_SHOT);
    if (NS_SUCCEEDED(rv)) {
      mTimer = timer;
      return NS_OK;
    }
  }

  return NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
  return NS_OK;
}

NS_IMETHODIMP
nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
                                nsIInputStream* aStream, uint64_t aOffset,
                                uint32_t aCount)
{
  uint32_t result;
  return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
}

NS_IMETHODIMP
nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
                              nsresult aStatus)
{
  mLoadGroup = nullptr;

  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }

  return NS_OK;
}

struct MOZ_STACK_CLASS SendPingInfo
{
  int32_t numPings;
  int32_t maxPings;
  bool requireSameHost;
  nsIURI* target;
  nsIURI* referrer;
  nsIDocShell* docShell;
  uint32_t referrerPolicy;
};

static void
SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
         nsIIOService* aIOService)
{
  SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
  if (info->maxPings > -1 && info->numPings >= info->maxPings) {
    return;
  }

  nsIDocument* doc = aContent->OwnerDoc();

  nsCOMPtr<nsIChannel> chan;
  NS_NewChannel(getter_AddRefs(chan),
                aURI,
                doc,
                info->requireSameHost
                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                nsIContentPolicy::TYPE_PING,
                nullptr, // aLoadGroup
                nullptr, // aCallbacks
                nsIRequest::LOAD_NORMAL, // aLoadFlags,
                aIOService);

  if (!chan) {
    return;
  }

  // Don't bother caching the result of this URI load, but do not exempt
  // it from Safe Browsing.
  chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI);

  nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
  if (!httpChan) {
    return;
  }

  // This is needed in order for 3rd-party cookie blocking to work.
  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
  if (httpInternal) {
    httpInternal->SetDocumentURI(doc->GetDocumentURI());
  }

  httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));

  // Remove extraneous request headers (to reduce request size)
  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
                             EmptyCString(), false);
  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"),
                             EmptyCString(), false);
  httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"),
                             EmptyCString(), false);

  // Always send a Ping-To header.
  nsAutoCString pingTo;
  if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
    httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false);
  }

  nsCOMPtr<nsIScriptSecurityManager> sm =
    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);

  if (sm && info->referrer) {
    bool referrerIsSecure;
    uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
    nsresult rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure);

    // Default to sending less data if NS_URIChainHasFlags() fails.
    referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;

    bool sameOrigin =
      NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false));

    // If both the address of the document containing the hyperlink being
    // audited and "ping URL" have the same origin or the document containing
    // the hyperlink being audited was not retrieved over an encrypted
    // connection, send a Ping-From header.
    if (sameOrigin || !referrerIsSecure) {
      nsAutoCString pingFrom;
      if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) {
        httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"), pingFrom,
                                   false);
      }
    }

    // If the document containing the hyperlink being audited was not retrieved
    // over an encrypted connection and its address does not have the same
    // origin as "ping URL", send a referrer.
    if (!sameOrigin && !referrerIsSecure) {
      httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
    }
  }

  nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
  if (!uploadChan) {
    return;
  }

  NS_NAMED_LITERAL_CSTRING(uploadData, "PING");

  nsCOMPtr<nsIInputStream> uploadStream;
  NS_NewPostDataStream(getter_AddRefs(uploadStream), false, uploadData);
  if (!uploadStream) {
    return;
  }

  uploadChan->ExplicitSetUploadStream(uploadStream,
                                      NS_LITERAL_CSTRING("text/ping"),
                                      uploadData.Length(),
                                      NS_LITERAL_CSTRING("POST"), false);

  // The channel needs to have a loadgroup associated with it, so that we can
  // cancel the channel and any redirected channels it may create.
  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
  if (!loadGroup) {
    return;
  }
  nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
  loadGroup->SetNotificationCallbacks(callbacks);
  chan->SetLoadGroup(loadGroup);

  RefPtr<nsPingListener> pingListener = new nsPingListener();
  chan->AsyncOpen2(pingListener);

  // Even if AsyncOpen failed, we still count this as a successful ping.  It's
  // possible that AsyncOpen may have failed after triggering some background
  // process that may have written something to the network.
  info->numPings++;

  // Prevent ping requests from stalling and never being garbage collected...
  if (NS_FAILED(pingListener->StartTimeout())) {
    // If we failed to setup the timer, then we should just cancel the channel
    // because we won't be able to ensure that it goes away in a timely manner.
    chan->Cancel(NS_ERROR_ABORT);
    return;
  }
  // if the channel openend successfully, then make the pingListener hold
  // a strong reference to the loadgroup which is released in ::OnStopRequest
  pingListener->SetLoadGroup(loadGroup);
}

// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
static void
DispatchPings(nsIDocShell* aDocShell,
              nsIContent* aContent,
              nsIURI* aTarget,
              nsIURI* aReferrer,
              uint32_t aReferrerPolicy)
{
  SendPingInfo info;

  if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
    return;
  }
  if (info.maxPings == 0) {
    return;
  }

  info.numPings = 0;
  info.target = aTarget;
  info.referrer = aReferrer;
  info.referrerPolicy = aReferrerPolicy;
  info.docShell = aDocShell;

  ForEachPing(aContent, SendPing, &info);
}

static nsDOMNavigationTiming::Type
ConvertLoadTypeToNavigationType(uint32_t aLoadType)
{
  // Not initialized, assume it's normal load.
  if (aLoadType == 0) {
    aLoadType = LOAD_NORMAL;
  }

  auto result = nsDOMNavigationTiming::TYPE_RESERVED;
  switch (aLoadType) {
    case LOAD_NORMAL:
    case LOAD_NORMAL_EXTERNAL:
    case LOAD_NORMAL_BYPASS_CACHE:
    case LOAD_NORMAL_BYPASS_PROXY:
    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
    case LOAD_NORMAL_REPLACE:
    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
    case LOAD_LINK:
    case LOAD_STOP_CONTENT:
    case LOAD_REPLACE_BYPASS_CACHE:
      result = nsDOMNavigationTiming::TYPE_NAVIGATE;
      break;
    case LOAD_HISTORY:
      result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
      break;
    case LOAD_RELOAD_NORMAL:
    case LOAD_RELOAD_CHARSET_CHANGE:
    case LOAD_RELOAD_BYPASS_CACHE:
    case LOAD_RELOAD_BYPASS_PROXY:
    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
      result = nsDOMNavigationTiming::TYPE_RELOAD;
      break;
    case LOAD_STOP_CONTENT_AND_REPLACE:
    case LOAD_REFRESH:
    case LOAD_BYPASS_HISTORY:
    case LOAD_ERROR_PAGE:
    case LOAD_PUSHSTATE:
      result = nsDOMNavigationTiming::TYPE_RESERVED;
      break;
    default:
      // NS_NOTREACHED("Unexpected load type value");
      result = nsDOMNavigationTiming::TYPE_RESERVED;
      break;
  }

  return result;
}

static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);

static void
IncreasePrivateDocShellCount()
{
  gNumberOfPrivateDocShells++;
  if (gNumberOfPrivateDocShells > 1 ||
      !XRE_IsContentProcess()) {
    return;
  }

  mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
  cc->SendPrivateDocShellsExist(true);
}

static void
DecreasePrivateDocShellCount()
{
  MOZ_ASSERT(gNumberOfPrivateDocShells > 0);
  gNumberOfPrivateDocShells--;
  if (!gNumberOfPrivateDocShells) {
    if (XRE_IsContentProcess()) {
      dom::ContentChild* cc = dom::ContentChild::GetSingleton();
      cc->SendPrivateDocShellsExist(false);
      return;
    }

    nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
    if (obsvc) {
      obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
    }
  }
}

static uint64_t gDocshellIDCounter = 0;

nsDocShell::nsDocShell()
  : nsDocLoader()
  , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto)
  , mTreeOwner(nullptr)
  , mChromeEventHandler(nullptr)
  , mCharsetReloadState(eCharsetReloadInit)
  , mChildOffset(0)
  , mBusyFlags(BUSY_FLAGS_NONE)
  , mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
  , mLoadType(0)
  , mMarginWidth(-1)
  , mMarginHeight(-1)
  , mItemType(typeContent)
  , mPreviousTransIndex(-1)
  , mLoadedTransIndex(-1)
  , mSandboxFlags(0)
  , mOrientationLock(eScreenOrientation_None)
  , mFullscreenAllowed(CHECK_ATTRIBUTES)
  , mCreated(false)
  , mAllowSubframes(true)
  , mAllowPlugins(true)
  , mAllowJavascript(true)
  , mAllowMetaRedirects(true)
  , mAllowImages(true)
  , mAllowMedia(true)
  , mAllowDNSPrefetch(true)
  , mAllowWindowControl(true)
  , mAllowContentRetargeting(true)
  , mAllowContentRetargetingOnChildren(true)
  , mUseErrorPages(false)
  , mObserveErrorPages(true)
  , mAllowAuth(true)
  , mAllowKeywordFixup(false)
  , mIsOffScreenBrowser(false)
  , mIsActive(true)
  , mDisableMetaRefreshWhenInactive(false)
  , mIsPrerendered(false)
  , mIsAppTab(false)
  , mUseGlobalHistory(false)
  , mUseRemoteTabs(false)
  , mDeviceSizeIsPageSize(false)
  , mWindowDraggingAllowed(false)
  , mInFrameSwap(false)
  , mInheritPrivateBrowsingId(true)
  , mCanExecuteScripts(false)
  , mFiredUnloadEvent(false)
  , mEODForCurrentDocument(false)
  , mURIResultedInDocument(false)
  , mIsBeingDestroyed(false)
  , mIsExecutingOnLoadHandler(false)
  , mIsPrintingOrPP(false)
  , mSavingOldViewer(false)
  , mAffectPrivateSessionLifetime(true)
  , mInvisible(false)
  , mHasLoadedNonBlankURI(false)
  , mBlankTiming(false)
  , mCreatingDocument(false)
#ifdef DEBUG
  , mInEnsureScriptEnv(false)
#endif
  , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
  , mFrameType(FRAME_TYPE_REGULAR)
  , mPrivateBrowsingId(0)
  , mParentCharsetSource(0)
  , mJSRunToCompletionDepth(0)
  , mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE)
{
  AssertOriginAttributesMatchPrivateBrowsing();
  mHistoryID = ++gDocshellIDCounter;
  if (gDocShellCount++ == 0) {
    NS_ASSERTION(sURIFixup == nullptr,
                 "Huh, sURIFixup not null in first nsDocShell ctor!");

    CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup);
  }

  MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this));

#ifdef DEBUG
  // We're counting the number of |nsDocShells| to help find leaks
  ++gNumberOfDocShells;
  if (!PR_GetEnv("MOZ_QUIET")) {
    printf_stderr("++DOCSHELL %p == %ld [pid = %d] [id = %llu]\n",
                  (void*)this,
                  gNumberOfDocShells,
                  getpid(),
                  AssertedCast<unsigned long long>(mHistoryID));
  }
#endif
}

nsDocShell::~nsDocShell()
{
  MOZ_ASSERT(!mObserved);

  // Avoid notifying observers while we're in the dtor.
  mIsBeingDestroyed = true;

  Destroy();

  nsCOMPtr<nsISHistoryInternal> shPrivate(do_QueryInterface(mSessionHistory));
  if (shPrivate) {
    shPrivate->SetRootDocShell(nullptr);
  }

  if (--gDocShellCount == 0) {
    NS_IF_RELEASE(sURIFixup);
  }

  if (gDocShellLeakLog) {
    MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this));
  }

#ifdef DEBUG
  // We're counting the number of |nsDocShells| to help find leaks
  --gNumberOfDocShells;
  if (!PR_GetEnv("MOZ_QUIET")) {
    printf_stderr("--DOCSHELL %p == %ld [pid = %d] [id = %llu]\n",
                  (void*)this,
                  gNumberOfDocShells,
                  getpid(),
                  AssertedCast<unsigned long long>(mHistoryID));
  }
#endif
}

nsresult
nsDocShell::Init()
{
  nsresult rv = nsDocLoader::Init();
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ASSERTION(mLoadGroup, "Something went wrong!");

  mContentListener = new nsDSURIContentListener(this);
  rv = mContentListener->Init();
  NS_ENSURE_SUCCESS(rv, rv);

  // We want to hold a strong ref to the loadgroup, so it better hold a weak
  // ref to us...  use an InterfaceRequestorProxy to do this.
  nsCOMPtr<nsIInterfaceRequestor> proxy =
    new InterfaceRequestorProxy(static_cast<nsIInterfaceRequestor*>(this));
  mLoadGroup->SetNotificationCallbacks(proxy);

  rv = nsDocLoader::AddDocLoaderAsChildOfRoot(this);
  NS_ENSURE_SUCCESS(rv, rv);

  // Add as |this| a progress listener to itself.  A little weird, but
  // simpler than reproducing all the listener-notification logic in
  // overrides of the various methods via which nsDocLoader can be
  // notified.   Note that this holds an nsWeakPtr to ourselves, so it's ok.
  return AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT |
                                     nsIWebProgress::NOTIFY_STATE_NETWORK);
}

void
nsDocShell::DestroyChildren()
{
  nsCOMPtr<nsIDocShellTreeItem> shell;
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    shell = do_QueryObject(iter.GetNext());
    NS_ASSERTION(shell, "docshell has null child");

    if (shell) {
      shell->SetTreeOwner(nullptr);
    }
  }

  nsDocLoader::DestroyChildren();
}

NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)

NS_INTERFACE_MAP_BEGIN(nsDocShell)
  NS_INTERFACE_MAP_ENTRY(nsIDocShell)
  NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
  NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
  NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
  NS_INTERFACE_MAP_ENTRY(nsIScrollable)
  NS_INTERFACE_MAP_ENTRY(nsITextScroll)
  NS_INTERFACE_MAP_ENTRY(nsIDocCharset)
  NS_INTERFACE_MAP_ENTRY(nsIRefreshURI)
  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer)
  NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
  NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
  NS_INTERFACE_MAP_ENTRY(nsILoadContext)
  NS_INTERFACE_MAP_ENTRY(nsIWebShellServices)
  NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
  NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands)
  NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
  NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
  NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner)
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)

NS_IMETHODIMP
nsDocShell::GetInterface(const nsIID& aIID, void** aSink)
{
  NS_PRECONDITION(aSink, "null out param");

  *aSink = nullptr;

  if (aIID.Equals(NS_GET_IID(nsICommandManager))) {
    NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE);
    *aSink = mCommandManager;
  } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) {
    *aSink = mContentListener;
  } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) ||
              aIID.Equals(NS_GET_IID(nsIGlobalObject)) ||
              aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) ||
              aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) ||
              aIID.Equals(NS_GET_IID(nsIDOMWindow)) ||
              aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) &&
             NS_SUCCEEDED(EnsureScriptEnvironment())) {
    return mScriptGlobal->QueryInterface(aIID, aSink);
  } else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) &&
             NS_SUCCEEDED(EnsureContentViewer())) {
    mContentViewer->GetDOMDocument((nsIDOMDocument**)aSink);
    return *aSink ? NS_OK : NS_NOINTERFACE;
  } else if (aIID.Equals(NS_GET_IID(nsIDocument)) &&
             NS_SUCCEEDED(EnsureContentViewer())) {
    nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
    doc.forget(aSink);
    return *aSink ? NS_OK : NS_NOINTERFACE;
  } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) {
    *aSink = nullptr;

    // Return application cache associated with this docshell, if any

    nsCOMPtr<nsIContentViewer> contentViewer;
    GetContentViewer(getter_AddRefs(contentViewer));
    if (!contentViewer) {
      return NS_ERROR_NO_INTERFACE;
    }

    nsCOMPtr<nsIDOMDocument> domDoc;
    contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
    NS_ASSERTION(domDoc, "Should have a document.");
    if (!domDoc) {
      return NS_ERROR_NO_INTERFACE;
    }

#if defined(DEBUG)
    MOZ_LOG(gDocShellLog, LogLevel::Debug,
           ("nsDocShell[%p]: returning app cache container %p",
            this, domDoc.get()));
#endif
    return domDoc->QueryInterface(aIID, aSink);
  } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) &&
             NS_SUCCEEDED(EnsureScriptEnvironment())) {
    nsresult rv;
    nsCOMPtr<nsIWindowWatcher> wwatch =
      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Get the an auth prompter for our window so that the parenting
    // of the dialogs works as it should when using tabs.
    nsIPrompt* prompt;
    rv = wwatch->GetNewPrompter(mScriptGlobal->AsOuter(), &prompt);
    NS_ENSURE_SUCCESS(rv, rv);

    *aSink = prompt;
    return NS_OK;
  } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
             aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
    return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) ?
      NS_OK : NS_NOINTERFACE;
  } else if (aIID.Equals(NS_GET_IID(nsISHistory))) {
    nsCOMPtr<nsISHistory> shistory;
    nsresult rv = GetSessionHistory(getter_AddRefs(shistory));
    if (NS_SUCCEEDED(rv) && shistory) {
      shistory.forget(aSink);
      return NS_OK;
    }
    return NS_NOINTERFACE;
  } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) {
    nsresult rv = EnsureFind();
    if (NS_FAILED(rv)) {
      return rv;
    }

    *aSink = mFind;
    NS_ADDREF((nsISupports*)*aSink);
    return NS_OK;
  } else if (aIID.Equals(NS_GET_IID(nsIEditingSession))) {
    nsCOMPtr<nsIEditingSession> es;
    GetEditingSession(getter_AddRefs(es));
    es.forget(aSink);
    return *aSink ? NS_OK : NS_NOINTERFACE;
  } else if (aIID.Equals(NS_GET_IID(nsIClipboardDragDropHookList)) &&
             NS_SUCCEEDED(EnsureTransferableHookData())) {
    *aSink = mTransferableHookData;
    NS_ADDREF((nsISupports*)*aSink);
    return NS_OK;
  } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) {
    nsIPresShell* shell = GetPresShell();
    if (shell) {
      return shell->QueryInterface(aIID, aSink);
    }
  } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) {
    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
    nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner));
    if (NS_SUCCEEDED(rv) && treeOwner) {
      return treeOwner->QueryInterface(aIID, aSink);
    }
  } else if (aIID.Equals(NS_GET_IID(nsITabChild))) {
    *aSink = GetTabChild().take();
    return *aSink ? NS_OK : NS_ERROR_FAILURE;
  } else if (aIID.Equals(NS_GET_IID(nsIContentFrameMessageManager))) {
    nsCOMPtr<nsITabChild> tabChild =
      do_GetInterface(static_cast<nsIDocShell*>(this));
    nsCOMPtr<nsIContentFrameMessageManager> mm;
    if (tabChild) {
      tabChild->GetMessageManager(getter_AddRefs(mm));
    } else {
      if (nsPIDOMWindowOuter* win = GetWindow()) {
        mm = do_QueryInterface(win->GetParentTarget());
      }
    }
    *aSink = mm.get();
  } else {
    return nsDocLoader::GetInterface(aIID, aSink);
  }

  NS_IF_ADDREF(((nsISupports*)*aSink));
  return *aSink ? NS_OK : NS_NOINTERFACE;
}

uint32_t
nsDocShell::ConvertDocShellLoadInfoToLoadType(
    nsDocShellInfoLoadType aDocShellLoadType)
{
  uint32_t loadType = LOAD_NORMAL;

  switch (aDocShellLoadType) {
    case nsIDocShellLoadInfo::loadNormal:
      loadType = LOAD_NORMAL;
      break;
    case nsIDocShellLoadInfo::loadNormalReplace:
      loadType = LOAD_NORMAL_REPLACE;
      break;
    case nsIDocShellLoadInfo::loadNormalExternal:
      loadType = LOAD_NORMAL_EXTERNAL;
      break;
    case nsIDocShellLoadInfo::loadHistory:
      loadType = LOAD_HISTORY;
      break;
    case nsIDocShellLoadInfo::loadNormalBypassCache:
      loadType = LOAD_NORMAL_BYPASS_CACHE;
      break;
    case nsIDocShellLoadInfo::loadNormalBypassProxy:
      loadType = LOAD_NORMAL_BYPASS_PROXY;
      break;
    case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache:
      loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE;
      break;
    case nsIDocShellLoadInfo::loadNormalAllowMixedContent:
      loadType = LOAD_NORMAL_ALLOW_MIXED_CONTENT;
      break;
    case nsIDocShellLoadInfo::loadReloadNormal:
      loadType = LOAD_RELOAD_NORMAL;
      break;
    case nsIDocShellLoadInfo::loadReloadCharsetChange:
      loadType = LOAD_RELOAD_CHARSET_CHANGE;
      break;
    case nsIDocShellLoadInfo::loadReloadBypassCache:
      loadType = LOAD_RELOAD_BYPASS_CACHE;
      break;
    case nsIDocShellLoadInfo::loadReloadBypassProxy:
      loadType = LOAD_RELOAD_BYPASS_PROXY;
      break;
    case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache:
      loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
      break;
    case nsIDocShellLoadInfo::loadLink:
      loadType = LOAD_LINK;
      break;
    case nsIDocShellLoadInfo::loadRefresh:
      loadType = LOAD_REFRESH;
      break;
    case nsIDocShellLoadInfo::loadBypassHistory:
      loadType = LOAD_BYPASS_HISTORY;
      break;
    case nsIDocShellLoadInfo::loadStopContent:
      loadType = LOAD_STOP_CONTENT;
      break;
    case nsIDocShellLoadInfo::loadStopContentAndReplace:
      loadType = LOAD_STOP_CONTENT_AND_REPLACE;
      break;
    case nsIDocShellLoadInfo::loadPushState:
      loadType = LOAD_PUSHSTATE;
      break;
    case nsIDocShellLoadInfo::loadReplaceBypassCache:
      loadType = LOAD_REPLACE_BYPASS_CACHE;
      break;
    case nsIDocShellLoadInfo::loadReloadMixedContent:
      loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT;
      break;
    default:
      NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value");
  }

  return loadType;
}

nsDocShellInfoLoadType
nsDocShell::ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType)
{
  nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal;
  switch (aLoadType) {
    case LOAD_NORMAL:
      docShellLoadType = nsIDocShellLoadInfo::loadNormal;
      break;
    case LOAD_NORMAL_REPLACE:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace;
      break;
    case LOAD_NORMAL_EXTERNAL:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal;
      break;
    case LOAD_NORMAL_BYPASS_CACHE:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache;
      break;
    case LOAD_NORMAL_BYPASS_PROXY:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy;
      break;
    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache;
      break;
    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
      docShellLoadType = nsIDocShellLoadInfo::loadNormalAllowMixedContent;
      break;
    case LOAD_HISTORY:
      docShellLoadType = nsIDocShellLoadInfo::loadHistory;
      break;
    case LOAD_RELOAD_NORMAL:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal;
      break;
    case LOAD_RELOAD_CHARSET_CHANGE:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
      break;
    case LOAD_RELOAD_BYPASS_CACHE:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache;
      break;
    case LOAD_RELOAD_BYPASS_PROXY:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy;
      break;
    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;
      break;
    case LOAD_LINK:
      docShellLoadType = nsIDocShellLoadInfo::loadLink;
      break;
    case LOAD_REFRESH:
      docShellLoadType = nsIDocShellLoadInfo::loadRefresh;
      break;
    case LOAD_BYPASS_HISTORY:
    case LOAD_ERROR_PAGE:
      docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory;
      break;
    case LOAD_STOP_CONTENT:
      docShellLoadType = nsIDocShellLoadInfo::loadStopContent;
      break;
    case LOAD_STOP_CONTENT_AND_REPLACE:
      docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace;
      break;
    case LOAD_PUSHSTATE:
      docShellLoadType = nsIDocShellLoadInfo::loadPushState;
      break;
    case LOAD_REPLACE_BYPASS_CACHE:
      docShellLoadType = nsIDocShellLoadInfo::loadReplaceBypassCache;
      break;
    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
      docShellLoadType = nsIDocShellLoadInfo::loadReloadMixedContent;
      break;
    default:
      NS_NOTREACHED("Unexpected load type value");
  }

  return docShellLoadType;
}

NS_IMETHODIMP
nsDocShell::LoadURI(nsIURI* aURI,
                    nsIDocShellLoadInfo* aLoadInfo,
                    uint32_t aLoadFlags,
                    bool aFirstParty)
{
  NS_PRECONDITION(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0,
                  "Unexpected flags");
  NS_PRECONDITION((aLoadFlags & 0xf) == 0, "Should not have these flags set");

  // Note: we allow loads to get through here even if mFiredUnloadEvent is
  // true; that case will get handled in LoadInternal or LoadHistoryEntry,
  // so we pass false as the second parameter to IsNavigationAllowed.
  // However, we don't allow the page to change location *in the middle of*
  // firing beforeunload, so we do need to check if *beforeunload* is currently
  // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP.
  if (!IsNavigationAllowed(true, false)) {
    return NS_OK; // JS may not handle returning of an error code
  }

  nsCOMPtr<nsIURI> referrer;
  nsCOMPtr<nsIURI> originalURI;
  bool loadReplace = false;
  nsCOMPtr<nsIInputStream> postStream;
  nsCOMPtr<nsIInputStream> headersStream;
  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
  bool inheritPrincipal = false;
  bool principalIsExplicit = false;
  bool sendReferrer = true;
  uint32_t referrerPolicy = mozilla::net::RP_Default;
  bool isSrcdoc = false;
  nsCOMPtr<nsISHEntry> shEntry;
  nsXPIDLString target;
  nsAutoString srcdoc;
  nsCOMPtr<nsIDocShell> sourceDocShell;
  nsCOMPtr<nsIURI> baseURI;

  uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);

  NS_ENSURE_ARG(aURI);

  if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) &&
      mItemType == typeContent && !NS_IsAboutBlank(aURI)) {
    StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI);
  }

  // Extract the info from the DocShellLoadInfo struct...
  if (aLoadInfo) {
    aLoadInfo->GetReferrer(getter_AddRefs(referrer));
    aLoadInfo->GetOriginalURI(getter_AddRefs(originalURI));
    aLoadInfo->GetLoadReplace(&loadReplace);
    nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
    aLoadInfo->GetLoadType(&lt);
    // Get the appropriate loadType from nsIDocShellLoadInfo type
    loadType = ConvertDocShellLoadInfoToLoadType(lt);

    aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
    aLoadInfo->GetInheritPrincipal(&inheritPrincipal);
    aLoadInfo->GetPrincipalIsExplicit(&principalIsExplicit);
    aLoadInfo->GetSHEntry(getter_AddRefs(shEntry));
    aLoadInfo->GetTarget(getter_Copies(target));
    aLoadInfo->GetPostDataStream(getter_AddRefs(postStream));
    aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream));
    aLoadInfo->GetSendReferrer(&sendReferrer);
    aLoadInfo->GetReferrerPolicy(&referrerPolicy);
    aLoadInfo->GetIsSrcdocLoad(&isSrcdoc);
    aLoadInfo->GetSrcdocData(srcdoc);
    aLoadInfo->GetSourceDocShell(getter_AddRefs(sourceDocShell));
    aLoadInfo->GetBaseURI(getter_AddRefs(baseURI));
  }

#if defined(DEBUG)
  if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
    nsAutoCString uristr;
    aURI->GetAsciiSpec(uristr);
    MOZ_LOG(gDocShellLog, LogLevel::Debug,
           ("nsDocShell[%p]: loading %s with flags 0x%08x",
            this, uristr.get(), aLoadFlags));
  }
#endif

  if (!shEntry &&
      !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) {
    // First verify if this is a subframe.
    nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
    GetSameTypeParent(getter_AddRefs(parentAsItem));
    nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
    uint32_t parentLoadType;

    if (parentDS && parentDS != static_cast<nsIDocShell*>(this)) {
      /* OK. It is a subframe. Checkout the
       * parent's loadtype. If the parent was loaded thro' a history
       * mechanism, then get the SH entry for the child from the parent.
       * This is done to restore frameset navigation while going back/forward.
       * If the parent was loaded through any other loadType, set the
       * child's loadType too accordingly, so that session history does not
       * get confused.
       */

      // Get the parent's load type
      parentDS->GetLoadType(&parentLoadType);

      // Get the ShEntry for the child from the parent
      nsCOMPtr<nsISHEntry> currentSH;
      bool oshe = false;
      parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
      bool dynamicallyAddedChild = mDynamicallyCreated;
      if (!dynamicallyAddedChild && !oshe && currentSH) {
        currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild);
      }
      if (!dynamicallyAddedChild) {
        // Only use the old SHEntry, if we're sure enough that
        // it wasn't originally for some other frame.
        parentDS->GetChildSHEntry(mChildOffset, getter_AddRefs(shEntry));
      }

      // Make some decisions on the child frame's loadType based on the
      // parent's loadType.
      if (!mCurrentURI) {
        // This is a newly created frame. Check for exception cases first.
        // By default the subframe will inherit the parent's loadType.
        if (shEntry && (parentLoadType == LOAD_NORMAL ||
                        parentLoadType == LOAD_LINK   ||
                        parentLoadType == LOAD_NORMAL_EXTERNAL)) {
          // The parent was loaded normally. In this case, this *brand new*
          // child really shouldn't have a SHEntry. If it does, it could be
          // because the parent is replacing an existing frame with a new frame,
          // in the onLoadHandler. We don't want this url to get into session
          // history. Clear off shEntry, and set load type to
          // LOAD_BYPASS_HISTORY.
          bool inOnLoadHandler = false;
          parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
          if (inOnLoadHandler) {
            loadType = LOAD_NORMAL_REPLACE;
            shEntry = nullptr;
          }
        } else if (parentLoadType == LOAD_REFRESH) {
          // Clear shEntry. For refresh loads, we have to load
          // what comes thro' the pipe, not what's in history.
          shEntry = nullptr;
        } else if ((parentLoadType == LOAD_BYPASS_HISTORY) ||
                   (shEntry &&
                    ((parentLoadType & LOAD_CMD_HISTORY) ||
                     (parentLoadType == LOAD_RELOAD_NORMAL) ||
                     (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE)))) {
          // If the parent url, bypassed history or was loaded from
          // history, pass on the parent's loadType to the new child
          // frame too, so that the child frame will also
          // avoid getting into history.
          loadType = parentLoadType;
        } else if (parentLoadType == LOAD_ERROR_PAGE) {
          // If the parent document is an error page, we don't
          // want to update global/session history. However,
          // this child frame is not an error page.
          loadType = LOAD_BYPASS_HISTORY;
        } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) ||
                   (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) ||
                   (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
          // the new frame should inherit the parent's load type so that it also
          // bypasses the cache and/or proxy
          loadType = parentLoadType;
        }
      } else {
        // This is a pre-existing subframe. If the load was not originally
        // initiated by session history, (if (!shEntry) condition succeeded) and
        // mCurrentURI is not null, it is possible that a parent's onLoadHandler
        // or even self's onLoadHandler is loading a new page in this child.
        // Check parent's and self's busy flag  and if it is set, we don't want
        // this onLoadHandler load to get in to session history.
        uint32_t parentBusy = BUSY_FLAGS_NONE;
        uint32_t selfBusy = BUSY_FLAGS_NONE;
        parentDS->GetBusyFlags(&parentBusy);
        GetBusyFlags(&selfBusy);
        if (parentBusy & BUSY_FLAGS_BUSY ||
            selfBusy & BUSY_FLAGS_BUSY) {
          loadType = LOAD_NORMAL_REPLACE;
          shEntry = nullptr;
        }
      }
    } // parentDS
    else {
      // This is the root docshell. If we got here while
      // executing an onLoad Handler,this load will not go
      // into session history.
      bool inOnLoadHandler = false;
      GetIsExecutingOnLoadHandler(&inOnLoadHandler);
      if (inOnLoadHandler) {
        loadType = LOAD_NORMAL_REPLACE;
      }
    }
  } // !shEntry

  if (shEntry) {
#ifdef DEBUG
    MOZ_LOG(gDocShellLog, LogLevel::Debug,
           ("nsDocShell[%p]: loading from session history", this));
#endif

    return LoadHistoryEntry(shEntry, loadType);
  }

  // On history navigation via Back/Forward buttons, don't execute
  // automatic JavaScript redirection such as |location.href = ...| or
  // |window.open()|
  //
  // LOAD_NORMAL:        window.open(...) etc.
  // LOAD_STOP_CONTENT:  location.href = ..., location.assign(...)
  if ((loadType == LOAD_NORMAL || loadType == LOAD_STOP_CONTENT) &&
      ShouldBlockLoadingForBackButton()) {
    return NS_OK;
  }

  // Perform the load...

  // We need a principalToInherit.
  //
  // If principalIsExplicit is not set there are 4 possibilities:
  // (1) If the system principal or an expanded principal was passed
  //     in and we're a typeContent docshell, inherit the principal
  //     from the current document instead.
  // (2) In all other cases when the principal passed in is not null,
  //     use that principal.
  // (3) If the caller has allowed inheriting from the current document,
  //     or if we're being called from system code (eg chrome JS or pure
  //     C++) then inheritPrincipal should be true and InternalLoad will get
  //     a principal from the current document. If none of these things are
  //     true, then
  // (4) we don't pass a principal into the channel, and a principal will be
  //     created later from the channel's internal data.
  //
  // If principalIsExplicit *is* set, there are 4 possibilities
  // (1) If the system principal or an expanded principal was passed in
  //     and we're a typeContent docshell, return an error.
  // (2) In all other cases when the principal passed in is not null,
  //     use that principal.
  // (3) If the caller has allowed inheriting from the current document,
  //     then inheritPrincipal should be true and InternalLoad will get
  //     a principal from the current document. If none of these things are
  //     true, then
  // (4) we dont' pass a principal into the channel, and a principal will be
  //     created later from the channel's internal data.
  nsCOMPtr<nsIPrincipal> principalToInherit = triggeringPrincipal;
  if (principalToInherit && mItemType != typeChrome) {
    if (nsContentUtils::IsSystemPrincipal(principalToInherit)) {
      if (principalIsExplicit) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }
      principalToInherit = nullptr;
      inheritPrincipal = true;
    } else if (nsContentUtils::IsExpandedPrincipal(principalToInherit)) {
      if (principalIsExplicit) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }
      // Don't inherit from the current page.  Just do the safe thing
      // and pretend that we were loaded by a nullprincipal.
      //
      // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
      // have origin attributes.
      principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this);
      inheritPrincipal = false;
    }
  }
  if (!principalToInherit && !inheritPrincipal && !principalIsExplicit) {
    // See if there's system or chrome JS code running
    inheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
  }

  if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
    inheritPrincipal = false;
    principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this);
  }

  // If the triggeringPrincipal is not passed explicitly, we first try to create
  // a principal from the referrer, since the referrer URI reflects the web origin
  // that triggered the load. If there is no referrer URI, we fall back to using
  // the SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
  // and no referrer simulate a load that was triggered by the system.
  // It's important to note that this block of code needs to appear *after* the block
  // where we munge the principalToInherit, because otherwise we would never enter
  // code blocks checking if the principalToInherit is null and we will end up with
  // a wrong inheritPrincipal flag.
  if (!triggeringPrincipal) {
    if (referrer) {
      nsresult rv = CreatePrincipalFromReferrer(referrer,
                                                getter_AddRefs(triggeringPrincipal));
      NS_ENSURE_SUCCESS(rv, rv);
    }
    else {
      triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
    }
  }

  uint32_t flags = 0;

  if (inheritPrincipal) {
    flags |= INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
  }

  if (!sendReferrer) {
    flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
  }

  if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
    flags |= INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
  }

  if (aLoadFlags & LOAD_FLAGS_FIRST_LOAD) {
    flags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD;
  }

  if (aLoadFlags & LOAD_FLAGS_BYPASS_CLASSIFIER) {
    flags |= INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
  }

  if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
    flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
  }

  if (isSrcdoc) {
    flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
  }

  return InternalLoad(aURI,
                      originalURI,
                      loadReplace,
                      referrer,
                      referrerPolicy,
                      triggeringPrincipal,
                      principalToInherit,
                      flags,
                      target,
                      nullptr,      // No type hint
                      NullString(), // No forced download
                      postStream,
                      headersStream,
                      loadType,
                      nullptr, // No SHEntry
                      aFirstParty,
                      srcdoc,
                      sourceDocShell,
                      baseURI,
                      nullptr,  // No nsIDocShell
                      nullptr); // No nsIRequest
}

NS_IMETHODIMP
nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI,
                       const nsACString& aContentType,
                       const nsACString& aContentCharset,
                       nsIDocShellLoadInfo* aLoadInfo)
{
  NS_ENSURE_ARG(aStream);

  mAllowKeywordFixup = false;

  // if the caller doesn't pass in a URI we need to create a dummy URI. necko
  // currently requires a URI in various places during the load. Some consumers
  // do as well.
  nsCOMPtr<nsIURI> uri = aURI;
  if (!uri) {
    // HACK ALERT
    nsresult rv = NS_OK;
    uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
    // Make sure that the URI spec "looks" like a protocol and path...
    // For now, just use a bogus protocol called "internal"
    rv = uri->SetSpec(NS_LITERAL_CSTRING("internal:load-stream"));
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  uint32_t loadType = LOAD_NORMAL;
  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
  if (aLoadInfo) {
    nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
    (void)aLoadInfo->GetLoadType(&lt);
    // Get the appropriate LoadType from nsIDocShellLoadInfo type
    loadType = ConvertDocShellLoadInfoToLoadType(lt);
    aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
  }

  NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE);

  mLoadType = loadType;

  if (!triggeringPrincipal) {
    triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
  }

  // build up a channel for this stream.
  nsCOMPtr<nsIChannel> channel;
  nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
                                         uri,
                                         aStream,
                                         triggeringPrincipal,
                                         nsILoadInfo::SEC_NORMAL,
                                         nsIContentPolicy::TYPE_OTHER,
                                         aContentType,
                                         aContentCharset);
  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);

  nsCOMPtr<nsIURILoader> uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID));
  NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE);

  NS_ENSURE_SUCCESS(DoChannelLoad(channel, uriLoader, false),
                    NS_ERROR_FAILURE);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::CreateLoadInfo(nsIDocShellLoadInfo** aLoadInfo)
{
  nsDocShellLoadInfo* loadInfo = new nsDocShellLoadInfo();
  nsCOMPtr<nsIDocShellLoadInfo> localRef(loadInfo);

  localRef.forget(aLoadInfo);
  return NS_OK;
}

/*
 * Reset state to a new content model within the current document and the
 * document viewer. Called by the document before initiating an out of band
 * document.write().
 */
NS_IMETHODIMP
nsDocShell::PrepareForNewContentModel()
{
  mEODForCurrentDocument = false;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::FirePageHideNotification(bool aIsUnload)
{
  if (mContentViewer && !mFiredUnloadEvent) {
    // Keep an explicit reference since calling PageHide could release
    // mContentViewer
    nsCOMPtr<nsIContentViewer> contentViewer(mContentViewer);
    mFiredUnloadEvent = true;

    if (mTiming) {
      mTiming->NotifyUnloadEventStart();
    }

    contentViewer->PageHide(aIsUnload);

    if (mTiming) {
      mTiming->NotifyUnloadEventEnd();
    }

    AutoTArray<nsCOMPtr<nsIDocShell>, 8> kids;
    uint32_t n = mChildList.Length();
    kids.SetCapacity(n);
    for (uint32_t i = 0; i < n; i++) {
      kids.AppendElement(do_QueryInterface(ChildAt(i)));
    }

    n = kids.Length();
    for (uint32_t i = 0; i < n; ++i) {
      if (kids[i]) {
        kids[i]->FirePageHideNotification(aIsUnload);
      }
    }
    // Now make sure our editor, if any, is detached before we go
    // any farther.
    DetachEditorFromWindow();
  }

  return NS_OK;
}

void
nsDocShell::MaybeInitTiming()
{
  if (mTiming && !mBlankTiming) {
    return;
  }

  if (mScriptGlobal && mBlankTiming) {
    nsPIDOMWindowInner* innerWin =
      mScriptGlobal->AsOuter()->GetCurrentInnerWindow();
    if (innerWin && innerWin->GetPerformance()) {
      mTiming = innerWin->GetPerformance()->GetDOMTiming();
      mBlankTiming = false;
    }
  }

  if (!mTiming) {
    mTiming = new nsDOMNavigationTiming();
  }

  mTiming->NotifyNavigationStart(
    mIsActive ? nsDOMNavigationTiming::DocShellState::eActive
              : nsDOMNavigationTiming::DocShellState::eInactive);
}

//
// Bug 13871: Prevent frameset spoofing
//
// This routine answers: 'Is origin's document from same domain as
// target's document?'
//
// file: uris are considered the same domain for the purpose of
// frame navigation regardless of script accessibility (bug 420425)
//
/* static */ bool
nsDocShell::ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem,
                           nsIDocShellTreeItem* aTargetTreeItem)
{
  // We want to bypass this check for chrome callers, but only if there's
  // JS on the stack. System callers still need to do it.
  if (nsContentUtils::GetCurrentJSContext() &&
      nsContentUtils::IsCallerChrome()) {
    return true;
  }

  MOZ_ASSERT(aOriginTreeItem && aTargetTreeItem, "need two docshells");

  // Get origin document principal
  nsCOMPtr<nsIDocument> originDocument = aOriginTreeItem->GetDocument();
  NS_ENSURE_TRUE(originDocument, false);

  // Get target principal
  nsCOMPtr<nsIDocument> targetDocument = aTargetTreeItem->GetDocument();
  NS_ENSURE_TRUE(targetDocument, false);

  bool equal;
  nsresult rv = originDocument->NodePrincipal()->Equals(
    targetDocument->NodePrincipal(), &equal);
  if (NS_SUCCEEDED(rv) && equal) {
    return true;
  }

  // Not strictly equal, special case if both are file: uris
  bool originIsFile = false;
  bool targetIsFile = false;
  nsCOMPtr<nsIURI> originURI;
  nsCOMPtr<nsIURI> targetURI;
  nsCOMPtr<nsIURI> innerOriginURI;
  nsCOMPtr<nsIURI> innerTargetURI;

  rv = originDocument->NodePrincipal()->GetURI(getter_AddRefs(originURI));
  if (NS_SUCCEEDED(rv) && originURI) {
    innerOriginURI = NS_GetInnermostURI(originURI);
  }

  rv = targetDocument->NodePrincipal()->GetURI(getter_AddRefs(targetURI));
  if (NS_SUCCEEDED(rv) && targetURI) {
    innerTargetURI = NS_GetInnermostURI(targetURI);
  }

  return innerOriginURI && innerTargetURI &&
         NS_SUCCEEDED(innerOriginURI->SchemeIs("file", &originIsFile)) &&
         NS_SUCCEEDED(innerTargetURI->SchemeIs("file", &targetIsFile)) &&
         originIsFile && targetIsFile;
}

nsresult
nsDocShell::GetEldestPresContext(nsPresContext** aPresContext)
{
  NS_ENSURE_ARG_POINTER(aPresContext);
  *aPresContext = nullptr;

  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
  while (viewer) {
    nsCOMPtr<nsIContentViewer> prevViewer;
    viewer->GetPreviousViewer(getter_AddRefs(prevViewer));
    if (!prevViewer) {
      return viewer->GetPresContext(aPresContext);
    }
    viewer = prevViewer;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetPresContext(nsPresContext** aPresContext)
{
  NS_ENSURE_ARG_POINTER(aPresContext);
  *aPresContext = nullptr;

  if (!mContentViewer) {
    return NS_OK;
  }

  return mContentViewer->GetPresContext(aPresContext);
}

NS_IMETHODIMP_(nsIPresShell*)
nsDocShell::GetPresShell()
{
  RefPtr<nsPresContext> presContext;
  (void)GetPresContext(getter_AddRefs(presContext));
  return presContext ? presContext->GetPresShell() : nullptr;
}

NS_IMETHODIMP
nsDocShell::GetEldestPresShell(nsIPresShell** aPresShell)
{
  nsresult rv = NS_OK;

  NS_ENSURE_ARG_POINTER(aPresShell);
  *aPresShell = nullptr;

  RefPtr<nsPresContext> presContext;
  (void)GetEldestPresContext(getter_AddRefs(presContext));

  if (presContext) {
    NS_IF_ADDREF(*aPresShell = presContext->GetPresShell());
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer)
{
  NS_ENSURE_ARG_POINTER(aContentViewer);

  *aContentViewer = mContentViewer;
  NS_IF_ADDREF(*aContentViewer);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler)
{
  // Weak reference. Don't addref.
  nsCOMPtr<EventTarget> handler = do_QueryInterface(aChromeEventHandler);
  mChromeEventHandler = handler.get();

  if (mScriptGlobal) {
    mScriptGlobal->SetChromeEventHandler(mChromeEventHandler);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetChromeEventHandler(nsIDOMEventTarget** aChromeEventHandler)
{
  NS_ENSURE_ARG_POINTER(aChromeEventHandler);
  nsCOMPtr<EventTarget> handler = mChromeEventHandler;
  handler.forget(aChromeEventHandler);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCurrentURI(nsIURI* aURI)
{
  // Note that securityUI will set STATE_IS_INSECURE, even if
  // the scheme of |aURI| is "https".
  SetCurrentURI(aURI, nullptr, true, 0);
  return NS_OK;
}

bool
nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
                          bool aFireOnLocationChange, uint32_t aLocationFlags)
{
  if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) {
    PR_LogPrint("DOCSHELL %p SetCurrentURI %s\n",
                this, aURI ? aURI->GetSpecOrDefault().get() : "");
  }

  // We don't want to send a location change when we're displaying an error
  // page, and we don't want to change our idea of "current URI" either
  if (mLoadType == LOAD_ERROR_PAGE) {
    return false;
  }

  mCurrentURI = NS_TryToMakeImmutable(aURI);

  if (!NS_IsAboutBlank(mCurrentURI)) {
    mHasLoadedNonBlankURI = true;
  }

  bool isRoot = false;  // Is this the root docshell
  bool isSubFrame = false;  // Is this a subframe navigation?

  nsCOMPtr<nsIDocShellTreeItem> root;

  GetSameTypeRootTreeItem(getter_AddRefs(root));
  if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) {
    // This is the root docshell
    isRoot = true;
  }
  if (mLSHE) {
    mLSHE->GetIsSubFrame(&isSubFrame);
  }

  if (!isSubFrame && !isRoot) {
    /*
     * We don't want to send OnLocationChange notifications when
     * a subframe is being loaded for the first time, while
     * visiting a frameset page
     */
    return false;
  }

  if (aFireOnLocationChange) {
    FireOnLocationChange(this, aRequest, aURI, aLocationFlags);
  }
  return !aFireOnLocationChange;
}

NS_IMETHODIMP
nsDocShell::GetCharset(nsACString& aCharset)
{
  aCharset.Truncate();

  nsIPresShell* presShell = GetPresShell();
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
  nsIDocument* doc = presShell->GetDocument();
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
  aCharset = doc->GetDocumentCharacterSet();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GatherCharsetMenuTelemetry()
{
  nsCOMPtr<nsIContentViewer> viewer;
  GetContentViewer(getter_AddRefs(viewer));
  if (!viewer) {
    return NS_OK;
  }

  nsIDocument* doc = viewer->GetDocument();
  if (!doc || doc->WillIgnoreCharsetOverride()) {
    return NS_OK;
  }

  Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_USED, true);

  bool isFileURL = false;
  nsIURI* url = doc->GetOriginalURI();
  if (url) {
    url->SchemeIs("file", &isFileURL);
  }

  int32_t charsetSource = doc->GetDocumentCharacterSetSource();
  switch (charsetSource) {
    case kCharsetFromTopLevelDomain:
      // Unlabeled doc on a domain that we map to a fallback encoding
      Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 7);
      break;
    case kCharsetFromFallback:
    case kCharsetFromDocTypeDefault:
    case kCharsetFromCache:
    case kCharsetFromParentFrame:
    case kCharsetFromHintPrevDoc:
      // Changing charset on an unlabeled doc.
      if (isFileURL) {
        Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 0);
      } else {
        Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 1);
      }
      break;
    case kCharsetFromAutoDetection:
      // Changing charset on unlabeled doc where chardet fired
      if (isFileURL) {
        Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 2);
      } else {
        Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 3);
      }
      break;
    case kCharsetFromMetaPrescan:
    case kCharsetFromMetaTag:
    case kCharsetFromChannel:
      // Changing charset on a doc that had a charset label.
      Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 4);
      break;
    case kCharsetFromParentForced:
    case kCharsetFromUserForced:
      // Changing charset on a document that already had an override.
      Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 5);
      break;
    case kCharsetFromIrreversibleAutoDetection:
    case kCharsetFromOtherComponent:
    case kCharsetFromByteOrderMark:
    case kCharsetUninitialized:
    default:
      // Bug. This isn't supposed to happen.
      Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 6);
      break;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCharset(const nsACString& aCharset)
{
  // set the charset override
  return SetForcedCharset(aCharset);
}

NS_IMETHODIMP
nsDocShell::SetForcedCharset(const nsACString& aCharset)
{
  if (aCharset.IsEmpty()) {
    mForcedCharset.Truncate();
    return NS_OK;
  }
  nsAutoCString encoding;
  if (!EncodingUtils::FindEncodingForLabel(aCharset, encoding)) {
    // Reject unknown labels
    return NS_ERROR_INVALID_ARG;
  }
  if (!EncodingUtils::IsAsciiCompatible(encoding)) {
    // Reject XSS hazards
    return NS_ERROR_INVALID_ARG;
  }
  mForcedCharset = encoding;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetForcedCharset(nsACString& aResult)
{
  aResult = mForcedCharset;
  return NS_OK;
}

void
nsDocShell::SetParentCharset(const nsACString& aCharset,
                             int32_t aCharsetSource,
                             nsIPrincipal* aPrincipal)
{
  mParentCharset = aCharset;
  mParentCharsetSource = aCharsetSource;
  mParentCharsetPrincipal = aPrincipal;
}

void
nsDocShell::GetParentCharset(nsACString& aCharset,
                             int32_t* aCharsetSource,
                             nsIPrincipal** aPrincipal)
{
  aCharset = mParentCharset;
  *aCharsetSource = mParentCharsetSource;
  NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal);
}

NS_IMETHODIMP
nsDocShell::GetChannelIsUnsafe(bool* aUnsafe)
{
  *aUnsafe = false;

  nsIChannel* channel = GetCurrentDocChannel();
  if (!channel) {
    return NS_OK;
  }

  nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
  if (!jarChannel) {
    return NS_OK;
  }

  return jarChannel->GetIsUnsafe(aUnsafe);
}

NS_IMETHODIMP
nsDocShell::GetHasMixedActiveContentLoaded(bool* aHasMixedActiveContentLoaded)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasMixedActiveContentLoaded = doc && doc->GetHasMixedActiveContentLoaded();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasMixedActiveContentBlocked(bool* aHasMixedActiveContentBlocked)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasMixedActiveContentBlocked =
    doc && doc->GetHasMixedActiveContentBlocked();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasMixedDisplayContentLoaded(bool* aHasMixedDisplayContentLoaded)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasMixedDisplayContentLoaded =
    doc && doc->GetHasMixedDisplayContentLoaded();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasMixedDisplayContentBlocked(
  bool* aHasMixedDisplayContentBlocked)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasMixedDisplayContentBlocked =
    doc && doc->GetHasMixedDisplayContentBlocked();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasTrackingContentBlocked(bool* aHasTrackingContentBlocked)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasTrackingContentBlocked = doc && doc->GetHasTrackingContentBlocked();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasTrackingContentLoaded(bool* aHasTrackingContentLoaded)
{
  nsCOMPtr<nsIDocument> doc(GetDocument());
  *aHasTrackingContentLoaded = doc && doc->GetHasTrackingContentLoaded();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowPlugins(bool* aAllowPlugins)
{
  NS_ENSURE_ARG_POINTER(aAllowPlugins);

  *aAllowPlugins = mAllowPlugins;
  if (!mAllowPlugins) {
    return NS_OK;
  }

  bool unsafe;
  *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowPlugins(bool aAllowPlugins)
{
  mAllowPlugins = aAllowPlugins;
  // XXX should enable or disable a plugin host
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowJavascript(bool* aAllowJavascript)
{
  NS_ENSURE_ARG_POINTER(aAllowJavascript);

  *aAllowJavascript = mAllowJavascript;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowJavascript(bool aAllowJavascript)
{
  mAllowJavascript = aAllowJavascript;
  RecomputeCanExecuteScripts();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing)
{
  NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
  AssertOriginAttributesMatchPrivateBrowsing();
  *aUsePrivateBrowsing = mPrivateBrowsingId > 0;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing)
{
  nsContentUtils::ReportToConsoleNonLocalized(
    NS_LITERAL_STRING("Only internal code is allowed to set the usePrivateBrowsing attribute"),
    nsIScriptError::warningFlag,
    NS_LITERAL_CSTRING("Internal API Used"),
    mContentViewer ? mContentViewer->GetDocument() : nullptr);

  if (!CanSetOriginAttributes()) {
    bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0);

    return changed ? NS_ERROR_FAILURE : NS_OK;
  }

  return SetPrivateBrowsing(aUsePrivateBrowsing);
}

NS_IMETHODIMP
nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing)
{
  bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0);
  if (changed) {
    mPrivateBrowsingId = aUsePrivateBrowsing ? 1 : 0;

    if (mItemType != typeChrome) {
      mOriginAttributes.SyncAttributesWithPrivateBrowsing(aUsePrivateBrowsing);
    }

    if (mAffectPrivateSessionLifetime) {
      if (aUsePrivateBrowsing) {
        IncreasePrivateDocShellCount();
      } else {
        DecreasePrivateDocShellCount();
      }
    }
  }

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsILoadContext> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      shell->SetPrivateBrowsing(aUsePrivateBrowsing);
    }
  }

  if (changed) {
    nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers);
    while (iter.HasMore()) {
      nsWeakPtr ref = iter.GetNext();
      nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_QueryReferent(ref);
      if (!obs) {
        mPrivacyObservers.RemoveElement(ref);
      } else {
        obs->PrivateModeChanged(aUsePrivateBrowsing);
      }
    }
  }

  AssertOriginAttributesMatchPrivateBrowsing();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasLoadedNonBlankURI(bool* aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  *aResult = mHasLoadedNonBlankURI;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs)
{
  NS_ENSURE_ARG_POINTER(aUseRemoteTabs);

  *aUseRemoteTabs = mUseRemoteTabs;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetRemoteTabs(bool aUseRemoteTabs)
{
#ifdef MOZ_CRASHREPORTER
  if (aUseRemoteTabs) {
    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("DOMIPCEnabled"),
                                       NS_LITERAL_CSTRING("1"));
  }
#endif

  mUseRemoteTabs = aUseRemoteTabs;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime)
{
  bool change = aAffectLifetime != mAffectPrivateSessionLifetime;
  if (change && UsePrivateBrowsing()) {
    AssertOriginAttributesMatchPrivateBrowsing();
    if (aAffectLifetime) {
      IncreasePrivateDocShellCount();
    } else {
      DecreasePrivateDocShellCount();
    }
  }
  mAffectPrivateSessionLifetime = aAffectLifetime;

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      shell->SetAffectPrivateSessionLifetime(aAffectLifetime);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime)
{
  *aAffectLifetime = mAffectPrivateSessionLifetime;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::AddWeakPrivacyTransitionObserver(
    nsIPrivacyTransitionObserver* aObserver)
{
  nsWeakPtr weakObs = do_GetWeakReference(aObserver);
  if (!weakObs) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return mPrivacyObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver)
{
  nsWeakPtr weakObs = do_GetWeakReference(aObserver);
  if (!weakObs) {
    return NS_ERROR_FAILURE;
  }
  return mReflowObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver)
{
  nsWeakPtr obs = do_GetWeakReference(aObserver);
  return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::NotifyReflowObservers(bool aInterruptible,
                                  DOMHighResTimeStamp aStart,
                                  DOMHighResTimeStamp aEnd)
{
  nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mReflowObservers);
  while (iter.HasMore()) {
    nsWeakPtr ref = iter.GetNext();
    nsCOMPtr<nsIReflowObserver> obs = do_QueryReferent(ref);
    if (!obs) {
      mReflowObservers.RemoveElement(ref);
    } else if (aInterruptible) {
      obs->ReflowInterruptible(aStart, aEnd);
    } else {
      obs->Reflow(aStart, aEnd);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowMetaRedirects(bool* aReturn)
{
  NS_ENSURE_ARG_POINTER(aReturn);

  *aReturn = mAllowMetaRedirects;
  if (!mAllowMetaRedirects) {
    return NS_OK;
  }

  bool unsafe;
  *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowMetaRedirects(bool aValue)
{
  mAllowMetaRedirects = aValue;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowSubframes(bool* aAllowSubframes)
{
  NS_ENSURE_ARG_POINTER(aAllowSubframes);

  *aAllowSubframes = mAllowSubframes;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowSubframes(bool aAllowSubframes)
{
  mAllowSubframes = aAllowSubframes;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowImages(bool* aAllowImages)
{
  NS_ENSURE_ARG_POINTER(aAllowImages);

  *aAllowImages = mAllowImages;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowImages(bool aAllowImages)
{
  mAllowImages = aAllowImages;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowMedia(bool* aAllowMedia)
{
  *aAllowMedia = mAllowMedia;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowMedia(bool aAllowMedia)
{
  mAllowMedia = aAllowMedia;

  // Mute or unmute audio contexts attached to the inner window.
  if (mScriptGlobal) {
    if (nsPIDOMWindowInner* innerWin =
        mScriptGlobal->AsOuter()->GetCurrentInnerWindow()) {
      if (aAllowMedia) {
        innerWin->UnmuteAudioContexts();
      } else {
        innerWin->MuteAudioContexts();
      }
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch)
{
  *aAllowDNSPrefetch = mAllowDNSPrefetch;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch)
{
  mAllowDNSPrefetch = aAllowDNSPrefetch;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl)
{
  *aAllowWindowControl = mAllowWindowControl;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowWindowControl(bool aAllowWindowControl)
{
  mAllowWindowControl = aAllowWindowControl;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting)
{
  *aAllowContentRetargeting = mAllowContentRetargeting;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting)
{
  mAllowContentRetargetingOnChildren = aAllowContentRetargeting;
  mAllowContentRetargeting = aAllowContentRetargeting;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowContentRetargetingOnChildren(
    bool* aAllowContentRetargetingOnChildren)
{
  *aAllowContentRetargetingOnChildren = mAllowContentRetargetingOnChildren;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowContentRetargetingOnChildren(
    bool aAllowContentRetargetingOnChildren)
{
  mAllowContentRetargetingOnChildren = aAllowContentRetargetingOnChildren;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetInheritPrivateBrowsingId(bool* aInheritPrivateBrowsingId)
{
  *aInheritPrivateBrowsingId = mInheritPrivateBrowsingId;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetInheritPrivateBrowsingId(bool aInheritPrivateBrowsingId)
{
  mInheritPrivateBrowsingId = aInheritPrivateBrowsingId;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetFullscreenAllowed(bool* aFullscreenAllowed)
{
  NS_ENSURE_ARG_POINTER(aFullscreenAllowed);

  // Browsers and apps have their mFullscreenAllowed retrieved from their
  // corresponding iframe in their parent upon creation.
  if (mFullscreenAllowed != CHECK_ATTRIBUTES) {
    *aFullscreenAllowed = (mFullscreenAllowed == PARENT_ALLOWS);
    return NS_OK;
  }

  // Assume false until we determine otherwise...
  *aFullscreenAllowed = false;

  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
  if (!win) {
    return NS_OK;
  }
  if (nsCOMPtr<Element> frameElement = win->GetFrameElementInternal()) {
    if (frameElement->IsXULElement()) {
      if (frameElement->HasAttr(kNameSpaceID_None,
                                nsGkAtoms::disablefullscreen)) {
        // Document inside this frame is explicitly disabled.
        return NS_OK;
      }
    } else {
      // We do not allow document inside any containing element other
      // than iframe to enter fullscreen.
      if (frameElement->IsHTMLElement(nsGkAtoms::iframe)) {
        // If any ancestor iframe does not have allowfullscreen attribute
        // set, then fullscreen is not allowed.
        if (!frameElement->HasAttr(kNameSpaceID_None,
                                  nsGkAtoms::allowfullscreen) &&
            !frameElement->HasAttr(kNameSpaceID_None,
                                  nsGkAtoms::mozallowfullscreen)) {
          return NS_OK;
        }
      } else if (frameElement->IsHTMLElement(nsGkAtoms::embed)) {
        // Respect allowfullscreen only if this is a rewritten YouTube embed.
        nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent =
          do_QueryInterface(frameElement);
        if (!objectLoadingContent) {
          return NS_OK;
        }
        nsObjectLoadingContent* olc =
          static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
        if (!olc->IsRewrittenYoutubeEmbed()) {
          return NS_OK;
        }
        // We don't have to check prefixed attributes because Flash does not
        // support them.
        if (!frameElement->HasAttr(kNameSpaceID_None,
                                  nsGkAtoms::allowfullscreen)) {
          return NS_OK;
        }
      } else {
        // neither iframe nor embed
        return NS_OK;
      }
    }
  }

  // If we have no parent then we're the root docshell; no ancestor of the
  // original docshell doesn't have a allowfullscreen attribute, so
  // report fullscreen as allowed.
  RefPtr<nsDocShell> parent = GetParentDocshell();
  if (!parent) {
    *aFullscreenAllowed = true;
    return NS_OK;
  }

  // Otherwise, we have a parent, continue the checking for
  // mozFullscreenAllowed in the parent docshell's ancestors.
  return parent->GetFullscreenAllowed(aFullscreenAllowed);
}

NS_IMETHODIMP
nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed)
{
  if (!nsIDocShell::GetIsMozBrowserOrApp()) {
    // Only allow setting of fullscreenAllowed on content/process boundaries.
    // At non-boundaries the fullscreenAllowed attribute is calculated based on
    // whether all enclosing frames have the "mozFullscreenAllowed" attribute
    // set to "true". fullscreenAllowed is set at the process boundaries to
    // propagate the value of the parent's "mozFullscreenAllowed" attribute
    // across process boundaries.
    return NS_ERROR_UNEXPECTED;
  }
  mFullscreenAllowed = (aFullscreenAllowed ? PARENT_ALLOWS : PARENT_PROHIBITS);
  return NS_OK;
}

ScreenOrientationInternal
nsDocShell::OrientationLock()
{
  return mOrientationLock;
}

void
nsDocShell::SetOrientationLock(ScreenOrientationInternal aOrientationLock)
{
  mOrientationLock = aOrientationLock;
}

NS_IMETHODIMP
nsDocShell::GetMayEnableCharacterEncodingMenu(
    bool* aMayEnableCharacterEncodingMenu)
{
  *aMayEnableCharacterEncodingMenu = false;
  if (!mContentViewer) {
    return NS_OK;
  }
  nsIDocument* doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_OK;
  }
  if (doc->WillIgnoreCharsetOverride()) {
    return NS_OK;
  }

  *aMayEnableCharacterEncodingMenu = true;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDocShellEnumerator(int32_t aItemType, int32_t aDirection,
                                  nsISimpleEnumerator** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = nullptr;

  RefPtr<nsDocShellEnumerator> docShellEnum;
  if (aDirection == ENUMERATE_FORWARDS) {
    docShellEnum = new nsDocShellForwardsEnumerator;
  } else {
    docShellEnum = new nsDocShellBackwardsEnumerator;
  }

  nsresult rv = docShellEnum->SetEnumDocShellType(aItemType);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = docShellEnum->SetEnumerationRootItem((nsIDocShellTreeItem*)this);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = docShellEnum->First();
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = docShellEnum->QueryInterface(NS_GET_IID(nsISimpleEnumerator),
                                    (void**)aResult);

  return rv;
}

NS_IMETHODIMP
nsDocShell::GetAppType(uint32_t* aAppType)
{
  *aAppType = mAppType;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAppType(uint32_t aAppType)
{
  mAppType = aAppType;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowAuth(bool* aAllowAuth)
{
  *aAllowAuth = mAllowAuth;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetAllowAuth(bool aAllowAuth)
{
  mAllowAuth = aAllowAuth;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetZoom(float* aZoom)
{
  NS_ENSURE_ARG_POINTER(aZoom);
  *aZoom = 1.0f;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetZoom(float aZoom)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDocShell::GetMarginWidth(int32_t* aWidth)
{
  NS_ENSURE_ARG_POINTER(aWidth);

  *aWidth = mMarginWidth;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetMarginWidth(int32_t aWidth)
{
  mMarginWidth = aWidth;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetMarginHeight(int32_t* aHeight)
{
  NS_ENSURE_ARG_POINTER(aHeight);

  *aHeight = mMarginHeight;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetMarginHeight(int32_t aHeight)
{
  mMarginHeight = aHeight;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetBusyFlags(uint32_t* aBusyFlags)
{
  NS_ENSURE_ARG_POINTER(aBusyFlags);

  *aBusyFlags = mBusyFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, bool* aTookFocus)
{
  NS_ENSURE_ARG_POINTER(aTookFocus);

  nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner);
  if (chromeFocus) {
    if (aForward) {
      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation));
    } else {
      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation));
    }
  } else {
    *aTookFocus = false;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSecurityUI(nsISecureBrowserUI** aSecurityUI)
{
  NS_IF_ADDREF(*aSecurityUI = mSecurityUI);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetSecurityUI(nsISecureBrowserUI* aSecurityUI)
{
  mSecurityUI = aSecurityUI;
  mSecurityUI->SetDocShell(this);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetUseErrorPages(bool* aUseErrorPages)
{
  *aUseErrorPages = UseErrorPages();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetUseErrorPages(bool aUseErrorPages)
{
  // If mUseErrorPages is set explicitly, stop using sUseErrorPages.
  if (mObserveErrorPages) {
    mObserveErrorPages = false;
  }
  mUseErrorPages = aUseErrorPages;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetPreviousTransIndex(int32_t* aPreviousTransIndex)
{
  *aPreviousTransIndex = mPreviousTransIndex;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetLoadedTransIndex(int32_t* aLoadedTransIndex)
{
  *aLoadedTransIndex = mLoadedTransIndex;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::HistoryPurged(int32_t aNumEntries)
{
  // These indices are used for fastback cache eviction, to determine
  // which session history entries are candidates for content viewer
  // eviction.  We need to adjust by the number of entries that we
  // just purged from history, so that we look at the right session history
  // entries during eviction.
  mPreviousTransIndex = std::max(-1, mPreviousTransIndex - aNumEntries);
  mLoadedTransIndex = std::max(0, mLoadedTransIndex - aNumEntries);

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      shell->HistoryPurged(aNumEntries);
    }
  }

  return NS_OK;
}

nsresult
nsDocShell::HistoryTransactionRemoved(int32_t aIndex)
{
  // These indices are used for fastback cache eviction, to determine
  // which session history entries are candidates for content viewer
  // eviction.  We need to adjust by the number of entries that we
  // just purged from history, so that we look at the right session history
  // entries during eviction.
  if (aIndex == mPreviousTransIndex) {
    mPreviousTransIndex = -1;
  } else if (aIndex < mPreviousTransIndex) {
    --mPreviousTransIndex;
  }
  if (mLoadedTransIndex == aIndex) {
    mLoadedTransIndex = 0;
  } else if (aIndex < mLoadedTransIndex) {
    --mLoadedTransIndex;
  }

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      static_cast<nsDocShell*>(shell.get())->HistoryTransactionRemoved(aIndex);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
{
  bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
  if (currentValue == aValue) {
    return NS_OK;
  }

  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
  if (!timelines) {
    return NS_OK;
  }

  if (aValue) {
    MOZ_ASSERT(!timelines->HasConsumer(this));
    timelines->AddConsumer(this);
    MOZ_ASSERT(timelines->HasConsumer(this));
    UseEntryScriptProfiling();
  } else {
    MOZ_ASSERT(timelines->HasConsumer(this));
    timelines->RemoveConsumer(this);
    MOZ_ASSERT(!timelines->HasConsumer(this));
    UnuseEntryScriptProfiling();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
{
  *aValue = !!mObserved;
  return NS_OK;
}

nsresult
nsDocShell::PopProfileTimelineMarkers(
    JSContext* aCx,
    JS::MutableHandle<JS::Value> aOut)
{
  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
  if (!timelines) {
    return NS_OK;
  }

  nsTArray<dom::ProfileTimelineMarker> store;
  SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store);

  timelines->PopMarkers(this, aCx, store);

  if (!ToJSValue(aCx, store, aOut)) {
    JS_ClearPendingException(aCx);
    return NS_ERROR_UNEXPECTED;
  }

  return NS_OK;
}

nsresult
nsDocShell::Now(DOMHighResTimeStamp* aWhen)
{
  bool ignore;
  *aWhen =
    (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetWindowDraggingAllowed(bool aValue)
{
  RefPtr<nsDocShell> parent = GetParentDocshell();
  if (!aValue && mItemType == typeChrome && !parent) {
    // Window dragging is always allowed for top level
    // chrome docshells.
    return NS_ERROR_FAILURE;
  }
  mWindowDraggingAllowed = aValue;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetWindowDraggingAllowed(bool* aValue)
{
  // window dragging regions in CSS (-moz-window-drag:drag)
  // can be slow. Default behavior is to only allow it for
  // chrome top level windows.
  RefPtr<nsDocShell> parent = GetParentDocshell();
  if (mItemType == typeChrome && !parent) {
    // Top level chrome window
    *aValue = true;
  } else {
    *aValue = mWindowDraggingAllowed;
  }
  return NS_OK;
}

nsIDOMStorageManager*
nsDocShell::TopSessionStorageManager()
{
  nsresult rv;

  nsCOMPtr<nsIDocShellTreeItem> topItem;
  rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem));
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  if (!topItem) {
    return nullptr;
  }

  nsDocShell* topDocShell = static_cast<nsDocShell*>(topItem.get());
  if (topDocShell != this) {
    return topDocShell->TopSessionStorageManager();
  }

  if (!mSessionStorageManager) {
    mSessionStorageManager =
      do_CreateInstance("@mozilla.org/dom/sessionStorage-manager;1");
  }

  return mSessionStorageManager;
}

NS_IMETHODIMP
nsDocShell::GetSessionStorageForPrincipal(nsIPrincipal* aPrincipal,
                                          const nsAString& aDocumentURI,
                                          bool aCreate,
                                          nsIDOMStorage** aStorage)
{
  nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager();
  if (!manager) {
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow();

  AssertOriginAttributesMatchPrivateBrowsing();
  if (aCreate) {
    return manager->CreateStorage(domWin->GetCurrentInnerWindow(), aPrincipal,
                                  aDocumentURI, UsePrivateBrowsing(), aStorage);
  }

  return manager->GetStorage(domWin->GetCurrentInnerWindow(), aPrincipal,
                             UsePrivateBrowsing(), aStorage);
}

nsresult
nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal, nsIDOMStorage* aStorage)
{
  RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage);
  if (!storage) {
    return NS_ERROR_UNEXPECTED;
  }

  nsIPrincipal* storagePrincipal = storage->GetPrincipal();
  if (storagePrincipal != aPrincipal) {
    NS_ERROR("Wanting to add a sessionStorage for different principal");
    return NS_ERROR_DOM_SECURITY_ERR;
  }

  nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager();
  if (!manager) {
    return NS_ERROR_UNEXPECTED;
  }

  return manager->CloneStorage(aStorage);
}

NS_IMETHODIMP
nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult)
{
  NS_IF_ADDREF(*aResult = GetCurrentDocChannel());
  return NS_OK;
}

nsIChannel*
nsDocShell::GetCurrentDocChannel()
{
  if (mContentViewer) {
    nsIDocument* doc = mContentViewer->GetDocument();
    if (doc) {
      return doc->GetChannel();
    }
  }
  return nullptr;
}

NS_IMETHODIMP
nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver)
{
  nsWeakPtr weakObs = do_GetWeakReference(aObserver);
  if (!weakObs) {
    return NS_ERROR_FAILURE;
  }
  return mScrollObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver)
{
  nsWeakPtr obs = do_GetWeakReference(aObserver);
  return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
}

void
nsDocShell::NotifyAsyncPanZoomStarted()
{
  nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
  while (iter.HasMore()) {
    nsWeakPtr ref = iter.GetNext();
    nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
    if (obs) {
      obs->AsyncPanZoomStarted();
    } else {
      mScrollObservers.RemoveElement(ref);
    }
  }
}

void
nsDocShell::NotifyAsyncPanZoomStopped()
{
  nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
  while (iter.HasMore()) {
    nsWeakPtr ref = iter.GetNext();
    nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
    if (obs) {
      obs->AsyncPanZoomStopped();
    } else {
      mScrollObservers.RemoveElement(ref);
    }
  }
}

NS_IMETHODIMP
nsDocShell::NotifyScrollObservers()
{
  nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
  while (iter.HasMore()) {
    nsWeakPtr ref = iter.GetNext();
    nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
    if (obs) {
      obs->ScrollPositionChanged();
    } else {
      mScrollObservers.RemoveElement(ref);
    }
  }
  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIDocShellTreeItem
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::GetName(nsAString& aName)
{
  aName = mName;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetName(const nsAString& aName)
{
  mName = aName;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::NameEquals(const nsAString& aName, bool* aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = mName.Equals(aName);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent)
{
  aCustomUserAgent = mCustomUserAgent;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent)
{
  mCustomUserAgent = aCustomUserAgent;
  RefPtr<nsGlobalWindow> win = mScriptGlobal ?
    mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
  if (win) {
    ErrorResult ignored;
    Navigator* navigator = win->GetNavigator(ignored);
    ignored.SuppressException();
    if (navigator) {
      navigator->ClearUserAgentCache();
    }
  }

  uint32_t childCount = mChildList.Length();
  for (uint32_t i = 0; i < childCount; ++i) {
    nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i));
    if (childShell) {
      childShell->SetCustomUserAgent(aCustomUserAgent);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetTouchEventsOverride(uint32_t* aTouchEventsOverride)
{
  NS_ENSURE_ARG_POINTER(aTouchEventsOverride);

  *aTouchEventsOverride = mTouchEventsOverride;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetTouchEventsOverride(uint32_t aTouchEventsOverride)
{
  if (!(aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE ||
        aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED ||
        aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_DISABLED)) {
    return NS_ERROR_INVALID_ARG;
  }

  mTouchEventsOverride = aTouchEventsOverride;

  uint32_t childCount = mChildList.Length();
  for (uint32_t i = 0; i < childCount; ++i) {
    nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i));
    if (childShell) {
      childShell->SetTouchEventsOverride(aTouchEventsOverride);
    }
  }
  return NS_OK;
}

/* virtual */ int32_t
nsDocShell::ItemType()
{
  return mItemType;
}

NS_IMETHODIMP
nsDocShell::GetItemType(int32_t* aItemType)
{
  NS_ENSURE_ARG_POINTER(aItemType);

  *aItemType = ItemType();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetItemType(int32_t aItemType)
{
  NS_ENSURE_ARG((aItemType == typeChrome) || (typeContent == aItemType));

  // Only allow setting the type on root docshells.  Those would be the ones
  // that have the docloader service as mParent or have no mParent at all.
  nsCOMPtr<nsIDocumentLoader> docLoaderService =
    do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
  NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED);

  NS_ENSURE_STATE(!mParent || mParent == docLoaderService);

  mItemType = aItemType;

  // disable auth prompting for anything but content
  mAllowAuth = mItemType == typeContent;

  RefPtr<nsPresContext> presContext = nullptr;
  GetPresContext(getter_AddRefs(presContext));
  if (presContext) {
    presContext->UpdateIsChrome();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetParent(nsIDocShellTreeItem** aParent)
{
  if (!mParent) {
    *aParent = nullptr;
  } else {
    CallQueryInterface(mParent, aParent);
  }
  // Note that in the case when the parent is not an nsIDocShellTreeItem we
  // don't want to throw; we just want to return null.
  return NS_OK;
}

already_AddRefed<nsDocShell>
nsDocShell::GetParentDocshell()
{
  nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
  return docshell.forget().downcast<nsDocShell>();
}

void
nsDocShell::RecomputeCanExecuteScripts()
{
  bool old = mCanExecuteScripts;
  RefPtr<nsDocShell> parent = GetParentDocshell();

  // If we have no tree owner, that means that we've been detached from the
  // docshell tree (this is distinct from having no parent dochshell, which
  // is the case for root docshells). It would be nice to simply disallow
  // script in detached docshells, but bug 986542 demonstrates that this
  // behavior breaks at least one website.
  //
  // So instead, we use our previous value, unless mAllowJavascript has been
  // explicitly set to false.
  if (!mTreeOwner) {
    mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript;
    // If scripting has been explicitly disabled on our docshell, we're done.
  } else if (!mAllowJavascript) {
    mCanExecuteScripts = false;
    // If we have a parent, inherit.
  } else if (parent) {
    mCanExecuteScripts = parent->mCanExecuteScripts;
    // Otherwise, we're the root of the tree, and we haven't explicitly disabled
    // script. Allow.
  } else {
    mCanExecuteScripts = true;
  }

  // Inform our active DOM window.
  //
  // This will pass the outer, which will be in the scope of the active inner.
  if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) {
    xpc::Scriptability& scriptability =
      xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject());
    scriptability.SetDocShellAllowsScript(mCanExecuteScripts);
  }

  // If our value has changed, our children might be affected. Recompute their
  // value as well.
  if (old != mCanExecuteScripts) {
    nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
    while (iter.HasMore()) {
      static_cast<nsDocShell*>(iter.GetNext())->RecomputeCanExecuteScripts();
    }
  }
}

nsresult
nsDocShell::SetDocLoaderParent(nsDocLoader* aParent)
{
  bool wasFrame = IsFrame();
#ifdef DEBUG
  bool wasPrivate = UsePrivateBrowsing();
#endif

  nsresult rv = nsDocLoader::SetDocLoaderParent(aParent);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup);
  if (wasFrame != IsFrame() && priorityGroup) {
    priorityGroup->AdjustPriority(wasFrame ? -1 : 1);
  }

  // Curse ambiguous nsISupports inheritance!
  nsISupports* parent = GetAsSupports(aParent);

  // If parent is another docshell, we inherit all their flags for
  // allowing plugins, scripting etc.
  bool value;
  nsString customUserAgent;
  nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));
  if (parentAsDocShell) {
    if (mAllowPlugins && NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) {
      SetAllowPlugins(value);
    }
    if (mAllowJavascript && NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) {
      SetAllowJavascript(value);
    }
    if (mAllowMetaRedirects && NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) {
      SetAllowMetaRedirects(value);
    }
    if (mAllowSubframes && NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) {
      SetAllowSubframes(value);
    }
    if (mAllowImages && NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) {
      SetAllowImages(value);
    }
    SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia);
    if (mAllowWindowControl && NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) {
      SetAllowWindowControl(value);
    }
    SetAllowContentRetargeting(mAllowContentRetargeting &&
      parentAsDocShell->GetAllowContentRetargetingOnChildren());
    if (parentAsDocShell->GetIsPrerendered()) {
      SetIsPrerendered();
    }
    if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) {
      // a prerendered docshell is not active yet
      SetIsActive(value && !mIsPrerendered);
    }
    if (NS_SUCCEEDED(parentAsDocShell->GetCustomUserAgent(customUserAgent)) &&
        !customUserAgent.IsEmpty()) {
      SetCustomUserAgent(customUserAgent);
    }
    if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) {
      value = false;
    }
    SetAllowDNSPrefetch(mAllowDNSPrefetch && value);
    if (mInheritPrivateBrowsingId) {
      value = parentAsDocShell->GetAffectPrivateSessionLifetime();
      SetAffectPrivateSessionLifetime(value);
    }
    uint32_t flags;
    if (NS_SUCCEEDED(parentAsDocShell->GetDefaultLoadFlags(&flags))) {
      SetDefaultLoadFlags(flags);
    }
    uint32_t touchEventsOverride;
    if (NS_SUCCEEDED(parentAsDocShell->GetTouchEventsOverride(&touchEventsOverride))) {
      SetTouchEventsOverride(touchEventsOverride);
    }
  }

  nsCOMPtr<nsILoadContext> parentAsLoadContext(do_QueryInterface(parent));
  if (parentAsLoadContext && mInheritPrivateBrowsingId &&
      NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value))) {
    SetPrivateBrowsing(value);
  }

  nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
  if (parentURIListener) {
    mContentListener->SetParentContentListener(parentURIListener);
  }

  // Our parent has changed. Recompute scriptability.
  RecomputeCanExecuteScripts();

  NS_ASSERTION(mInheritPrivateBrowsingId || wasPrivate == UsePrivateBrowsing(),
               "Private browsing state changed while inheritance was disabled");

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent)
{
  NS_ENSURE_ARG_POINTER(aParent);
  *aParent = nullptr;

  if (nsIDocShell::GetIsMozBrowserOrApp()) {
    return NS_OK;
  }

  nsCOMPtr<nsIDocShellTreeItem> parent =
    do_QueryInterface(GetAsSupports(mParent));
  if (!parent) {
    return NS_OK;
  }

  if (parent->ItemType() == mItemType) {
    parent.swap(*aParent);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSameTypeParentIgnoreBrowserAndAppBoundaries(nsIDocShell** aParent)
{
  NS_ENSURE_ARG_POINTER(aParent);
  *aParent = nullptr;

  nsCOMPtr<nsIDocShellTreeItem> parent =
    do_QueryInterface(GetAsSupports(mParent));
  if (!parent) {
    return NS_OK;
  }

  if (parent->ItemType() == mItemType) {
    nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent);
    parentDS.forget(aParent);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
{
  NS_ENSURE_ARG_POINTER(aRootTreeItem);

  RefPtr<nsDocShell> root = this;
  RefPtr<nsDocShell> parent = root->GetParentDocshell();
  while (parent) {
    root = parent;
    parent = root->GetParentDocshell();
  }

  root.forget(aRootTreeItem);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
{
  NS_ENSURE_ARG_POINTER(aRootTreeItem);
  *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);

  nsCOMPtr<nsIDocShellTreeItem> parent;
  NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)),
                    NS_ERROR_FAILURE);
  while (parent) {
    *aRootTreeItem = parent;
    NS_ENSURE_SUCCESS(
      (*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)),
      NS_ERROR_FAILURE);
  }
  NS_ADDREF(*aRootTreeItem);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(nsIDocShell ** aRootTreeItem)
{
    NS_ENSURE_ARG_POINTER(aRootTreeItem);
    *aRootTreeItem = static_cast<nsIDocShell *>(this);

    nsCOMPtr<nsIDocShell> parent;
    NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
                      NS_ERROR_FAILURE);
    while (parent) {
      *aRootTreeItem = parent;
      NS_ENSURE_SUCCESS((*aRootTreeItem)->
        GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
        NS_ERROR_FAILURE);
    }
    NS_ADDREF(*aRootTreeItem);
    return NS_OK;
}

/* static */
bool
nsDocShell::CanAccessItem(nsIDocShellTreeItem* aTargetItem,
                          nsIDocShellTreeItem* aAccessingItem,
                          bool aConsiderOpener)
{
  NS_PRECONDITION(aTargetItem, "Must have target item!");

  if (!gValidateOrigin || !aAccessingItem) {
    // Good to go
    return true;
  }

  // XXXbz should we care if aAccessingItem or the document therein is
  // chrome?  Should those get extra privileges?

  // For historical context, see:
  //
  // Bug 13871:  Prevent frameset spoofing
  // Bug 103638: Targets with same name in different windows open in wrong
  //             window with javascript
  // Bug 408052: Adopt "ancestor" frame navigation policy

  // Now do a security check.
  //
  // Disallow navigation if the two frames are not part of the same app, or if
  // they have different is-in-browser-element states.
  //
  // Allow navigation if
  //  1) aAccessingItem can script aTargetItem or one of its ancestors in
  //     the frame hierarchy or
  //  2) aTargetItem is a top-level frame and aAccessingItem is its descendant
  //  3) aTargetItem is a top-level frame and aAccessingItem can target
  //     its opener per rule (1) or (2).

  if (aTargetItem == aAccessingItem) {
    // A frame is allowed to navigate itself.
    return true;
  }

  nsCOMPtr<nsIDocShell> targetDS = do_QueryInterface(aTargetItem);
  nsCOMPtr<nsIDocShell> accessingDS = do_QueryInterface(aAccessingItem);
  if (!targetDS || !accessingDS) {
    // We must be able to convert both to nsIDocShell.
    return false;
  }

  if (targetDS->GetIsInIsolatedMozBrowserElement() !=
        accessingDS->GetIsInIsolatedMozBrowserElement() ||
      targetDS->GetAppId() != accessingDS->GetAppId()) {
    return false;
  }

  nsCOMPtr<nsIDocShellTreeItem> accessingRoot;
  aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot));
  nsCOMPtr<nsIDocShell> accessingRootDS = do_QueryInterface(accessingRoot);

  nsCOMPtr<nsIDocShellTreeItem> targetRoot;
  aTargetItem->GetSameTypeRootTreeItem(getter_AddRefs(targetRoot));
  nsCOMPtr<nsIDocShell> targetRootDS = do_QueryInterface(targetRoot);

  DocShellOriginAttributes targetOA =
    static_cast<nsDocShell*>(targetDS.get())->GetOriginAttributes();
  DocShellOriginAttributes accessingOA =
    static_cast<nsDocShell*>(accessingDS.get())->GetOriginAttributes();

  // When the first party isolation is on, the top-level docShell may not have
  // the firstPartyDomain in its originAttributes, but its document will have
  // it. So we get the firstPartyDomain from the nodePrincipal of the document
  // before we compare the originAttributes.
  if (OriginAttributes::IsFirstPartyEnabled()) {
    if (accessingDS == accessingRootDS &&
        aAccessingItem->ItemType() == nsIDocShellTreeItem::typeContent &&
        !accessingDS->GetIsMozBrowserOrApp()) {

      nsCOMPtr<nsIDocument> accessingDoc = aAccessingItem->GetDocument();

      if (accessingDoc) {
        nsCOMPtr<nsIPrincipal> accessingPrincipal = accessingDoc->NodePrincipal();

        accessingOA.mFirstPartyDomain =
          BasePrincipal::Cast(accessingPrincipal)->OriginAttributesRef().mFirstPartyDomain;
      }
    }

    if (targetDS == targetRootDS &&
        aTargetItem->ItemType() == nsIDocShellTreeItem::typeContent &&
        !targetDS->GetIsMozBrowserOrApp()) {

      nsCOMPtr<nsIDocument> targetDoc = aAccessingItem->GetDocument();

      if (targetDoc) {
        nsCOMPtr<nsIPrincipal> targetPrincipal = targetDoc->NodePrincipal();

        targetOA.mFirstPartyDomain =
          BasePrincipal::Cast(targetPrincipal)->OriginAttributesRef().mFirstPartyDomain;
      }
    }
  }

  if (targetOA != accessingOA) {
    return false;
  }

  // A private document can't access a non-private one, and vice versa.
  if (static_cast<nsDocShell*>(targetDS.get())->UsePrivateBrowsing() !=
      static_cast<nsDocShell*>(accessingDS.get())->UsePrivateBrowsing()) {
    return false;
  }

  if (aTargetItem == accessingRoot) {
    // A frame can navigate its root.
    return true;
  }

  // Check if aAccessingItem can navigate one of aTargetItem's ancestors.
  nsCOMPtr<nsIDocShellTreeItem> target = aTargetItem;
  do {
    if (ValidateOrigin(aAccessingItem, target)) {
      return true;
    }

    nsCOMPtr<nsIDocShellTreeItem> parent;
    target->GetSameTypeParent(getter_AddRefs(parent));
    parent.swap(target);
  } while (target);

  if (aTargetItem != targetRoot) {
    // target is a subframe, not in accessor's frame hierarchy, and all its
    // ancestors have origins different from that of the accessor. Don't
    // allow access.
    return false;
  }

  if (!aConsiderOpener) {
    // All done here
    return false;
  }

  nsCOMPtr<nsPIDOMWindowOuter> targetWindow = aTargetItem->GetWindow();
  if (!targetWindow) {
    NS_ERROR("This should not happen, really");
    return false;
  }

  nsCOMPtr<mozIDOMWindowProxy> targetOpener = targetWindow->GetOpener();
  nsCOMPtr<nsIWebNavigation> openerWebNav(do_GetInterface(targetOpener));
  nsCOMPtr<nsIDocShellTreeItem> openerItem(do_QueryInterface(openerWebNav));

  if (!openerItem) {
    return false;
  }

  return CanAccessItem(openerItem, aAccessingItem, false);
}

static bool
ItemIsActive(nsIDocShellTreeItem* aItem)
{
  if (nsCOMPtr<nsPIDOMWindowOuter> window = aItem->GetWindow()) {
    auto* win = nsGlobalWindow::Cast(window);
    MOZ_ASSERT(win->IsOuterWindow());
    if (!win->GetClosedOuter()) {
      return true;
    }
  }

  return false;
}

NS_IMETHODIMP
nsDocShell::FindItemWithName(const nsAString& aName,
                             nsISupports* aRequestor,
                             nsIDocShellTreeItem* aOriginalRequestor,
                             nsIDocShellTreeItem** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  // If we don't find one, we return NS_OK and a null result
  *aResult = nullptr;

  if (aName.IsEmpty()) {
    return NS_OK;
  }

  if (aRequestor) {
    // If aRequestor is not null we don't need to check special names, so
    // just hand straight off to the search by actual name function.
    return DoFindItemWithName(aName, aRequestor, aOriginalRequestor, aResult);
  } else {
    // This is the entry point into the target-finding algorithm.  Check
    // for special names.  This should only be done once, hence the check
    // for a null aRequestor.

    nsCOMPtr<nsIDocShellTreeItem> foundItem;
    if (aName.LowerCaseEqualsLiteral("_self")) {
      foundItem = this;
    } else if (aName.LowerCaseEqualsLiteral("_blank")) {
      // Just return null.  Caller must handle creating a new window with
      // a blank name himself.
      return NS_OK;
    } else if (aName.LowerCaseEqualsLiteral("_parent")) {
      GetSameTypeParent(getter_AddRefs(foundItem));
      if (!foundItem) {
        foundItem = this;
      }
    } else if (aName.LowerCaseEqualsLiteral("_top")) {
      GetSameTypeRootTreeItem(getter_AddRefs(foundItem));
      NS_ASSERTION(foundItem, "Must have this; worst case it's us!");
    } else {
      // Do the search for item by an actual name.
      DoFindItemWithName(aName, aRequestor, aOriginalRequestor,
                         getter_AddRefs(foundItem));
    }

    if (foundItem && !CanAccessItem(foundItem, aOriginalRequestor)) {
      foundItem = nullptr;
    }

    // DoFindItemWithName only returns active items and we don't check if
    // the item is active for the special cases.
    if (foundItem) {
      foundItem.swap(*aResult);
    }
    return NS_OK;
  }
}

void
nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() {
  // Chrome docshells must not have a private browsing OriginAttribute
  // Content docshells must maintain the equality:
  // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId
  if (mItemType == typeChrome) {
    MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0);
  } else {
    MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId);
  }
}

nsresult
nsDocShell::DoFindItemWithName(const nsAString& aName,
                               nsISupports* aRequestor,
                               nsIDocShellTreeItem* aOriginalRequestor,
                               nsIDocShellTreeItem** aResult)
{
  // First we check our name.
  if (mName.Equals(aName) && ItemIsActive(this) &&
      CanAccessItem(this, aOriginalRequestor)) {
    NS_ADDREF(*aResult = this);
    return NS_OK;
  }

  // This QI may fail, but the places where we want to compare, comparing
  // against nullptr serves the same purpose.
  nsCOMPtr<nsIDocShellTreeItem> reqAsTreeItem(do_QueryInterface(aRequestor));

  // Second we check our children making sure not to ask a child if
  // it is the aRequestor.
#ifdef DEBUG
  nsresult rv =
#endif
  FindChildWithName(aName, true, true, reqAsTreeItem, aOriginalRequestor,
                    aResult);
  NS_ASSERTION(NS_SUCCEEDED(rv),
               "FindChildWithName should not be failing here.");
  if (*aResult) {
    return NS_OK;
  }

  // Third if we have a parent and it isn't the requestor then we
  // should ask it to do the search.  If it is the requestor we
  // should just stop here and let the parent do the rest.  If we
  // don't have a parent, then we should ask the
  // docShellTreeOwner to do the search.
  nsCOMPtr<nsIDocShellTreeItem> parentAsTreeItem =
    do_QueryInterface(GetAsSupports(mParent));
  if (parentAsTreeItem) {
    if (parentAsTreeItem == reqAsTreeItem) {
      return NS_OK;
    }

    // If we have a same-type parent, respecting browser and app boundaries.
    // NOTE: Could use GetSameTypeParent if the issues described in bug 1310344 are fixed.
    if (!GetIsMozBrowserOrApp() && parentAsTreeItem->ItemType() == mItemType) {
      return parentAsTreeItem->FindItemWithName(
        aName,
        static_cast<nsIDocShellTreeItem*>(this),
        aOriginalRequestor,
        aResult);
    }
  }

  // If we have a null parent or the parent is not of the same type, we need to
  // give up on finding it in our tree, and start looking in our TabGroup.
  nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
  if (window) {
    RefPtr<mozilla::dom::TabGroup> tabGroup = window->TabGroup();
    // We don't want to make the request to our TabGroup if they are the ones
    // which made a request to us.
    if (tabGroup != aRequestor) {
      tabGroup->FindItemWithName(aName, this, aOriginalRequestor, aResult);
    }
  }

  return NS_OK;
}

bool
nsDocShell::IsSandboxedFrom(nsIDocShell* aTargetDocShell)
{
  // If no target then not sandboxed.
  if (!aTargetDocShell) {
    return false;
  }

  // We cannot be sandboxed from ourselves.
  if (aTargetDocShell == this) {
    return false;
  }

  // Default the sandbox flags to our flags, so that if we can't retrieve the
  // active document, we will still enforce our own.
  uint32_t sandboxFlags = mSandboxFlags;
  if (mContentViewer) {
    nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
    if (doc) {
      sandboxFlags = doc->GetSandboxFlags();
    }
  }

  // If no flags, we are not sandboxed at all.
  if (!sandboxFlags) {
    return false;
  }

  // If aTargetDocShell has an ancestor, it is not top level.
  nsCOMPtr<nsIDocShellTreeItem> ancestorOfTarget;
  aTargetDocShell->GetSameTypeParent(getter_AddRefs(ancestorOfTarget));
  if (ancestorOfTarget) {
    do {
      // We are not sandboxed if we are an ancestor of target.
      if (ancestorOfTarget == this) {
        return false;
      }
      nsCOMPtr<nsIDocShellTreeItem> tempTreeItem;
      ancestorOfTarget->GetSameTypeParent(getter_AddRefs(tempTreeItem));
      tempTreeItem.swap(ancestorOfTarget);
    } while (ancestorOfTarget);

    // Otherwise, we are sandboxed from aTargetDocShell.
    return true;
  }

  // aTargetDocShell is top level, are we the "one permitted sandboxed
  // navigator", i.e. did we open aTargetDocShell?
  nsCOMPtr<nsIDocShell> permittedNavigator;
  aTargetDocShell->GetOnePermittedSandboxedNavigator(
    getter_AddRefs(permittedNavigator));
  if (permittedNavigator == this) {
    return false;
  }

  // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed
  // from our top.
  if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) {
    nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
    GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem));
    if (SameCOMIdentity(aTargetDocShell, rootTreeItem)) {
      return false;
    }
  }

  // Otherwise, we are sandboxed from aTargetDocShell.
  return true;
}

NS_IMETHODIMP
nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner)
{
  NS_ENSURE_ARG_POINTER(aTreeOwner);

  *aTreeOwner = mTreeOwner;
  NS_IF_ADDREF(*aTreeOwner);
  return NS_OK;
}

#ifdef DEBUG_DOCSHELL_FOCUS
static void
PrintDocTree(nsIDocShellTreeItem* aParentNode, int aLevel)
{
  for (int32_t i = 0; i < aLevel; i++) {
    printf("  ");
  }

  int32_t childWebshellCount;
  aParentNode->GetChildCount(&childWebshellCount);
  nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(aParentNode));
  int32_t type = aParentNode->ItemType();
  nsCOMPtr<nsIPresShell> presShell = parentAsDocShell->GetPresShell();
  RefPtr<nsPresContext> presContext;
  parentAsDocShell->GetPresContext(getter_AddRefs(presContext));
  nsIDocument* doc = presShell->GetDocument();

  nsCOMPtr<nsPIDOMWindowOuter> domwin(doc->GetWindow());

  nsCOMPtr<nsIWidget> widget;
  nsViewManager* vm = presShell->GetViewManager();
  if (vm) {
    vm->GetWidget(getter_AddRefs(widget));
  }
  dom::Element* rootElement = doc->GetRootElement();

  printf("DS %p  Ty %s  Doc %p DW %p EM %p CN %p\n",
         (void*)parentAsDocShell.get(),
         type == nsIDocShellTreeItem::typeChrome ? "Chr" : "Con",
         (void*)doc, (void*)domwin.get(),
         (void*)presContext->EventStateManager(), (void*)rootElement);

  if (childWebshellCount > 0) {
    for (int32_t i = 0; i < childWebshellCount; i++) {
      nsCOMPtr<nsIDocShellTreeItem> child;
      aParentNode->GetChildAt(i, getter_AddRefs(child));
      PrintDocTree(child, aLevel + 1);
    }
  }
}

static void
PrintDocTree(nsIDocShellTreeItem* aParentNode)
{
  NS_ASSERTION(aParentNode, "Pointer is null!");

  nsCOMPtr<nsIDocShellTreeItem> parentItem;
  aParentNode->GetParent(getter_AddRefs(parentItem));
  while (parentItem) {
    nsCOMPtr<nsIDocShellTreeItem> tmp;
    parentItem->GetParent(getter_AddRefs(tmp));
    if (!tmp) {
      break;
    }
    parentItem = tmp;
  }

  if (!parentItem) {
    parentItem = aParentNode;
  }

  PrintDocTree(parentItem, 0);
}
#endif

NS_IMETHODIMP
nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner)
{
#ifdef DEBUG_DOCSHELL_FOCUS
  nsCOMPtr<nsIDocShellTreeItem> item(do_QueryInterface(aTreeOwner));
  if (item) {
    PrintDocTree(item);
  }
#endif

  // Don't automatically set the progress based on the tree owner for frames
  if (!IsFrame()) {
    nsCOMPtr<nsIWebProgress> webProgress =
      do_QueryInterface(GetAsSupports(this));

    if (webProgress) {
      nsCOMPtr<nsIWebProgressListener> oldListener =
        do_QueryInterface(mTreeOwner);
      nsCOMPtr<nsIWebProgressListener> newListener =
        do_QueryInterface(aTreeOwner);

      if (oldListener) {
        webProgress->RemoveProgressListener(oldListener);
      }

      if (newListener) {
        webProgress->AddProgressListener(newListener,
                                         nsIWebProgress::NOTIFY_ALL);
      }
    }
  }

  mTreeOwner = aTreeOwner;  // Weak reference per API

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
    NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);

    if (child->ItemType() == mItemType) {
      child->SetTreeOwner(aTreeOwner);
    }
  }

  // Our tree owner has changed. Recompute scriptability.
  //
  // Note that this is near-redundant with the recomputation in
  // SetDocLoaderParent(), but not so for the root DocShell, where the call to
  // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
  // and we never set another parent. Given that this is neither expensive nor
  // performance-critical, let's be safe and unconditionally recompute this
  // state whenever dependent state changes.
  RecomputeCanExecuteScripts();

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetChildOffset(uint32_t aChildOffset)
{
  mChildOffset = aChildOffset;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHistoryID(uint64_t* aID)
{
  *aID = mHistoryID;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsInUnload(bool* aIsInUnload)
{
  *aIsInUnload = mFiredUnloadEvent;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetChildCount(int32_t* aChildCount)
{
  NS_ENSURE_ARG_POINTER(aChildCount);
  *aChildCount = mChildList.Length();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::AddChild(nsIDocShellTreeItem* aChild)
{
  NS_ENSURE_ARG_POINTER(aChild);

  RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild);
  NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED);

  // Make sure we're not creating a loop in the docshell tree
  nsDocLoader* ancestor = this;
  do {
    if (childAsDocLoader == ancestor) {
      return NS_ERROR_ILLEGAL_VALUE;
    }
    ancestor = ancestor->GetParent();
  } while (ancestor);

  // Make sure to remove the child from its current parent.
  nsDocLoader* childsParent = childAsDocLoader->GetParent();
  if (childsParent) {
    nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Make sure to clear the treeowner in case this child is a different type
  // from us.
  aChild->SetTreeOwner(nullptr);

  nsresult res = AddChildLoader(childAsDocLoader);
  NS_ENSURE_SUCCESS(res, res);
  NS_ASSERTION(!mChildList.IsEmpty(),
               "child list must not be empty after a successful add");

  nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(aChild);
  bool dynamic = false;
  childDocShell->GetCreatedDynamically(&dynamic);
  if (!dynamic) {
    nsCOMPtr<nsISHEntry> currentSH;
    bool oshe = false;
    GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
    if (currentSH) {
      currentSH->HasDynamicallyAddedChild(&dynamic);
    }
  }
  childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1);

  /* Set the child's global history if the parent has one */
  if (mUseGlobalHistory) {
    childDocShell->SetUseGlobalHistory(true);
  }

  if (aChild->ItemType() != mItemType) {
    return NS_OK;
  }

  aChild->SetTreeOwner(mTreeOwner);

  nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
  if (!childAsDocShell) {
    return NS_OK;
  }

  // charset, style-disabling, and zoom will be inherited in SetupNewViewer()

  // Now take this document's charset and set the child's parentCharset field
  // to it. We'll later use that field, in the loading process, for the
  // charset choosing algorithm.
  // If we fail, at any point, we just return NS_OK.
  // This code has some performance impact. But this will be reduced when
  // the current charset will finally be stored as an Atom, avoiding the
  // alias resolution extra look-up.

  // we are NOT going to propagate the charset is this Chrome's docshell
  if (mItemType == nsIDocShellTreeItem::typeChrome) {
    return NS_OK;
  }

  // get the parent's current charset
  if (!mContentViewer) {
    return NS_OK;
  }
  nsIDocument* doc = mContentViewer->GetDocument();
  if (!doc) {
    return NS_OK;
  }

  bool isWyciwyg = false;

  if (mCurrentURI) {
    // Check if the url is wyciwyg
    mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg);
  }

  if (!isWyciwyg) {
    // If this docshell is loaded from a wyciwyg: URI, don't
    // advertise our charset since it does not in any way reflect
    // the actual source charset, which is what we're trying to
    // expose here.

    const nsACString& parentCS = doc->GetDocumentCharacterSet();
    int32_t charsetSource = doc->GetDocumentCharacterSetSource();
    // set the child's parentCharset
    childAsDocShell->SetParentCharset(parentCS,
                                      charsetSource,
                                      doc->NodePrincipal());
  }

  // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n",
  //        NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild)
{
  NS_ENSURE_ARG_POINTER(aChild);

  RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild);
  NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED);

  nsresult rv = RemoveChildLoader(childAsDocLoader);
  NS_ENSURE_SUCCESS(rv, rv);

  aChild->SetTreeOwner(nullptr);

  return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader);
}

NS_IMETHODIMP
nsDocShell::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild)
{
  NS_ENSURE_ARG_POINTER(aChild);

#ifdef DEBUG
  if (aIndex < 0) {
    NS_WARNING("Negative index passed to GetChildAt");
  } else if (static_cast<uint32_t>(aIndex) >= mChildList.Length()) {
    NS_WARNING("Too large an index passed to GetChildAt");
  }
#endif

  nsIDocumentLoader* child = ChildAt(aIndex);
  NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED);

  return CallQueryInterface(child, aChild);
}

NS_IMETHODIMP
nsDocShell::FindChildWithName(const nsAString& aName,
                              bool aRecurse, bool aSameType,
                              nsIDocShellTreeItem* aRequestor,
                              nsIDocShellTreeItem* aOriginalRequestor,
                              nsIDocShellTreeItem** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  // if we don't find one, we return NS_OK and a null result
  *aResult = nullptr;

  if (aName.IsEmpty()) {
    return NS_OK;
  }

  nsXPIDLString childName;
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
    NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
    int32_t childType = child->ItemType();

    if (aSameType && (childType != mItemType)) {
      continue;
    }

    bool childNameEquals = false;
    child->NameEquals(aName, &childNameEquals);
    if (childNameEquals && ItemIsActive(child) &&
        CanAccessItem(child, aOriginalRequestor)) {
      child.swap(*aResult);
      break;
    }

    // Only ask it to check children if it is same type
    if (childType != mItemType) {
      continue;
    }

    // Only ask the child if it isn't the requestor
    if (aRecurse && (aRequestor != child)) {
      // See if child contains the shell with the given name
#ifdef DEBUG
      nsresult rv =
#endif
      child->FindChildWithName(aName, true, aSameType,
                               static_cast<nsIDocShellTreeItem*>(this),
                               aOriginalRequestor, aResult);
      NS_ASSERTION(NS_SUCCEEDED(rv), "FindChildWithName should not fail here");
      if (*aResult) {
        // found it
        return NS_OK;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetChildSHEntry(int32_t aChildOffset, nsISHEntry** aResult)
{
  nsresult rv = NS_OK;

  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = nullptr;

  // A nsISHEntry for a child is *only* available when the parent is in
  // the progress of loading a document too...

  if (mLSHE) {
    /* Before looking for the subframe's url, check
     * the expiration status of the parent. If the parent
     * has expired from cache, then subframes will not be
     * loaded from history in certain situations.
     */
    bool parentExpired = false;
    mLSHE->GetExpirationStatus(&parentExpired);

    /* Get the parent's Load Type so that it can be set on the child too.
     * By default give a loadHistory value
     */
    uint32_t loadType = nsIDocShellLoadInfo::loadHistory;
    mLSHE->GetLoadType(&loadType);
    // If the user did a shift-reload on this frameset page,
    // we don't want to load the subframes from history.
    if (loadType == nsIDocShellLoadInfo::loadReloadBypassCache ||
        loadType == nsIDocShellLoadInfo::loadReloadBypassProxy ||
        loadType == nsIDocShellLoadInfo::loadReloadBypassProxyAndCache ||
        loadType == nsIDocShellLoadInfo::loadRefresh) {
      return rv;
    }

    /* If the user pressed reload and the parent frame has expired
     *  from cache, we do not want to load the child frame from history.
     */
    if (parentExpired && (loadType == nsIDocShellLoadInfo::loadReloadNormal)) {
      // The parent has expired. Return null.
      *aResult = nullptr;
      return rv;
    }

    nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE));
    if (container) {
      // Get the child subframe from session history.
      rv = container->GetChildAt(aChildOffset, aResult);
      if (*aResult) {
        (*aResult)->SetLoadType(loadType);
      }
    }
  }
  return rv;
}

NS_IMETHODIMP
nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
                            int32_t aChildOffset, uint32_t aLoadType,
                            bool aCloneChildren)
{
  nsresult rv = NS_OK;

  if (mLSHE && aLoadType != LOAD_PUSHSTATE) {
    /* You get here if you are currently building a
     * hierarchy ie.,you just visited a frameset page
     */
    nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE, &rv));
    if (container) {
      if (NS_FAILED(container->ReplaceChild(aNewEntry))) {
        rv = container->AddChild(aNewEntry, aChildOffset);
      }
    }
  } else if (!aCloneRef) {
    /* This is an initial load in some subframe.  Just append it if we can */
    nsCOMPtr<nsISHContainer> container(do_QueryInterface(mOSHE, &rv));
    if (container) {
      rv = container->AddChild(aNewEntry, aChildOffset);
    }
  } else {
    rv = AddChildSHEntryInternal(aCloneRef, aNewEntry, aChildOffset,
                                 aLoadType, aCloneChildren);
  }
  return rv;
}

nsresult
nsDocShell::AddChildSHEntryInternal(nsISHEntry* aCloneRef,
                                    nsISHEntry* aNewEntry,
                                    int32_t aChildOffset,
                                    uint32_t aLoadType,
                                    bool aCloneChildren)
{
  nsresult rv = NS_OK;
  if (mSessionHistory) {
    /* You are currently in the rootDocShell.
     * You will get here when a subframe has a new url
     * to load and you have walked up the tree all the
     * way to the top to clone the current SHEntry hierarchy
     * and replace the subframe where a new url was loaded with
     * a new entry.
     */
    int32_t index = -1;
    nsCOMPtr<nsISHEntry> currentHE;
    mSessionHistory->GetIndex(&index);
    if (index < 0) {
      return NS_ERROR_FAILURE;
    }

    rv = mSessionHistory->GetEntryAtIndex(index, false,
                                          getter_AddRefs(currentHE));
    NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);

    nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE));
    if (currentEntry) {
      uint32_t cloneID = 0;
      nsCOMPtr<nsISHEntry> nextEntry;
      aCloneRef->GetID(&cloneID);
      rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry,
                           aCloneChildren, getter_AddRefs(nextEntry));

      if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<nsISHistoryInternal> shPrivate =
          do_QueryInterface(mSessionHistory);
        NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
        rv = shPrivate->AddEntry(nextEntry, true);
      }
    }
  } else {
    /* Just pass this along */
    nsCOMPtr<nsIDocShell> parent =
      do_QueryInterface(GetAsSupports(mParent), &rv);
    if (parent) {
      rv = static_cast<nsDocShell*>(parent.get())->AddChildSHEntryInternal(
        aCloneRef, aNewEntry, aChildOffset, aLoadType, aCloneChildren);
    }
  }
  return rv;
}

nsresult
nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
                                    bool aCloneChildren)
{
  /* You will get here when you are in a subframe and
   * a new url has been loaded on you.
   * The mOSHE in this subframe will be the previous url's
   * mOSHE. This mOSHE will be used as the identification
   * for this subframe in the  CloneAndReplace function.
   */

  // In this case, we will end up calling AddEntry, which increases the
  // current index by 1
  nsCOMPtr<nsISHistory> rootSH;
  GetRootSessionHistory(getter_AddRefs(rootSH));
  if (rootSH) {
    rootSH->GetIndex(&mPreviousTransIndex);
  }

  nsresult rv;
  nsCOMPtr<nsIDocShell> parent = do_QueryInterface(GetAsSupports(mParent), &rv);
  if (parent) {
    rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType,
                                 aCloneChildren);
  }

  if (rootSH) {
    rootSH->GetIndex(&mLoadedTransIndex);
#ifdef DEBUG_PAGE_CACHE
    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex,
           mLoadedTransIndex);
#endif
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory)
{
  nsresult rv;

  mUseGlobalHistory = aUseGlobalHistory;

  if (!aUseGlobalHistory) {
    mGlobalHistory = nullptr;
    return NS_OK;
  }

  // No need to initialize mGlobalHistory if IHistory is available.
  nsCOMPtr<IHistory> history = services::GetHistoryService();
  if (history) {
    return NS_OK;
  }

  if (mGlobalHistory) {
    return NS_OK;
  }

  mGlobalHistory = do_GetService(NS_GLOBALHISTORY2_CONTRACTID, &rv);
  return rv;
}

NS_IMETHODIMP
nsDocShell::GetUseGlobalHistory(bool* aUseGlobalHistory)
{
  *aUseGlobalHistory = mUseGlobalHistory;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::RemoveFromSessionHistory()
{
  nsCOMPtr<nsISHistoryInternal> internalHistory;
  nsCOMPtr<nsISHistory> sessionHistory;
  nsCOMPtr<nsIDocShellTreeItem> root;
  GetSameTypeRootTreeItem(getter_AddRefs(root));
  if (root) {
    nsCOMPtr<nsIWebNavigation> rootAsWebnav = do_QueryInterface(root);
    if (rootAsWebnav) {
      rootAsWebnav->GetSessionHistory(getter_AddRefs(sessionHistory));
      internalHistory = do_QueryInterface(sessionHistory);
    }
  }
  if (!internalHistory) {
    return NS_OK;
  }

  int32_t index = 0;
  sessionHistory->GetIndex(&index);
  AutoTArray<uint64_t, 16> ids({mHistoryID});
  internalHistory->RemoveEntries(ids, index);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCreatedDynamically(bool aDynamic)
{
  mDynamicallyCreated = aDynamic;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCreatedDynamically(bool* aDynamic)
{
  *aDynamic = mDynamicallyCreated;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE)
{
  *aOSHE = false;
  *aEntry = nullptr;
  if (mLSHE) {
    NS_ADDREF(*aEntry = mLSHE);
  } else if (mOSHE) {
    NS_ADDREF(*aEntry = mOSHE);
    *aOSHE = true;
  }
  return NS_OK;
}

nsIScriptGlobalObject*
nsDocShell::GetScriptGlobalObject()
{
  NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr);
  return mScriptGlobal;
}

nsIDocument*
nsDocShell::GetDocument()
{
  NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr);
  return mContentViewer->GetDocument();
}

nsPIDOMWindowOuter*
nsDocShell::GetWindow()
{
  if (NS_FAILED(EnsureScriptEnvironment())) {
    return nullptr;
  }
  return mScriptGlobal->AsOuter();
}

NS_IMETHODIMP
nsDocShell::SetDeviceSizeIsPageSize(bool aValue)
{
  if (mDeviceSizeIsPageSize != aValue) {
    mDeviceSizeIsPageSize = aValue;
    RefPtr<nsPresContext> presContext;
    GetPresContext(getter_AddRefs(presContext));
    if (presContext) {
      presContext->MediaFeatureValuesChanged(nsRestyleHint(0));
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDeviceSizeIsPageSize(bool* aValue)
{
  *aValue = mDeviceSizeIsPageSize;
  return NS_OK;
}

void
nsDocShell::ClearFrameHistory(nsISHEntry* aEntry)
{
  nsCOMPtr<nsISHContainer> shcontainer = do_QueryInterface(aEntry);
  nsCOMPtr<nsISHistory> rootSH;
  GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsISHistoryInternal> history = do_QueryInterface(rootSH);
  if (!history || !shcontainer) {
    return;
  }

  int32_t count = 0;
  shcontainer->GetChildCount(&count);
  AutoTArray<uint64_t, 16> ids;
  for (int32_t i = 0; i < count; ++i) {
    nsCOMPtr<nsISHEntry> child;
    shcontainer->GetChildAt(i, getter_AddRefs(child));
    if (child) {
      uint64_t id = 0;
      child->GetDocshellID(&id);
      ids.AppendElement(id);
    }
  }
  int32_t index = 0;
  rootSH->GetIndex(&index);
  history->RemoveEntries(ids, index);
}

//-------------------------------------
//-- Helper Method for Print discovery
//-------------------------------------
bool
nsDocShell::IsPrintingOrPP(bool aDisplayErrorDialog)
{
  if (mIsPrintingOrPP && aDisplayErrorDialog) {
    DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr);
  }

  return mIsPrintingOrPP;
}

bool
nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog,
                                bool aCheckIfUnloadFired)
{
  bool isAllowed = !IsPrintingOrPP(aDisplayPrintErrorDialog) &&
                   (!aCheckIfUnloadFired || !mFiredUnloadEvent);
  if (!isAllowed) {
    return false;
  }
  if (!mContentViewer) {
    return true;
  }
  bool firingBeforeUnload;
  mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload);
  return !firingBeforeUnload;
}

//*****************************************************************************
// nsDocShell::nsIWebNavigation
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::GetCanGoBack(bool* aCanGoBack)
{
  if (!IsNavigationAllowed(false)) {
    *aCanGoBack = false;
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
  NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
  rv = webnav->GetCanGoBack(aCanGoBack);
  return rv;
}

NS_IMETHODIMP
nsDocShell::GetCanGoForward(bool* aCanGoForward)
{
  if (!IsNavigationAllowed(false)) {
    *aCanGoForward = false;
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
  NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
  rv = webnav->GetCanGoForward(aCanGoForward);
  return rv;
}

NS_IMETHODIMP
nsDocShell::GoBack()
{
  if (!IsNavigationAllowed()) {
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
  NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
  rv = webnav->GoBack();
  return rv;
}

NS_IMETHODIMP
nsDocShell::GoForward()
{
  if (!IsNavigationAllowed()) {
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
  NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
  rv = webnav->GoForward();
  return rv;
}

NS_IMETHODIMP
nsDocShell::GotoIndex(int32_t aIndex)
{
  if (!IsNavigationAllowed()) {
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
  NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
  rv = webnav->GotoIndex(aIndex);
  return rv;
}

NS_IMETHODIMP
nsDocShell::LoadURI(const char16_t* aURI,
                    uint32_t aLoadFlags,
                    nsIURI* aReferringURI,
                    nsIInputStream* aPostStream,
                    nsIInputStream* aHeaderStream)
{
  return LoadURIWithOptions(aURI, aLoadFlags, aReferringURI,
                            mozilla::net::RP_Default, aPostStream,
                            aHeaderStream, nullptr);
}

NS_IMETHODIMP
nsDocShell::LoadURIWithOptions(const char16_t* aURI,
                               uint32_t aLoadFlags,
                               nsIURI* aReferringURI,
                               uint32_t aReferrerPolicy,
                               nsIInputStream* aPostStream,
                               nsIInputStream* aHeaderStream,
                               nsIURI* aBaseURI)
{
  NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags");

  if (!IsNavigationAllowed()) {
    return NS_OK; // JS may not handle returning of an error code
  }
  nsCOMPtr<nsIURI> uri;
  nsCOMPtr<nsIInputStream> postStream(aPostStream);
  nsresult rv = NS_OK;

  // Create a URI from our string; if that succeeds, we want to
  // change aLoadFlags to not include the ALLOW_THIRD_PARTY_FIXUP
  // flag.

  NS_ConvertUTF16toUTF8 uriString(aURI);
  // Cleanup the empty spaces that might be on each end.
  uriString.Trim(" ");
  // Eliminate embedded newlines, which single-line text fields now allow:
  uriString.StripChars("\r\n");
  NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);

  rv = NS_NewURI(getter_AddRefs(uri), uriString);
  if (uri) {
    aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
  }

  nsCOMPtr<nsIURIFixupInfo> fixupInfo;
  if (sURIFixup) {
    // Call the fixup object.  This will clobber the rv from NS_NewURI
    // above, but that's fine with us.  Note that we need to do this even
    // if NS_NewURI returned a URI, because fixup handles nested URIs, etc
    // (things like view-source:mozilla.org for example).
    uint32_t fixupFlags = 0;
    if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
      fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    }
    if (aLoadFlags & LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
      fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
    }
    nsCOMPtr<nsIInputStream> fixupStream;
    rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
                                    getter_AddRefs(fixupStream),
                                    getter_AddRefs(fixupInfo));

    if (NS_SUCCEEDED(rv)) {
      fixupInfo->GetPreferredURI(getter_AddRefs(uri));
      fixupInfo->SetConsumer(GetAsSupports(this));
    }

    if (fixupStream) {
      // GetFixupURIInfo only returns a post data stream if it succeeded
      // and changed the URI, in which case we should override the
      // passed-in post data.
      postStream = fixupStream;
    }

    if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
      nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
      if (serv) {
        serv->NotifyObservers(fixupInfo, "keyword-uri-fixup", aURI);
      }
    }
  }
  // else no fixup service so just use the URI we created and see
  // what happens

  if (NS_ERROR_MALFORMED_URI == rv) {
    if (DisplayLoadError(rv, uri, aURI, nullptr) &&
        (aLoadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
      return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
    }
  }

  if (NS_FAILED(rv) || !uri) {
    return NS_ERROR_FAILURE;
  }

  PopupControlState popupState;
  if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) {
    popupState = openAllowed;
    aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS;
  } else {
    popupState = openOverridden;
  }
  nsAutoPopupStatePusher statePusher(popupState);

  // Don't pass certain flags that aren't needed and end up confusing
  // ConvertLoadTypeToDocShellLoadInfo.  We do need to ensure that they are
  // passed to LoadURI though, since it uses them.
  uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS);
  aLoadFlags &= ~EXTRA_LOAD_FLAGS;

  nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
  rv = CreateLoadInfo(getter_AddRefs(loadInfo));
  if (NS_FAILED(rv)) {
    return rv;
  }

  /*
   * If the user "Disables Protection on This Page", we have to make sure to
   * remember the users decision when opening links in child tabs [Bug 906190]
   */
  uint32_t loadType;
  if (aLoadFlags & LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
    loadType = MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags);
  } else {
    loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
  }

  loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType));
  loadInfo->SetPostDataStream(postStream);
  loadInfo->SetReferrer(aReferringURI);
  loadInfo->SetReferrerPolicy(aReferrerPolicy);
  loadInfo->SetHeadersStream(aHeaderStream);
  loadInfo->SetBaseURI(aBaseURI);

  if (fixupInfo) {
    nsAutoString searchProvider, keyword;
    fixupInfo->GetKeywordProviderName(searchProvider);
    fixupInfo->GetKeywordAsSent(keyword);
    MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
  }

  rv = LoadURI(uri, loadInfo, extraFlags, true);

  // Save URI string in case it's needed later when
  // sending to search engine service in EndPageLoad()
  mOriginalUriString = uriString;

  return rv;
}

NS_IMETHODIMP
nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
                             const char16_t* aURL,
                             nsIChannel* aFailedChannel,
                             bool* aDisplayedErrorPage)
{
  *aDisplayedErrorPage = false;
  // Get prompt and string bundle servcies
  nsCOMPtr<nsIPrompt> prompter;
  nsCOMPtr<nsIStringBundle> stringBundle;
  GetPromptAndStringBundle(getter_AddRefs(prompter),
                           getter_AddRefs(stringBundle));

  NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE);
  NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE);

  nsAutoString error;
  const uint32_t kMaxFormatStrArgs = 3;
  nsAutoString formatStrs[kMaxFormatStrArgs];
  uint32_t formatStrCount = 0;
  bool addHostPort = false;
  nsresult rv = NS_OK;
  nsAutoString messageStr;
  nsAutoCString cssClass;
  nsAutoCString errorPage;

  errorPage.AssignLiteral("neterror");

  // Turn the error code into a human readable error message.
  if (NS_ERROR_UNKNOWN_PROTOCOL == aError) {
    NS_ENSURE_ARG_POINTER(aURI);

    // Extract the schemes into a comma delimited list.
    nsAutoCString scheme;
    aURI->GetScheme(scheme);
    CopyASCIItoUTF16(scheme, formatStrs[0]);
    nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI);
    while (nestedURI) {
      nsCOMPtr<nsIURI> tempURI;
      nsresult rv2;
      rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI));
      if (NS_SUCCEEDED(rv2) && tempURI) {
        tempURI->GetScheme(scheme);
        formatStrs[0].AppendLiteral(", ");
        AppendASCIItoUTF16(scheme, formatStrs[0]);
      }
      nestedURI = do_QueryInterface(tempURI);
    }
    formatStrCount = 1;
    error.AssignLiteral("unknownProtocolFound");
  } else if (NS_ERROR_FILE_NOT_FOUND == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    error.AssignLiteral("fileNotFound");
  } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    error.AssignLiteral("fileAccessDenied");
  } else if (NS_ERROR_UNKNOWN_HOST == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    // Get the host
    nsAutoCString host;
    nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(aURI);
    innermostURI->GetHost(host);
    CopyUTF8toUTF16(host, formatStrs[0]);
    formatStrCount = 1;
    error.AssignLiteral("dnsNotFound");
  } else if (NS_ERROR_CONNECTION_REFUSED == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    addHostPort = true;
    error.AssignLiteral("connectionFailure");
  } else if (NS_ERROR_NET_INTERRUPT == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    addHostPort = true;
    error.AssignLiteral("netInterrupt");
  } else if (NS_ERROR_NET_TIMEOUT == aError) {
    NS_ENSURE_ARG_POINTER(aURI);
    // Get the host
    nsAutoCString host;
    aURI->GetHost(host);
    CopyUTF8toUTF16(host, formatStrs[0]);
    formatStrCount = 1;
    error.AssignLiteral("netTimeout");
  } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError ||
             NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError) {
    // CSP error
    cssClass.AssignLiteral("neterror");
    error.AssignLiteral("cspBlocked");
  } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
    nsCOMPtr<nsINSSErrorsService> nsserr =
      do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);

    uint32_t errorClass;
    if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) {
      errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL;
    }

    nsCOMPtr<nsISupports> securityInfo;
    nsCOMPtr<nsITransportSecurityInfo> tsi;
    if (aFailedChannel) {
      aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
    }
    tsi = do_QueryInterface(securityInfo);
    if (tsi) {
      uint32_t securityState;
      tsi->GetSecurityState(&securityState);
      if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) {
        error.AssignLiteral("sslv3Used");
        addHostPort = true;
      } else if (securityState & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
        error.AssignLiteral("weakCryptoUsed");
        addHostPort = true;
      } else {
        // Usually we should have aFailedChannel and get a detailed message
        tsi->GetErrorMessage(getter_Copies(messageStr));
      }
    } else {
      // No channel, let's obtain the generic error message
      if (nsserr) {
        nsserr->GetErrorMessage(aError, messageStr);
      }
    }
    if (!messageStr.IsEmpty()) {
      if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) {
        error.AssignLiteral("nssBadCert");

        // If this is an HTTP Strict Transport Security host or a pinned host
        // and the certificate is bad, don't allow overrides (RFC 6797 section
        // 12.1, HPKP draft spec section 2.6).
        uint32_t flags =
          UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
        bool isStsHost = false;
        bool isPinnedHost = false;
        if (XRE_IsParentProcess()) {
          nsCOMPtr<nsISiteSecurityService> sss =
            do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
          rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI,
                                flags, nullptr, &isStsHost);
          NS_ENSURE_SUCCESS(rv, rv);
          rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI,
                                flags, nullptr, &isPinnedHost);
          NS_ENSURE_SUCCESS(rv, rv);
        } else {
          mozilla::dom::ContentChild* cc =
            mozilla::dom::ContentChild::GetSingleton();
          mozilla::ipc::URIParams uri;
          SerializeURI(aURI, uri);
          cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, flags,
                              &isStsHost);
          cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HPKP, uri, flags,
                              &isPinnedHost);
        }

        if (Preferences::GetBool(
              "browser.xul.error_pages.expert_bad_cert", false)) {
          cssClass.AssignLiteral("expertBadCert");
        }

        // HSTS/pinning takes precedence over the expert bad cert pref. We
        // never want to show the "Add Exception" button for these sites.
        // In the future we should differentiate between an HSTS host and a
        // pinned host and display a more informative message to the user.
        if (isStsHost || isPinnedHost) {
          cssClass.AssignLiteral("badStsCert");
        }

        uint32_t bucketId;
        if (isStsHost) {
          // measuring STS separately allows us to measure click through
          // rates easily
          bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP_STS;
        } else {
          bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP;
        }

        // See if an alternate cert error page is registered
        nsAdoptingCString alternateErrorPage =
          Preferences::GetCString("security.alternate_certificate_error_page");
        if (alternateErrorPage) {
          errorPage.Assign(alternateErrorPage);
        }

        if (!IsFrame() && errorPage.EqualsIgnoreCase("certerror")) {
          Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId);
        }

      } else {
        error.AssignLiteral("nssFailure2");
      }
    }
  } else if (NS_ERROR_PHISHING_URI == aError ||
             NS_ERROR_MALWARE_URI == aError ||
             NS_ERROR_UNWANTED_URI == aError) {
    nsAutoCString host;
    aURI->GetHost(host);
    CopyUTF8toUTF16(host, formatStrs[0]);
    formatStrCount = 1;

    // Malware and phishing detectors may want to use an alternate error
    // page, but if the pref's not set, we'll fall back on the standard page
    nsAdoptingCString alternateErrorPage =
      Preferences::GetCString("urlclassifier.alternate_error_page");
    if (alternateErrorPage) {
      errorPage.Assign(alternateErrorPage);
    }

    uint32_t bucketId;
    bool sendTelemetry = false;
    if (NS_ERROR_PHISHING_URI == aError) {
      sendTelemetry = true;
      error.AssignLiteral("deceptiveBlocked");
      bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
                           : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
    } else if (NS_ERROR_MALWARE_URI == aError) {
      sendTelemetry = true;
      error.AssignLiteral("malwareBlocked");
      bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
                           : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
    } else if (NS_ERROR_UNWANTED_URI == aError) {
      sendTelemetry = true;
      error.AssignLiteral("unwantedBlocked");
      bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
                           : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
    }

    if (sendTelemetry && errorPage.EqualsIgnoreCase("blocked")) {
      Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId);
    }

    cssClass.AssignLiteral("blacklist");
  } else if (NS_ERROR_CONTENT_CRASHED == aError) {
    errorPage.AssignLiteral("tabcrashed");
    error.AssignLiteral("tabcrashed");

    nsCOMPtr<EventTarget> handler = mChromeEventHandler;
    if (handler) {
      nsCOMPtr<Element> element = do_QueryInterface(handler);
      element->GetAttribute(NS_LITERAL_STRING("crashedPageTitle"), messageStr);
    }

    // DisplayLoadError requires a non-empty messageStr to proceed and call
    // LoadErrorPage. If the page doesn't have a title, we will use a blank
    // space which will be trimmed and thus treated as empty by the front-end.
    if (messageStr.IsEmpty()) {
      messageStr.AssignLiteral(u" ");
    }
  } else {
    // Errors requiring simple formatting
    switch (aError) {
      case NS_ERROR_MALFORMED_URI:
        // URI is malformed
        error.AssignLiteral("malformedURI");
        break;
      case NS_ERROR_REDIRECT_LOOP:
        // Doc failed to load because the server generated too many redirects
        error.AssignLiteral("redirectLoop");
        break;
      case NS_ERROR_UNKNOWN_SOCKET_TYPE:
        // Doc failed to load because PSM is not installed
        error.AssignLiteral("unknownSocketType");
        break;
      case NS_ERROR_NET_RESET:
        // Doc failed to load because the server kept reseting the connection
        // before we could read any data from it
        error.AssignLiteral("netReset");
        break;
      case NS_ERROR_DOCUMENT_NOT_CACHED:
        // Doc failed to load because the cache does not contain a copy of
        // the document.
        error.AssignLiteral("notCached");
        break;
      case NS_ERROR_OFFLINE:
        // Doc failed to load because we are offline.
        error.AssignLiteral("netOffline");
        break;
      case NS_ERROR_DOCUMENT_IS_PRINTMODE:
        // Doc navigation attempted while Printing or Print Preview
        error.AssignLiteral("isprinting");
        break;
      case NS_ERROR_PORT_ACCESS_NOT_ALLOWED:
        // Port blocked for security reasons
        addHostPort = true;
        error.AssignLiteral("deniedPortAccess");
        break;
      case NS_ERROR_UNKNOWN_PROXY_HOST:
        // Proxy hostname could not be resolved.
        error.AssignLiteral("proxyResolveFailure");
        break;
      case NS_ERROR_PROXY_CONNECTION_REFUSED:
        // Proxy connection was refused.
        error.AssignLiteral("proxyConnectFailure");
        break;
      case NS_ERROR_INVALID_CONTENT_ENCODING:
        // Bad Content Encoding.
        error.AssignLiteral("contentEncodingError");
        break;
      case NS_ERROR_REMOTE_XUL:
        error.AssignLiteral("remoteXUL");
        break;
      case NS_ERROR_UNSAFE_CONTENT_TYPE:
        // Channel refused to load from an unrecognized content type.
        error.AssignLiteral("unsafeContentType");
        break;
      case NS_ERROR_CORRUPTED_CONTENT:
        // Broken Content Detected. e.g. Content-MD5 check failure.
        error.AssignLiteral("corruptedContentErrorv2");
        break;
      case NS_ERROR_INTERCEPTION_FAILED:
        // ServiceWorker intercepted request, but something went wrong.
        error.AssignLiteral("corruptedContentErrorv2");
        break;
      case NS_ERROR_NET_INADEQUATE_SECURITY:
        // Server negotiated bad TLS for HTTP/2.
        error.AssignLiteral("inadequateSecurityError");
        addHostPort = true;
        break;
      default:
        break;
    }
  }

  // Test if the error should be displayed
  if (error.IsEmpty()) {
    return NS_OK;
  }

  // Test if the error needs to be formatted
  if (!messageStr.IsEmpty()) {
    // already obtained message
  } else {
    if (addHostPort) {
      // Build up the host:port string.
      nsAutoCString hostport;
      if (aURI) {
        aURI->GetHostPort(hostport);
      } else {
        hostport.Assign('?');
      }
      CopyUTF8toUTF16(hostport, formatStrs[formatStrCount++]);
    }

    nsAutoCString spec;
    rv = NS_ERROR_NOT_AVAILABLE;
    if (aURI) {
      // displaying "file://" is aesthetically unpleasing and could even be
      // confusing to the user
      bool isFileURI = false;
      rv = aURI->SchemeIs("file", &isFileURI);
      if (NS_SUCCEEDED(rv) && isFileURI) {
        aURI->GetPath(spec);
      } else {
        aURI->GetSpec(spec);
      }

      nsAutoCString charset;
      // unescape and convert from origin charset
      aURI->GetOriginCharset(charset);
      nsCOMPtr<nsITextToSubURI> textToSubURI(
        do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv));
      if (NS_SUCCEEDED(rv)) {
        rv = textToSubURI->UnEscapeURIForUI(charset, spec,
                                            formatStrs[formatStrCount]);
      }
    } else {
      spec.Assign('?');
    }
    if (NS_FAILED(rv)) {
      CopyUTF8toUTF16(spec, formatStrs[formatStrCount]);
    }
    rv = NS_OK;
    ++formatStrCount;

    const char16_t* strs[kMaxFormatStrArgs];
    for (uint32_t i = 0; i < formatStrCount; i++) {
      strs[i] = formatStrs[i].get();
    }
    nsXPIDLString str;
    rv = stringBundle->FormatStringFromName(error.get(), strs, formatStrCount,
                                            getter_Copies(str));
    NS_ENSURE_SUCCESS(rv, rv);
    messageStr.Assign(str.get());
  }

  // Display the error as a page or an alert prompt
  NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE);

  if (NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) {
    bool isSecureURI = false;
    rv = aURI->SchemeIs("https", &isSecureURI);
    if (NS_SUCCEEDED(rv) && isSecureURI) {
      // Maybe TLS intolerant. Treat this as an SSL error.
      error.AssignLiteral("nssFailure2");
    }
  }

  if (UseErrorPages()) {
    // Display an error page
    nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(),
                                        error.get(), messageStr.get(),
                                        cssClass.get(), aFailedChannel);
    *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage);
  } else {
    // The prompter reqires that our private window has a document (or it
    // asserts). Satisfy that assertion now since GetDoc will force
    // creation of one if it hasn't already been created.
    if (mScriptGlobal) {
      Unused << mScriptGlobal->GetDoc();
    }

    // Display a message box
    prompter->Alert(nullptr, messageStr.get());
  }

  return NS_OK;
}

#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride"

NS_IMETHODIMP
nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
                          const char* aErrorPage,
                          const char16_t* aErrorType,
                          const char16_t* aDescription,
                          const char* aCSSClass,
                          nsIChannel* aFailedChannel)
{
#if defined(DEBUG)
  if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
    nsAutoCString chanName;
    if (aFailedChannel) {
      aFailedChannel->GetName(chanName);
    } else {
      chanName.AssignLiteral("<no channel>");
    }

    MOZ_LOG(gDocShellLog, LogLevel::Debug,
           ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this,
            aURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(aURL).get(),
            chanName.get()));
  }
#endif
  mFailedChannel = aFailedChannel;
  mFailedURI = aURI;
  mFailedLoadType = mLoadType;

  if (mLSHE) {
    // Abandon mLSHE's BFCache entry and create a new one.  This way, if
    // we go back or forward to another SHEntry with the same doc
    // identifier, the error page won't persist.
    mLSHE->AbandonBFCacheEntry();
  }

  nsAutoCString url;
  nsAutoCString charset;
  if (aURI) {
    nsresult rv = aURI->GetSpec(url);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = aURI->GetOriginCharset(charset);
    NS_ENSURE_SUCCESS(rv, rv);
  } else if (aURL) {
    CopyUTF16toUTF8(aURL, url);
  } else {
    return NS_ERROR_INVALID_POINTER;
  }

  // Create a URL to pass all the error information through to the page.

#undef SAFE_ESCAPE
#define SAFE_ESCAPE(output, input, params)                                     \
  if (NS_WARN_IF(!NS_Escape(input, output, params))) {                         \
    return NS_ERROR_OUT_OF_MEMORY;                                             \
  }

  nsCString escapedUrl, escapedCharset, escapedError, escapedDescription,
    escapedCSSClass;
  SAFE_ESCAPE(escapedUrl, url, url_Path);
  SAFE_ESCAPE(escapedCharset, charset, url_Path);
  SAFE_ESCAPE(escapedError, NS_ConvertUTF16toUTF8(aErrorType), url_Path);
  SAFE_ESCAPE(escapedDescription,
              NS_ConvertUTF16toUTF8(aDescription), url_Path);
  if (aCSSClass) {
    nsCString cssClass(aCSSClass);
    SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path);
  }
  nsCString errorPageUrl("about:");
  errorPageUrl.AppendASCII(aErrorPage);
  errorPageUrl.AppendLiteral("?e=");

  errorPageUrl.AppendASCII(escapedError.get());
  errorPageUrl.AppendLiteral("&u=");
  errorPageUrl.AppendASCII(escapedUrl.get());
  if ((strcmp(aErrorPage, "blocked") == 0) &&
      Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) {
    errorPageUrl.AppendLiteral("&o=1");
  }
  if (!escapedCSSClass.IsEmpty()) {
    errorPageUrl.AppendLiteral("&s=");
    errorPageUrl.AppendASCII(escapedCSSClass.get());
  }
  errorPageUrl.AppendLiteral("&c=");
  errorPageUrl.AppendASCII(escapedCharset.get());

  nsAutoCString frameType(FrameTypeToString(mFrameType));
  errorPageUrl.AppendLiteral("&f=");
  errorPageUrl.AppendASCII(frameType.get());

  // Append the manifest URL if the error comes from an app.
  nsString manifestURL;
  nsresult rv = GetAppManifestURL(manifestURL);
  if (manifestURL.Length() > 0) {
    nsCString manifestParam;
    SAFE_ESCAPE(manifestParam, NS_ConvertUTF16toUTF8(manifestURL), url_Path);
    errorPageUrl.AppendLiteral("&m=");
    errorPageUrl.AppendASCII(manifestParam.get());
  }

  nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID);
  int32_t cpsState;
  if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) &&
      cpsState == nsICaptivePortalService::LOCKED_PORTAL) {
    errorPageUrl.AppendLiteral("&captive=true");
  }

  // netError.xhtml's getDescription only handles the "d" parameter at the
  // end of the URL, so append it last.
  errorPageUrl.AppendLiteral("&d=");
  errorPageUrl.AppendASCII(escapedDescription.get());

  nsCOMPtr<nsIURI> errorPageURI;
  rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
  NS_ENSURE_SUCCESS(rv, rv);

  return InternalLoad(errorPageURI, nullptr, false, nullptr,
                      mozilla::net::RP_Default,
                      nsContentUtils::GetSystemPrincipal(), nullptr,
                      INTERNAL_LOAD_FLAGS_NONE, EmptyString(),
                      nullptr, NullString(), nullptr, nullptr, LOAD_ERROR_PAGE,
                      nullptr, true, NullString(), this, nullptr, nullptr,
                      nullptr);
}

NS_IMETHODIMP
nsDocShell::Reload(uint32_t aReloadFlags)
{
  if (!IsNavigationAllowed()) {
    return NS_OK; // JS may not handle returning of an error code
  }
  nsresult rv;
  NS_ASSERTION(((aReloadFlags & 0xf) == 0),
               "Reload command not updated to use load flags!");
  NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0,
               "Don't pass these flags to Reload");

  uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags);
  NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG);

  // Send notifications to the HistoryListener if any, about the impending
  // reload
  nsCOMPtr<nsISHistory> rootSH;
  rv = GetRootSessionHistory(getter_AddRefs(rootSH));
  nsCOMPtr<nsISHistoryInternal> shistInt(do_QueryInterface(rootSH));
  bool canReload = true;
  if (rootSH) {
    shistInt->NotifyOnHistoryReload(mCurrentURI, aReloadFlags, &canReload);
  }

  if (!canReload) {
    return NS_OK;
  }

  /* If you change this part of code, make sure bug 45297 does not re-occur */
  if (mOSHE) {
    rv = LoadHistoryEntry(mOSHE, loadType);
  } else if (mLSHE) { // In case a reload happened before the current load is done
    rv = LoadHistoryEntry(mLSHE, loadType);
  } else {
    nsCOMPtr<nsIDocument> doc(GetDocument());

    if (!doc) {
      return NS_OK;
    }

    // Do not inherit owner from document
    uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
    nsAutoString srcdoc;
    nsCOMPtr<nsIURI> baseURI;
    nsCOMPtr<nsIURI> originalURI;
    bool loadReplace = false;

    nsIPrincipal* triggeringPrincipal = doc->NodePrincipal();
    nsAutoString contentTypeHint;
    doc->GetContentType(contentTypeHint);

    if (doc->IsSrcdocDocument()) {
      doc->GetSrcdocData(srcdoc);
      flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
      baseURI = doc->GetBaseURI();
    }
    nsCOMPtr<nsIChannel> chan = doc->GetChannel();
    if (chan) {
      uint32_t loadFlags;
      chan->GetLoadFlags(&loadFlags);
      loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
      nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
      if (httpChan) {
        httpChan->GetOriginalURI(getter_AddRefs(originalURI));
      }
    }

    MOZ_ASSERT(triggeringPrincipal, "Need a valid triggeringPrincipal");

    // Stack variables to ensure changes to the member variables don't affect to
    // the call.
    nsCOMPtr<nsIURI> currentURI = mCurrentURI;
    nsCOMPtr<nsIURI> referrerURI = mReferrerURI;
    uint32_t referrerPolicy = mReferrerPolicy;
    rv = InternalLoad(currentURI,
                      originalURI,
                      loadReplace,
                      referrerURI,
                      referrerPolicy,
                      triggeringPrincipal,
                      triggeringPrincipal,
                      flags,
                      EmptyString(),   // No window target
                      NS_LossyConvertUTF16toASCII(contentTypeHint).get(),
                      NullString(),    // No forced download
                      nullptr,         // No post data
                      nullptr,         // No headers data
                      loadType,        // Load type
                      nullptr,         // No SHEntry
                      true,
                      srcdoc,          // srcdoc argument for iframe
                      this,            // For reloads we are the source
                      baseURI,
                      nullptr,         // No nsIDocShell
                      nullptr);        // No nsIRequest
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::Stop(uint32_t aStopFlags)
{
  // Revoke any pending event related to content viewer restoration
  mRestorePresentationEvent.Revoke();

  if (mLoadType == LOAD_ERROR_PAGE) {
    if (mLSHE) {
      // Since error page loads never unset mLSHE, do so now
      SetHistoryEntry(&mOSHE, mLSHE);
      SetHistoryEntry(&mLSHE, nullptr);
    }

    mFailedChannel = nullptr;
    mFailedURI = nullptr;
  }

  if (nsIWebNavigation::STOP_CONTENT & aStopFlags) {
    // Stop the document loading
    if (mContentViewer) {
      nsCOMPtr<nsIContentViewer> cv = mContentViewer;
      cv->Stop();
    }
  }

  if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
    // Suspend any timers that were set for this loader.  We'll clear
    // them out for good in CreateContentViewer.
    if (mRefreshURIList) {
      SuspendRefreshURIs();
      mSavedRefreshURIList.swap(mRefreshURIList);
      mRefreshURIList = nullptr;
    }

    // XXXbz We could also pass |this| to nsIURILoader::Stop.  That will
    // just call Stop() on us as an nsIDocumentLoader... We need fewer
    // redundant apis!
    Stop();
  }

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIWebNavigation> shellAsNav(do_QueryObject(iter.GetNext()));
    if (shellAsNav) {
      shellAsNav->Stop(aStopFlags);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDocument(nsIDOMDocument** aDocument)
{
  NS_ENSURE_ARG_POINTER(aDocument);
  NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE);

  return mContentViewer->GetDOMDocument(aDocument);
}

NS_IMETHODIMP
nsDocShell::GetCurrentURI(nsIURI** aURI)
{
  NS_ENSURE_ARG_POINTER(aURI);

  if (mCurrentURI) {
    return NS_EnsureSafeToReturn(mCurrentURI, aURI);
  }

  *aURI = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetReferringURI(nsIURI** aURI)
{
  NS_ENSURE_ARG_POINTER(aURI);

  *aURI = mReferrerURI;
  NS_IF_ADDREF(*aURI);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetSessionHistory(nsISHistory* aSessionHistory)
{
  NS_ENSURE_TRUE(aSessionHistory, NS_ERROR_FAILURE);
  // make sure that we are the root docshell and
  // set a handle to root docshell in SH.

  nsCOMPtr<nsIDocShellTreeItem> root;
  /* Get the root docshell. If *this* is the root docshell
   * then save a handle to *this* in SH. SH needs it to do
   * traversions thro' its entries
   */
  GetSameTypeRootTreeItem(getter_AddRefs(root));
  NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
  if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) {
    mSessionHistory = aSessionHistory;
    nsCOMPtr<nsISHistoryInternal> shPrivate =
      do_QueryInterface(mSessionHistory);
    NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
    shPrivate->SetRootDocShell(this);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::GetSessionHistory(nsISHistory** aSessionHistory)
{
  NS_ENSURE_ARG_POINTER(aSessionHistory);
  *aSessionHistory = mSessionHistory;
  NS_IF_ADDREF(*aSessionHistory);
  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIWebPageDescriptor
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::LoadPage(nsISupports* aPageDescriptor, uint32_t aDisplayType)
{
  nsCOMPtr<nsISHEntry> shEntryIn(do_QueryInterface(aPageDescriptor));

  // Currently, the opaque 'page descriptor' is an nsISHEntry...
  if (!shEntryIn) {
    return NS_ERROR_INVALID_POINTER;
  }

  // Now clone shEntryIn, since we might end up modifying it later on, and we
  // want a page descriptor to be reusable.
  nsCOMPtr<nsISHEntry> shEntry;
  nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry));
  NS_ENSURE_SUCCESS(rv, rv);

  // Give our cloned shEntry a new bfcache entry so this load is independent
  // of all other loads.  (This is important, in particular, for bugs 582795
  // and 585298.)
  rv = shEntry->AbandonBFCacheEntry();
  NS_ENSURE_SUCCESS(rv, rv);

  //
  // load the page as view-source
  //
  if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) {
    nsCOMPtr<nsIURI> oldUri, newUri;
    nsCString spec, newSpec;

    // Create a new view-source URI and replace the original.
    rv = shEntry->GetURI(getter_AddRefs(oldUri));
    if (NS_FAILED(rv)) {
      return rv;
    }

    oldUri->GetSpec(spec);
    newSpec.AppendLiteral("view-source:");
    newSpec.Append(spec);

    rv = NS_NewURI(getter_AddRefs(newUri), newSpec);
    if (NS_FAILED(rv)) {
      return rv;
    }
    shEntry->SetURI(newUri);
    shEntry->SetOriginalURI(nullptr);
  }

  rv = LoadHistoryEntry(shEntry, LOAD_HISTORY);
  return rv;
}

NS_IMETHODIMP
nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor)
{
  NS_PRECONDITION(aPageDescriptor, "Null out param?");

  *aPageDescriptor = nullptr;

  nsISHEntry* src = mOSHE ? mOSHE : mLSHE;
  if (src) {
    nsCOMPtr<nsISHEntry> dest;

    nsresult rv = src->Clone(getter_AddRefs(dest));
    if (NS_FAILED(rv)) {
      return rv;
    }

    // null out inappropriate cloned attributes...
    dest->SetParent(nullptr);
    dest->SetIsSubFrame(false);

    return CallQueryInterface(dest, aPageDescriptor);
  }

  return NS_ERROR_NOT_AVAILABLE;
}

//*****************************************************************************
// nsDocShell::nsIBaseWindow
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::InitWindow(nativeWindow aParentNativeWindow,
                       nsIWidget* aParentWidget, int32_t aX, int32_t aY,
                       int32_t aWidth, int32_t aHeight)
{
  SetParentWidget(aParentWidget);
  SetPositionAndSize(aX, aY, aWidth, aHeight, 0);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::Create()
{
  if (mCreated) {
    // We've already been created
    return NS_OK;
  }

  NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
               "Unexpected item type in docshell");

  NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
  mCreated = true;

  if (gValidateOrigin == 0xffffffff) {
    // Check pref to see if we should prevent frameset spoofing
    gValidateOrigin =
      Preferences::GetBool("browser.frame.validate_origin", true);
  }

  // Should we use XUL error pages instead of alerts if possible?
  mUseErrorPages =
    Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages);

  if (!gAddedPreferencesVarCache) {
    Preferences::AddBoolVarCache(&sUseErrorPages,
                                 "browser.xul.error_pages.enabled",
                                 mUseErrorPages);
    gAddedPreferencesVarCache = true;
  }

  mDisableMetaRefreshWhenInactive =
    Preferences::GetBool("browser.meta_refresh_when_inactive.disabled",
                         mDisableMetaRefreshWhenInactive);

  mDeviceSizeIsPageSize =
    Preferences::GetBool("docshell.device_size_is_page_size",
                         mDeviceSizeIsPageSize);

  nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
  if (serv) {
    const char* msg = mItemType == typeContent ?
      NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE;
    serv->NotifyObservers(GetAsSupports(this), msg, nullptr);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::Destroy()
{
  NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
               "Unexpected item type in docshell");

  if (!mIsBeingDestroyed) {
    nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
    if (serv) {
      const char* msg = mItemType == typeContent ?
        NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY;
      serv->NotifyObservers(GetAsSupports(this), msg, nullptr);
    }
  }

  mIsBeingDestroyed = true;

  // Make sure we don't record profile timeline markers anymore
  SetRecordProfileTimelineMarkers(false);

  // Remove our pref observers
  if (mObserveErrorPages) {
    mObserveErrorPages = false;
  }

  // Make sure to blow away our mLoadingURI just in case.  No loads
  // from inside this pagehide.
  mLoadingURI = nullptr;

  // Fire unload event before we blow anything away.
  (void)FirePageHideNotification(true);

  // Clear pointers to any detached nsEditorData that's lying
  // around in shistory entries. Breaks cycle. See bug 430921.
  if (mOSHE) {
    mOSHE->SetEditorData(nullptr);
  }
  if (mLSHE) {
    mLSHE->SetEditorData(nullptr);
  }

  // Note: mContentListener can be null if Init() failed and we're being
  // called from the destructor.
  if (mContentListener) {
    mContentListener->DropDocShellReference();
    mContentListener->SetParentContentListener(nullptr);
    // Note that we do NOT set mContentListener to null here; that
    // way if someone tries to do a load in us after this point
    // the nsDSURIContentListener will block it.  All of which
    // means that we should do this before calling Stop(), of
    // course.
  }

  // Stop any URLs that are currently being loaded...
  Stop(nsIWebNavigation::STOP_ALL);

  mEditorData = nullptr;

  mTransferableHookData = nullptr;

  // Save the state of the current document, before destroying the window.
  // This is needed to capture the state of a frameset when the new document
  // causes the frameset to be destroyed...
  PersistLayoutHistoryState();

  // Remove this docshell from its parent's child list
  nsCOMPtr<nsIDocShellTreeItem> docShellParentAsItem =
    do_QueryInterface(GetAsSupports(mParent));
  if (docShellParentAsItem) {
    docShellParentAsItem->RemoveChild(this);
  }

  if (mContentViewer) {
    mContentViewer->Close(nullptr);
    mContentViewer->Destroy();
    mContentViewer = nullptr;
  }

  nsDocLoader::Destroy();

  mParentWidget = nullptr;
  mCurrentURI = nullptr;

  if (mScriptGlobal) {
    mScriptGlobal->DetachFromDocShell();
    mScriptGlobal = nullptr;
  }

  if (mSessionHistory) {
    // We want to destroy these content viewers now rather than
    // letting their destruction wait for the session history
    // entries to get garbage collected.  (Bug 488394)
    nsCOMPtr<nsISHistoryInternal> shPrivate =
      do_QueryInterface(mSessionHistory);
    if (shPrivate) {
      shPrivate->EvictAllContentViewers();
    }
    mSessionHistory = nullptr;
  }

  SetTreeOwner(nullptr);

  mOnePermittedSandboxedNavigator = nullptr;

  // required to break ref cycle
  mSecurityUI = nullptr;

  // Cancel any timers that were set for this docshell; this is needed
  // to break the cycle between us and the timers.
  CancelRefreshURITimers();

  if (UsePrivateBrowsing()) {
    mPrivateBrowsingId = 0;
    mOriginAttributes.SyncAttributesWithPrivateBrowsing(false);
    if (mAffectPrivateSessionLifetime) {
      DecreasePrivateDocShellCount();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetUnscaledDevicePixelsPerCSSPixel(double* aScale)
{
  if (mParentWidget) {
    *aScale = mParentWidget->GetDefaultScale().scale;
    return NS_OK;
  }

  nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
  if (ownerWindow) {
    return ownerWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale);
  }

  *aScale = 1.0;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale)
{
  if (mParentWidget) {
    *aScale = mParentWidget->GetDesktopToDeviceScale().scale;
    return NS_OK;
  }

  nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
  if (ownerWindow) {
    return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale);
  }

  *aScale = 1.0;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetPosition(int32_t aX, int32_t aY)
{
  mBounds.x = aX;
  mBounds.y = aY;

  if (mContentViewer) {
    NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY)
{
  nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
  if (ownerWindow) {
    return ownerWindow->SetPositionDesktopPix(aX, aY);
  }

  double scale = 1.0;
  GetDevicePixelsPerDesktopPixel(&scale);
  return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
}

NS_IMETHODIMP
nsDocShell::GetPosition(int32_t* aX, int32_t* aY)
{
  return GetPositionAndSize(aX, aY, nullptr, nullptr);
}

NS_IMETHODIMP
nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint)
{
  int32_t x = 0, y = 0;
  GetPosition(&x, &y);
  return SetPositionAndSize(x, y, aWidth, aHeight,
                            aRepaint ? nsIBaseWindow::eRepaint : 0);
}

NS_IMETHODIMP
nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight)
{
  return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight);
}

NS_IMETHODIMP
nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth,
                               int32_t aHeight, uint32_t aFlags)
{
  mBounds.x = aX;
  mBounds.y = aY;
  mBounds.width = aWidth;
  mBounds.height = aHeight;

  // Hold strong ref, since SetBounds can make us null out mContentViewer
  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
  if (viewer) {
    uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) ?
                           nsIContentViewer::eDelayResize : 0;
    // XXX Border figured in here or is that handled elsewhere?
    nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
                               int32_t* aHeight)
{
  if (mParentWidget) {
    // ensure size is up-to-date if window has changed resolution
    LayoutDeviceIntRect r = mParentWidget->GetClientBounds();
    SetPositionAndSize(mBounds.x, mBounds.y, r.width, r.height, 0);
  }

  // We should really consider just getting this information from
  // our window instead of duplicating the storage and code...
  if (aWidth || aHeight) {
    // Caller wants to know our size; make sure to give them up to
    // date information.
    nsCOMPtr<nsIDocument> doc(do_GetInterface(GetAsSupports(mParent)));
    if (doc) {
      doc->FlushPendingNotifications(Flush_Layout);
    }
  }

  DoGetPositionAndSize(aX, aY, aWidth, aHeight);
  return NS_OK;
}

void
nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
                                 int32_t* aHeight)
{
  if (aX) {
    *aX = mBounds.x;
  }
  if (aY) {
    *aY = mBounds.y;
  }
  if (aWidth) {
    *aWidth = mBounds.width;
  }
  if (aHeight) {
    *aHeight = mBounds.height;
  }
}

NS_IMETHODIMP
nsDocShell::Repaint(bool aForce)
{
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);

  nsViewManager* viewManager = presShell->GetViewManager();
  NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE);

  viewManager->InvalidateAllViews();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetParentWidget(nsIWidget** aParentWidget)
{
  NS_ENSURE_ARG_POINTER(aParentWidget);

  *aParentWidget = mParentWidget;
  NS_IF_ADDREF(*aParentWidget);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetParentWidget(nsIWidget* aParentWidget)
{
  mParentWidget = aParentWidget;

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
{
  NS_ENSURE_ARG_POINTER(aParentNativeWindow);

  if (mParentWidget) {
    *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET);
  } else {
    *aParentNativeWindow = nullptr;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDocShell::GetNativeHandle(nsAString& aNativeHandle)
{
  // the nativeHandle should be accessed from nsIXULWindow
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDocShell::GetVisibility(bool* aVisibility)
{
  NS_ENSURE_ARG_POINTER(aVisibility);

  *aVisibility = false;

  if (!mContentViewer) {
    return NS_OK;
  }

  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
  if (!presShell) {
    return NS_OK;
  }

  // get the view manager
  nsViewManager* vm = presShell->GetViewManager();
  NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);

  // get the root view
  nsView* view = vm->GetRootView(); // views are not ref counted
  NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);

  // if our root view is hidden, we are not visible
  if (view->GetVisibility() == nsViewVisibility_kHide) {
    return NS_OK;
  }

  // otherwise, we must walk up the document and view trees checking
  // for a hidden view, unless we're an off screen browser, which
  // would make this test meaningless.

  RefPtr<nsDocShell> docShell = this;
  RefPtr<nsDocShell> parentItem = docShell->GetParentDocshell();
  while (parentItem) {
    presShell = docShell->GetPresShell();

    nsCOMPtr<nsIPresShell> pPresShell = parentItem->GetPresShell();

    // Null-check for crash in bug 267804
    if (!pPresShell) {
      NS_NOTREACHED("parent docshell has null pres shell");
      return NS_OK;
    }

    vm = presShell->GetViewManager();
    if (vm) {
      view = vm->GetRootView();
    }

    if (view) {
      view = view->GetParent(); // anonymous inner view
      if (view) {
        view = view->GetParent(); // subdocumentframe's view
      }
    }

    nsIFrame* frame = view ? view->GetFrame() : nullptr;
    bool isDocShellOffScreen = false;
    docShell->GetIsOffScreenBrowser(&isDocShellOffScreen);
    if (frame &&
        !frame->IsVisibleConsideringAncestors(
          nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) &&
        !isDocShellOffScreen) {
      return NS_OK;
    }

    docShell = parentItem;
    parentItem = docShell->GetParentDocshell();
  }

  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner));
  if (!treeOwnerAsWin) {
    *aVisibility = true;
    return NS_OK;
  }

  // Check with the tree owner as well to give embedders a chance to
  // expose visibility as well.
  return treeOwnerAsWin->GetVisibility(aVisibility);
}

NS_IMETHODIMP
nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen)
{
  mIsOffScreenBrowser = aIsOffScreen;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen)
{
  *aIsOffScreen = mIsOffScreenBrowser;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetIsActive(bool aIsActive)
{
  // We disallow setting active on chrome docshells.
  if (mItemType == nsIDocShellTreeItem::typeChrome) {
    return NS_ERROR_INVALID_ARG;
  }

  // Keep track ourselves.
  mIsActive = aIsActive;

  // Clear prerender flag if necessary.
  mIsPrerendered &= !aIsActive;

  // Tell the PresShell about it.
  nsCOMPtr<nsIPresShell> pshell = GetPresShell();
  if (pshell) {
    pshell->SetIsActive(aIsActive);
  }

  // Tell the window about it
  if (mScriptGlobal) {
    mScriptGlobal->SetIsBackground(!aIsActive);
    if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) {
      // Update orientation when the top-level browsing context becomes active.
      // We make an exception for apps because they currently rely on
      // orientation locks persisting across browsing contexts.
      if (aIsActive && !GetIsApp()) {
        nsCOMPtr<nsIDocShellTreeItem> parent;
        GetSameTypeParent(getter_AddRefs(parent));
        if (!parent) {
          // We only care about the top-level browsing context.
          uint16_t orientation = OrientationLock();
          ScreenOrientation::UpdateActiveOrientationLock(orientation);
        }
      }

      doc->PostVisibilityUpdateEvent();
    }
  }

  // Tell the nsDOMNavigationTiming about it
  RefPtr<nsDOMNavigationTiming> timing = mTiming;
  if (!timing && mContentViewer) {
    nsIDocument* doc = mContentViewer->GetDocument();
    if (doc) {
      timing = doc->GetNavigationTiming();
    }
  }
  if (timing) {
    timing->NotifyDocShellStateChanged(
      aIsActive ? nsDOMNavigationTiming::DocShellState::eActive
                : nsDOMNavigationTiming::DocShellState::eInactive);
  }

  // Recursively tell all of our children, but don't tell <iframe mozbrowser>
  // children; they handle their state separately.
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
    if (!docshell) {
      continue;
    }

    if (!docshell->GetIsMozBrowserOrApp()) {
      docshell->SetIsActive(aIsActive);
    }
  }

  // Restart or stop meta refresh timers if necessary
  if (mDisableMetaRefreshWhenInactive) {
    if (mIsActive) {
      ResumeRefreshURIs();
    } else {
      SuspendRefreshURIs();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsActive(bool* aIsActive)
{
  *aIsActive = mIsActive;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetIsPrerendered()
{
  MOZ_ASSERT(!mIsPrerendered,
             "SetIsPrerendered() called on already prerendered docshell");
  SetIsActive(false);
  mIsPrerendered = true;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsPrerendered(bool* aIsPrerendered)
{
  *aIsPrerendered = mIsPrerendered;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetIsAppTab(bool aIsAppTab)
{
  mIsAppTab = aIsAppTab;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsAppTab(bool* aIsAppTab)
{
  *aIsAppTab = mIsAppTab;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetSandboxFlags(uint32_t aSandboxFlags)
{
  mSandboxFlags = aSandboxFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetSandboxFlags(uint32_t* aSandboxFlags)
{
  *aSandboxFlags = mSandboxFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetOnePermittedSandboxedNavigator(nsIDocShell* aSandboxedNavigator)
{
  if (mOnePermittedSandboxedNavigator) {
    NS_ERROR("One Permitted Sandboxed Navigator should only be set once.");
    return NS_OK;
  }

  mOnePermittedSandboxedNavigator = do_GetWeakReference(aSandboxedNavigator);
  NS_ASSERTION(mOnePermittedSandboxedNavigator,
               "One Permitted Sandboxed Navigator must support weak references.");

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetOnePermittedSandboxedNavigator(nsIDocShell** aSandboxedNavigator)
{
  NS_ENSURE_ARG_POINTER(aSandboxedNavigator);
  nsCOMPtr<nsIDocShell> permittedNavigator =
    do_QueryReferent(mOnePermittedSandboxedNavigator);
  permittedNavigator.forget(aSandboxedNavigator);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags)
{
  mDefaultLoadFlags = aDefaultLoadFlags;

  // Tell the load group to set these flags all requests in the group
  if (mLoadGroup) {
    mLoadGroup->SetDefaultLoadFlags(aDefaultLoadFlags);
  } else {
    NS_WARNING("nsDocShell::SetDefaultLoadFlags has no loadGroup to propagate the flags to");
  }

  // Recursively tell all of our children.  We *do not* skip
  // <iframe mozbrowser> children - if someone sticks custom flags in this
  // docShell then they too get the same flags.
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
    if (!docshell) {
      continue;
    }
    docshell->SetDefaultLoadFlags(aDefaultLoadFlags);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags)
{
  *aDefaultLoadFlags = mDefaultLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel)
{
#ifdef DEBUG
  // if the channel is non-null
  if (aMixedContentChannel) {
    // Get the root docshell.
    nsCOMPtr<nsIDocShellTreeItem> root;
    GetSameTypeRootTreeItem(getter_AddRefs(root));
    NS_WARNING_ASSERTION(root.get() == static_cast<nsIDocShellTreeItem*>(this),
                         "Setting mMixedContentChannel on a docshell that is "
                         "not the root docshell");
  }
#endif
  mMixedContentChannel = aMixedContentChannel;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel)
{
  NS_ENSURE_ARG_POINTER(aFailedChannel);
  nsIDocument* doc = GetDocument();
  if (!doc) {
    *aFailedChannel = nullptr;
    return NS_OK;
  }
  NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel());
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetMixedContentChannel(nsIChannel** aMixedContentChannel)
{
  NS_ENSURE_ARG_POINTER(aMixedContentChannel);
  NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAllowMixedContentAndConnectionData(bool* aRootHasSecureConnection,
                                                  bool* aAllowMixedContent,
                                                  bool* aIsRootDocShell)
{
  *aRootHasSecureConnection = true;
  *aAllowMixedContent = false;
  *aIsRootDocShell = false;

  nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
  GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
  NS_ASSERTION(sameTypeRoot,
               "No document shell root tree item from document shell tree item!");
  *aIsRootDocShell =
    sameTypeRoot.get() == static_cast<nsIDocShellTreeItem*>(this);

  // now get the document from sameTypeRoot
  nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
  if (rootDoc) {
    nsCOMPtr<nsIPrincipal> rootPrincipal = rootDoc->NodePrincipal();

    // For things with system principal (e.g. scratchpad) there is no uri
    // aRootHasSecureConnection should be false.
    nsCOMPtr<nsIURI> rootUri;
    if (nsContentUtils::IsSystemPrincipal(rootPrincipal) ||
        NS_FAILED(rootPrincipal->GetURI(getter_AddRefs(rootUri))) || !rootUri ||
        NS_FAILED(rootUri->SchemeIs("https", aRootHasSecureConnection))) {
      *aRootHasSecureConnection = false;
    }

    // Check the root doc's channel against the root docShell's
    // mMixedContentChannel to see if they are the same. If they are the same,
    // the user has overriden the block.
    nsCOMPtr<nsIDocShell> rootDocShell = do_QueryInterface(sameTypeRoot);
    nsCOMPtr<nsIChannel> mixedChannel;
    rootDocShell->GetMixedContentChannel(getter_AddRefs(mixedChannel));
    *aAllowMixedContent =
      mixedChannel && (mixedChannel == rootDoc->GetChannel());
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetVisibility(bool aVisibility)
{
  // Show()/Hide() may change mContentViewer.
  nsCOMPtr<nsIContentViewer> cv = mContentViewer;
  if (!cv) {
    return NS_OK;
  }
  if (aVisibility) {
    cv->Show();
  } else {
    cv->Hide();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetEnabled(bool* aEnabled)
{
  NS_ENSURE_ARG_POINTER(aEnabled);
  *aEnabled = true;
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDocShell::SetEnabled(bool aEnabled)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDocShell::SetFocus()
{
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetMainWidget(nsIWidget** aMainWidget)
{
  // We don't create our own widget, so simply return the parent one.
  return GetParentWidget(aMainWidget);
}

NS_IMETHODIMP
nsDocShell::GetTitle(char16_t** aTitle)
{
  NS_ENSURE_ARG_POINTER(aTitle);

  *aTitle = ToNewUnicode(mTitle);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetTitle(const char16_t* aTitle)
{
  // Store local title
  mTitle = aTitle;

  nsCOMPtr<nsIDocShellTreeItem> parent;
  GetSameTypeParent(getter_AddRefs(parent));

  // When title is set on the top object it should then be passed to the
  // tree owner.
  if (!parent) {
    nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner));
    if (treeOwnerAsWin) {
      treeOwnerAsWin->SetTitle(aTitle);
    }
  }

  AssertOriginAttributesMatchPrivateBrowsing();
  if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory &&
      !UsePrivateBrowsing()) {
    nsCOMPtr<IHistory> history = services::GetHistoryService();
    if (history) {
      history->SetURITitle(mCurrentURI, mTitle);
    } else if (mGlobalHistory) {
      mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle));
    }
  }

  // Update SessionHistory with the document's title.
  if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY &&
      mLoadType != LOAD_ERROR_PAGE) {
    mOSHE->SetTitle(mTitle);
  }

  return NS_OK;
}

nsresult
nsDocShell::GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos)
{
  NS_ENSURE_ARG_POINTER(aCurPos);

  nsIScrollableFrame* sf = GetRootScrollFrame();
  if (!sf) {
    return NS_ERROR_FAILURE;
  }

  nsPoint pt = sf->GetScrollPosition();

  switch (aScrollOrientation) {
    case ScrollOrientation_X:
      *aCurPos = pt.x;
      return NS_OK;

    case ScrollOrientation_Y:
      *aCurPos = pt.y;
      return NS_OK;

    default:
      NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG);
  }
}

nsresult
nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos,
                              int32_t aCurVerticalPos)
{
  nsIScrollableFrame* sf = GetRootScrollFrame();
  NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE);

  sf->ScrollTo(nsPoint(aCurHorizontalPos, aCurVerticalPos),
               nsIScrollableFrame::INSTANT);
  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIScrollable
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::GetDefaultScrollbarPreferences(int32_t aScrollOrientation,
                                           int32_t* aScrollbarPref)
{
  NS_ENSURE_ARG_POINTER(aScrollbarPref);
  switch (aScrollOrientation) {
    case ScrollOrientation_X:
      *aScrollbarPref = mDefaultScrollbarPref.x;
      return NS_OK;

    case ScrollOrientation_Y:
      *aScrollbarPref = mDefaultScrollbarPref.y;
      return NS_OK;

    default:
      NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG);
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::SetDefaultScrollbarPreferences(int32_t aScrollOrientation,
                                           int32_t aScrollbarPref)
{
  switch (aScrollOrientation) {
    case ScrollOrientation_X:
      mDefaultScrollbarPref.x = aScrollbarPref;
      return NS_OK;

    case ScrollOrientation_Y:
      mDefaultScrollbarPref.y = aScrollbarPref;
      return NS_OK;

    default:
      NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG);
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::GetScrollbarVisibility(bool* aVerticalVisible,
                                   bool* aHorizontalVisible)
{
  nsIScrollableFrame* sf = GetRootScrollFrame();
  NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE);

  uint32_t scrollbarVisibility = sf->GetScrollbarVisibility();
  if (aVerticalVisible) {
    *aVerticalVisible =
      (scrollbarVisibility & nsIScrollableFrame::VERTICAL) != 0;
  }
  if (aHorizontalVisible) {
    *aHorizontalVisible =
      (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) != 0;
  }

  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsITextScroll
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::ScrollByLines(int32_t aNumLines)
{
  nsIScrollableFrame* sf = GetRootScrollFrame();
  NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE);

  sf->ScrollBy(nsIntPoint(0, aNumLines), nsIScrollableFrame::LINES,
               nsIScrollableFrame::SMOOTH);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::ScrollByPages(int32_t aNumPages)
{
  nsIScrollableFrame* sf = GetRootScrollFrame();
  NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE);

  sf->ScrollBy(nsIntPoint(0, aNumPages), nsIScrollableFrame::PAGES,
               nsIScrollableFrame::SMOOTH);
  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIRefreshURI
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::RefreshURI(nsIURI* aURI,
                       int32_t aDelay, bool aRepeat,
                       bool aMetaRefresh,
                       nsIPrincipal* aPrincipal)
{
  NS_ENSURE_ARG(aURI);

  /* Check if Meta refresh/redirects are permitted. Some
   * embedded applications may not want to do this.
   * Must do this before sending out NOTIFY_REFRESH events
   * because listeners may have side effects (e.g. displaying a
   * button to manually trigger the refresh later).
   */
  bool allowRedirects = true;
  GetAllowMetaRedirects(&allowRedirects);
  if (!allowRedirects) {
    return NS_OK;
  }

  // If any web progress listeners are listening for NOTIFY_REFRESH events,
  // give them a chance to block this refresh.
  bool sameURI;
  nsresult rv = aURI->Equals(mCurrentURI, &sameURI);
  if (NS_FAILED(rv)) {
    sameURI = false;
  }
  if (!RefreshAttempted(this, aURI, aDelay, sameURI)) {
    return NS_OK;
  }

  nsRefreshTimer* refreshTimer = new nsRefreshTimer();
  uint32_t busyFlags = 0;
  GetBusyFlags(&busyFlags);

  nsCOMPtr<nsISupports> dataRef = refreshTimer;  // Get the ref count to 1

  refreshTimer->mDocShell = this;
  refreshTimer->mPrincipal = aPrincipal;
  refreshTimer->mURI = aURI;
  refreshTimer->mDelay = aDelay;
  refreshTimer->mRepeat = aRepeat;
  refreshTimer->mMetaRefresh = aMetaRefresh;

  if (!mRefreshURIList) {
    mRefreshURIList = nsArray::Create();
  }

  if (busyFlags & BUSY_FLAGS_BUSY || (!mIsActive && mDisableMetaRefreshWhenInactive)) {
    // We don't  want to create the timer right now. Instead queue up the request
    // and trigger the timer in EndPageLoad() or whenever we become active.
    mRefreshURIList->AppendElement(refreshTimer, /*weak =*/ false);
  } else {
    // There is no page loading going on right now.  Create the
    // timer and fire it right away.
    nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
    NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE);

    mRefreshURIList->AppendElement(timer, /*weak =*/ false);  // owning timer ref
    timer->InitWithCallback(refreshTimer, aDelay, nsITimer::TYPE_ONE_SHOT);
  }
  return NS_OK;
}

nsresult
nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI,
                                     int32_t aDelay,
                                     bool aMetaRefresh,
                                     nsITimer* aTimer,
                                     nsIPrincipal* aPrincipal)
{
  NS_PRECONDITION(aTimer, "Must have a timer here");

  // Remove aTimer from mRefreshURIList if needed
  if (mRefreshURIList) {
    uint32_t n = 0;
    mRefreshURIList->GetLength(&n);

    for (uint32_t i = 0; i < n; ++i) {
      nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
      if (timer == aTimer) {
        mRefreshURIList->RemoveElementAt(i);
        break;
      }
    }
  }

  return ForceRefreshURI(aURI, aDelay, aMetaRefresh, aPrincipal);
}

NS_IMETHODIMP
nsDocShell::ForceRefreshURI(nsIURI* aURI, int32_t aDelay, bool aMetaRefresh, nsIPrincipal* aPrincipal)
{
  NS_ENSURE_ARG(aURI);

  nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
  CreateLoadInfo(getter_AddRefs(loadInfo));
  NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY);

  /* We do need to pass in a referrer, but we don't want it to
   * be sent to the server.
   */
  loadInfo->SetSendReferrer(false);

  /* for most refreshes the current URI is an appropriate
   * internal referrer
   */
  loadInfo->SetReferrer(mCurrentURI);

  /* Don't ever "guess" on which principal to use to avoid picking
   * the current principal.
   */
  loadInfo->SetPrincipalIsExplicit(true);

  /* Check if this META refresh causes a redirection
   * to another site.
   */
  bool equalUri = false;
  nsresult rv = aURI->Equals(mCurrentURI, &equalUri);
  if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh &&
      aDelay <= REFRESH_REDIRECT_TIMER) {
    /* It is a META refresh based redirection within the threshold time
     * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER).
     * Pass a REPLACE flag to LoadURI().
     */
    loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormalReplace);

    /* for redirects we mimic HTTP, which passes the
     *  original referrer
     */
    nsCOMPtr<nsIURI> internalReferrer;
    GetReferringURI(getter_AddRefs(internalReferrer));
    if (internalReferrer) {
      loadInfo->SetReferrer(internalReferrer);
    }
  } else {
    loadInfo->SetLoadType(nsIDocShellLoadInfo::loadRefresh);
  }

  // If the principal is null, the refresh will have a triggeringPrincipal
  // derived from the referrer URI, or will be set to the system principal
  // if there is no refererrer. See LoadURI()
  if (aPrincipal) {
    loadInfo->SetTriggeringPrincipal(aPrincipal);
  }

  /*
   * LoadURI(...) will cancel all refresh timers... This causes the
   * Timer and its refreshData instance to be released...
   */
  LoadURI(aURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL, true);

  return NS_OK;
}

nsresult
nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI,
                                      nsIPrincipal* aPrincipal,
                                      const nsACString& aHeader)
{
  // Refresh headers are parsed with the following format in mind
  // <META HTTP-EQUIV=REFRESH CONTENT="5; URL=http://uri">
  // By the time we are here, the following is true:
  // header = "REFRESH"
  // content = "5; URL=http://uri" // note the URL attribute is
  // optional, if it is absent, the currently loaded url is used.
  // Also note that the seconds and URL separator can be either
  // a ';' or a ','. The ',' separator should be illegal but CNN
  // is using it.
  //
  // We need to handle the following strings, where
  //  - X is a set of digits
  //  - URI is either a relative or absolute URI
  //
  // Note that URI should start with "url=" but we allow omission
  //
  // "" || ";" || ","
  //  empty string. use the currently loaded URI
  //  and refresh immediately.
  // "X" || "X;" || "X,"
  //  Refresh the currently loaded URI in X seconds.
  // "X; URI" || "X, URI"
  //  Refresh using URI as the destination in X seconds.
  // "URI" || "; URI" || ", URI"
  //  Refresh immediately using URI as the destination.
  //
  // Currently, anything immediately following the URI, if
  // separated by any char in the set "'\"\t\r\n " will be
  // ignored. So "10; url=go.html ; foo=bar" will work,
  // and so will "10; url='go.html'; foo=bar". However,
  // "10; url=go.html; foo=bar" will result in the uri
  // "go.html;" since ';' and ',' are valid uri characters.
  //
  // Note that we need to remove any tokens wrapping the URI.
  // These tokens currently include spaces, double and single
  // quotes.

  // when done, seconds is 0 or the given number of seconds
  //            uriAttrib is empty or the URI specified
  MOZ_ASSERT(aPrincipal);

  nsAutoCString uriAttrib;
  int32_t seconds = 0;
  bool specifiesSeconds = false;

  nsACString::const_iterator iter, tokenStart, doneIterating;

  aHeader.BeginReading(iter);
  aHeader.EndReading(doneIterating);

  // skip leading whitespace
  while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
    ++iter;
  }

  tokenStart = iter;

  // skip leading + and -
  if (iter != doneIterating && (*iter == '-' || *iter == '+')) {
    ++iter;
  }

  // parse number
  while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) {
    seconds = seconds * 10 + (*iter - '0');
    specifiesSeconds = true;
    ++iter;
  }

  if (iter != doneIterating) {
    // if we started with a '-', number is negative
    if (*tokenStart == '-') {
      seconds = -seconds;
    }

    // skip to next ';' or ','
    nsACString::const_iterator iterAfterDigit = iter;
    while (iter != doneIterating && !(*iter == ';' || *iter == ',')) {
      if (specifiesSeconds) {
        // Non-whitespace characters here mean that the string is
        // malformed but tolerate sites that specify a decimal point,
        // even though meta refresh only works on whole seconds.
        if (iter == iterAfterDigit &&
            !nsCRT::IsAsciiSpace(*iter) && *iter != '.') {
          // The characters between the seconds and the next
          // section are just garbage!
          //   e.g. content="2a0z+,URL=http://www.mozilla.org/"
          // Just ignore this redirect.
          return NS_ERROR_FAILURE;
        } else if (nsCRT::IsAsciiSpace(*iter)) {
          // We've had at least one whitespace so tolerate the mistake
          // and drop through.
          // e.g. content="10 foo"
          ++iter;
          break;
        }
      }
      ++iter;
    }

    // skip any remaining whitespace
    while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
      ++iter;
    }

    // skip ';' or ','
    if (iter != doneIterating && (*iter == ';' || *iter == ',')) {
      ++iter;
    }

    // skip whitespace
    while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
      ++iter;
    }
  }

  // possible start of URI
  tokenStart = iter;

  // skip "url = " to real start of URI
  if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) {
    ++iter;
    if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) {
      ++iter;
      if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) {
        ++iter;

        // skip whitespace
        while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
          ++iter;
        }

        if (iter != doneIterating && *iter == '=') {
          ++iter;

          // skip whitespace
          while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) {
            ++iter;
          }

          // found real start of URI
          tokenStart = iter;
        }
      }
    }
  }

  // skip a leading '"' or '\''.

  bool isQuotedURI = false;
  if (tokenStart != doneIterating &&
      (*tokenStart == '"' || *tokenStart == '\'')) {
    isQuotedURI = true;
    ++tokenStart;
  }

  // set iter to start of URI
  iter = tokenStart;

  // tokenStart here points to the beginning of URI

  // grab the rest of the URI
  while (iter != doneIterating) {
    if (isQuotedURI && (*iter == '"' || *iter == '\'')) {
      break;
    }
    ++iter;
  }

  // move iter one back if the last character is a '"' or '\''
  if (iter != tokenStart && isQuotedURI) {
    --iter;
    if (!(*iter == '"' || *iter == '\'')) {
      ++iter;
    }
  }

  // URI is whatever's contained from tokenStart to iter.
  // note: if tokenStart == doneIterating, so is iter.

  nsresult rv = NS_OK;

  nsCOMPtr<nsIURI> uri;
  bool specifiesURI = false;
  if (tokenStart == iter) {
    uri = aBaseURI;
  } else {
    uriAttrib = Substring(tokenStart, iter);
    // NS_NewURI takes care of any whitespace surrounding the URL
    rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI);
    specifiesURI = true;
  }

  // No URI or seconds were specified
  if (!specifiesSeconds && !specifiesURI) {
    // Do nothing because the alternative is to spin around in a refresh
    // loop forever!
    return NS_ERROR_FAILURE;
  }

  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIScriptSecurityManager> securityManager(
      do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
    if (NS_SUCCEEDED(rv)) {
      rv = securityManager->CheckLoadURIWithPrincipal(
        aPrincipal, uri,
        nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT);

      if (NS_SUCCEEDED(rv)) {
        bool isjs = true;
        rv = NS_URIChainHasFlags(
          uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs);
        NS_ENSURE_SUCCESS(rv, rv);

        if (isjs) {
          return NS_ERROR_FAILURE;
        }
      }

      if (NS_SUCCEEDED(rv)) {
        // Since we can't travel back in time yet, just pretend
        // negative numbers do nothing at all.
        if (seconds < 0) {
          return NS_ERROR_FAILURE;
        }

        rv = RefreshURI(uri, seconds * 1000, false, true, aPrincipal);
      }
    }
  }
  return rv;
}

NS_IMETHODIMP
nsDocShell::SetupRefreshURI(nsIChannel* aChannel)
{
  nsresult rv;
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel, &rv));
  if (NS_SUCCEEDED(rv)) {
    nsAutoCString refreshHeader;
    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
                                        refreshHeader);

    if (!refreshHeader.IsEmpty()) {
      nsCOMPtr<nsIScriptSecurityManager> secMan =
        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCOMPtr<nsIPrincipal> principal;
      rv = secMan->GetChannelResultPrincipal(aChannel,
                                             getter_AddRefs(principal));
      NS_ENSURE_SUCCESS(rv, rv);

      SetupReferrerFromChannel(aChannel);
      rv = SetupRefreshURIFromHeader(mCurrentURI, principal, refreshHeader);
      if (NS_SUCCEEDED(rv)) {
        return NS_REFRESHURI_HEADER_FOUND;
      }
    }
  }
  return rv;
}

static void
DoCancelRefreshURITimers(nsIMutableArray* aTimerList)
{
  if (!aTimerList) {
    return;
  }

  uint32_t n = 0;
  aTimerList->GetLength(&n);

  while (n) {
    nsCOMPtr<nsITimer> timer(do_QueryElementAt(aTimerList, --n));

    aTimerList->RemoveElementAt(n);  // bye bye owning timer ref

    if (timer) {
      timer->Cancel();
    }
  }
}

NS_IMETHODIMP
nsDocShell::CancelRefreshURITimers()
{
  DoCancelRefreshURITimers(mRefreshURIList);
  DoCancelRefreshURITimers(mSavedRefreshURIList);
  mRefreshURIList = nullptr;
  mSavedRefreshURIList = nullptr;

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetRefreshPending(bool* aResult)
{
  if (!mRefreshURIList) {
    *aResult = false;
    return NS_OK;
  }

  uint32_t count;
  nsresult rv = mRefreshURIList->GetLength(&count);
  if (NS_SUCCEEDED(rv)) {
    *aResult = (count != 0);
  }
  return rv;
}

NS_IMETHODIMP
nsDocShell::SuspendRefreshURIs()
{
  if (mRefreshURIList) {
    uint32_t n = 0;
    mRefreshURIList->GetLength(&n);

    for (uint32_t i = 0; i < n; ++i) {
      nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
      if (!timer) {
        continue; // this must be a nsRefreshURI already
      }

      // Replace this timer object with a nsRefreshTimer object.
      nsCOMPtr<nsITimerCallback> callback;
      timer->GetCallback(getter_AddRefs(callback));

      timer->Cancel();

      nsCOMPtr<nsITimerCallback> rt = do_QueryInterface(callback);
      NS_ASSERTION(rt,
                   "RefreshURIList timer callbacks should only be RefreshTimer objects");

      mRefreshURIList->ReplaceElementAt(rt, i, /*weak =*/ false);
    }
  }

  // Suspend refresh URIs for our child shells as well.
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      shell->SuspendRefreshURIs();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::ResumeRefreshURIs()
{
  RefreshURIFromQueue();

  // Resume refresh URIs for our child shells as well.
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
    if (shell) {
      shell->ResumeRefreshURIs();
    }
  }

  return NS_OK;
}

nsresult
nsDocShell::RefreshURIFromQueue()
{
  if (!mRefreshURIList) {
    return NS_OK;
  }
  uint32_t n = 0;
  mRefreshURIList->GetLength(&n);

  while (n) {
    nsCOMPtr<nsITimerCallback> refreshInfo =
        do_QueryElementAt(mRefreshURIList, --n);

    if (refreshInfo) {
      // This is the nsRefreshTimer object, waiting to be
      // setup in a timer object and fired.
      // Create the timer and  trigger it.
      uint32_t delay =
        static_cast<nsRefreshTimer*>(
          static_cast<nsITimerCallback*>(refreshInfo))->GetDelay();
      nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
      if (timer) {
        // Replace the nsRefreshTimer element in the queue with
        // its corresponding timer object, so that in case another
        // load comes through before the timer can go off, the timer will
        // get cancelled in CancelRefreshURITimer()
        mRefreshURIList->ReplaceElementAt(timer, n, /*weak =*/ false);
        timer->InitWithCallback(refreshInfo, delay, nsITimer::TYPE_ONE_SHOT);
      }
    }
  }

  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIContentViewerContainer
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::Embed(nsIContentViewer* aContentViewer,
                  const char* aCommand, nsISupports* aExtraInfo)
{
  // Save the LayoutHistoryState of the previous document, before
  // setting up new document
  PersistLayoutHistoryState();

  nsresult rv = SetupNewViewer(aContentViewer);
  NS_ENSURE_SUCCESS(rv, rv);

  // If we are loading a wyciwyg url from history, change the base URI for
  // the document to the original http url that created the document.write().
  // This makes sure that all relative urls in a document.written page loaded
  // via history work properly.
  if (mCurrentURI &&
      (mLoadType & LOAD_CMD_HISTORY ||
       mLoadType == LOAD_RELOAD_NORMAL ||
       mLoadType == LOAD_RELOAD_CHARSET_CHANGE)) {
    bool isWyciwyg = false;
    // Check if the url is wyciwyg
    rv = mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg);
    if (isWyciwyg && NS_SUCCEEDED(rv)) {
      SetBaseUrlForWyciwyg(aContentViewer);
    }
  }
  // XXX What if SetupNewViewer fails?
  if (mLSHE) {
    // Restore the editing state, if it's stored in session history.
    if (mLSHE->HasDetachedEditor()) {
      ReattachEditorToWindow(mLSHE);
    }
    // Set history.state
    SetDocCurrentStateObj(mLSHE);

    SetHistoryEntry(&mOSHE, mLSHE);
  }

  bool updateHistory = true;

  // Determine if this type of load should update history
  switch (mLoadType) {
    case LOAD_NORMAL_REPLACE:
    case LOAD_STOP_CONTENT_AND_REPLACE:
    case LOAD_RELOAD_BYPASS_CACHE:
    case LOAD_RELOAD_BYPASS_PROXY:
    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
    case LOAD_REPLACE_BYPASS_CACHE:
      updateHistory = false;
      break;
    default:
      break;
  }

  if (!updateHistory) {
    SetLayoutHistoryState(nullptr);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetIsPrinting(bool aIsPrinting)
{
  mIsPrintingOrPP = aIsPrinting;
  return NS_OK;
}

//*****************************************************************************
// nsDocShell::nsIWebProgressListener
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::OnProgressChange(nsIWebProgress* aProgress,
                             nsIRequest* aRequest,
                             int32_t aCurSelfProgress,
                             int32_t aMaxSelfProgress,
                             int32_t aCurTotalProgress,
                             int32_t aMaxTotalProgress)
{
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
                          uint32_t aStateFlags, nsresult aStatus)
{
  if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) {
    // Save timing statistics.
    nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
    nsCOMPtr<nsIURI> uri;
    channel->GetURI(getter_AddRefs(uri));
    nsAutoCString aURI;
    uri->GetAsciiSpec(aURI);

    nsCOMPtr<nsIWyciwygChannel> wcwgChannel(do_QueryInterface(aRequest));
    nsCOMPtr<nsIWebProgress> webProgress =
      do_QueryInterface(GetAsSupports(this));

    // We don't update navigation timing for wyciwyg channels
    if (this == aProgress && !wcwgChannel) {
      MaybeInitTiming();
      mTiming->NotifyFetchStart(uri,
                                ConvertLoadTypeToNavigationType(mLoadType));
    }

    // Was the wyciwyg document loaded on this docshell?
    if (wcwgChannel && !mLSHE && (mItemType == typeContent) &&
        aProgress == webProgress.get()) {
      bool equalUri = true;
      // Store the wyciwyg url in session history, only if it is
      // being loaded fresh for the first time. We don't want
      // multiple entries for successive loads
      if (mCurrentURI &&
          NS_SUCCEEDED(uri->Equals(mCurrentURI, &equalUri)) &&
          !equalUri) {
        nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
        GetSameTypeParent(getter_AddRefs(parentAsItem));
        nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
        bool inOnLoadHandler = false;
        if (parentDS) {
          parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
        }
        if (inOnLoadHandler) {
          // We're handling parent's load event listener, which causes
          // document.write in a subdocument.
          // Need to clear the session history for all child
          // docshells so that we can handle them like they would
          // all be added dynamically.
          nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
          if (parent) {
            bool oshe = false;
            nsCOMPtr<nsISHEntry> entry;
            parent->GetCurrentSHEntry(getter_AddRefs(entry), &oshe);
            static_cast<nsDocShell*>(parent.get())->ClearFrameHistory(entry);
          }
        }

        // This is a document.write(). Get the made-up url
        // from the channel and store it in session history.
        // Pass false for aCloneChildren, since we're creating
        // a new DOM here.
        AddToSessionHistory(uri, wcwgChannel, nullptr, nullptr, false,
                            getter_AddRefs(mLSHE));
        SetCurrentURI(uri, aRequest, true, 0);
        // Save history state of the previous page
        PersistLayoutHistoryState();
        // We'll never get an Embed() for this load, so just go ahead
        // and SetHistoryEntry now.
        SetHistoryEntry(&mOSHE, mLSHE);
      }
    }
    // Page has begun to load
    mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD;

    if ((aStateFlags & STATE_RESTORING) == 0) {
      // Show the progress cursor if the pref is set
      if (nsContentUtils::UseActivityCursor()) {
        nsCOMPtr<nsIWidget> mainWidget;
        GetMainWidget(getter_AddRefs(mainWidget));
        if (mainWidget) {
          mainWidget->SetCursor(eCursor_spinning);
        }
      }
    }
  } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) {
    // Page is loading
    mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING;
  } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) {
    // Page has finished loading
    mBusyFlags = BUSY_FLAGS_NONE;

    // Hide the progress cursor if the pref is set
    if (nsContentUtils::UseActivityCursor()) {
      nsCOMPtr<nsIWidget> mainWidget;
      GetMainWidget(getter_AddRefs(mainWidget));
      if (mainWidget) {
        mainWidget->SetCursor(eCursor_standard);
      }
    }
  }
  if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) {
    nsCOMPtr<nsIWebProgress> webProgress =
      do_QueryInterface(GetAsSupports(this));
    // Is the document stop notification for this document?
    if (aProgress == webProgress.get()) {
      nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
      EndPageLoad(aProgress, channel, aStatus);
    }
  }
  // note that redirect state changes will go through here as well, but it
  // is better to handle those in OnRedirectStateChange where more
  // information is available.
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
                             nsIURI* aURI, uint32_t aFlags)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

void
nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
                                  nsIChannel* aNewChannel,
                                  uint32_t aRedirectFlags,
                                  uint32_t aStateFlags)
{
  NS_ASSERTION(aStateFlags & STATE_REDIRECTING,
               "Calling OnRedirectStateChange when there is no redirect");

  // If mixed content is allowed for the old channel, we forward
  // the permission to the new channel if it has the same origin
  // as the old one.
  if (mMixedContentChannel && mMixedContentChannel == aOldChannel) {
    nsresult rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel);
    if (NS_SUCCEEDED(rv)) {
      SetMixedContentChannel(aNewChannel); // Same origin: forward permission.
    } else {
      SetMixedContentChannel(nullptr); // Different origin: clear mMixedContentChannel.
    }
  }

  if (!(aStateFlags & STATE_IS_DOCUMENT)) {
    return;  // not a toplevel document
  }

  nsCOMPtr<nsIURI> oldURI, newURI;
  aOldChannel->GetURI(getter_AddRefs(oldURI));
  aNewChannel->GetURI(getter_AddRefs(newURI));
  if (!oldURI || !newURI) {
    return;
  }

  // Below a URI visit is saved (see AddURIVisit method doc).
  // The visit chain looks something like:
  //   ...
  //   Site N - 1
  //                =>  Site N
  //   (redirect to =>) Site N + 1 (we are here!)

  // Get N - 1 and transition type
  nsCOMPtr<nsIURI> previousURI;
  uint32_t previousFlags = 0;
  ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags);

  if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL ||
      ChannelIsPost(aOldChannel)) {
    // 1. Internal redirects are ignored because they are specific to the
    //    channel implementation.
    // 2. POSTs are not saved by global history.
    //
    // Regardless, we need to propagate the previous visit to the new
    // channel.
    SaveLastVisit(aNewChannel, previousURI, previousFlags);
  } else {
    nsCOMPtr<nsIURI> referrer;
    // Treat referrer as null if there is an error getting it.
    (void)NS_GetReferrerFromChannel(aOldChannel, getter_AddRefs(referrer));

    // Get the HTTP response code, if available.
    uint32_t responseStatus = 0;
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
    if (httpChannel) {
      (void)httpChannel->GetResponseStatus(&responseStatus);
    }

    // Add visit N -1 => N
    AddURIVisit(oldURI, referrer, previousURI, previousFlags, responseStatus);

    // Since N + 1 could be the final destination, we will not save N => N + 1
    // here.  OnNewURI will do that, so we will cache it.
    SaveLastVisit(aNewChannel, oldURI, aRedirectFlags);
  }

  // check if the new load should go through the application cache.
  nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
    do_QueryInterface(aNewChannel);
  if (appCacheChannel) {
    if (GeckoProcessType_Default != XRE_GetProcessType()) {
      // Permission will be checked in the parent process.
      appCacheChannel->SetChooseApplicationCache(true);
    } else {
      nsCOMPtr<nsIScriptSecurityManager> secMan =
        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);

      if (secMan) {
        nsCOMPtr<nsIPrincipal> principal;
        secMan->GetDocShellCodebasePrincipal(newURI, this,
                                             getter_AddRefs(principal));
        appCacheChannel->SetChooseApplicationCache(
          NS_ShouldCheckAppCache(principal, UsePrivateBrowsing()));
      }
    }
  }

  if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
      mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
    mLoadType = LOAD_NORMAL_REPLACE;
    SetHistoryEntry(&mLSHE, nullptr);
  }
}

NS_IMETHODIMP
nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress,
                           nsIRequest* aRequest,
                           nsresult aStatus, const char16_t* aMessage)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress,
                             nsIRequest* aRequest, uint32_t aState)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

nsresult
nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
                        nsIChannel* aChannel, nsresult aStatus)
{
  if (!aChannel) {
    return NS_ERROR_NULL_POINTER;
  }

  nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
  if (reporter) {
    reporter->FlushConsoleReports(GetDocument());
  }

  nsCOMPtr<nsIURI> url;
  nsresult rv = aChannel->GetURI(getter_AddRefs(url));
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsITimedChannel> timingChannel = do_QueryInterface(aChannel);
  if (timingChannel) {
    TimeStamp channelCreationTime;
    rv = timingChannel->GetChannelCreation(&channelCreationTime);
    if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
      Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME,
                                     channelCreationTime);
      nsCOMPtr<nsPILoadGroupInternal> internalLoadGroup =
        do_QueryInterface(mLoadGroup);
      if (internalLoadGroup) {
        internalLoadGroup->OnEndPageLoad(aChannel);
      }
    }
  }

  // Timing is picked up by the window, we don't need it anymore
  mTiming = nullptr;

  // clean up reload state for meta charset
  if (eCharsetReloadRequested == mCharsetReloadState) {
    mCharsetReloadState = eCharsetReloadStopOrigional;
  } else {
    mCharsetReloadState = eCharsetReloadInit;
  }

  // Save a pointer to the currently-loading history entry.
  // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history
  // entry further down in this method.
  nsCOMPtr<nsISHEntry> loadingSHE = mLSHE;
  mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore

  //
  // one of many safeguards that prevent death and destruction if
  // someone is so very very rude as to bring this window down
  // during this load handler.
  //
  nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);

  // Notify the ContentViewer that the Document has finished loading.  This
  // will cause any OnLoad(...) and PopState(...) handlers to fire.
  if (!mEODForCurrentDocument && mContentViewer) {
    mIsExecutingOnLoadHandler = true;
    mContentViewer->LoadComplete(aStatus);
    mIsExecutingOnLoadHandler = false;

    mEODForCurrentDocument = true;

    // If all documents have completed their loading
    // favor native event dispatch priorities
    // over performance
    if (--gNumberOfDocumentsLoading == 0) {
      // Hint to use normal native event dispatch priorities
      FavorPerformanceHint(false);
    }
  }
  /* Check if the httpChannel has any cache-control related response headers,
   * like no-store, no-cache. If so, update SHEntry so that
   * when a user goes back/forward to this page, we appropriately do
   * form value restoration or load from server.
   */
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
  if (!httpChannel) {
    // HttpChannel could be hiding underneath a Multipart channel.
    GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
  }

  if (httpChannel) {
    // figure out if SH should be saving layout state.
    bool discardLayoutState = ShouldDiscardLayoutState(httpChannel);
    if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) &&
        (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) {
      mLSHE->SetSaveLayoutStateFlag(false);
    }
  }

  // Clear mLSHE after calling the onLoadHandlers. This way, if the
  // onLoadHandler tries to load something different in
  // itself or one of its children, we can deal with it appropriately.
  if (mLSHE) {
    mLSHE->SetLoadType(nsIDocShellLoadInfo::loadHistory);

    // Clear the mLSHE reference to indicate document loading is done one
    // way or another.
    SetHistoryEntry(&mLSHE, nullptr);
  }
  // if there's a refresh header in the channel, this method
  // will set it up for us.
  if (mIsActive || !mDisableMetaRefreshWhenInactive)
    RefreshURIFromQueue();

  // Test whether this is the top frame or a subframe
  bool isTopFrame = true;
  nsCOMPtr<nsIDocShellTreeItem> targetParentTreeItem;
  rv = GetSameTypeParent(getter_AddRefs(targetParentTreeItem));
  if (NS_SUCCEEDED(rv) && targetParentTreeItem) {
    isTopFrame = false;
  }

  //
  // If the page load failed, then deal with the error condition...
  // Errors are handled as follows:
  //   1. Check to see if it's a file not found error or bad content
  //      encoding error.
  //   2. Send the URI to a keyword server (if enabled)
  //   3. If the error was DNS failure, then add www and .com to the URI
  //      (if appropriate).
  //   4. Throw an error dialog box...
  //
  if (url && NS_FAILED(aStatus)) {
    if (aStatus == NS_ERROR_FILE_NOT_FOUND ||
        aStatus == NS_ERROR_FILE_ACCESS_DENIED ||
        aStatus == NS_ERROR_CORRUPTED_CONTENT ||
        aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
      DisplayLoadError(aStatus, url, nullptr, aChannel);
      return NS_OK;
    } else if (aStatus == NS_ERROR_INVALID_SIGNATURE) {
      // NS_ERROR_INVALID_SIGNATURE indicates a content-signature error.
      // This currently only happens in case a remote about page fails.
      // We have to load a fallback in this case.
      // XXX: We always load about blank here, firefox has to overwrite this if
      // it wants to display something else.
      return LoadURI(u"about:blank",            // URI string
                     nsIChannel::LOAD_NORMAL,   // Load flags
                     nullptr,                   // Referring URI
                     nullptr,                   // Post data stream
                     nullptr);                  // Headers stream
    }

    // Handle iframe document not loading error because source was
    // a tracking URL. We make a note of this iframe node by including
    // it in a dedicated array of blocked tracking nodes under its parent
    // document. (document of parent window of blocked document)
    if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) {
      // frameElement is our nsIContent to be annotated
      nsCOMPtr<nsIDOMElement> frameElement;
      nsPIDOMWindowOuter* thisWindow = GetWindow();
      if (!thisWindow) {
        return NS_OK;
      }

      frameElement = thisWindow->GetFrameElement();
      if (!frameElement) {
        return NS_OK;
      }

      // Parent window
      nsCOMPtr<nsIDocShellTreeItem> parentItem;
      GetSameTypeParent(getter_AddRefs(parentItem));
      if (!parentItem) {
        return NS_OK;
      }

      nsCOMPtr<nsIDocument> parentDoc;
      parentDoc = parentItem->GetDocument();
      if (!parentDoc) {
        return NS_OK;
      }

      nsCOMPtr<nsIContent> cont = do_QueryInterface(frameElement);
      parentDoc->AddBlockedTrackingNode(cont);

      return NS_OK;
    }

    if (sURIFixup) {
      //
      // Try and make an alternative URI from the old one
      //
      nsCOMPtr<nsIURI> newURI;
      nsCOMPtr<nsIInputStream> newPostData;

      nsAutoCString oldSpec;
      url->GetSpec(oldSpec);

      //
      // First try keyword fixup
      //
      nsAutoString keywordProviderName, keywordAsSent;
      if (aStatus == NS_ERROR_UNKNOWN_HOST && mAllowKeywordFixup) {
        bool keywordsEnabled = Preferences::GetBool("keyword.enabled", false);

        nsAutoCString host;
        url->GetHost(host);

        nsAutoCString scheme;
        url->GetScheme(scheme);

        int32_t dotLoc = host.FindChar('.');

        // we should only perform a keyword search under the following
        // conditions:
        // (0) Pref keyword.enabled is true
        // (1) the url scheme is http (or https)
        // (2) the url does not have a protocol scheme
        // If we don't enforce such a policy, then we end up doing
        // keyword searchs on urls we don't intend like imap, file,
        // mailbox, etc. This could lead to a security problem where we
        // send data to the keyword server that we shouldn't be.
        // Someone needs to clean up keywords in general so we can
        // determine on a per url basis if we want keywords
        // enabled...this is just a bandaid...
        if (keywordsEnabled && !scheme.IsEmpty() &&
            (scheme.Find("http") != 0)) {
          keywordsEnabled = false;
        }

        if (keywordsEnabled && (kNotFound == dotLoc)) {
          nsCOMPtr<nsIURIFixupInfo> info;
          // only send non-qualified hosts to the keyword server
          if (!mOriginalUriString.IsEmpty()) {
            sURIFixup->KeywordToURI(mOriginalUriString,
                                    getter_AddRefs(newPostData),
                                    getter_AddRefs(info));
          } else {
            //
            // If this string was passed through nsStandardURL by
            // chance, then it may have been converted from UTF-8 to
            // ACE, which would result in a completely bogus keyword
            // query.  Here we try to recover the original Unicode
            // value, but this is not 100% correct since the value may
            // have been normalized per the IDN normalization rules.
            //
            // Since we don't have access to the exact original string
            // that was entered by the user, this will just have to do.
            bool isACE;
            nsAutoCString utf8Host;
            nsCOMPtr<nsIIDNService> idnSrv =
              do_GetService(NS_IDNSERVICE_CONTRACTID);
            if (idnSrv &&
                NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE &&
                NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) {
              sURIFixup->KeywordToURI(utf8Host,
                                      getter_AddRefs(newPostData),
                                      getter_AddRefs(info));
            } else {
              sURIFixup->KeywordToURI(host,
                                      getter_AddRefs(newPostData),
                                      getter_AddRefs(info));
            }
          }

          info->GetPreferredURI(getter_AddRefs(newURI));
          if (newURI) {
            info->GetKeywordAsSent(keywordAsSent);
            info->GetKeywordProviderName(keywordProviderName);
          }
        } // end keywordsEnabled
      }

      //
      // Now try change the address, e.g. turn http://foo into
      // http://www.foo.com
      //
      if (aStatus == NS_ERROR_UNKNOWN_HOST ||
          aStatus == NS_ERROR_NET_RESET) {
        bool doCreateAlternate = true;

        // Skip fixup for anything except a normal document load
        // operation on the topframe.

        if (mLoadType != LOAD_NORMAL || !isTopFrame) {
          doCreateAlternate = false;
        } else {
          // Test if keyword lookup produced a new URI or not
          if (newURI) {
            bool sameURI = false;
            url->Equals(newURI, &sameURI);
            if (!sameURI) {
              // Keyword lookup made a new URI so no need to try
              // an alternate one.
              doCreateAlternate = false;
            }
          }

          if (doCreateAlternate) {
            // Skip doing this if our channel was redirected, because we
            // shouldn't be guessing things about the post-redirect URI.
            nsLoadFlags loadFlags = 0;
            if (NS_FAILED(aChannel->GetLoadFlags(&loadFlags)) ||
                (loadFlags & nsIChannel::LOAD_REPLACE)) {
              doCreateAlternate = false;
            }
          }
        }
        if (doCreateAlternate) {
          newURI = nullptr;
          newPostData = nullptr;
          keywordProviderName.Truncate();
          keywordAsSent.Truncate();
          sURIFixup->CreateFixupURI(oldSpec,
                                    nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI,
                                    getter_AddRefs(newPostData),
                                    getter_AddRefs(newURI));
        }
      }

      // Did we make a new URI that is different to the old one? If so
      // load it.
      //
      if (newURI) {
        // Make sure the new URI is different from the old one,
        // otherwise there's little point trying to load it again.
        bool sameURI = false;
        url->Equals(newURI, &sameURI);
        if (!sameURI) {
          nsAutoCString newSpec;
          newURI->GetSpec(newSpec);
          NS_ConvertUTF8toUTF16 newSpecW(newSpec);

          // This notification is meant for Firefox Health Report so it
          // can increment counts from the search engine
          MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent);

          return LoadURI(newSpecW.get(),  // URI string
                         LOAD_FLAGS_NONE, // Load flags
                         nullptr,         // Referring URI
                         newPostData,     // Post data stream
                         nullptr);        // Headers stream
        }
      }
    }

    // Well, fixup didn't work :-(
    // It is time to throw an error dialog box, and be done with it...

    // Errors to be shown only on top-level frames
    if ((aStatus == NS_ERROR_UNKNOWN_HOST ||
         aStatus == NS_ERROR_CONNECTION_REFUSED ||
         aStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
         aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED) &&
        (isTopFrame || UseErrorPages())) {
      DisplayLoadError(aStatus, url, nullptr, aChannel);
    } else if (aStatus == NS_ERROR_NET_TIMEOUT ||
               aStatus == NS_ERROR_REDIRECT_LOOP ||
               aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
               aStatus == NS_ERROR_NET_INTERRUPT ||
               aStatus == NS_ERROR_NET_RESET ||
               aStatus == NS_ERROR_OFFLINE ||
               aStatus == NS_ERROR_MALWARE_URI ||
               aStatus == NS_ERROR_PHISHING_URI ||
               aStatus == NS_ERROR_UNWANTED_URI ||
               aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
               aStatus == NS_ERROR_REMOTE_XUL ||
               aStatus == NS_ERROR_INTERCEPTION_FAILED ||
               aStatus == NS_ERROR_NET_INADEQUATE_SECURITY ||
               NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
      // Errors to be shown for any frame
      DisplayLoadError(aStatus, url, nullptr, aChannel);
    } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
      // Non-caching channels will simply return NS_ERROR_OFFLINE.
      // Caching channels would have to look at their flags to work
      // out which error to return. Or we can fix up the error here.
      if (!(mLoadType & LOAD_CMD_HISTORY)) {
        aStatus = NS_ERROR_OFFLINE;
      }
      DisplayLoadError(aStatus, url, nullptr, aChannel);
    }
  } else if (url && NS_SUCCEEDED(aStatus)) {
    // If we have a host
    mozilla::net::PredictorLearnRedirect(url, aChannel, this);
  }

  return NS_OK;
}

//*****************************************************************************
// nsDocShell: Content Viewer Management
//*****************************************************************************

nsresult
nsDocShell::EnsureContentViewer()
{
  if (mContentViewer) {
    return NS_OK;
  }
  if (mIsBeingDestroyed) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIURI> baseURI;
  nsIPrincipal* principal = GetInheritedPrincipal(false);
  nsCOMPtr<nsIDocShellTreeItem> parentItem;
  GetSameTypeParent(getter_AddRefs(parentItem));
  if (parentItem) {
    if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) {
      nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal();
      if (parentElement) {
        baseURI = parentElement->GetBaseURI();
      }
    }
  }

  nsresult rv = CreateAboutBlankContentViewer(principal, baseURI);

  NS_ENSURE_STATE(mContentViewer);

  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIDocument> doc(GetDocument());
    NS_ASSERTION(doc,
                 "Should have doc if CreateAboutBlankContentViewer "
                 "succeeded!");

    doc->SetIsInitialDocument(true);
  }

  return rv;
}

nsresult
nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
                                          nsIURI* aBaseURI,
                                          bool aTryToSaveOldPresentation)
{
  nsCOMPtr<nsIDocument> blankDoc;
  nsCOMPtr<nsIContentViewer> viewer;
  nsresult rv = NS_ERROR_FAILURE;

  /* mCreatingDocument should never be true at this point. However, it's
     a theoretical possibility. We want to know about it and make it stop,
     and this sounds like a job for an assertion. */
  NS_ASSERTION(!mCreatingDocument,
               "infinite(?) loop creating document averted");
  if (mCreatingDocument) {
    return NS_ERROR_FAILURE;
  }

  // mContentViewer->PermitUnload may release |this| docshell.
  nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);

  AutoRestore<bool> creatingDocument(mCreatingDocument);
  mCreatingDocument = true;

  if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) &&
      mItemType != typeChrome) {
    MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(
      BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(),
      mOriginAttributes));
  }

  // Make sure timing is created.  But first record whether we had it
  // already, so we don't clobber the timing for an in-progress load.
  bool hadTiming = mTiming;
  MaybeInitTiming();
  if (mContentViewer) {
    // We've got a content viewer already. Make sure the user
    // permits us to discard the current document and replace it
    // with about:blank. And also ensure we fire the unload events
    // in the current document.

    // Unload gets fired first for
    // document loaded from the session history.
    mTiming->NotifyBeforeUnload();

    bool okToUnload;
    rv = mContentViewer->PermitUnload(&okToUnload);

    if (NS_SUCCEEDED(rv) && !okToUnload) {
      // The user chose not to unload the page, interrupt the load.
      return NS_ERROR_FAILURE;
    }

    mSavingOldViewer = aTryToSaveOldPresentation &&
                       CanSavePresentation(LOAD_NORMAL, nullptr, nullptr);

    if (mTiming) {
      mTiming->NotifyUnloadAccepted(mCurrentURI);
    }

    // Make sure to blow away our mLoadingURI just in case.  No loads
    // from inside this pagehide.
    mLoadingURI = nullptr;

    // Stop any in-progress loading, so that we don't accidentally trigger any
    // PageShow notifications from Embed() interrupting our loading below.
    Stop();

    // Notify the current document that it is about to be unloaded!!
    //
    // It is important to fire the unload() notification *before* any state
    // is changed within the DocShell - otherwise, javascript will get the
    // wrong information :-(
    //
    (void)FirePageHideNotification(!mSavingOldViewer);
  }

  // Now make sure we don't think we're in the middle of firing unload after
  // this point.  This will make us fire unload when the about:blank document
  // unloads... but that's ok, more or less.  Would be nice if it fired load
  // too, of course.
  mFiredUnloadEvent = false;

  nsCOMPtr<nsIDocumentLoaderFactory> docFactory =
    nsContentUtils::FindInternalContentViewer(NS_LITERAL_CSTRING("text/html"));

  if (docFactory) {
    nsCOMPtr<nsIPrincipal> principal;
    if (mSandboxFlags & SANDBOXED_ORIGIN) {
      if (aPrincipal) {
        principal = nsNullPrincipal::CreateWithInheritedAttributes(aPrincipal);
      } else {
        principal = nsNullPrincipal::CreateWithInheritedAttributes(this);
      }
    } else {
      principal = aPrincipal;
    }
    // generate (about:blank) document to load
    docFactory->CreateBlankDocument(mLoadGroup, principal,
                                    getter_AddRefs(blankDoc));
    if (blankDoc) {
      // Hack: set the base URI manually, since this document never
      // got Reset() with a channel.
      blankDoc->SetBaseURI(aBaseURI);

      blankDoc->SetContainer(this);

      // Copy our sandbox flags to the document. These are immutable
      // after being set here.
      blankDoc->SetSandboxFlags(mSandboxFlags);

      // create a content viewer for us and the new document
      docFactory->CreateInstanceForDocument(
        NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view",
        getter_AddRefs(viewer));

      // hook 'em up
      if (viewer) {
        viewer->SetContainer(this);
        rv = Embed(viewer, "", 0);
        NS_ENSURE_SUCCESS(rv, rv);

        SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0);
        rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK;
      }
    }
  }

  // The transient about:blank viewer doesn't have a session history entry.
  SetHistoryEntry(&mOSHE, nullptr);

  // Clear out our mTiming like we would in EndPageLoad, if we didn't
  // have one before entering this function.
  if (!hadTiming) {
    mTiming = nullptr;
    mBlankTiming = true;
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal)
{
  return CreateAboutBlankContentViewer(aPrincipal, nullptr);
}

bool
nsDocShell::CanSavePresentation(uint32_t aLoadType,
                                nsIRequest* aNewRequest,
                                nsIDocument* aNewDocument)
{
  if (!mOSHE) {
    return false;  // no entry to save into
  }

  nsCOMPtr<nsIContentViewer> viewer;
  mOSHE->GetContentViewer(getter_AddRefs(viewer));
  if (viewer) {
    NS_WARNING("mOSHE already has a content viewer!");
    return false;
  }

  // Only save presentation for "normal" loads and link loads.  Anything else
  // probably wants to refetch the page, so caching the old presentation
  // would be incorrect.
  if (aLoadType != LOAD_NORMAL &&
      aLoadType != LOAD_HISTORY &&
      aLoadType != LOAD_LINK &&
      aLoadType != LOAD_STOP_CONTENT &&
      aLoadType != LOAD_STOP_CONTENT_AND_REPLACE &&
      aLoadType != LOAD_ERROR_PAGE) {
    return false;
  }

  // If the session history entry has the saveLayoutState flag set to false,
  // then we should not cache the presentation.
  bool canSaveState;
  mOSHE->GetSaveLayoutStateFlag(&canSaveState);
  if (!canSaveState) {
    return false;
  }

  // If the document is not done loading, don't cache it.
  if (!mScriptGlobal || mScriptGlobal->IsLoading()) {
    return false;
  }

  if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) {
    return false;
  }

  // Avoid doing the work of saving the presentation state in the case where
  // the content viewer cache is disabled.
  if (nsSHistory::GetMaxTotalViewers() == 0) {
    return false;
  }

  // Don't cache the content viewer if we're in a subframe and the subframe
  // pref is disabled.
  bool cacheFrames =
    Preferences::GetBool("browser.sessionhistory.cache_subframes", false);
  if (!cacheFrames) {
    nsCOMPtr<nsIDocShellTreeItem> root;
    GetSameTypeParent(getter_AddRefs(root));
    if (root && root != this) {
      return false;  // this is a subframe load
    }
  }

  // If the document does not want its presentation cached, then don't.
  nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc();
  return doc && doc->CanSavePresentation(aNewRequest);
}

void
nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry)
{
  NS_ASSERTION(!mEditorData,
               "Why reattach an editor when we already have one?");
  NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(),
               "Reattaching when there's not a detached editor.");

  if (mEditorData || !aSHEntry) {
    return;
  }

  mEditorData = aSHEntry->ForgetEditorData();
  if (mEditorData) {
#ifdef DEBUG
    nsresult rv =
#endif
      mEditorData->ReattachToWindow(this);
    NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session");
  }
}

void
nsDocShell::DetachEditorFromWindow()
{
  if (!mEditorData || mEditorData->WaitingForLoad()) {
    // If there's nothing to detach, or if the editor data is actually set
    // up for the _new_ page that's coming in, don't detach.
    return;
  }

  NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(),
               "Detaching editor when it's already detached.");

  nsresult res = mEditorData->DetachFromWindow();
  NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor");

  if (NS_SUCCEEDED(res)) {
    // Make mOSHE hold the owning ref to the editor data.
    if (mOSHE) {
      mOSHE->SetEditorData(mEditorData.forget());
    } else {
      mEditorData = nullptr;
    }
  }

#ifdef DEBUG
  {
    bool isEditable;
    GetEditable(&isEditable);
    NS_ASSERTION(!isEditable,
                 "Window is still editable after detaching editor.");
  }
#endif // DEBUG
}

nsresult
nsDocShell::CaptureState()
{
  if (!mOSHE || mOSHE == mLSHE) {
    // No entry to save into, or we're replacing the existing entry.
    return NS_ERROR_FAILURE;
  }

  if (!mScriptGlobal) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsISupports> windowState = mScriptGlobal->SaveWindowState();
  NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE);

#ifdef DEBUG_PAGE_CACHE
  nsCOMPtr<nsIURI> uri;
  mOSHE->GetURI(getter_AddRefs(uri));
  nsAutoCString spec;
  if (uri) {
    uri->GetSpec(spec);
  }
  printf("Saving presentation into session history\n");
  printf("  SH URI: %s\n", spec.get());
#endif

  nsresult rv = mOSHE->SetWindowState(windowState);
  NS_ENSURE_SUCCESS(rv, rv);

  // Suspend refresh URIs and save off the timer queue
  rv = mOSHE->SetRefreshURIList(mSavedRefreshURIList);
  NS_ENSURE_SUCCESS(rv, rv);

  // Capture the current content viewer bounds.
  if (mContentViewer) {
    nsIntRect bounds;
    mContentViewer->GetBounds(bounds);
    rv = mOSHE->SetViewerBounds(bounds);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Capture the docshell hierarchy.
  mOSHE->ClearChildShells();

  uint32_t childCount = mChildList.Length();
  for (uint32_t i = 0; i < childCount; ++i) {
    nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i));
    NS_ASSERTION(childShell, "null child shell");

    mOSHE->AddChildShell(childShell);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::RestorePresentationEvent::Run()
{
  if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) {
    NS_WARNING("RestoreFromHistory failed");
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop)
{
  nsresult rv;
  if (!aContentViewer) {
    rv = EnsureContentViewer();
    NS_ENSURE_SUCCESS(rv, rv);

    aContentViewer = mContentViewer;
  }

  // Dispatch events for restoring the presentation.  We try to simulate
  // the progress notifications loading the document would cause, so we add
  // the document's channel to the loadgroup to initiate stateChange
  // notifications.

  nsCOMPtr<nsIDOMDocument> domDoc;
  aContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
  if (doc) {
    nsIChannel* channel = doc->GetChannel();
    if (channel) {
      mEODForCurrentDocument = false;
      mIsRestoringDocument = true;
      mLoadGroup->AddRequest(channel, nullptr);
      mIsRestoringDocument = false;
    }
  }

  if (!aTop) {
    // This point corresponds to us having gotten OnStartRequest or
    // STATE_START, so do the same thing that CreateContentViewer does at
    // this point to ensure that unload/pagehide events for this document
    // will fire when it's unloaded again.
    mFiredUnloadEvent = false;

    // For non-top frames, there is no notion of making sure that the
    // previous document is in the domwindow when STATE_START notifications
    // happen.  We can just call BeginRestore for all of the child shells
    // now.
    rv = BeginRestoreChildren();
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

nsresult
nsDocShell::BeginRestoreChildren()
{
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext());
    if (child) {
      nsresult rv = child->BeginRestore(nullptr, false);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::FinishRestore()
{
  // First we call finishRestore() on our children.  In the simulated load,
  // all of the child frames finish loading before the main document.

  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext());
    if (child) {
      child->FinishRestore();
    }
  }

  if (mOSHE && mOSHE->HasDetachedEditor()) {
    ReattachEditorToWindow(mOSHE);
  }

  nsCOMPtr<nsIDocument> doc = GetDocument();
  if (doc) {
    // Finally, we remove the request from the loadgroup.  This will
    // cause onStateChange(STATE_STOP) to fire, which will fire the
    // pageshow event to the chrome.

    nsIChannel* channel = doc->GetChannel();
    if (channel) {
      mIsRestoringDocument = true;
      mLoadGroup->RemoveRequest(channel, nullptr, NS_OK);
      mIsRestoringDocument = false;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetRestoringDocument(bool* aRestoring)
{
  *aRestoring = mIsRestoringDocument;
  return NS_OK;
}

nsresult
nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring)
{
  NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY,
               "RestorePresentation should only be called for history loads");

  nsCOMPtr<nsIContentViewer> viewer;
  aSHEntry->GetContentViewer(getter_AddRefs(viewer));

#ifdef DEBUG_PAGE_CACHE
  nsCOMPtr<nsIURI> uri;
  aSHEntry->GetURI(getter_AddRefs(uri));

  nsAutoCString spec;
  if (uri) {
    uri->GetSpec(spec);
  }
#endif

  *aRestoring = false;

  if (!viewer) {
#ifdef DEBUG_PAGE_CACHE
    printf("no saved presentation for uri: %s\n", spec.get());
#endif
    return NS_OK;
  }

  // We need to make sure the content viewer's container is this docshell.
  // In subframe navigation, it's possible for the docshell that the
  // content viewer was originally loaded into to be replaced with a
  // different one.  We don't currently support restoring the presentation
  // in that case.

  nsCOMPtr<nsIDocShell> container;
  viewer->GetContainer(getter_AddRefs(container));
  if (!::SameCOMIdentity(container, GetAsSupports(this))) {
#ifdef DEBUG_PAGE_CACHE
    printf("No valid container, clearing presentation\n");
#endif
    aSHEntry->SetContentViewer(nullptr);
    return NS_ERROR_FAILURE;
  }

  NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation");

#ifdef DEBUG_PAGE_CACHE
  printf("restoring presentation from session history: %s\n", spec.get());
#endif

  SetHistoryEntry(&mLSHE, aSHEntry);

  // Post an event that will remove the request after we've returned
  // to the event loop.  This mimics the way it is called by nsIChannel
  // implementations.

  // Revoke any pending restore (just in case)
  NS_ASSERTION(!mRestorePresentationEvent.IsPending(),
               "should only have one RestorePresentationEvent");
  mRestorePresentationEvent.Revoke();

  RefPtr<RestorePresentationEvent> evt = new RestorePresentationEvent(this);
  nsresult rv = NS_DispatchToCurrentThread(evt);
  if (NS_SUCCEEDED(rv)) {
    mRestorePresentationEvent = evt.get();
    // The rest of the restore processing will happen on our event
    // callback.
    *aRestoring = true;
  }

  return rv;
}

namespace {
class MOZ_STACK_CLASS PresentationEventForgetter
{
public:
  explicit PresentationEventForgetter(
        nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
          aRestorePresentationEvent)
    : mRestorePresentationEvent(aRestorePresentationEvent)
    , mEvent(aRestorePresentationEvent.get())
  {
  }

  ~PresentationEventForgetter()
  {
    Forget();
  }

  void Forget()
  {
    if (mRestorePresentationEvent.get() == mEvent) {
      mRestorePresentationEvent.Forget();
      mEvent = nullptr;
    }
  }

private:
  nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
    mRestorePresentationEvent;
  RefPtr<nsDocShell::RestorePresentationEvent> mEvent;
};

} // namespace

nsresult
nsDocShell::RestoreFromHistory()
{
  MOZ_ASSERT(mRestorePresentationEvent.IsPending());
  PresentationEventForgetter forgetter(mRestorePresentationEvent);

  // This section of code follows the same ordering as CreateContentViewer.
  if (!mLSHE) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIContentViewer> viewer;
  mLSHE->GetContentViewer(getter_AddRefs(viewer));
  if (!viewer) {
    return NS_ERROR_FAILURE;
  }

  if (mSavingOldViewer) {
    // We determined that it was safe to cache the document presentation
    // at the time we initiated the new load.  We need to check whether
    // it's still safe to do so, since there may have been DOM mutations
    // or new requests initiated.
    nsCOMPtr<nsIDOMDocument> domDoc;
    viewer->GetDOMDocument(getter_AddRefs(domDoc));
    nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
    nsIRequest* request = nullptr;
    if (doc) {
      request = doc->GetChannel();
    }
    mSavingOldViewer = CanSavePresentation(mLoadType, request, doc);
  }

  nsCOMPtr<nsIContentViewer> oldCv(mContentViewer);
  nsCOMPtr<nsIContentViewer> newCv(viewer);
  int32_t minFontSize = 0;
  float textZoom = 1.0f;
  float pageZoom = 1.0f;
  float overrideDPPX = 0.0f;

  bool styleDisabled = false;
  if (oldCv && newCv) {
    oldCv->GetMinFontSize(&minFontSize);
    oldCv->GetTextZoom(&textZoom);
    oldCv->GetFullZoom(&pageZoom);
    oldCv->GetOverrideDPPX(&overrideDPPX);
    oldCv->GetAuthorStyleDisabled(&styleDisabled);
  }

  // Protect against mLSHE going away via a load triggered from
  // pagehide or unload.
  nsCOMPtr<nsISHEntry> origLSHE = mLSHE;

  // Make sure to blow away our mLoadingURI just in case.  No loads
  // from inside this pagehide.
  mLoadingURI = nullptr;

  // Notify the old content viewer that it's being hidden.
  FirePageHideNotification(!mSavingOldViewer);

  // If mLSHE was changed as a result of the pagehide event, then
  // something else was loaded.  Don't finish restoring.
  if (mLSHE != origLSHE) {
    return NS_OK;
  }

  // Add the request to our load group.  We do this before swapping out
  // the content viewers so that consumers of STATE_START can access
  // the old document.  We only deal with the toplevel load at this time --
  // to be consistent with normal document loading, subframes cannot start
  // loading until after data arrives, which is after STATE_START completes.

  RefPtr<RestorePresentationEvent> currentPresentationRestoration =
    mRestorePresentationEvent.get();
  Stop();
  // Make sure we're still restoring the same presentation.
  // If we aren't, docshell is in process doing another load already.
  NS_ENSURE_STATE(currentPresentationRestoration ==
                  mRestorePresentationEvent.get());
  BeginRestore(viewer, true);
  NS_ENSURE_STATE(currentPresentationRestoration ==
                  mRestorePresentationEvent.get());
  forgetter.Forget();

  // Set mFiredUnloadEvent = false so that the unload handler for the
  // *new* document will fire.
  mFiredUnloadEvent = false;

  mURIResultedInDocument = true;
  nsCOMPtr<nsISHistory> rootSH;
  GetRootSessionHistory(getter_AddRefs(rootSH));
  if (rootSH) {
    nsCOMPtr<nsISHistoryInternal> hist = do_QueryInterface(rootSH);
    rootSH->GetIndex(&mPreviousTransIndex);
    hist->UpdateIndex();
    rootSH->GetIndex(&mLoadedTransIndex);
#ifdef DEBUG_PAGE_CACHE
    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex,
           mLoadedTransIndex);
#endif
  }

  // Rather than call Embed(), we will retrieve the viewer from the session
  // history entry and swap it in.
  // XXX can we refactor this so that we can just call Embed()?
  PersistLayoutHistoryState();
  nsresult rv;
  if (mContentViewer) {
    if (mSavingOldViewer && NS_FAILED(CaptureState())) {
      if (mOSHE) {
        mOSHE->SyncPresentationState();
      }
      mSavingOldViewer = false;
    }
  }

  mSavedRefreshURIList = nullptr;

  // In cases where we use a transient about:blank viewer between loads,
  // we never show the transient viewer, so _its_ previous viewer is never
  // unhooked from the view hierarchy.  Destroy any such previous viewer now,
  // before we grab the root view sibling, so that we don't grab a view
  // that's about to go away.

  if (mContentViewer) {
    nsCOMPtr<nsIContentViewer> previousViewer;
    mContentViewer->GetPreviousViewer(getter_AddRefs(previousViewer));
    if (previousViewer) {
      mContentViewer->SetPreviousViewer(nullptr);
      previousViewer->Destroy();
    }
  }

  // Save off the root view's parent and sibling so that we can insert the
  // new content viewer's root view at the same position.  Also save the
  // bounds of the root view's widget.

  nsView* rootViewSibling = nullptr;
  nsView* rootViewParent = nullptr;
  nsIntRect newBounds(0, 0, 0, 0);

  nsCOMPtr<nsIPresShell> oldPresShell = GetPresShell();
  if (oldPresShell) {
    nsViewManager* vm = oldPresShell->GetViewManager();
    if (vm) {
      nsView* oldRootView = vm->GetRootView();

      if (oldRootView) {
        rootViewSibling = oldRootView->GetNextSibling();
        rootViewParent = oldRootView->GetParent();

        mContentViewer->GetBounds(newBounds);
      }
    }
  }

  nsCOMPtr<nsIContent> container;
  nsCOMPtr<nsIDocument> sibling;
  if (rootViewParent && rootViewParent->GetParent()) {
    nsIFrame* frame = rootViewParent->GetParent()->GetFrame();
    container = frame ? frame->GetContent() : nullptr;
  }
  if (rootViewSibling) {
    nsIFrame* frame = rootViewSibling->GetFrame();
    sibling =
      frame ? frame->PresContext()->PresShell()->GetDocument() : nullptr;
  }

  // Transfer ownership to mContentViewer.  By ensuring that either the
  // docshell or the session history, but not both, have references to the
  // content viewer, we prevent the viewer from being torn down after
  // Destroy() is called.

  if (mContentViewer) {
    mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
    viewer->SetPreviousViewer(mContentViewer);
  }
  if (mOSHE && (!mContentViewer || !mSavingOldViewer)) {
    // We don't plan to save a viewer in mOSHE; tell it to drop
    // any other state it's holding.
    mOSHE->SyncPresentationState();
  }

  // Order the mContentViewer setup just like Embed does.
  mContentViewer = nullptr;

  // Now that we're about to switch documents, forget all of our children.
  // Note that we cached them as needed up in CaptureState above.
  DestroyChildren();

  mContentViewer.swap(viewer);

  // Grab all of the related presentation from the SHEntry now.
  // Clearing the viewer from the SHEntry will clear all of this state.
  nsCOMPtr<nsISupports> windowState;
  mLSHE->GetWindowState(getter_AddRefs(windowState));
  mLSHE->SetWindowState(nullptr);

  bool sticky;
  mLSHE->GetSticky(&sticky);

  nsCOMPtr<nsIDOMDocument> domDoc;
  mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));

  nsCOMArray<nsIDocShellTreeItem> childShells;
  int32_t i = 0;
  nsCOMPtr<nsIDocShellTreeItem> child;
  while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) &&
         child) {
    childShells.AppendObject(child);
  }

  // get the previous content viewer size
  nsIntRect oldBounds(0, 0, 0, 0);
  mLSHE->GetViewerBounds(oldBounds);

  // Restore the refresh URI list.  The refresh timers will be restarted
  // when EndPageLoad() is called.
  nsCOMPtr<nsIMutableArray> refreshURIList;
  mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIList));

  // Reattach to the window object.
  mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive
  rv = mContentViewer->Open(windowState, mLSHE);
  mIsRestoringDocument = false;

  // Hack to keep nsDocShellEditorData alive across the
  // SetContentViewer(nullptr) call below.
  nsAutoPtr<nsDocShellEditorData> data(mLSHE->ForgetEditorData());

  // Now remove it from the cached presentation.
  mLSHE->SetContentViewer(nullptr);
  mEODForCurrentDocument = false;

  mLSHE->SetEditorData(data.forget());

#ifdef DEBUG
  {
    nsCOMPtr<nsIMutableArray> refreshURIs;
    mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIs));
    nsCOMPtr<nsIDocShellTreeItem> childShell;
    mLSHE->ChildShellAt(0, getter_AddRefs(childShell));
    NS_ASSERTION(!refreshURIs && !childShell,
                 "SHEntry should have cleared presentation state");
  }
#endif

  // Restore the sticky state of the viewer.  The viewer has set this state
  // on the history entry in Destroy() just before marking itself non-sticky,
  // to avoid teardown of the presentation.
  mContentViewer->SetSticky(sticky);

  NS_ENSURE_SUCCESS(rv, rv);

  // mLSHE is now our currently-loaded document.
  SetHistoryEntry(&mOSHE, mLSHE);

  // XXX special wyciwyg handling in Embed()?

  // We aren't going to restore any items from the LayoutHistoryState,
  // but we don't want them to stay around in case the page is reloaded.
  SetLayoutHistoryState(nullptr);

  // This is the end of our Embed() replacement

  mSavingOldViewer = false;
  mEODForCurrentDocument = false;

  // Tell the event loop to favor plevents over user events, see comments
  // in CreateContentViewer.
  if (++gNumberOfDocumentsLoading == 1) {
    FavorPerformanceHint(true);
  }

  if (oldCv && newCv) {
    newCv->SetMinFontSize(minFontSize);
    newCv->SetTextZoom(textZoom);
    newCv->SetFullZoom(pageZoom);
    newCv->SetOverrideDPPX(overrideDPPX);
    newCv->SetAuthorStyleDisabled(styleDisabled);
  }

  nsCOMPtr<nsIDocument> document = do_QueryInterface(domDoc);
  if (document) {
    RefPtr<nsDocShell> parent = GetParentDocshell();
    if (parent) {
      nsCOMPtr<nsIDocument> d = parent->GetDocument();
      if (d) {
        if (d->EventHandlingSuppressed()) {
          document->SuppressEventHandling(nsIDocument::eEvents,
                                          d->EventHandlingSuppressed());
        }

        // Ick, it'd be nicer to not rewalk all of the subdocs here.
        if (d->AnimationsPaused()) {
          document->SuppressEventHandling(nsIDocument::eAnimationsOnly,
                                          d->AnimationsPaused());
        }
      }
    }

    // Use the uri from the mLSHE we had when we entered this function
    // (which need not match the document's URI if anchors are involved),
    // since that's the history entry we're loading.  Note that if we use
    // origLSHE we don't have to worry about whether the entry in question
    // is still mLSHE or whether it's now mOSHE.
    nsCOMPtr<nsIURI> uri;
    origLSHE->GetURI(getter_AddRefs(uri));
    SetCurrentURI(uri, document->GetChannel(), true, 0);
  }

  // This is the end of our CreateContentViewer() replacement.
  // Now we simulate a load.  First, we restore the state of the javascript
  // window object.
  nsCOMPtr<nsPIDOMWindowOuter> privWin = GetWindow();
  NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface");

  // Now, dispatch a title change event which would happen as the
  // <head> is parsed.
  document->NotifyPossibleTitleChange(false);

  // Now we simulate appending child docshells for subframes.
  for (i = 0; i < childShells.Count(); ++i) {
    nsIDocShellTreeItem* childItem = childShells.ObjectAt(i);
    nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(childItem);

    // Make sure to not clobber the state of the child.  Since AddChild
    // always clobbers it, save it off first.
    bool allowPlugins;
    childShell->GetAllowPlugins(&allowPlugins);

    bool allowJavascript;
    childShell->GetAllowJavascript(&allowJavascript);

    bool allowRedirects;
    childShell->GetAllowMetaRedirects(&allowRedirects);

    bool allowSubframes;
    childShell->GetAllowSubframes(&allowSubframes);

    bool allowImages;
    childShell->GetAllowImages(&allowImages);

    bool allowMedia = childShell->GetAllowMedia();

    bool allowDNSPrefetch;
    childShell->GetAllowDNSPrefetch(&allowDNSPrefetch);

    bool allowContentRetargeting = childShell->GetAllowContentRetargeting();
    bool allowContentRetargetingOnChildren =
      childShell->GetAllowContentRetargetingOnChildren();

    uint32_t defaultLoadFlags;
    childShell->GetDefaultLoadFlags(&defaultLoadFlags);

    // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that
    // the child inherits our state. Among other things, this means that the
    // child inherits our mIsActive, mIsPrerendered and mPrivateBrowsingId,
    // which is what we want.
    AddChild(childItem);

    childShell->SetAllowPlugins(allowPlugins);
    childShell->SetAllowJavascript(allowJavascript);
    childShell->SetAllowMetaRedirects(allowRedirects);
    childShell->SetAllowSubframes(allowSubframes);
    childShell->SetAllowImages(allowImages);
    childShell->SetAllowMedia(allowMedia);
    childShell->SetAllowDNSPrefetch(allowDNSPrefetch);
    childShell->SetAllowContentRetargeting(allowContentRetargeting);
    childShell->SetAllowContentRetargetingOnChildren(
      allowContentRetargetingOnChildren);
    childShell->SetDefaultLoadFlags(defaultLoadFlags);

    rv = childShell->BeginRestore(nullptr, false);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Make sure to restore the window state after adding the child shells back
  // to the tree.  This is necessary for Thaw() and Resume() to propagate
  // properly.
  rv = privWin->RestoreWindowState(windowState);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIPresShell> shell = GetPresShell();

  // We may be displayed on a different monitor (or in a different
  // HiDPI mode) than when we got into the history list.  So we need
  // to check if this has happened. See bug 838239.

  // Because the prescontext normally handles resolution changes via
  // a runnable (see nsPresContext::UIResolutionChanged), its device
  // context won't be -immediately- updated as a result of calling
  // shell->BackingScaleFactorChanged().

  // But we depend on that device context when adjusting the view size
  // via mContentViewer->SetBounds(newBounds) below. So we need to
  // explicitly tell it to check for changed resolution here.
  if (shell && shell->GetPresContext()->DeviceContext()->CheckDPIChange()) {
    shell->BackingScaleFactorChanged();
  }

  nsViewManager* newVM = shell ? shell->GetViewManager() : nullptr;
  nsView* newRootView = newVM ? newVM->GetRootView() : nullptr;

  // Insert the new root view at the correct location in the view tree.
  if (container) {
    nsSubDocumentFrame* subDocFrame =
      do_QueryFrame(container->GetPrimaryFrame());
    rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr;
  } else {
    rootViewParent = nullptr;
  }
  if (sibling &&
      sibling->GetShell() &&
      sibling->GetShell()->GetViewManager()) {
    rootViewSibling = sibling->GetShell()->GetViewManager()->GetRootView();
  } else {
    rootViewSibling = nullptr;
  }
  if (rootViewParent && newRootView &&
      newRootView->GetParent() != rootViewParent) {
    nsViewManager* parentVM = rootViewParent->GetViewManager();
    if (parentVM) {
      // InsertChild(parent, child, sib, true) inserts the child after
      // sib in content order, which is before sib in view order. BUT
      // when sib is null it inserts at the end of the the document
      // order, i.e., first in view order.  But when oldRootSibling is
      // null, the old root as at the end of the view list --- last in
      // content order --- and we want to call InsertChild(parent, child,
      // nullptr, false) in that case.
      parentVM->InsertChild(rootViewParent, newRootView,
                            rootViewSibling,
                            rootViewSibling ? true : false);

      NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling,
                   "error in InsertChild");
    }
  }

  nsCOMPtr<nsPIDOMWindowInner> privWinInner = privWin->GetCurrentInnerWindow();

  // If parent is suspended, increase suspension count.
  // This can't be done as early as event suppression since this
  // depends on docshell tree.
  privWinInner->SyncStateFromParentWindow();

  // Now that all of the child docshells have been put into place, we can
  // restart the timers for the window and all of the child frames.
  privWinInner->Resume();

  // Restore the refresh URI list.  The refresh timers will be restarted
  // when EndPageLoad() is called.
  mRefreshURIList = refreshURIList;

  // Meta-refresh timers have been restarted for this shell, but not
  // for our children.  Walk the child shells and restart their timers.
  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
  while (iter.HasMore()) {
    nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext());
    if (child) {
      child->ResumeRefreshURIs();
    }
  }

  // Make sure this presentation is the same size as the previous
  // presentation.  If this is not the same size we showed it at last time,
  // then we need to resize the widget.

  // XXXbryner   This interacts poorly with Firefox's infobar.  If the old
  // presentation had the infobar visible, then we will resize the new
  // presentation to that smaller size.  However, firing the locationchanged
  // event will hide the infobar, which will immediately resize the window
  // back to the larger size.  A future optimization might be to restore
  // the presentation at the "wrong" size, then fire the locationchanged
  // event and check whether the docshell's new size is the same as the
  // cached viewer size (skipping the resize if they are equal).

  if (newRootView) {
    if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) {
#ifdef DEBUG_PAGE_CACHE
      printf("resize widget(%d, %d, %d, %d)\n", newBounds.x,
             newBounds.y, newBounds.width, newBounds.height);
#endif
      mContentViewer->SetBounds(newBounds);
    } else {
      nsIScrollableFrame* rootScrollFrame =
        shell->GetRootScrollFrameAsScrollableExternal();
      if (rootScrollFrame) {
        rootScrollFrame->PostScrolledAreaEventForCurrentArea();
      }
    }
  }

  // The FinishRestore call below can kill these, null them out so we don't
  // have invalid pointer lying around.
  newRootView = rootViewSibling = rootViewParent = nullptr;
  newVM = nullptr;

  // Simulate the completion of the load.
  nsDocShell::FinishRestore();

  // Restart plugins, and paint the content.
  if (shell) {
    shell->Thaw();
  }

  return privWin->FireDelayedDOMEvents();
}

nsresult
nsDocShell::CreateContentViewer(const nsACString& aContentType,
                                nsIRequest* aRequest,
                                nsIStreamListener** aContentHandler)
{
  *aContentHandler = nullptr;

  // Can we check the content type of the current content viewer
  // and reuse it without destroying it and re-creating it?

  NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?");

  // Instantiate the content viewer object
  nsCOMPtr<nsIContentViewer> viewer;
  nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup,
                                    aContentHandler, getter_AddRefs(viewer));

  if (NS_FAILED(rv)) {
    return rv;
  }

  // Notify the current document that it is about to be unloaded!!
  //
  // It is important to fire the unload() notification *before* any state
  // is changed within the DocShell - otherwise, javascript will get the
  // wrong information :-(
  //

  if (mSavingOldViewer) {
    // We determined that it was safe to cache the document presentation
    // at the time we initiated the new load.  We need to check whether
    // it's still safe to do so, since there may have been DOM mutations
    // or new requests initiated.
    nsCOMPtr<nsIDOMDocument> domDoc;
    viewer->GetDOMDocument(getter_AddRefs(domDoc));
    nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
    mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc);
  }

  NS_ASSERTION(!mLoadingURI, "Re-entering unload?");

  nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
  if (aOpenedChannel) {
    aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI));
  }
  FirePageHideNotification(!mSavingOldViewer);
  mLoadingURI = nullptr;

  // Set mFiredUnloadEvent = false so that the unload handler for the
  // *new* document will fire.
  mFiredUnloadEvent = false;

  // we've created a new document so go ahead and call
  // OnLoadingSite(), but don't fire OnLocationChange()
  // notifications before we've called Embed(). See bug 284993.
  mURIResultedInDocument = true;

  if (mLoadType == LOAD_ERROR_PAGE) {
    // We need to set the SH entry and our current URI here and not
    // at the moment we load the page. We want the same behavior
    // of Stop() as for a normal page load. See bug 514232 for details.

    // Revert mLoadType to load type to state the page load failed,
    // following function calls need it.
    mLoadType = mFailedLoadType;

    nsCOMPtr<nsIChannel> failedChannel = mFailedChannel;

    nsIDocument* doc = viewer->GetDocument();
    if (doc) {
      doc->SetFailedChannel(failedChannel);
    }

    // Make sure we have a URI to set currentURI.
    nsCOMPtr<nsIURI> failedURI;
    if (failedChannel) {
      NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI));
    }

    if (!failedURI) {
      failedURI = mFailedURI;
    }
    if (!failedURI) {
      // We need a URI object to store a session history entry, so make up a URI
      NS_NewURI(getter_AddRefs(failedURI), "about:blank");
    }

    // When we don't have failedURI, something wrong will happen. See
    // bug 291876.
    MOZ_ASSERT(failedURI, "We don't have a URI for history APIs.");

    mFailedChannel = nullptr;
    mFailedURI = nullptr;

    // Create an shistory entry for the old load.
    if (failedURI) {
      bool errorOnLocationChangeNeeded = OnNewURI(
        failedURI, failedChannel, nullptr, nullptr, mLoadType, false, false, false);

      if (errorOnLocationChangeNeeded) {
        FireOnLocationChange(this, failedChannel, failedURI,
                             LOCATION_CHANGE_ERROR_PAGE);
      }
    }

    // Be sure to have a correct mLSHE, it may have been cleared by
    // EndPageLoad. See bug 302115.
    if (mSessionHistory && !mLSHE) {
      int32_t idx;
      mSessionHistory->GetRequestedIndex(&idx);
      if (idx == -1) {
        mSessionHistory->GetIndex(&idx);
      }
      mSessionHistory->GetEntryAtIndex(idx, false, getter_AddRefs(mLSHE));
    }

    mLoadType = LOAD_ERROR_PAGE;
  }

  bool onLocationChangeNeeded = OnLoadingSite(aOpenedChannel, false);

  // let's try resetting the load group if we need to...
  nsCOMPtr<nsILoadGroup> currentLoadGroup;
  NS_ENSURE_SUCCESS(
    aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)),
    NS_ERROR_FAILURE);

  if (currentLoadGroup != mLoadGroup) {
    nsLoadFlags loadFlags = 0;

    // Cancel any URIs that are currently loading...
    // XXX: Need to do this eventually      Stop();
    //
    // Retarget the document to this loadgroup...
    //
    /* First attach the channel to the right loadgroup
     * and then remove from the old loadgroup. This
     * puts the notifications in the right order and
     * we don't null-out mLSHE in OnStateChange() for
     * all redirected urls
     */
    aOpenedChannel->SetLoadGroup(mLoadGroup);

    // Mark the channel as being a document URI...
    aOpenedChannel->GetLoadFlags(&loadFlags);
    loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;

    aOpenedChannel->SetLoadFlags(loadFlags);

    mLoadGroup->AddRequest(aRequest, nullptr);
    if (currentLoadGroup) {
      currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED);
    }

    // Update the notification callbacks, so that progress and
    // status information are sent to the right docshell...
    aOpenedChannel->SetNotificationCallbacks(this);
  }

  NS_ENSURE_SUCCESS(Embed(viewer, "", nullptr), NS_ERROR_FAILURE);

  mSavedRefreshURIList = nullptr;
  mSavingOldViewer = false;
  mEODForCurrentDocument = false;

  // if this document is part of a multipart document,
  // the ID can be used to distinguish it from the other parts.
  nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest));
  if (multiPartChannel) {
    nsCOMPtr<nsIPresShell> shell = GetPresShell();
    if (NS_SUCCEEDED(rv) && shell) {
      nsIDocument* doc = shell->GetDocument();
      if (doc) {
        uint32_t partID;
        multiPartChannel->GetPartID(&partID);
        doc->SetPartID(partID);
      }
    }
  }

  // Give hint to native plevent dispatch mechanism. If a document
  // is loading the native plevent dispatch mechanism should favor
  // performance over normal native event dispatch priorities.
  if (++gNumberOfDocumentsLoading == 1) {
    // Hint to favor performance for the plevent notification mechanism.
    // We want the pages to load as fast as possible even if its means
    // native messages might be starved.
    FavorPerformanceHint(true);
  }

  if (onLocationChangeNeeded) {
    FireOnLocationChange(this, aRequest, mCurrentURI, 0);
  }

  return NS_OK;
}

nsresult
nsDocShell::NewContentViewerObj(const nsACString& aContentType,
                                nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
                                nsIStreamListener** aContentHandler,
                                nsIContentViewer** aViewer)
{
  nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);

  nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
    nsContentUtils::FindInternalContentViewer(aContentType);
  if (!docLoaderFactory) {
    return NS_ERROR_FAILURE;
  }

  // Now create an instance of the content viewer nsLayoutDLF makes the
  // determination if it should be a "view-source" instead of "view"
  nsresult rv = docLoaderFactory->CreateInstance("view",
                                                 aOpenedChannel,
                                                 aLoadGroup, aContentType,
                                                 this,
                                                 nullptr,
                                                 aContentHandler,
                                                 aViewer);
  NS_ENSURE_SUCCESS(rv, rv);

  (*aViewer)->SetContainer(this);
  return NS_OK;
}

nsresult
nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer)
{
  //
  // Copy content viewer state from previous or parent content viewer.
  //
  // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad!
  //
  // Do NOT to maintain a reference to the old content viewer outside
  // of this "copying" block, or it will not be destroyed until the end of
  // this routine and all <SCRIPT>s and event handlers fail! (bug 20315)
  //
  // In this block of code, if we get an error result, we return it
  // but if we get a null pointer, that's perfectly legal for parent
  // and parentContentViewer.
  //

  int32_t x = 0;
  int32_t y = 0;
  int32_t cx = 0;
  int32_t cy = 0;

  // This will get the size from the current content viewer or from the
  // Init settings
  DoGetPositionAndSize(&x, &y, &cx, &cy);

  nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
  NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parentAsItem)),
                    NS_ERROR_FAILURE);
  nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));

  nsAutoCString forceCharset;
  nsAutoCString hintCharset;
  int32_t hintCharsetSource;
  int32_t minFontSize;
  float textZoom;
  float pageZoom;
  float overrideDPPX;
  bool styleDisabled;
  // |newMUDV| also serves as a flag to set the data from the above vars
  nsCOMPtr<nsIContentViewer> newCv;

  if (mContentViewer || parent) {
    nsCOMPtr<nsIContentViewer> oldCv;
    if (mContentViewer) {
      // Get any interesting state from old content viewer
      // XXX: it would be far better to just reuse the document viewer ,
      //      since we know we're just displaying the same document as before
      oldCv = mContentViewer;

      // Tell the old content viewer to hibernate in session history when
      // it is destroyed.

      if (mSavingOldViewer && NS_FAILED(CaptureState())) {
        if (mOSHE) {
          mOSHE->SyncPresentationState();
        }
        mSavingOldViewer = false;
      }
    } else {
      // No old content viewer, so get state from parent's content viewer
      parent->GetContentViewer(getter_AddRefs(oldCv));
    }

    if (oldCv) {
      newCv = aNewViewer;
      if (newCv) {
        NS_ENSURE_SUCCESS(oldCv->GetForceCharacterSet(forceCharset),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSet(hintCharset),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetMinFontSize(&minFontSize),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetTextZoom(&textZoom),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetFullZoom(&pageZoom),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX),
                          NS_ERROR_FAILURE);
        NS_ENSURE_SUCCESS(oldCv->GetAuthorStyleDisabled(&styleDisabled),
                          NS_ERROR_FAILURE);
      }
    }
  }

  nscolor bgcolor = NS_RGBA(0, 0, 0, 0);
  // Ensure that the content viewer is destroyed *after* the GC - bug 71515
  nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
  if (contentViewer) {
    // Stop any activity that may be happening in the old document before
    // releasing it...
    contentViewer->Stop();

    // Try to extract the canvas background color from the old
    // presentation shell, so we can use it for the next document.
    nsCOMPtr<nsIPresShell> shell;
    contentViewer->GetPresShell(getter_AddRefs(shell));

    if (shell) {
      bgcolor = shell->GetCanvasBackground();
    }

    contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
    aNewViewer->SetPreviousViewer(contentViewer);
  }
  if (mOSHE && (!mContentViewer || !mSavingOldViewer)) {
    // We don't plan to save a viewer in mOSHE; tell it to drop
    // any other state it's holding.
    mOSHE->SyncPresentationState();
  }

  mContentViewer = nullptr;

  // Now that we're about to switch documents, forget all of our children.
  // Note that we cached them as needed up in CaptureState above.
  DestroyChildren();

  mContentViewer = aNewViewer;

  nsCOMPtr<nsIWidget> widget;
  NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);

  nsIntRect bounds(x, y, cx, cy);

  mContentViewer->SetNavigationTiming(mTiming);

  if (NS_FAILED(mContentViewer->Init(widget, bounds))) {
    mContentViewer = nullptr;
    NS_WARNING("ContentViewer Initialization failed");
    return NS_ERROR_FAILURE;
  }

  // If we have old state to copy, set the old state onto the new content
  // viewer
  if (newCv) {
    NS_ENSURE_SUCCESS(newCv->SetForceCharacterSet(forceCharset),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetHintCharacterSet(hintCharset),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetHintCharacterSetSource(hintCharsetSource),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetMinFontSize(minFontSize),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetTextZoom(textZoom),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetFullZoom(pageZoom),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX),
                      NS_ERROR_FAILURE);
    NS_ENSURE_SUCCESS(newCv->SetAuthorStyleDisabled(styleDisabled),
                      NS_ERROR_FAILURE);
  }

  // Stuff the bgcolor from the old pres shell into the new
  // pres shell. This improves page load continuity.
  nsCOMPtr<nsIPresShell> shell;
  mContentViewer->GetPresShell(getter_AddRefs(shell));

  if (shell) {
    shell->SetCanvasBackground(bgcolor);
  }

  // XXX: It looks like the LayoutState gets restored again in Embed()
  //      right after the call to SetupNewViewer(...)

  // We don't show the mContentViewer yet, since we want to draw the old page
  // until we have enough of the new page to show.  Just return with the new
  // viewer still set to hidden.

  return NS_OK;
}

nsresult
nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry)
{
  NS_ENSURE_STATE(mContentViewer);
  nsCOMPtr<nsIDocument> document = GetDocument();
  NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);

  nsCOMPtr<nsIStructuredCloneContainer> scContainer;
  if (aShEntry) {
    nsresult rv = aShEntry->GetStateData(getter_AddRefs(scContainer));
    NS_ENSURE_SUCCESS(rv, rv);

    // If aShEntry is null, just set the document's state object to null.
  }

  // It's OK for scContainer too be null here; that just means there's no
  // state data associated with this history entry.
  document->SetStateObject(scContainer);

  return NS_OK;
}

nsresult
nsDocShell::CheckLoadingPermissions()
{
  // This method checks whether the caller may load content into
  // this docshell. Even though we've done our best to hide windows
  // from code that doesn't have the right to access them, it's
  // still possible for an evil site to open a window and access
  // frames in the new window through window.frames[] (which is
  // allAccess for historic reasons), so we still need to do this
  // check on load.
  nsresult rv = NS_OK;

  if (!gValidateOrigin || !IsFrame()) {
    // Origin validation was turned off, or we're not a frame.
    // Permit all loads.

    return rv;
  }

  // Note - The check for a current JSContext here isn't necessarily sensical.
  // It's just designed to preserve the old semantics during a mass-conversion
  // patch.
  if (!nsContentUtils::GetCurrentJSContext()) {
    return NS_OK;
  }

  // Check if the caller is from the same origin as this docshell,
  // or any of its ancestors.
  nsCOMPtr<nsIDocShellTreeItem> item(this);
  do {
    nsCOMPtr<nsIScriptGlobalObject> sgo = do_GetInterface(item);
    nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo));

    nsIPrincipal* p;
    if (!sop || !(p = sop->GetPrincipal())) {
      return NS_ERROR_UNEXPECTED;
    }

    if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) {
      // Same origin, permit load
      return NS_OK;
    }

    nsCOMPtr<nsIDocShellTreeItem> tmp;
    item->GetSameTypeParent(getter_AddRefs(tmp));
    item.swap(tmp);
  } while (item);

  return NS_ERROR_DOM_PROP_ACCESS_DENIED;
}

//*****************************************************************************
// nsDocShell: Site Loading
//*****************************************************************************

namespace {

#ifdef MOZ_PLACES
// Callback used by CopyFavicon to inform the favicon service that one URI
// (mNewURI) has the same favicon URI (OnComplete's aFaviconURI) as another.
class nsCopyFaviconCallback final : public nsIFaviconDataCallback
{
public:
  NS_DECL_ISUPPORTS

  nsCopyFaviconCallback(mozIAsyncFavicons* aSvc,
                        nsIURI* aNewURI,
                        nsIPrincipal* aLoadingPrincipal,
                        bool aInPrivateBrowsing)
    : mSvc(aSvc)
    , mNewURI(aNewURI)
    , mLoadingPrincipal(aLoadingPrincipal)
    , mInPrivateBrowsing(aInPrivateBrowsing)
  {
  }

  NS_IMETHOD
  OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
             const uint8_t* aData, const nsACString& aMimeType) override
  {
    // Continue only if there is an associated favicon.
    if (!aFaviconURI) {
      return NS_OK;
    }

    MOZ_ASSERT(aDataLen == 0,
               "We weren't expecting the callback to deliver data.");

    nsCOMPtr<mozIPlacesPendingOperation> po;
    return mSvc->SetAndFetchFaviconForPage(
      mNewURI, aFaviconURI, false,
      mInPrivateBrowsing ? nsIFaviconService::FAVICON_LOAD_PRIVATE :
                           nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
      nullptr, mLoadingPrincipal, getter_AddRefs(po));
  }

private:
  ~nsCopyFaviconCallback() {}

  nsCOMPtr<mozIAsyncFavicons> mSvc;
  nsCOMPtr<nsIURI> mNewURI;
  nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
  bool mInPrivateBrowsing;
};

NS_IMPL_ISUPPORTS(nsCopyFaviconCallback, nsIFaviconDataCallback)
#endif

} // namespace

void
nsDocShell::CopyFavicon(nsIURI* aOldURI,
                        nsIURI* aNewURI,
                        nsIPrincipal* aLoadingPrincipal,
                        bool aInPrivateBrowsing)
{
  if (XRE_IsContentProcess()) {
    dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
    if (contentChild) {
      mozilla::ipc::URIParams oldURI, newURI;
      SerializeURI(aOldURI, oldURI);
      SerializeURI(aNewURI, newURI);
      contentChild->SendCopyFavicon(oldURI, newURI,
                                    IPC::Principal(aLoadingPrincipal),
                                    aInPrivateBrowsing);
    }
    return;
  }

#ifdef MOZ_PLACES
  nsCOMPtr<mozIAsyncFavicons> favSvc =
    do_GetService("@mozilla.org/browser/favicon-service;1");
  if (favSvc) {
    nsCOMPtr<nsIFaviconDataCallback> callback =
      new nsCopyFaviconCallback(favSvc, aNewURI,
                                aLoadingPrincipal,
                                aInPrivateBrowsing);
    favSvc->GetFaviconURLForPage(aOldURI, callback);
  }
#endif
}

class InternalLoadEvent : public Runnable
{
public:
  InternalLoadEvent(nsDocShell* aDocShell, nsIURI* aURI,
                    nsIURI* aOriginalURI, bool aLoadReplace,
                    nsIURI* aReferrer, uint32_t aReferrerPolicy,
                    nsIPrincipal* aTriggeringPrincipal,
                    nsIPrincipal* aPrincipalToInherit, uint32_t aFlags,
                    const char* aTypeHint, nsIInputStream* aPostData,
                    nsIInputStream* aHeadersData, uint32_t aLoadType,
                    nsISHEntry* aSHEntry, bool aFirstParty,
                    const nsAString& aSrcdoc, nsIDocShell* aSourceDocShell,
                    nsIURI* aBaseURI)
    : mSrcdoc(aSrcdoc)
    , mDocShell(aDocShell)
    , mURI(aURI)
    , mOriginalURI(aOriginalURI)
    , mLoadReplace(aLoadReplace)
    , mReferrer(aReferrer)
    , mReferrerPolicy(aReferrerPolicy)
    , mTriggeringPrincipal(aTriggeringPrincipal)
    , mPrincipalToInherit(aPrincipalToInherit)
    , mPostData(aPostData)
    , mHeadersData(aHeadersData)
    , mSHEntry(aSHEntry)
    , mFlags(aFlags)
    , mLoadType(aLoadType)
    , mFirstParty(aFirstParty)
    , mSourceDocShell(aSourceDocShell)
    , mBaseURI(aBaseURI)
  {
    // Make sure to keep null things null as needed
    if (aTypeHint) {
      mTypeHint = aTypeHint;
    }
  }

  NS_IMETHOD
  Run() override
  {
    return mDocShell->InternalLoad(mURI, mOriginalURI,
                                   mLoadReplace,
                                   mReferrer,
                                   mReferrerPolicy,
                                   mTriggeringPrincipal, mPrincipalToInherit,
                                   mFlags, EmptyString(), mTypeHint.get(),
                                   NullString(), mPostData, mHeadersData,
                                   mLoadType, mSHEntry, mFirstParty,
                                   mSrcdoc, mSourceDocShell, mBaseURI,
                                   nullptr, nullptr);
  }

private:
  // Use IDL strings so .get() returns null by default
  nsXPIDLString mWindowTarget;
  nsXPIDLCString mTypeHint;
  nsString mSrcdoc;

  RefPtr<nsDocShell> mDocShell;
  nsCOMPtr<nsIURI> mURI;
  nsCOMPtr<nsIURI> mOriginalURI;
  bool mLoadReplace;
  nsCOMPtr<nsIURI> mReferrer;
  uint32_t mReferrerPolicy;
  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
  nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
  nsCOMPtr<nsIInputStream> mPostData;
  nsCOMPtr<nsIInputStream> mHeadersData;
  nsCOMPtr<nsISHEntry> mSHEntry;
  uint32_t mFlags;
  uint32_t mLoadType;
  bool mFirstParty;
  nsCOMPtr<nsIDocShell> mSourceDocShell;
  nsCOMPtr<nsIURI> mBaseURI;
};

/**
 * Returns true if we started an asynchronous load (i.e., from the network), but
 * the document we're loading there hasn't yet become this docshell's active
 * document.
 *
 * When JustStartedNetworkLoad is true, you should be careful about modifying
 * mLoadType and mLSHE.  These are both set when the asynchronous load first
 * starts, and the load expects that, when it eventually runs InternalLoad,
 * mLoadType and mLSHE will have their original values.
 */
bool
nsDocShell::JustStartedNetworkLoad()
{
  return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel();
}

nsresult
nsDocShell::CreatePrincipalFromReferrer(nsIURI* aReferrer,
                                        nsIPrincipal** aResult)
{
  PrincipalOriginAttributes attrs;
  attrs.InheritFromDocShellToDoc(mOriginAttributes, aReferrer);
  nsCOMPtr<nsIPrincipal> prin =
    BasePrincipal::CreateCodebasePrincipal(aReferrer, attrs);
  prin.forget(aResult);

  return *aResult ? NS_OK : NS_ERROR_FAILURE;
}

bool
nsDocShell::IsAboutNewtab(nsIURI* aURI)
{
  if (!aURI) {
    return false;
  }
  bool isAbout;
  if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs("about", &isAbout)))) {
    return false;
  }
  if (!isAbout) {
    return false;
  }

  nsAutoCString module;
  if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, module)))) {
    return false;
  }
  return module.Equals("newtab");
}

NS_IMETHODIMP
nsDocShell::InternalLoad(nsIURI* aURI,
                         nsIURI* aOriginalURI,
                         bool aLoadReplace,
                         nsIURI* aReferrer,
                         uint32_t aReferrerPolicy,
                         nsIPrincipal* aTriggeringPrincipal,
                         nsIPrincipal* aPrincipalToInherit,
                         uint32_t aFlags,
                         const nsAString& aWindowTarget,
                         const char* aTypeHint,
                         const nsAString& aFileName,
                         nsIInputStream* aPostData,
                         nsIInputStream* aHeadersData,
                         uint32_t aLoadType,
                         nsISHEntry* aSHEntry,
                         bool aFirstParty,
                         const nsAString& aSrcdoc,
                         nsIDocShell* aSourceDocShell,
                         nsIURI* aBaseURI,
                         nsIDocShell** aDocShell,
                         nsIRequest** aRequest)
{
  MOZ_ASSERT(aTriggeringPrincipal, "need a valid TriggeringPrincipal");

  nsresult rv = NS_OK;
  mOriginalUriString.Truncate();

  if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) {
    PR_LogPrint("DOCSHELL %p InternalLoad %s\n",
                this, aURI ? aURI->GetSpecOrDefault().get() : "");
  }
  // Initialize aDocShell/aRequest
  if (aDocShell) {
    *aDocShell = nullptr;
  }
  if (aRequest) {
    *aRequest = nullptr;
  }

  if (!aURI) {
    return NS_ERROR_NULL_POINTER;
  }

  NS_ENSURE_TRUE(IsValidLoadType(aLoadType), NS_ERROR_INVALID_ARG);

  NS_ENSURE_TRUE(!mIsBeingDestroyed, NS_ERROR_NOT_AVAILABLE);

  rv = EnsureScriptEnvironment();
  if (NS_FAILED(rv)) {
    return rv;
  }

  // wyciwyg urls can only be loaded through history. Any normal load of
  // wyciwyg through docshell is  illegal. Disallow such loads.
  if (aLoadType & LOAD_CMD_NORMAL) {
    bool isWyciwyg = false;
    rv = aURI->SchemeIs("wyciwyg", &isWyciwyg);
    if ((isWyciwyg && NS_SUCCEEDED(rv)) || NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }
  }

  bool isJavaScript = false;
  if (NS_FAILED(aURI->SchemeIs("javascript", &isJavaScript))) {
    isJavaScript = false;
  }

  bool isTargetTopLevelDocShell = false;
  nsCOMPtr<nsIDocShell> targetDocShell;
  if (!aWindowTarget.IsEmpty()) {
    // Locate the target DocShell.
    nsCOMPtr<nsIDocShellTreeItem> targetItem;
    // Only _self, _parent, and _top are supported in noopener case.  But we
    // have to be careful to not apply that to the noreferrer case.  See bug
    // 1358469.
    bool allowNamedTarget = !(aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) ||
                            (aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER);
    if (allowNamedTarget ||
        aWindowTarget.LowerCaseEqualsLiteral("_self") ||
        aWindowTarget.LowerCaseEqualsLiteral("_parent") ||
        aWindowTarget.LowerCaseEqualsLiteral("_top")) {
      rv = FindItemWithName(aWindowTarget, nullptr, this,
                            getter_AddRefs(targetItem));
      NS_ENSURE_SUCCESS(rv, rv);
    }

    targetDocShell = do_QueryInterface(targetItem);
    if (targetDocShell) {
      // If the targetDocShell and the rootDocShell are the same, then the
      // targetDocShell is the top level document and hence we should
      // consider this TYPE_DOCUMENT
      //
      // For example:
      // 1. target="_top"
      // 2. target="_parent", where this docshell is in the 2nd level of
      //    docshell tree.
      nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
      targetDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
      NS_ASSERTION(sameTypeRoot,
                   "No document shell root tree item from targetDocShell!");
      nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(sameTypeRoot);
      NS_ASSERTION(rootShell,
                   "No root docshell from document shell root tree item.");
      isTargetTopLevelDocShell = targetDocShell == rootShell;
    } else {
      // If the targetDocShell doesn't exist, then this is a new docShell
      // and we should consider this a TYPE_DOCUMENT load
      //
      // For example, when target="_blank"
      isTargetTopLevelDocShell = true;
    }
  }

  // The contentType will be INTERNAL_(I)FRAME if:
  // 1. This docshell is for iframe.
  // 2. AND aWindowTarget is not a new window, nor a top-level window.
  //
  // This variable will be used when we call NS_CheckContentLoadPolicy, and
  // later when we call DoURILoad.
  uint32_t contentType;
  if (IsFrame() && !isTargetTopLevelDocShell) {
    nsCOMPtr<Element> requestingElement =
      mScriptGlobal->AsOuter()->GetFrameElementInternal();
    if (requestingElement) {
      contentType = requestingElement->IsHTMLElement(nsGkAtoms::iframe) ?
        nsIContentPolicy::TYPE_INTERNAL_IFRAME : nsIContentPolicy::TYPE_INTERNAL_FRAME;
    } else {
      // If we have lost our frame element by now, just assume we're
      // an iframe since that's more common.
      contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
    }
  } else {
    contentType = nsIContentPolicy::TYPE_DOCUMENT;
  }

  // If there's no targetDocShell, that means we are about to create a new window,
  // perform a content policy check before creating the window.
  if (!targetDocShell) {
    nsCOMPtr<Element> requestingElement;
    nsISupports* requestingContext = nullptr;

    if (contentType == nsIContentPolicy::TYPE_DOCUMENT) {
      if (XRE_IsContentProcess()) {
        // In e10s the child process doesn't have access to the element that
        // contains the browsing context (because that element is in the chrome
        // process). So we just pass mScriptGlobal.
        requestingContext = ToSupports(mScriptGlobal);
      } else {
        // This is for loading non-e10s tabs and toplevel windows of various
        // sorts.
        // For the toplevel window cases, requestingElement will be null.
        requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal();
        requestingContext = requestingElement;
      }
    } else {
      requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal();
      requestingContext = requestingElement;

#ifdef DEBUG
      if (requestingElement) {
        // Get the docshell type for requestingElement.
        nsCOMPtr<nsIDocument> requestingDoc = requestingElement->OwnerDoc();
        nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();

        // requestingElement docshell type = current docshell type.
        MOZ_ASSERT(mItemType == elementDocShell->ItemType(),
                  "subframes should have the same docshell type as their parent");
      }
#endif
    }

    int16_t shouldLoad = nsIContentPolicy::ACCEPT;
    rv = NS_CheckContentLoadPolicy(contentType,
                                   aURI,
                                   aTriggeringPrincipal,
                                   requestingContext,
                                   EmptyCString(),  // mime guess
                                   nullptr,  // extra
                                   &shouldLoad);

    if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
      if (NS_SUCCEEDED(rv) && shouldLoad == nsIContentPolicy::REJECT_TYPE) {
        return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
      }

      return NS_ERROR_CONTENT_BLOCKED;
    }

    // If HSTS priming was set by nsMixedContentBlocker::ShouldLoad, and we
    // would block due to mixed content, go ahead and block here. If we try to
    // proceed with priming, we will error out later on.
    nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(requestingContext);
    // When loading toplevel windows, requestingContext can be null.  We don't
    // really care about HSTS in that situation, though; loads in toplevel
    // windows should all be browser UI.
    if (docShell) {
      nsIDocument* document = docShell->GetDocument();
      NS_ENSURE_TRUE(document, NS_OK);

      HSTSPrimingState state = document->GetHSTSPrimingStateForLocation(aURI);
      if (state == HSTSPrimingState::eHSTS_PRIMING_BLOCK) {
        // HSTS Priming currently disabled for InternalLoad, so we need to clear
        // the location that was added by nsMixedContentBlocker::ShouldLoad
        // Bug 1269815 will address images loaded via InternalLoad
        document->ClearHSTSPrimingLocation(aURI);
        return NS_ERROR_CONTENT_BLOCKED;
      }
    }
  }

  nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit;
  //
  // Get a principal from the current document if necessary.  Note that we only
  // do this for URIs that inherit a security context and local file URIs;
  // in particular we do NOT do this for about:blank.  This way, random
  // about:blank loads that have no principal (which basically means they were
  // done by someone from chrome manually messing with our nsIWebNavigation
  // or by C++ setting document.location) don't get a funky principal.  If
  // callers want something interesting to happen with the about:blank
  // principal in this case, they should pass aPrincipalToInherit in.
  //
  {
    bool inherits;
    // One more twist: Don't inherit the principal for external loads.
    if (aLoadType != LOAD_NORMAL_EXTERNAL && !principalToInherit &&
        (aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL) &&
         NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(aURI,
                                                                 &inherits)) &&
         inherits) {
      principalToInherit = GetInheritedPrincipal(true);
    }
  }

  // Don't allow loads that would inherit our security context
  // if this document came from an unsafe channel.
  {
    bool willInherit;
    // This condition needs to match the one in
    // nsContentUtils::ChannelShouldInheritPrincipal.
    // Except we reverse the rv check to be safe in case
    // nsContentUtils::URIInheritsSecurityContext fails here and
    // succeeds there.
    rv = nsContentUtils::URIInheritsSecurityContext(aURI, &willInherit);
    if (NS_FAILED(rv) || willInherit || NS_IsAboutBlank(aURI)) {
      nsCOMPtr<nsIDocShellTreeItem> treeItem = this;
      do {
        nsCOMPtr<nsIDocShell> itemDocShell = do_QueryInterface(treeItem);
        bool isUnsafe;
        if (itemDocShell &&
            NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) &&
            isUnsafe) {
          return NS_ERROR_DOM_SECURITY_ERR;
        }

        nsCOMPtr<nsIDocShellTreeItem> parent;
        treeItem->GetSameTypeParent(getter_AddRefs(parent));
        parent.swap(treeItem);
      } while (treeItem);
    }
  }

  //
  // Resolve the window target before going any further...
  // If the load has been targeted to another DocShell, then transfer the
  // load to it...
  //
  if (!aWindowTarget.IsEmpty()) {
    // We've already done our owner-inheriting.  Mask out that bit, so we
    // don't try inheriting an owner from the target window if we came up
    // with a null owner above.
    aFlags = aFlags & ~INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;

    bool isNewWindow = false;
    if (!targetDocShell) {
      // If the docshell's document is sandboxed, only open a new window
      // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.
      // (i.e. if allow-popups is specified)
      NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
      nsIDocument* doc = mContentViewer->GetDocument();
      uint32_t sandboxFlags = 0;

      if (doc) {
        sandboxFlags = doc->GetSandboxFlags();
        if (sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) {
          return NS_ERROR_DOM_INVALID_ACCESS_ERR;
        }
      }

      nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
      NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);

      nsCOMPtr<nsPIDOMWindowOuter> newWin;
      nsAutoCString spec;
      if (aURI) {
        aURI->GetSpec(spec);
      }
      // If we are a noopener load, we just hand the whole thing over to our
      // window.
      if (aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) {
        // Various asserts that we know to hold because NO_OPENER loads can only
        // happen for links.
        MOZ_ASSERT(!aLoadReplace);
        MOZ_ASSERT(aPrincipalToInherit == aTriggeringPrincipal);
        MOZ_ASSERT(aFlags == INTERNAL_LOAD_FLAGS_NO_OPENER ||
                   aFlags == (INTERNAL_LOAD_FLAGS_NO_OPENER |
                              INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER));
        MOZ_ASSERT(!aPostData);
        MOZ_ASSERT(!aHeadersData);
        MOZ_ASSERT(aLoadType == LOAD_LINK);
        MOZ_ASSERT(!aSHEntry);
        MOZ_ASSERT(aFirstParty); // Windowwatcher will assume this.

        nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
        rv = CreateLoadInfo(getter_AddRefs(loadInfo));
        if (NS_FAILED(rv)) {
          return rv;
        }

        // Set up our loadinfo so it will do the load as much like we would have
        // as possible.
        loadInfo->SetReferrer(aReferrer);
        loadInfo->SetReferrerPolicy(aReferrerPolicy);
        loadInfo->SetSendReferrer(!(aFlags &
                                    INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER));
        loadInfo->SetOriginalURI(aOriginalURI);
        loadInfo->SetLoadReplace(aLoadReplace);
        loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
        loadInfo->SetInheritPrincipal(
          aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
        // Explicit principal because we do not want any guesses as to what the
        // principal to inherit is: it should be aTriggeringPrincipal.
        loadInfo->SetPrincipalIsExplicit(true);
        loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK));

        rv = win->Open(NS_ConvertUTF8toUTF16(spec),
                       aWindowTarget, // window name
                       EmptyString(), // Features
                       loadInfo,
                       true, // aForceNoOpener
                       getter_AddRefs(newWin));
        MOZ_ASSERT(!newWin);
        return rv;
      }

      rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
                               aWindowTarget,  // window name
                               EmptyString(), // Features
                               getter_AddRefs(newWin));

      // In some cases the Open call doesn't actually result in a new
      // window being opened.  We can detect these cases by examining the
      // document in |newWin|, if any.
      nsCOMPtr<nsPIDOMWindowOuter> piNewWin = do_QueryInterface(newWin);
      if (piNewWin) {
        nsCOMPtr<nsIDocument> newDoc = piNewWin->GetExtantDoc();
        if (!newDoc || newDoc->IsInitialDocument()) {
          isNewWindow = true;
          aFlags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD;
        }
      }

      nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(newWin);
      targetDocShell = do_QueryInterface(webNav);
    }

    //
    // Transfer the load to the target DocShell...  Pass nullptr as the
    // window target name from to prevent recursive retargeting!
    //
    if (NS_SUCCEEDED(rv) && targetDocShell) {
      rv = targetDocShell->InternalLoad(aURI,
                                        aOriginalURI,
                                        aLoadReplace,
                                        aReferrer,
                                        aReferrerPolicy,
                                        aTriggeringPrincipal,
                                        principalToInherit,
                                        aFlags,
                                        EmptyString(),   // No window target
                                        aTypeHint,
                                        NullString(),    // No forced download
                                        aPostData,
                                        aHeadersData,
                                        aLoadType,
                                        aSHEntry,
                                        aFirstParty,
                                        aSrcdoc,
                                        aSourceDocShell,
                                        aBaseURI,
                                        aDocShell,
                                        aRequest);
      if (rv == NS_ERROR_NO_CONTENT) {
        // XXXbz except we never reach this code!
        if (isNewWindow) {
          //
          // At this point, a new window has been created, but the
          // URI did not have any data associated with it...
          //
          // So, the best we can do, is to tear down the new window
          // that was just created!
          //
          if (nsCOMPtr<nsPIDOMWindowOuter> domWin = targetDocShell->GetWindow()) {
            domWin->Close();
          }
        }
        //
        // NS_ERROR_NO_CONTENT should not be returned to the
        // caller... This is an internal error code indicating that
        // the URI had no data associated with it - probably a
        // helper-app style protocol (ie. mailto://)
        //
        rv = NS_OK;
      } else if (isNewWindow) {
        // XXX: Once new windows are created hidden, the new
        //      window will need to be made visible...  For now,
        //      do nothing.
      }
    }

    // Else we ran out of memory, or were a popup and got blocked,
    // or something.

    return rv;
  }

  //
  // Load is being targetted at this docshell so return an error if the
  // docshell is in the process of being destroyed.
  //
  if (mIsBeingDestroyed) {
    return NS_ERROR_FAILURE;
  }

  NS_ENSURE_STATE(!HasUnloadedParent());

  rv = CheckLoadingPermissions();
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (mFiredUnloadEvent) {
    if (IsOKToLoadURI(aURI)) {
      NS_PRECONDITION(aWindowTarget.IsEmpty(),
                      "Shouldn't have a window target here!");

      // If this is a replace load, make whatever load triggered
      // the unload event also a replace load, so we don't
      // create extra history entries.
      if (LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
        mLoadType = LOAD_NORMAL_REPLACE;
      }

      // Do this asynchronously
      nsCOMPtr<nsIRunnable> ev =
        new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace,
                              aReferrer, aReferrerPolicy,
                              aTriggeringPrincipal, principalToInherit,
                              aFlags, aTypeHint, aPostData, aHeadersData,
                              aLoadType, aSHEntry, aFirstParty, aSrcdoc,
                              aSourceDocShell, aBaseURI);
      return NS_DispatchToCurrentThread(ev);
    }

    // Just ignore this load attempt
    return NS_OK;
  }

  // If a source docshell has been passed, check to see if we are sandboxed
  // from it as the result of an iframe or CSP sandbox.
  if (aSourceDocShell && aSourceDocShell->IsSandboxedFrom(this)) {
    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
  }

  // If this docshell is owned by a frameloader, make sure to cancel
  // possible frameloader initialization before loading a new page.
  nsCOMPtr<nsIDocShellTreeItem> parent = GetParentDocshell();
  if (parent) {
    nsCOMPtr<nsIDocument> doc = parent->GetDocument();
    if (doc) {
      doc->TryCancelFrameLoaderInitialization(this);
    }
  }

  // Before going any further vet loads initiated by external programs.
  if (aLoadType == LOAD_NORMAL_EXTERNAL) {
    // Disallow external chrome: loads targetted at content windows
    bool isChrome = false;
    if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) {
      NS_WARNING("blocked external chrome: url -- use '--chrome' option");
      return NS_ERROR_FAILURE;
    }

    // clear the decks to prevent context bleed-through (bug 298255)
    rv = CreateAboutBlankContentViewer(nullptr, nullptr);
    if (NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }

    // reset loadType so we don't have to add lots of tests for
    // LOAD_NORMAL_EXTERNAL after this point
    aLoadType = LOAD_NORMAL;
  }

  mAllowKeywordFixup =
    (aFlags & INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) != 0;
  mURIResultedInDocument = false;  // reset the clock...

  if (aLoadType == LOAD_NORMAL ||
      aLoadType == LOAD_STOP_CONTENT ||
      LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) ||
      aLoadType == LOAD_HISTORY ||
      aLoadType == LOAD_LINK) {
    nsCOMPtr<nsIURI> currentURI = mCurrentURI;

    nsAutoCString curHash, newHash;
    bool curURIHasRef = false, newURIHasRef = false;

    nsresult rvURINew = aURI->GetRef(newHash);
    if (NS_SUCCEEDED(rvURINew)) {
      rvURINew = aURI->GetHasRef(&newURIHasRef);
    }

    bool sameExceptHashes = false;
    if (currentURI && NS_SUCCEEDED(rvURINew)) {
      nsresult rvURIOld = currentURI->GetRef(curHash);
      if (NS_SUCCEEDED(rvURIOld)) {
        rvURIOld = currentURI->GetHasRef(&curURIHasRef);
      }
      if (NS_SUCCEEDED(rvURIOld)) {
        if (NS_FAILED(currentURI->EqualsExceptRef(aURI, &sameExceptHashes))) {
          sameExceptHashes = false;
        }
      }
    }

    if (!sameExceptHashes && sURIFixup && currentURI &&
        NS_SUCCEEDED(rvURINew)) {
      // Maybe aURI came from the exposable form of currentURI?
      nsCOMPtr<nsIURI> currentExposableURI;
      rv = sURIFixup->CreateExposableURI(currentURI,
                                         getter_AddRefs(currentExposableURI));
      NS_ENSURE_SUCCESS(rv, rv);
      nsresult rvURIOld = currentExposableURI->GetRef(curHash);
      if (NS_SUCCEEDED(rvURIOld)) {
        rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef);
      }
      if (NS_SUCCEEDED(rvURIOld)) {
        if (NS_FAILED(currentExposableURI->EqualsExceptRef(aURI, &sameExceptHashes))) {
          sameExceptHashes = false;
        }
      }
    }

    bool historyNavBetweenSameDoc = false;
    if (mOSHE && aSHEntry) {
      // We're doing a history load.

      mOSHE->SharesDocumentWith(aSHEntry, &historyNavBetweenSameDoc);

#ifdef DEBUG
      if (historyNavBetweenSameDoc) {
        nsCOMPtr<nsIInputStream> currentPostData;
        mOSHE->GetPostData(getter_AddRefs(currentPostData));
        NS_ASSERTION(currentPostData == aPostData,
                     "Different POST data for entries for the same page?");
      }
#endif
    }

    // A short-circuited load happens when we navigate between two SHEntries
    // for the same document.  We do a short-circuited load under two
    // circumstances.  Either
    //
    //  a) we're navigating between two different SHEntries which share a
    //     document, or
    //
    //  b) we're navigating to a new shentry whose URI differs from the
    //     current URI only in its hash, the new hash is non-empty, and
    //     we're not doing a POST.
    //
    // The restriction tha the SHEntries in (a) must be different ensures
    // that history.go(0) and the like trigger full refreshes, rather than
    // short-circuited loads.
    bool doShortCircuitedLoad =
      (historyNavBetweenSameDoc && mOSHE != aSHEntry) ||
      (!aSHEntry && !aPostData &&
       sameExceptHashes && newURIHasRef);

    if (doShortCircuitedLoad) {
      // Save the position of the scrollers.
      nscoord cx = 0, cy = 0;
      GetCurScrollPos(ScrollOrientation_X, &cx);
      GetCurScrollPos(ScrollOrientation_Y, &cy);

      // Reset mLoadType to its original value once we exit this block,
      // because this short-circuited load might have started after a
      // normal, network load, and we don't want to clobber its load type.
      // See bug 737307.
      AutoRestore<uint32_t> loadTypeResetter(mLoadType);

      // If a non-short-circuit load (i.e., a network load) is pending,
      // make this a replacement load, so that we don't add a SHEntry here
      // and the network load goes into the SHEntry it expects to.
      if (JustStartedNetworkLoad() && (aLoadType & LOAD_CMD_NORMAL)) {
        mLoadType = LOAD_NORMAL_REPLACE;
      } else {
        mLoadType = aLoadType;
      }

      mURIResultedInDocument = true;

      nsCOMPtr<nsISHEntry> oldLSHE = mLSHE;

      /* we need to assign mLSHE to aSHEntry right here, so that on History
       * loads, SetCurrentURI() called from OnNewURI() will send proper
       * onLocationChange() notifications to the browser to update
       * back/forward buttons.
       */
      SetHistoryEntry(&mLSHE, aSHEntry);

      // Set the doc's URI according to the new history entry's URI.
      nsCOMPtr<nsIDocument> doc = GetDocument();
      NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
      doc->SetDocumentURI(aURI);

      /* This is a anchor traversal with in the same page.
       * call OnNewURI() so that, this traversal will be
       * recorded in session and global history.
       */
      nsCOMPtr<nsIPrincipal> triggeringPrincipal, principalToInherit;
      if (mOSHE) {
        mOSHE->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
        mOSHE->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
      }
      // Pass true for aCloneSHChildren, since we're not
      // changing documents here, so all of our subframes are
      // still relevant to the new session history entry.
      //
      // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT
      // flag on firing onLocationChange(...).
      // Anyway, aCloneSHChildren param is simply reflecting
      // doShortCircuitedLoad in this scope.
      OnNewURI(aURI, nullptr, triggeringPrincipal, principalToInherit,
               mLoadType, true, true, true);

      nsCOMPtr<nsIInputStream> postData;
      nsCOMPtr<nsISupports> cacheKey;

      bool scrollRestorationIsManual = false;
      if (mOSHE) {
        /* save current position of scroller(s) (bug 59774) */
        mOSHE->SetScrollPosition(cx, cy);
        mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
        // Get the postdata and page ident from the current page, if
        // the new load is being done via normal means.  Note that
        // "normal means" can be checked for just by checking for
        // LOAD_CMD_NORMAL, given the loadType and allowScroll check
        // above -- it filters out some LOAD_CMD_NORMAL cases that we
        // wouldn't want here.
        if (aLoadType & LOAD_CMD_NORMAL) {
          mOSHE->GetPostData(getter_AddRefs(postData));
          mOSHE->GetCacheKey(getter_AddRefs(cacheKey));

          // Link our new SHEntry to the old SHEntry's back/forward
          // cache data, since the two SHEntries correspond to the
          // same document.
          if (mLSHE) {
            if (!aSHEntry) {
              // If we're not doing a history load, scroll restoration
              // should be inherited from the previous session history entry.
              mLSHE->SetScrollRestorationIsManual(scrollRestorationIsManual);
            }
            mLSHE->AdoptBFCacheEntry(mOSHE);
          }
        }
      }

      // If we're doing a history load, use its scroll restoration state.
      if (aSHEntry) {
        aSHEntry->GetScrollRestorationIsManual(&scrollRestorationIsManual);
      }

      /* Assign mOSHE to mLSHE. This will either be a new entry created
       * by OnNewURI() for normal loads or aSHEntry for history loads.
       */
      if (mLSHE) {
        SetHistoryEntry(&mOSHE, mLSHE);
        // Save the postData obtained from the previous page
        // in to the session history entry created for the
        // anchor page, so that any history load of the anchor
        // page will restore the appropriate postData.
        if (postData) {
          mOSHE->SetPostData(postData);
        }

        // Make sure we won't just repost without hitting the
        // cache first
        if (cacheKey) {
          mOSHE->SetCacheKey(cacheKey);
        }
      }

      /* Restore the original LSHE if we were loading something
       * while short-circuited load was initiated.
       */
      SetHistoryEntry(&mLSHE, oldLSHE);
      /* Set the title for the SH entry for this target url. so that
       * SH menus in go/back/forward buttons won't be empty for this.
       */
      if (mSessionHistory) {
        int32_t index = -1;
        mSessionHistory->GetIndex(&index);
        nsCOMPtr<nsISHEntry> shEntry;
        mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(shEntry));
        NS_ENSURE_TRUE(shEntry, NS_ERROR_FAILURE);
        shEntry->SetTitle(mTitle);
      }

      /* Set the title for the Global History entry for this anchor url.
       */
      if (mUseGlobalHistory && !UsePrivateBrowsing()) {
        nsCOMPtr<IHistory> history = services::GetHistoryService();
        if (history) {
          history->SetURITitle(aURI, mTitle);
        } else if (mGlobalHistory) {
          mGlobalHistory->SetPageTitle(aURI, mTitle);
        }
      }

      SetDocCurrentStateObj(mOSHE);

      // Inform the favicon service that the favicon for oldURI also
      // applies to aURI.
      CopyFavicon(currentURI, aURI, doc->NodePrincipal(), UsePrivateBrowsing());

      RefPtr<nsGlobalWindow> scriptGlobal = mScriptGlobal;
      RefPtr<nsGlobalWindow> win = scriptGlobal ?
        scriptGlobal->GetCurrentInnerWindowInternal() : nullptr;

      // ScrollToAnchor doesn't necessarily cause us to scroll the window;
      // the function decides whether a scroll is appropriate based on the
      // arguments it receives.  But even if we don't end up scrolling,
      // ScrollToAnchor performs other important tasks, such as informing
      // the presShell that we have a new hash.  See bug 680257.
      rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash, aLoadType);
      NS_ENSURE_SUCCESS(rv, rv);

      /* restore previous position of scroller(s), if we're moving
       * back in history (bug 59774)
       */
      nscoord bx = 0;
      nscoord by = 0;
      bool needsScrollPosUpdate = false;
      if (mOSHE && (aLoadType == LOAD_HISTORY ||
                    aLoadType == LOAD_RELOAD_NORMAL) &&
          !scrollRestorationIsManual) {
        needsScrollPosUpdate = true;
        mOSHE->GetScrollPosition(&bx, &by);
      }

      // Dispatch the popstate and hashchange events, as appropriate.
      //
      // The event dispatch below can cause us to re-enter script and
      // destroy the docshell, nulling out mScriptGlobal. Hold a stack
      // reference to avoid null derefs. See bug 914521.
      if (win) {
        // Fire a hashchange event URIs differ, and only in their hashes.
        bool doHashchange = sameExceptHashes &&
                            (curURIHasRef != newURIHasRef || !curHash.Equals(newHash));

        if (historyNavBetweenSameDoc || doHashchange) {
          win->DispatchSyncPopState();
        }

        if (needsScrollPosUpdate && win->AsInner()->HasActiveDocument()) {
          SetCurScrollPosEx(bx, by);
        }

        if (doHashchange) {
          // Note that currentURI hasn't changed because it's on the
          // stack, so we can just use it directly as the old URI.
          win->DispatchAsyncHashchange(currentURI, aURI);
        }
      }

      return NS_OK;
    }
  }

  // mContentViewer->PermitUnload can destroy |this| docShell, which
  // causes the next call of CanSavePresentation to crash.
  // Hold onto |this| until we return, to prevent a crash from happening.
  // (bug#331040)
  nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);

  // Don't init timing for javascript:, since it generally doesn't
  // actually start a load or anything.  If it does, we'll init
  // timing then, from OnStateChange.

  // XXXbz mTiming should know what channel it's for, so we don't
  // need this hackery.  Note that this is still broken in cases
  // when we're loading something that's not javascript: and the
  // beforeunload handler denies the load.  That will screw up
  // timing for the next load!
  if (!isJavaScript) {
    MaybeInitTiming();
  }
  bool timeBeforeUnload = aFileName.IsVoid();
  if (mTiming && timeBeforeUnload) {
    mTiming->NotifyBeforeUnload();
  }
  // Check if the page doesn't want to be unloaded. The javascript:
  // protocol handler deals with this for javascript: URLs.
  if (!isJavaScript && aFileName.IsVoid() && mContentViewer) {
    bool okToUnload;
    rv = mContentViewer->PermitUnload(&okToUnload);

    if (NS_SUCCEEDED(rv) && !okToUnload) {
      // The user chose not to unload the page, interrupt the
      // load.
      return NS_OK;
    }
  }

  if (mTiming && timeBeforeUnload) {
    mTiming->NotifyUnloadAccepted(mCurrentURI);
  }

  // Check if the webbrowser chrome wants the load to proceed; this can be
  // used to cancel attempts to load URIs in the wrong process.
  nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
  if (browserChrome3) {
    // In case this is a remote newtab load, set aURI to aOriginalURI (newtab).
    // This ensures that the verifySignedContent flag is set on loadInfo in
    // DoURILoad.
    nsIURI* uriForShouldLoadCheck = aURI;
    if (IsAboutNewtab(aOriginalURI)) {
      uriForShouldLoadCheck = aOriginalURI;
    }
    bool shouldLoad;
    rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer,
                                       &shouldLoad);
    if (NS_SUCCEEDED(rv) && !shouldLoad) {
      return NS_OK;
    }
  }

  // Whenever a top-level browsing context is navigated, the user agent MUST
  // lock the orientation of the document to the document's default
  // orientation. We don't explicitly check for a top-level browsing context
  // here because orientation is only set on top-level browsing contexts.
  // We make an exception for apps because they currently rely on
  // orientation locks persisting across browsing contexts.
  if (OrientationLock() != eScreenOrientation_None && !GetIsApp()) {
#ifdef DEBUG
    nsCOMPtr<nsIDocShellTreeItem> parent;
    GetSameTypeParent(getter_AddRefs(parent));
    MOZ_ASSERT(!parent);
#endif
    SetOrientationLock(eScreenOrientation_None);
    if (mIsActive) {
      ScreenOrientation::UpdateActiveOrientationLock(eScreenOrientation_None);
    }
  }

  // Check for saving the presentation here, before calling Stop().
  // This is necessary so that we can catch any pending requests.
  // Since the new request has not been created yet, we pass null for the
  // new request parameter.
  // Also pass nullptr for the document, since it doesn't affect the return
  // value for our purposes here.
  bool savePresentation = CanSavePresentation(aLoadType, nullptr, nullptr);

  // Don't stop current network activity for javascript: URL's since
  // they might not result in any data, and thus nothing should be
  // stopped in those cases. In the case where they do result in
  // data, the javascript: URL channel takes care of stopping
  // current network activity.
  if (!isJavaScript && aFileName.IsVoid()) {
    // Stop any current network activity.
    // Also stop content if this is a zombie doc. otherwise
    // the onload will be delayed by other loads initiated in the
    // background by the first document that
    // didn't fully load before the next load was initiated.
    // If not a zombie, don't stop content until data
    // starts arriving from the new URI...

    nsCOMPtr<nsIContentViewer> zombieViewer;
    if (mContentViewer) {
      mContentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
    }

    if (zombieViewer ||
        LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_STOP_CONTENT)) {
      rv = Stop(nsIWebNavigation::STOP_ALL);
    } else {
      rv = Stop(nsIWebNavigation::STOP_NETWORK);
    }

    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  mLoadType = aLoadType;

  // mLSHE should be assigned to aSHEntry, only after Stop() has
  // been called. But when loading an error page, do not clear the
  // mLSHE for the real page.
  if (mLoadType != LOAD_ERROR_PAGE) {
    SetHistoryEntry(&mLSHE, aSHEntry);
  }

  mSavingOldViewer = savePresentation;

  // If we have a saved content viewer in history, restore and show it now.
  if (aSHEntry && (mLoadType & LOAD_CMD_HISTORY)) {
    // Make sure our history ID points to the same ID as
    // SHEntry's docshell ID.
    aSHEntry->GetDocshellID(&mHistoryID);

    // It's possible that the previous viewer of mContentViewer is the
    // viewer that will end up in aSHEntry when it gets closed.  If that's
    // the case, we need to go ahead and force it into its shentry so we
    // can restore it.
    if (mContentViewer) {
      nsCOMPtr<nsIContentViewer> prevViewer;
      mContentViewer->GetPreviousViewer(getter_AddRefs(prevViewer));
      if (prevViewer) {
#ifdef DEBUG
        nsCOMPtr<nsIContentViewer> prevPrevViewer;
        prevViewer->GetPreviousViewer(getter_AddRefs(prevPrevViewer));
        NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here");
#endif
        nsCOMPtr<nsISHEntry> viewerEntry;
        prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry));
        if (viewerEntry == aSHEntry) {
          // Make sure this viewer ends up in the right place
          mContentViewer->SetPreviousViewer(nullptr);
          prevViewer->Destroy();
        }
      }
    }
    nsCOMPtr<nsISHEntry> oldEntry = mOSHE;
    bool restoring;
    rv = RestorePresentation(aSHEntry, &restoring);
    if (restoring) {
      return rv;
    }

    // We failed to restore the presentation, so clean up.
    // Both the old and new history entries could potentially be in
    // an inconsistent state.
    if (NS_FAILED(rv)) {
      if (oldEntry) {
        oldEntry->SyncPresentationState();
      }

      aSHEntry->SyncPresentationState();
    }
  }

  nsAutoString srcdoc;
  if (aFlags & INTERNAL_LOAD_FLAGS_IS_SRCDOC) {
    srcdoc = aSrcdoc;
  } else {
    srcdoc = NullString();
  }

  net::PredictorLearn(aURI, nullptr,
                      nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, this);
  net::PredictorPredict(aURI, nullptr,
                        nsINetworkPredictor::PREDICT_LOAD, this, nullptr);

  nsCOMPtr<nsIRequest> req;
  rv = DoURILoad(aURI, aOriginalURI, aLoadReplace, aReferrer,
                 !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
                 aReferrerPolicy,
                 aTriggeringPrincipal, principalToInherit, aTypeHint,
                 aFileName, aPostData, aHeadersData,
                 aFirstParty, aDocShell, getter_AddRefs(req),
                 (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
                 (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
                 (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
                 srcdoc, aBaseURI, contentType);
  if (req && aRequest) {
    NS_ADDREF(*aRequest = req);
  }

  if (NS_FAILED(rv)) {
    nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
    if (DisplayLoadError(rv, aURI, nullptr, chan) &&
        (aFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
      return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
    }
  }

  return rv;
}

nsIPrincipal*
nsDocShell::GetInheritedPrincipal(bool aConsiderCurrentDocument)
{
  nsCOMPtr<nsIDocument> document;
  bool inheritedFromCurrent = false;

  if (aConsiderCurrentDocument && mContentViewer) {
    document = mContentViewer->GetDocument();
    inheritedFromCurrent = true;
  }

  if (!document) {
    nsCOMPtr<nsIDocShellTreeItem> parentItem;
    GetSameTypeParent(getter_AddRefs(parentItem));
    if (parentItem) {
      document = parentItem->GetDocument();
    }
  }

  if (!document) {
    if (!aConsiderCurrentDocument) {
      return nullptr;
    }

    // Make sure we end up with _something_ as the principal no matter
    // what.If this fails, we'll just get a null docViewer and bail.
    EnsureContentViewer();
    if (!mContentViewer) {
      return nullptr;
    }
    document = mContentViewer->GetDocument();
  }

  //-- Get the document's principal
  if (document) {
    nsIPrincipal* docPrincipal = document->NodePrincipal();

    // Don't allow loads in typeContent docShells to inherit the system
    // principal from existing documents.
    if (inheritedFromCurrent &&
        mItemType == typeContent &&
        nsContentUtils::IsSystemPrincipal(docPrincipal)) {
      return nullptr;
    }

    return docPrincipal;
  }

  return nullptr;
}

nsresult
nsDocShell::DoURILoad(nsIURI* aURI,
                      nsIURI* aOriginalURI,
                      bool aLoadReplace,
                      nsIURI* aReferrerURI,
                      bool aSendReferrer,
                      uint32_t aReferrerPolicy,
                      nsIPrincipal* aTriggeringPrincipal,
                      nsIPrincipal* aPrincipalToInherit,
                      const char* aTypeHint,
                      const nsAString& aFileName,
                      nsIInputStream* aPostData,
                      nsIInputStream* aHeadersData,
                      bool aFirstParty,
                      nsIDocShell** aDocShell,
                      nsIRequest** aRequest,
                      bool aIsNewWindowTarget,
                      bool aBypassClassifier,
                      bool aForceAllowCookies,
                      const nsAString& aSrcdoc,
                      nsIURI* aBaseURI,
                      nsContentPolicyType aContentPolicyType)
{
  // Double-check that we're still around to load this URI.
  if (mIsBeingDestroyed) {
    // Return NS_OK despite not doing anything to avoid throwing exceptions from
    // nsLocation::SetHref if the unload handler of the existing page tears us
    // down.
    return NS_OK;
  }

  nsresult rv;
  nsCOMPtr<nsIURILoader> uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (IsFrame()) {

    MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
               aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME,
               "DoURILoad thinks this is a frame and InternalLoad does not");

    // Only allow view-source scheme in top-level docshells. view-source is
    // the only scheme to which this applies at the moment due to potential
    // timing attacks to read data from cross-origin iframes. If this widens
    // we should add a protocol flag for whether the scheme is allowed in
    // frames and use something like nsNetUtil::NS_URIChainHasFlags.
    nsCOMPtr<nsIURI> tempURI = aURI;
    nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
    while (nestedURI) {
      // view-source should always be an nsINestedURI, loop and check the
      // scheme on this and all inner URIs that are also nested URIs.
      bool isViewSource = false;
      rv = tempURI->SchemeIs("view-source", &isViewSource);
      if (NS_FAILED(rv) || isViewSource) {
        return NS_ERROR_UNKNOWN_PROTOCOL;
      }
      nestedURI->GetInnerURI(getter_AddRefs(tempURI));
      nestedURI = do_QueryInterface(tempURI);
    }
  } else {
    MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
    "DoURILoad thinks this is a document and InternalLoad does not");
  }

  // open a channel for the url
  nsCOMPtr<nsIChannel> channel;

  bool isSrcdoc = !aSrcdoc.IsVoid();

  // There are two cases we care about:
  // * Top-level load: In this case, loadingNode is null, but loadingWindow
  //   is our mScriptGlobal. We pass null for loadingPrincipal in this case.
  // * Subframe load: loadingWindow is null, but loadingNode is the frame
  //   element for the load. loadingPrincipal is the NodePrincipal of the frame
  //   element.
  nsCOMPtr<nsINode> loadingNode;
  nsCOMPtr<nsPIDOMWindowOuter> loadingWindow;
  nsCOMPtr<nsIPrincipal> loadingPrincipal;

  if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
    loadingNode = nullptr;
    loadingPrincipal = nullptr;
    loadingWindow = mScriptGlobal->AsOuter();
  } else {
    loadingWindow = nullptr;
    loadingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal();
    if (loadingNode) {
      // If we have a loading node, then use that as our loadingPrincipal.
      loadingPrincipal = loadingNode->NodePrincipal();
    } else {
      // If this isn't a top-level load and mScriptGlobal's frame element is
      // null, then the element got removed from the DOM while we were trying
      // to load this resource. This docshell is scheduled for destruction
      // already, so bail out here.
      return NS_OK;
    }
  }

  // Getting the right triggeringPrincipal needs to be updated and is only
  // ready for use once bug 1182569 landed. Until then, we cannot rely on
  // the triggeringPrincipal for TYPE_DOCUMENT loads.
  MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal");

  bool isSandBoxed = mSandboxFlags & SANDBOXED_ORIGIN;
  // only inherit if we have a aPrincipalToInherit
  bool inherit = false;

  if (aPrincipalToInherit) {
    inherit = nsContentUtils::ChannelShouldInheritPrincipal(
      aPrincipalToInherit,
      aURI,
      true, // aInheritForAboutBlank
      isSrcdoc);
  }

  nsLoadFlags loadFlags = mDefaultLoadFlags;
  nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL;

  if (aFirstParty) {
    // tag first party URL loads
    loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
  }

  if (mLoadType == LOAD_ERROR_PAGE) {
    // Error pages are LOAD_BACKGROUND
    loadFlags |= nsIChannel::LOAD_BACKGROUND;
    securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
  }

  if (inherit) {
    securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
  }
  if (isSandBoxed) {
    securityFlags |= nsILoadInfo::SEC_SANDBOXED;
  }

  nsCOMPtr<nsILoadInfo> loadInfo =
    (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ?
      new LoadInfo(loadingWindow, aTriggeringPrincipal,
                   securityFlags) :
      new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
                   securityFlags, aContentPolicyType);

  if (aPrincipalToInherit) {
    loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
  }

  // We have to do this in case our OriginAttributes are different from the
  // OriginAttributes of the parent document. Or in case there isn't a
  // parent document.
  NeckoOriginAttributes neckoAttrs;
  bool isTopLevelDoc = aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT &&
                       mItemType == typeContent &&
                       !GetIsMozBrowserOrApp();
  neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes(), isTopLevelDoc, aURI);
  rv = loadInfo->SetOriginAttributes(neckoAttrs);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (!isSrcdoc) {
    rv = NS_NewChannelInternal(getter_AddRefs(channel),
                               aURI,
                               loadInfo,
                               nullptr,   // loadGroup
                               static_cast<nsIInterfaceRequestor*>(this),
                               loadFlags);

    if (NS_FAILED(rv)) {
      if (rv == NS_ERROR_UNKNOWN_PROTOCOL) {
        // This is a uri with a protocol scheme we don't know how
        // to handle.  Embedders might still be interested in
        // handling the load, though, so we fire a notification
        // before throwing the load away.
        bool abort = false;
        nsresult rv2 = mContentListener->OnStartURIOpen(aURI, &abort);
        if (NS_SUCCEEDED(rv2) && abort) {
          // Hey, they're handling the load for us!  How convenient!
          return NS_OK;
        }
      }
      return rv;
    }

    if (aBaseURI) {
        nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel);
        if (vsc) {
            vsc->SetBaseURI(aBaseURI);
        }
    }
  } else {
    nsAutoCString scheme;
    rv = aURI->GetScheme(scheme);
    NS_ENSURE_SUCCESS(rv, rv);
    bool isViewSource;
    aURI->SchemeIs("view-source", &isViewSource);

    if (isViewSource) {
      nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance();
      NS_ENSURE_TRUE(vsh, NS_ERROR_FAILURE);

      rv = vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc,
                                 loadInfo, getter_AddRefs(channel));
    } else {
      rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
                                            aURI,
                                            aSrcdoc,
                                            NS_LITERAL_CSTRING("text/html"),
                                            loadInfo,
                                            true);
      NS_ENSURE_SUCCESS(rv, rv);
      nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
      MOZ_ASSERT(isc);
      isc->SetBaseURI(aBaseURI);
    }
  }

  // Navigational requests that are same origin need to be upgraded in case
  // upgrade-insecure-requests is present. Please note that in that case
  // the triggeringPrincipal is holding the CSP that potentially
  // holds upgrade-insecure-requests.
  nsCOMPtr<nsIContentSecurityPolicy> csp;
  aTriggeringPrincipal->GetCsp(getter_AddRefs(csp));
  if (csp) {
    bool upgradeInsecureRequests = false;
    csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
    if (upgradeInsecureRequests) {
      // only upgrade if the navigation is same origin
      nsCOMPtr<nsIPrincipal> resultPrincipal;
      rv = nsContentUtils::GetSecurityManager()->
             GetChannelResultPrincipal(channel,
                                       getter_AddRefs(resultPrincipal));
      NS_ENSURE_SUCCESS(rv, rv);
      if (resultPrincipal->Equals(aTriggeringPrincipal)) {
        static_cast<mozilla::LoadInfo*>(loadInfo.get())->SetUpgradeInsecureRequests();
      }
    }
  }


  nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
    do_QueryInterface(channel);
  if (appCacheChannel) {
    // Any document load should not inherit application cache.
    appCacheChannel->SetInheritApplicationCache(false);

    // Loads with the correct permissions should check for a matching
    // application cache.
    if (GeckoProcessType_Default != XRE_GetProcessType()) {
      // Permission will be checked in the parent process
      appCacheChannel->SetChooseApplicationCache(true);
    } else {
      nsCOMPtr<nsIScriptSecurityManager> secMan =
        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);

      if (secMan) {
        nsCOMPtr<nsIPrincipal> principal;
        secMan->GetDocShellCodebasePrincipal(aURI, this,
                                             getter_AddRefs(principal));
        appCacheChannel->SetChooseApplicationCache(
          NS_ShouldCheckAppCache(principal, UsePrivateBrowsing()));
      }
    }
  }

  // Make sure to give the caller a channel if we managed to create one
  // This is important for correct error page/session history interaction
  if (aRequest) {
    NS_ADDREF(*aRequest = channel);
  }

  if (aOriginalURI) {
    channel->SetOriginalURI(aOriginalURI);
    if (aLoadReplace) {
      uint32_t loadFlags;
      channel->GetLoadFlags(&loadFlags);
      NS_ENSURE_SUCCESS(rv, rv);
      channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
    }
  } else {
    channel->SetOriginalURI(aURI);
  }

  if (aTypeHint && *aTypeHint) {
    channel->SetContentType(nsDependentCString(aTypeHint));
    mContentTypeHint = aTypeHint;
  } else {
    mContentTypeHint.Truncate();
  }

  if (!aFileName.IsVoid()) {
    rv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
    NS_ENSURE_SUCCESS(rv, rv);
    if (!aFileName.IsEmpty()) {
      rv = channel->SetContentDispositionFilename(aFileName);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT ||
      mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
    rv = SetMixedContentChannel(channel);
    NS_ENSURE_SUCCESS(rv, rv);
  } else if (mMixedContentChannel) {
    /*
     * If the user "Disables Protection on This Page", we call
     * SetMixedContentChannel for the first time, otherwise
     * mMixedContentChannel is still null.
     * Later, if the new channel passes a same orign check, we remember the
     * users decision by calling SetMixedContentChannel using the new channel.
     * This way, the user does not have to click the disable protection button
     * over and over for browsing the same site.
     */
    rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, channel);
    if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(channel))) {
      SetMixedContentChannel(nullptr);
    }
  }

  // hack
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
  nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
    do_QueryInterface(channel));
  if (httpChannelInternal) {
    if (aForceAllowCookies) {
      httpChannelInternal->SetThirdPartyFlags(
        nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
    }
    if (aFirstParty) {
      httpChannelInternal->SetDocumentURI(aURI);
    } else {
      httpChannelInternal->SetDocumentURI(aReferrerURI);
    }
    httpChannelInternal->SetRedirectMode(
      nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
  }

  nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));
  if (props) {
    // save true referrer for those who need it (e.g. xpinstall whitelisting)
    // Currently only http and ftp channels support this.
    props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
                                  aReferrerURI);
  }

  nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
  /* Get the cache Key from SH */
  nsCOMPtr<nsISupports> cacheKey;
  if (cacheChannel) {
    if (mLSHE) {
      mLSHE->GetCacheKey(getter_AddRefs(cacheKey));
    } else if (mOSHE) {  // for reload cases
      mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
    }
  }

  // figure out if we need to set the post data stream on the channel...
  if (aPostData) {
    nsCOMPtr<nsIFormPOSTActionChannel> postChannel(do_QueryInterface(channel));
    if (postChannel) {
      // XXX it's a bit of a hack to rewind the postdata stream here but
      // it has to be done in case the post data is being reused multiple
      // times.
      nsCOMPtr<nsISeekableStream> postDataSeekable =
        do_QueryInterface(aPostData);
      if (postDataSeekable) {
        rv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      // we really need to have a content type associated with this stream!!
      postChannel->SetUploadStream(aPostData, EmptyCString(), -1);
    }

    /* If there is a valid postdata *and* it is a History Load,
     * set up the cache key on the channel, to retrieve the
     * data *only* from the cache. If it is a normal reload, the
     * cache is free to go to the server for updated postdata.
     */
    if (cacheChannel && cacheKey) {
      if (mLoadType == LOAD_HISTORY ||
          mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
        cacheChannel->SetCacheKey(cacheKey);
        uint32_t loadFlags;
        if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) {
          channel->SetLoadFlags(
            loadFlags | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
        }
      } else if (mLoadType == LOAD_RELOAD_NORMAL) {
        cacheChannel->SetCacheKey(cacheKey);
      }
    }
  } else {
    /* If there is no postdata, set the cache key on the channel, and
     * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel
     * will be free to get it from net if it is not found in cache.
     * New cache may use it creatively on CGI pages with GET
     * method and even on those that say "no-cache"
     */
    if (mLoadType == LOAD_HISTORY ||
        mLoadType == LOAD_RELOAD_NORMAL ||
        mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
      if (cacheChannel && cacheKey) {
        cacheChannel->SetCacheKey(cacheKey);
      }
    }
  }

  if (httpChannel) {
    if (aHeadersData) {
      rv = AddHeadersToChannel(aHeadersData, httpChannel);
    }
    // Set the referrer explicitly
    if (aReferrerURI && aSendReferrer) {
      // Referrer is currenly only set for link clicks here.
      httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
    }
    // set Content-Signature enforcing bit if aOriginalURI == about:newtab
    if (aOriginalURI && httpChannel) {
      if (IsAboutNewtab(aOriginalURI)) {
        nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
        if (loadInfo) {
          loadInfo->SetVerifySignedContent(true);
        }
      }
    }
  }

  nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
  if (scriptChannel) {
    // Allow execution against our context if the principals match
    scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
  }

  if (aIsNewWindowTarget) {
    nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel);
    if (props) {
      props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
                               true);
    }
  }

  nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(channel));
  if (timedChannel) {
    timedChannel->SetTimingEnabled(true);

    nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
    if (IsFrame() && win) {
      nsCOMPtr<Element> frameElement = win->GetFrameElementInternal();
      if (frameElement) {
        timedChannel->SetInitiatorType(frameElement->LocalName());
      }
    }
  }

  rv = DoChannelLoad(channel, uriLoader, aBypassClassifier);

  //
  // If the channel load failed, we failed and nsIWebProgress just ain't
  // gonna happen.
  //
  if (NS_SUCCEEDED(rv)) {
    if (aDocShell) {
      *aDocShell = this;
      NS_ADDREF(*aDocShell);
    }
  }

  return rv;
}

static nsresult
AppendSegmentToString(nsIInputStream* aIn,
                      void* aClosure,
                      const char* aFromRawSegment,
                      uint32_t aToOffset,
                      uint32_t aCount,
                      uint32_t* aWriteCount)
{
  // aFromSegment now contains aCount bytes of data.

  nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure);
  buf->Append(aFromRawSegment, aCount);

  // Indicate that we have consumed all of aFromSegment
  *aWriteCount = aCount;
  return NS_OK;
}

nsresult
nsDocShell::AddHeadersToChannel(nsIInputStream* aHeadersData,
                                nsIChannel* aGenericChannel)
{
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel);
  NS_ENSURE_STATE(httpChannel);

  uint32_t numRead;
  nsAutoCString headersString;
  nsresult rv = aHeadersData->ReadSegments(AppendSegmentToString,
                                           &headersString,
                                           UINT32_MAX,
                                           &numRead);
  NS_ENSURE_SUCCESS(rv, rv);

  // used during the manipulation of the String from the InputStream
  nsAutoCString headerName;
  nsAutoCString headerValue;
  int32_t crlf;
  int32_t colon;

  //
  // Iterate over the headersString: for each "\r\n" delimited chunk,
  // add the value as a header to the nsIHttpChannel
  //

  static const char kWhitespace[] = "\b\t\r\n ";
  while (true) {
    crlf = headersString.Find("\r\n");
    if (crlf == kNotFound) {
      return NS_OK;
    }

    const nsCSubstring& oneHeader = StringHead(headersString, crlf);

    colon = oneHeader.FindChar(':');
    if (colon == kNotFound) {
      return NS_ERROR_UNEXPECTED;
    }

    headerName = StringHead(oneHeader, colon);
    headerValue = Substring(oneHeader, colon + 1);

    headerName.Trim(kWhitespace);
    headerValue.Trim(kWhitespace);

    headersString.Cut(0, crlf + 2);

    //
    // FINALLY: we can set the header!
    //

    rv = httpChannel->SetRequestHeader(headerName, headerValue, true);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  NS_NOTREACHED("oops");
  return NS_ERROR_UNEXPECTED;
}

nsresult
nsDocShell::DoChannelLoad(nsIChannel* aChannel,
                          nsIURILoader* aURILoader,
                          bool aBypassClassifier)
{
  nsresult rv;
  // Mark the channel as being a document URI and allow content sniffing...
  nsLoadFlags loadFlags = 0;
  (void)aChannel->GetLoadFlags(&loadFlags);
  loadFlags |= nsIChannel::LOAD_DOCUMENT_URI |
               nsIChannel::LOAD_CALL_CONTENT_SNIFFERS;

  // Load attributes depend on load type...
  switch (mLoadType) {
    case LOAD_HISTORY: {
      // Only send VALIDATE_NEVER if mLSHE's URI was never changed via
      // push/replaceState (bug 669671).
      bool uriModified = false;
      if (mLSHE) {
        mLSHE->GetURIWasModified(&uriModified);
      }

      if (!uriModified) {
        loadFlags |= nsIRequest::VALIDATE_NEVER;
      }
      break;
    }

    case LOAD_RELOAD_CHARSET_CHANGE: {
      // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we only want
      // to force cache load for this channel, not the whole loadGroup.
      nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(aChannel);
      if (cachingChannel) {
        cachingChannel->SetAllowStaleCacheContent(true);
      }
      break;
    }

    case LOAD_RELOAD_NORMAL:
    case LOAD_REFRESH:
      loadFlags |= nsIRequest::VALIDATE_ALWAYS;
      break;

    case LOAD_NORMAL_BYPASS_CACHE:
    case LOAD_NORMAL_BYPASS_PROXY:
    case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
    case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
    case LOAD_RELOAD_BYPASS_CACHE:
    case LOAD_RELOAD_BYPASS_PROXY:
    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
    case LOAD_REPLACE_BYPASS_CACHE:
      loadFlags |= nsIRequest::LOAD_BYPASS_CACHE |
                   nsIRequest::LOAD_FRESH_CONNECTION;
      break;

    case LOAD_NORMAL:
    case LOAD_LINK:
      // Set cache checking flags
      switch (Preferences::GetInt("browser.cache.check_doc_frequency", -1)) {
        case 0:
          loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION;
          break;
        case 1:
          loadFlags |= nsIRequest::VALIDATE_ALWAYS;
          break;
        case 2:
          loadFlags |= nsIRequest::VALIDATE_NEVER;
          break;
      }
      break;
  }

  if (!aBypassClassifier) {
    loadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
  }

  // If the user pressed shift-reload, then do not allow ServiceWorker
  // interception to occur. See step 12.1 of the SW HandleFetch algorithm.
  if (IsForceReloadType(mLoadType)) {
    loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
  }

  (void)aChannel->SetLoadFlags(loadFlags);

  uint32_t openFlags = 0;
  if (mLoadType == LOAD_LINK) {
    openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
  }
  if (!mAllowContentRetargeting) {
    openFlags |= nsIURILoader::DONT_RETARGET;
  }
  rv = aURILoader->OpenURI(aChannel, openFlags, this);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult
nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
                           nsACString& aNewHash, uint32_t aLoadType)
{
  if (!mCurrentURI) {
    return NS_OK;
  }

  nsCOMPtr<nsIPresShell> shell = GetPresShell();
  if (!shell) {
    // If we failed to get the shell, or if there is no shell,
    // nothing left to do here.
    return NS_OK;
  }

  nsIScrollableFrame* rootScroll = shell->GetRootScrollFrameAsScrollable();
  if (rootScroll) {
    rootScroll->ClearDidHistoryRestore();
  }

  // If we have no new anchor, we do not want to scroll, unless there is a
  // current anchor and we are doing a history load.  So return if we have no
  // new anchor, and there is no current anchor or the load is not a history
  // load.
  if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) {
    return NS_OK;
  }

  // Both the new and current URIs refer to the same page. We can now
  // browse to the hash stored in the new URI.

  if (!aNewHash.IsEmpty()) {
    // anchor is there, but if it's a load from history,
    // we don't have any anchor jumping to do
    bool scroll = aLoadType != LOAD_HISTORY &&
                  aLoadType != LOAD_RELOAD_NORMAL;

    char* str = ToNewCString(aNewHash);
    if (!str) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    // nsUnescape modifies the string that is passed into it.
    nsUnescape(str);

    // We assume that the bytes are in UTF-8, as it says in the
    // spec:
    // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1

    // We try the UTF-8 string first, and then try the document's
    // charset (see below).  If the string is not UTF-8,
    // conversion will fail and give us an empty Unicode string.
    // In that case, we should just fall through to using the
    // page's charset.
    nsresult rv = NS_ERROR_FAILURE;
    NS_ConvertUTF8toUTF16 uStr(str);
    if (!uStr.IsEmpty()) {
      rv = shell->GoToAnchor(NS_ConvertUTF8toUTF16(str), scroll,
                             nsIPresShell::SCROLL_SMOOTH_AUTO);
    }
    free(str);

    // Above will fail if the anchor name is not UTF-8.  Need to
    // convert from document charset to unicode.
    if (NS_FAILED(rv)) {
      // Get a document charset
      NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
      nsIDocument* doc = mContentViewer->GetDocument();
      NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
      const nsACString& aCharset = doc->GetDocumentCharacterSet();

      nsCOMPtr<nsITextToSubURI> textToSubURI =
        do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
      NS_ENSURE_SUCCESS(rv, rv);

      // Unescape and convert to unicode
      nsXPIDLString uStr;

      rv = textToSubURI->UnEscapeAndConvert(PromiseFlatCString(aCharset).get(),
                                            PromiseFlatCString(aNewHash).get(),
                                            getter_Copies(uStr));
      NS_ENSURE_SUCCESS(rv, rv);

      // Ignore return value of GoToAnchor, since it will return an error
      // if there is no such anchor in the document, which is actually a
      // success condition for us (we want to update the session history
      // with the new URI no matter whether we actually scrolled
      // somewhere).
      //
      // When aNewHash contains "%00", unescaped string may be empty.
      // And GoToAnchor asserts if we ask it to scroll to an empty ref.
      shell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(),
                        nsIPresShell::SCROLL_SMOOTH_AUTO);
    }
  } else {
    // Tell the shell it's at an anchor, without scrolling.
    shell->GoToAnchor(EmptyString(), false);

    // An empty anchor was found, but if it's a load from history,
    // we don't have to jump to the top of the page. Scrollbar
    // position will be restored by the caller, based on positions
    // stored in session history.
    if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) {
      return NS_OK;
    }
    // An empty anchor. Scroll to the top of the page.  Ignore the
    // return value; failure to scroll here (e.g. if there is no
    // root scrollframe) is not grounds for canceling the load!
    SetCurScrollPosEx(0, 0);
  }

  return NS_OK;
}

void
nsDocShell::SetupReferrerFromChannel(nsIChannel* aChannel)
{
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
  if (httpChannel) {
    nsCOMPtr<nsIURI> referrer;
    nsresult rv = httpChannel->GetReferrer(getter_AddRefs(referrer));
    if (NS_SUCCEEDED(rv)) {
      SetReferrerURI(referrer);
    }
    uint32_t referrerPolicy;
    rv = httpChannel->GetReferrerPolicy(&referrerPolicy);
    if (NS_SUCCEEDED(rv)) {
      SetReferrerPolicy(referrerPolicy);
    }
  }
}

bool
nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
                     nsIPrincipal* aTriggeringPrincipal,
                     nsIPrincipal* aPrincipalToInherit,
                     uint32_t aLoadType, bool aFireOnLocationChange,
                     bool aAddToGlobalHistory, bool aCloneSHChildren)
{
  NS_PRECONDITION(aURI, "uri is null");
  NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");

  MOZ_ASSERT(!aPrincipalToInherit || (aPrincipalToInherit && aTriggeringPrincipal));

#if defined(DEBUG)
  if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
    nsAutoCString chanName;
    if (aChannel) {
      aChannel->GetName(chanName);
    } else {
      chanName.AssignLiteral("<no channel>");
    }

    MOZ_LOG(gDocShellLog, LogLevel::Debug,
            ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n",
             this, aURI->GetSpecOrDefault().get(), chanName.get(), aLoadType));
  }
#endif

  bool equalUri = false;

  // Get the post data and the HTTP response code from the channel.
  uint32_t responseStatus = 0;
  nsCOMPtr<nsIInputStream> inputStream;
  if (aChannel) {
    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));

    // Check if the HTTPChannel is hiding under a multiPartChannel
    if (!httpChannel) {
      GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
    }

    if (httpChannel) {
      nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
      if (uploadChannel) {
        uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
      }

      // If the response status indicates an error, unlink this session
      // history entry from any entries sharing its document.
      nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
      if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
        mLSHE->AbandonBFCacheEntry();
      }
    }
  }

  // Determine if this type of load should update history.
  bool updateGHistory = !(aLoadType == LOAD_BYPASS_HISTORY ||
                          aLoadType == LOAD_ERROR_PAGE ||
                          aLoadType & LOAD_CMD_HISTORY);

  // We don't update session history on reload unless we're loading
  // an iframe in shift-reload case.
  bool updateSHistory = updateGHistory &&
                        (!(aLoadType & LOAD_CMD_RELOAD) ||
                         (IsForceReloadType(aLoadType) && IsFrame()));

  // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
  // current frame or in the root docshell.
  nsCOMPtr<nsISHistory> rootSH = mSessionHistory;
  if (!rootSH) {
    // Get the handle to SH from the root docshell
    GetRootSessionHistory(getter_AddRefs(rootSH));
    if (!rootSH) {
      updateSHistory = false;
      updateGHistory = false; // XXX Why global history too?
    }
  }

  // Check if the url to be loaded is the same as the one already loaded.
  if (mCurrentURI) {
    aURI->Equals(mCurrentURI, &equalUri);
  }

#ifdef DEBUG
  bool shAvailable = (rootSH != nullptr);

  // XXX This log message is almost useless because |updateSHistory|
  //     and |updateGHistory| are not correct at this point.

  MOZ_LOG(gDocShellLog, LogLevel::Debug,
         ("  shAvailable=%i updateSHistory=%i updateGHistory=%i"
          " equalURI=%i\n",
          shAvailable, updateSHistory, updateGHistory, equalUri));

  if (shAvailable && mCurrentURI && !mOSHE && aLoadType != LOAD_ERROR_PAGE) {
    NS_ASSERTION(NS_IsAboutBlank(mCurrentURI),
                 "no SHEntry for a non-transient viewer?");
  }
#endif

  /* If the url to be loaded is the same as the one already there,
   * and the original loadType is LOAD_NORMAL, LOAD_LINK, or
   * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that
   * AddToSessionHistory() won't mess with the current SHEntry and
   * if this page has any frame children, it also will be handled
   * properly. see bug 83684
   *
   * NB: If mOSHE is null but we have a current URI, then it means
   * that we must be at the transient about:blank content viewer
   * (asserted above) and we should let the normal load continue,
   * since there's nothing to replace.
   *
   * XXX Hopefully changing the loadType at this time will not hurt
   *  anywhere. The other way to take care of sequentially repeating
   *  frameset pages is to add new methods to nsIDocShellTreeItem.
   * Hopefully I don't have to do that.
   */
  if (equalUri &&
      mOSHE &&
      (mLoadType == LOAD_NORMAL ||
       mLoadType == LOAD_LINK ||
       mLoadType == LOAD_STOP_CONTENT) &&
      !inputStream) {
    mLoadType = LOAD_NORMAL_REPLACE;
  }

  // If this is a refresh to the currently loaded url, we don't
  // have to update session or global history.
  if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
    SetHistoryEntry(&mLSHE, mOSHE);
  }

  /* If the user pressed shift-reload, cache will create a new cache key
   * for the page. Save the new cacheKey in Session History.
   * see bug 90098
   */
  if (aChannel && IsForceReloadType(aLoadType)) {
    MOZ_ASSERT(!updateSHistory || IsFrame(),
               "We shouldn't be updating session history for forced"
               " reloads unless we're in a newly created iframe!");

    nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel));
    nsCOMPtr<nsISupports> cacheKey;
    // Get the Cache Key and store it in SH.
    if (cacheChannel) {
      cacheChannel->GetCacheKey(getter_AddRefs(cacheKey));
    }
    // If we already have a loading history entry, store the new cache key
    // in it.  Otherwise, since we're doing a reload and won't be updating
    // our history entry, store the cache key in our current history entry.
    if (mLSHE) {
      mLSHE->SetCacheKey(cacheKey);
    } else if (mOSHE) {
      mOSHE->SetCacheKey(cacheKey);
    }

    // Since we're force-reloading, clear all the sub frame history.
    ClearFrameHistory(mLSHE);
    ClearFrameHistory(mOSHE);
  }

  if (aLoadType == LOAD_RELOAD_NORMAL) {
    nsCOMPtr<nsISHEntry> currentSH;
    bool oshe = false;
    GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
    bool dynamicallyAddedChild = false;
    if (currentSH) {
      currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild);
    }
    if (dynamicallyAddedChild) {
      ClearFrameHistory(currentSH);
    }
  }

  if (aLoadType == LOAD_REFRESH) {
    ClearFrameHistory(mLSHE);
    ClearFrameHistory(mOSHE);
  }

  if (updateSHistory) {
    // Update session history if necessary...
    if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) {
      /* This is  a fresh page getting loaded for the first time
       *.Create a Entry for it and add it to SH, if this is the
       * rootDocShell
       */
      (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
                                aPrincipalToInherit, aCloneSHChildren,
                                getter_AddRefs(mLSHE));
    }
  } else if (mSessionHistory && mLSHE && mURIResultedInDocument) {
    // Even if we don't add anything to SHistory, ensure the current index
    // points to the same SHEntry as our mLSHE.
    int32_t index = 0;
    mSessionHistory->GetRequestedIndex(&index);
    if (index == -1) {
      mSessionHistory->GetIndex(&index);
    }
    nsCOMPtr<nsISHEntry> currentSH;
    mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(currentSH));
    if (currentSH != mLSHE) {
      nsCOMPtr<nsISHistoryInternal> shPrivate =
        do_QueryInterface(mSessionHistory);
      shPrivate->ReplaceEntry(index, mLSHE);
    }
  }

  // If this is a POST request, we do not want to include this in global
  // history.
  if (updateGHistory && aAddToGlobalHistory && !ChannelIsPost(aChannel)) {
    nsCOMPtr<nsIURI> previousURI;
    uint32_t previousFlags = 0;

    if (aLoadType & LOAD_CMD_RELOAD) {
      // On a reload request, we don't set redirecting flags.
      previousURI = aURI;
    } else {
      ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags);
    }

    // Note: We don't use |referrer| when our global history is
    //       based on IHistory.
    nsCOMPtr<nsIURI> referrer;
    // Treat referrer as null if there is an error getting it.
    (void)NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));

    AddURIVisit(aURI, referrer, previousURI, previousFlags, responseStatus);
  }

  // If this was a history load or a refresh, or it was a history load but
  // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index
  // in session history.
  if (rootSH &&
       ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) ||
         mLoadType == LOAD_NORMAL_REPLACE)) {
    nsCOMPtr<nsISHistoryInternal> shInternal(do_QueryInterface(rootSH));
    if (shInternal) {
      rootSH->GetIndex(&mPreviousTransIndex);
      shInternal->UpdateIndex();
      rootSH->GetIndex(&mLoadedTransIndex);
#ifdef DEBUG_PAGE_CACHE
      printf("Previous index: %d, Loaded index: %d\n\n",
             mPreviousTransIndex, mLoadedTransIndex);
#endif
    }
  }

  // aCloneSHChildren exactly means "we are not loading a new document".
  uint32_t locationFlags =
    aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0;

  bool onLocationChangeNeeded = SetCurrentURI(aURI, aChannel,
                                              aFireOnLocationChange,
                                              locationFlags);
  // Make sure to store the referrer from the channel, if any
  SetupReferrerFromChannel(aChannel);
  return onLocationChangeNeeded;
}

bool
nsDocShell::OnLoadingSite(nsIChannel* aChannel, bool aFireOnLocationChange,
                          bool aAddToGlobalHistory)
{
  nsCOMPtr<nsIURI> uri;
  // If this a redirect, use the final url (uri)
  // else use the original url
  //
  // Note that this should match what documents do (see nsDocument::Reset).
  NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
  NS_ENSURE_TRUE(uri, false);

  // Pass false for aCloneSHChildren, since we're loading a new page here.
  return OnNewURI(uri, aChannel, nullptr, nullptr, mLoadType, aFireOnLocationChange,
                  aAddToGlobalHistory, false);
}

void
nsDocShell::SetReferrerURI(nsIURI* aURI)
{
  mReferrerURI = aURI;  // This assigment addrefs
}

void
nsDocShell::SetReferrerPolicy(uint32_t aReferrerPolicy)
{
  mReferrerPolicy = aReferrerPolicy;
}

//*****************************************************************************
// nsDocShell: Session History
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
                     const nsAString& aURL, bool aReplace, JSContext* aCx)
{
  // Implements History.pushState and History.replaceState

  // Here's what we do, roughly in the order specified by HTML5:
  // 1. Serialize aData using structured clone.
  // 2. If the third argument is present,
  //     a. Resolve the url, relative to the first script's base URL
  //     b. If (a) fails, raise a SECURITY_ERR
  //     c. Compare the resulting absolute URL to the document's address.  If
  //        any part of the URLs difer other than the <path>, <query>, and
  //        <fragment> components, raise a SECURITY_ERR and abort.
  // 3. If !aReplace:
  //     Remove from the session history all entries after the current entry,
  //     as we would after a regular navigation, and save the current
  //     entry's scroll position (bug 590573).
  // 4. As apropriate, either add a state object entry to the session history
  //    after the current entry with the following properties, or modify the
  //    current session history entry to set
  //      a. cloned data as the state object,
  //      b. if the third argument was present, the absolute URL found in
  //         step 2
  //    Also clear the new history entry's POST data (see bug 580069).
  // 5. If aReplace is false (i.e. we're doing a pushState instead of a
  //    replaceState), notify bfcache that we've navigated to a new page.
  // 6. If the third argument is present, set the document's current address
  //    to the absolute URL found in step 2.
  //
  // It's important that this function not run arbitrary scripts after step 1
  // and before completing step 5.  For example, if a script called
  // history.back() before we completed step 5, bfcache might destroy an
  // active content viewer.  Since EvictOutOfRangeContentViewers at the end of
  // step 5 might run script, we can't just put a script blocker around the
  // critical section.
  //
  // Note that we completely ignore the aTitle parameter.

  nsresult rv;

  // Don't clobber the load type of an existing network load.
  AutoRestore<uint32_t> loadTypeResetter(mLoadType);

  // pushState effectively becomes replaceState when we've started a network
  // load but haven't adopted its document yet.  This mirrors what we do with
  // changes to the hash at this stage of the game.
  if (JustStartedNetworkLoad()) {
    aReplace = true;
  }

  nsCOMPtr<nsIDocument> document = GetDocument();
  NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);

  // Step 1: Serialize aData using structured clone.
  nsCOMPtr<nsIStructuredCloneContainer> scContainer;

  // scContainer->Init might cause arbitrary JS to run, and this code might
  // navigate the page we're on, potentially to a different origin! (bug
  // 634834)  To protect against this, we abort if our principal changes due
  // to the InitFromJSVal() call.
  {
    nsCOMPtr<nsIDocument> origDocument = GetDocument();
    if (!origDocument) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }
    nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal();

    scContainer = new nsStructuredCloneContainer();
    rv = scContainer->InitFromJSVal(aData, aCx);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIDocument> newDocument = GetDocument();
    if (!newDocument) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }
    nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal();

    bool principalsEqual = false;
    origPrincipal->Equals(newPrincipal, &principalsEqual);
    NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR);
  }

  // Check that the state object isn't too long.
  // Default max length: 640k bytes.
  int32_t maxStateObjSize =
    Preferences::GetInt("browser.history.maxStateObjectSize", 0xA0000);
  if (maxStateObjSize < 0) {
    maxStateObjSize = 0;
  }

  uint64_t scSize;
  rv = scContainer->GetSerializedNBytes(&scSize);
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE);

  // Step 2: Resolve aURL
  bool equalURIs = true;
  nsCOMPtr<nsIURI> currentURI;
  if (sURIFixup && mCurrentURI) {
    rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(currentURI));
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    currentURI = mCurrentURI;
  }
  nsCOMPtr<nsIURI> oldURI = currentURI;
  nsCOMPtr<nsIURI> newURI;
  if (aURL.Length() == 0) {
    newURI = currentURI;
  } else {
    // 2a: Resolve aURL relative to mURI

    nsIURI* docBaseURI = document->GetDocBaseURI();
    if (!docBaseURI) {
      return NS_ERROR_FAILURE;
    }

    nsAutoCString spec;
    docBaseURI->GetSpec(spec);

    nsAutoCString charset;
    rv = docBaseURI->GetOriginCharset(charset);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);

    rv = NS_NewURI(getter_AddRefs(newURI), aURL, charset.get(), docBaseURI);

    // 2b: If 2a fails, raise a SECURITY_ERR
    if (NS_FAILED(rv)) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }

    // 2c: Same-origin check.
    if (!nsContentUtils::URIIsLocalFile(newURI)) {
      // In addition to checking that the security manager says that
      // the new URI has the same origin as our current URI, we also
      // check that the two URIs have the same userpass. (The
      // security manager says that |http://foo.com| and
      // |http://me@foo.com| have the same origin.)  currentURI
      // won't contain the password part of the userpass, so this
      // means that it's never valid to specify a password in a
      // pushState or replaceState URI.

      nsCOMPtr<nsIScriptSecurityManager> secMan =
        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
      NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);

      // It's very important that we check that newURI is of the same
      // origin as currentURI, not docBaseURI, because a page can
      // set docBaseURI arbitrarily to any domain.
      nsAutoCString currentUserPass, newUserPass;
      NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass),
                        NS_ERROR_FAILURE);
      NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE);
      if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true)) ||
          !currentUserPass.Equals(newUserPass)) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }
    } else {
      // It's a file:// URI
      nsCOMPtr<nsIScriptObjectPrincipal> docScriptObj =
        do_QueryInterface(document);

      if (!docScriptObj) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }

      nsCOMPtr<nsIPrincipal> principal = docScriptObj->GetPrincipal();

      if (!principal ||
          NS_FAILED(principal->CheckMayLoad(newURI, true, false))) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }
    }

    if (currentURI) {
      currentURI->Equals(newURI, &equalURIs);
    } else {
      equalURIs = false;
    }

  } // end of same-origin check

  // Step 3: Create a new entry in the session history. This will erase
  // all SHEntries after the new entry and make this entry the current
  // one.  This operation may modify mOSHE, which we need later, so we
  // keep a reference here.
  NS_ENSURE_TRUE(mOSHE, NS_ERROR_FAILURE);
  nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;

  mLoadType = LOAD_PUSHSTATE;

  nsCOMPtr<nsISHEntry> newSHEntry;
  if (!aReplace) {
    // Save the current scroll position (bug 590573).
    nscoord cx = 0, cy = 0;
    GetCurScrollPos(ScrollOrientation_X, &cx);
    GetCurScrollPos(ScrollOrientation_Y, &cy);
    mOSHE->SetScrollPosition(cx, cy);

    bool scrollRestorationIsManual = false;
    mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);

    // Since we're not changing which page we have loaded, pass
    // true for aCloneChildren.
    rv = AddToSessionHistory(newURI, nullptr, nullptr, nullptr, true,
                             getter_AddRefs(newSHEntry));
    NS_ENSURE_SUCCESS(rv, rv);

    NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);

    // Session history entries created by pushState inherit scroll restoration
    // mode from the current entry.
    newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);

    // Link the new SHEntry to the old SHEntry's BFCache entry, since the
    // two entries correspond to the same document.
    NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), NS_ERROR_FAILURE);

    // Set the new SHEntry's title (bug 655273).
    nsString title;
    mOSHE->GetTitle(getter_Copies(title));
    newSHEntry->SetTitle(title);

    // AddToSessionHistory may not modify mOSHE.  In case it doesn't,
    // we'll just set mOSHE here.
    mOSHE = newSHEntry;

  } else {
    newSHEntry = mOSHE;
    newSHEntry->SetURI(newURI);
    newSHEntry->SetOriginalURI(newURI);
    newSHEntry->SetLoadReplace(false);
  }

  // Step 4: Modify new/original session history entry and clear its POST
  // data, if there is any.
  newSHEntry->SetStateData(scContainer);
  newSHEntry->SetPostData(nullptr);

  // If this push/replaceState changed the document's current URI and the new
  // URI differs from the old URI in more than the hash, or if the old
  // SHEntry's URI was modified in this way by a push/replaceState call
  // set URIWasModified to true for the current SHEntry (bug 669671).
  bool sameExceptHashes = true, oldURIWasModified = false;
  newURI->EqualsExceptRef(currentURI, &sameExceptHashes);
  oldOSHE->GetURIWasModified(&oldURIWasModified);
  newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified);

  // Step 5: If aReplace is false, indicating that we're doing a pushState
  // rather than a replaceState, notify bfcache that we've added a page to
  // the history so it can evict content viewers if appropriate. Otherwise
  // call ReplaceEntry so that we notify nsIHistoryListeners that an entry
  // was replaced.
  nsCOMPtr<nsISHistory> rootSH;
  GetRootSessionHistory(getter_AddRefs(rootSH));
  NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED);

  nsCOMPtr<nsISHistoryInternal> internalSH = do_QueryInterface(rootSH);
  NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED);

  if (!aReplace) {
    int32_t curIndex = -1;
    rv = rootSH->GetIndex(&curIndex);
    if (NS_SUCCEEDED(rv) && curIndex > -1) {
      internalSH->EvictOutOfRangeContentViewers(curIndex);
    }
  } else {
    nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);

    int32_t index = -1;
    rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
    if (NS_SUCCEEDED(rv) && index > -1) {
      internalSH->ReplaceEntry(index, rootSHEntry);
    }
  }

  // Step 6: If the document's URI changed, update document's URI and update
  // global history.
  //
  // We need to call FireOnLocationChange so that the browser's address bar
  // gets updated and the back button is enabled, but we only need to
  // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
  // since SetCurrentURI will call FireOnLocationChange for us.
  //
  // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass
  // nullptr for aRequest param to FireOnLocationChange(...). Such an update
  // notification is allowed only when we know docshell is not loading a new
  // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
  // FireOnLocationChange(...) breaks security UI.
  if (!equalURIs) {
    document->SetDocumentURI(newURI);
    // We can't trust SetCurrentURI to do always fire locationchange events
    // when we expect it to, so we hack around that by doing it ourselves...
    SetCurrentURI(newURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT);
    if (mLoadType != LOAD_ERROR_PAGE) {
      FireDummyOnLocationChange();
    }

    AddURIVisit(newURI, oldURI, oldURI, 0);

    // AddURIVisit doesn't set the title for the new URI in global history,
    // so do that here.
    if (mUseGlobalHistory && !UsePrivateBrowsing()) {
      nsCOMPtr<IHistory> history = services::GetHistoryService();
      if (history) {
        history->SetURITitle(newURI, mTitle);
      } else if (mGlobalHistory) {
        mGlobalHistory->SetPageTitle(newURI, mTitle);
      }
    }

    // Inform the favicon service that our old favicon applies to this new
    // URI.
    CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing());
  } else {
    FireDummyOnLocationChange();
  }
  document->SetStateObject(scContainer);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual)
{
  *aIsManual = false;
  if (mOSHE) {
    mOSHE->GetScrollRestorationIsManual(aIsManual);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual)
{
  if (mOSHE) {
    mOSHE->SetScrollRestorationIsManual(aIsManual);
  }

  return NS_OK;
}

bool
nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI)
{
  // I believe none of the about: urls should go in the history. But then
  // that could just be me... If the intent is only deny about:blank then we
  // should just do a spec compare, rather than two gets of the scheme and
  // then the path.  -Gagan
  nsresult rv;
  nsAutoCString buf;

  rv = aURI->GetScheme(buf);
  if (NS_FAILED(rv)) {
    return false;
  }

  if (buf.EqualsLiteral("about")) {
    rv = aURI->GetPath(buf);
    if (NS_FAILED(rv)) {
      return false;
    }

    if (buf.EqualsLiteral("blank") || buf.EqualsLiteral("newtab")) {
      return false;
    }
  }

  return true;
}

nsresult
nsDocShell::AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
                                nsIPrincipal* aTriggeringPrincipal,
                                nsIPrincipal* aPrincipalToInherit,
                                bool aCloneChildren,
                                nsISHEntry** aNewEntry)
{
  NS_PRECONDITION(aURI, "uri is null");
  NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");

#if defined(DEBUG)
  if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
    nsAutoCString chanName;
    if (aChannel) {
      aChannel->GetName(chanName);
    } else {
      chanName.AssignLiteral("<no channel>");
    }

    MOZ_LOG(gDocShellLog, LogLevel::Debug,
            ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n",
             this, aURI->GetSpecOrDefault().get(), chanName.get()));
  }
#endif

  nsresult rv = NS_OK;
  nsCOMPtr<nsISHEntry> entry;
  bool shouldPersist;

  shouldPersist = ShouldAddToSessionHistory(aURI);

  // Get a handle to the root docshell
  nsCOMPtr<nsIDocShellTreeItem> root;
  GetSameTypeRootTreeItem(getter_AddRefs(root));
  /*
   * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use
   * the existing SH entry in the page and replace the url and
   * other vitalities.
   */
  if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) &&
      root != static_cast<nsIDocShellTreeItem*>(this)) {
    // This is a subframe
    entry = mOSHE;
    nsCOMPtr<nsISHContainer> shContainer(do_QueryInterface(entry));
    if (shContainer) {
      int32_t childCount = 0;
      shContainer->GetChildCount(&childCount);
      // Remove all children of this entry
      for (int32_t i = childCount - 1; i >= 0; i--) {
        nsCOMPtr<nsISHEntry> child;
        shContainer->GetChildAt(i, getter_AddRefs(child));
        shContainer->RemoveChild(child);
      }
      entry->AbandonBFCacheEntry();
    }
  }

  // Create a new entry if necessary.
  if (!entry) {
    entry = do_CreateInstance(NS_SHENTRY_CONTRACTID);

    if (!entry) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }

  // Get the post data & referrer
  nsCOMPtr<nsIInputStream> inputStream;
  nsCOMPtr<nsIURI> originalURI;
  bool loadReplace = false;
  nsCOMPtr<nsIURI> referrerURI;
  uint32_t referrerPolicy = mozilla::net::RP_Default;
  nsCOMPtr<nsISupports> cacheKey;
  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal;
  nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit;
  bool expired = false;
  bool discardLayoutState = false;
  nsCOMPtr<nsICacheInfoChannel> cacheChannel;
  if (aChannel) {
    cacheChannel = do_QueryInterface(aChannel);

    /* If there is a caching channel, get the Cache Key and store it
     * in SH.
     */
    if (cacheChannel) {
      cacheChannel->GetCacheKey(getter_AddRefs(cacheKey));
    }
    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));

    // Check if the httpChannel is hiding under a multipartChannel
    if (!httpChannel) {
      GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
    }
    if (httpChannel) {
      nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
      if (uploadChannel) {
        uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
      }
      httpChannel->GetOriginalURI(getter_AddRefs(originalURI));
      uint32_t loadFlags;
      aChannel->GetLoadFlags(&loadFlags);
      loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
      httpChannel->GetReferrer(getter_AddRefs(referrerURI));
      httpChannel->GetReferrerPolicy(&referrerPolicy);

      discardLayoutState = ShouldDiscardLayoutState(httpChannel);
    }

    // XXX Bug 1286838: Replace channel owner with loadInfo triggeringPrincipal
    nsCOMPtr<nsISupports> owner;
    aChannel->GetOwner(getter_AddRefs(owner));
    triggeringPrincipal = do_QueryInterface(owner);

    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
    if (loadInfo) {
      if (!triggeringPrincipal) {
        triggeringPrincipal = loadInfo->TriggeringPrincipal();
      }

      // For now keep storing just the principal in the SHEntry.
      if (!principalToInherit) {
        if (loadInfo->GetLoadingSandboxed()) {
          if (loadInfo->LoadingPrincipal()) {
            principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(
            loadInfo->LoadingPrincipal());
          } else {
            // get the OriginAttributes
            NeckoOriginAttributes nAttrs;
            loadInfo->GetOriginAttributes(&nAttrs);
            PrincipalOriginAttributes pAttrs;
            pAttrs.InheritFromNecko(nAttrs);
            principalToInherit = nsNullPrincipal::Create(pAttrs);
          }
        } else {
          principalToInherit = loadInfo->PrincipalToInherit();
        }
      }
    }
  }

  // Title is set in nsDocShell::SetTitle()
  entry->Create(aURI,                // uri
                EmptyString(),       // Title
                inputStream,         // Post data stream
                nullptr,             // LayoutHistory state
                cacheKey,            // CacheKey
                mContentTypeHint,    // Content-type
                triggeringPrincipal, // Channel or provided principal
                principalToInherit,
                mHistoryID,
                mDynamicallyCreated);

  entry->SetOriginalURI(originalURI);
  entry->SetLoadReplace(loadReplace);
  entry->SetReferrerURI(referrerURI);
  entry->SetReferrerPolicy(referrerPolicy);
  nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel);
  if (inStrmChan) {
    bool isSrcdocChannel;
    inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
    if (isSrcdocChannel) {
      nsAutoString srcdoc;
      inStrmChan->GetSrcdocData(srcdoc);
      entry->SetSrcdocData(srcdoc);
      nsCOMPtr<nsIURI> baseURI;
      inStrmChan->GetBaseURI(getter_AddRefs(baseURI));
      entry->SetBaseURI(baseURI);
    }
  }
  /* If cache got a 'no-store', ask SH not to store
   * HistoryLayoutState. By default, SH will set this
   * flag to true and save HistoryLayoutState.
   */
  if (discardLayoutState) {
    entry->SetSaveLayoutStateFlag(false);
  }
  if (cacheChannel) {
    // Check if the page has expired from cache
    uint32_t expTime = 0;
    cacheChannel->GetCacheTokenExpirationTime(&expTime);
    uint32_t now = PRTimeToSeconds(PR_Now());
    if (expTime <= now) {
      expired = true;
    }
  }
  if (expired) {
    entry->SetExpirationStatus(true);
  }

  if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) {
    // If we need to clone our children onto the new session
    // history entry, do so now.
    if (aCloneChildren && mOSHE) {
      uint32_t cloneID;
      mOSHE->GetID(&cloneID);
      nsCOMPtr<nsISHEntry> newEntry;
      CloneAndReplace(mOSHE, this, cloneID, entry, true,
                      getter_AddRefs(newEntry));
      NS_ASSERTION(entry == newEntry,
                   "The new session history should be in the new entry");
    }

    // This is the root docshell
    bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY);
    if (!addToSHistory) {
      // Replace current entry in session history; If the requested index is
      // valid, it indicates the loading was triggered by a history load, and
      // we should replace the entry at requested index instead.
      int32_t index = 0;
      mSessionHistory->GetRequestedIndex(&index);
      if (index == -1) {
        mSessionHistory->GetIndex(&index);
      }
      nsCOMPtr<nsISHistoryInternal> shPrivate =
        do_QueryInterface(mSessionHistory);
      // Replace the current entry with the new entry
      if (index >= 0) {
        if (shPrivate) {
          rv = shPrivate->ReplaceEntry(index, entry);
        }
      } else {
        // If we're trying to replace an inexistant shistory entry, append.
        addToSHistory = true;
      }
    }

    if (addToSHistory) {
      // Add to session history
      nsCOMPtr<nsISHistoryInternal> shPrivate =
        do_QueryInterface(mSessionHistory);
      NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
      mSessionHistory->GetIndex(&mPreviousTransIndex);
      rv = shPrivate->AddEntry(entry, shouldPersist);
      mSessionHistory->GetIndex(&mLoadedTransIndex);
#ifdef DEBUG_PAGE_CACHE
      printf("Previous index: %d, Loaded index: %d\n\n",
             mPreviousTransIndex, mLoadedTransIndex);
#endif
    }
  } else {
    // This is a subframe.
    if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
      rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren);
    }
  }

  // Return the new SH entry...
  if (aNewEntry) {
    *aNewEntry = nullptr;
    if (NS_SUCCEEDED(rv)) {
      entry.forget(aNewEntry);
    }
  }

  return rv;
}

nsresult
nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType)
{
  if (!IsNavigationAllowed()) {
    return NS_OK;
  }

  nsCOMPtr<nsIURI> uri;
  nsCOMPtr<nsIURI> originalURI;
  bool loadReplace = false;
  nsCOMPtr<nsIInputStream> postData;
  nsCOMPtr<nsIURI> referrerURI;
  uint32_t referrerPolicy;
  nsAutoCString contentType;
  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
  nsCOMPtr<nsIPrincipal> principalToInherit;

  NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE);

  NS_ENSURE_SUCCESS(aEntry->GetURI(getter_AddRefs(uri)), NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetOriginalURI(getter_AddRefs(originalURI)),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetLoadReplace(&loadReplace),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetReferrerURI(getter_AddRefs(referrerURI)),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetReferrerPolicy(&referrerPolicy),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetPostData(getter_AddRefs(postData)),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetContentType(contentType), NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)),
                    NS_ERROR_FAILURE);
  NS_ENSURE_SUCCESS(aEntry->GetPrincipalToInherit(getter_AddRefs(principalToInherit)),
                    NS_ERROR_FAILURE);

  // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if
  // that's the only thing holding a ref to aEntry that will cause aEntry to
  // die while we're loading it.  So hold a strong ref to aEntry here, just
  // in case.
  nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry);
  bool isJS;
  nsresult rv = uri->SchemeIs("javascript", &isJS);
  if (NS_FAILED(rv) || isJS) {
    // We're loading a URL that will execute script from inside asyncOpen.
    // Replace the current document with about:blank now to prevent
    // anything from the current document from leaking into any JavaScript
    // code in the URL.
    // Don't cache the presentation if we're going to just reload the
    // current entry. Caching would lead to trying to save the different
    // content viewers in the same nsISHEntry object.
    rv = CreateAboutBlankContentViewer(principalToInherit, nullptr,
                                       aEntry != mOSHE);

    if (NS_FAILED(rv)) {
      // The creation of the intermittent about:blank content
      // viewer failed for some reason (potentially because the
      // user prevented it). Interrupt the history load.
      return NS_OK;
    }

    if (!triggeringPrincipal) {
      // Ensure that we have a triggeringPrincipal.  Otherwise javascript:
      // URIs will pick it up from the about:blank page we just loaded,
      // and we don't really want even that in this case.
      triggeringPrincipal = nsNullPrincipal::CreateWithInheritedAttributes(this);
    }
  }

  /* If there is a valid postdata *and* the user pressed
   * reload or shift-reload, take user's permission before we
   * repost the data to the server.
   */
  if ((aLoadType & LOAD_CMD_RELOAD) && postData) {
    bool repost;
    rv = ConfirmRepost(&repost);
    if (NS_FAILED(rv)) {
      return rv;
    }

    // If the user pressed cancel in the dialog, return.  We're done here.
    if (!repost) {
      return NS_BINDING_ABORTED;
    }
  }

  // Do not inherit principal from document (security-critical!);
  uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;

  nsAutoString srcdoc;
  bool isSrcdoc;
  nsCOMPtr<nsIURI> baseURI;
  aEntry->GetIsSrcdocEntry(&isSrcdoc);
  if (isSrcdoc) {
    aEntry->GetSrcdocData(srcdoc);
    aEntry->GetBaseURI(getter_AddRefs(baseURI));
    flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
  } else {
    srcdoc = NullString();
  }

  // If there is no triggeringPrincipal we can fall back to using the
  // SystemPrincipal as the triggeringPrincipal for loading the history
  // entry, since the history entry can only end up in history if security
  // checks passed in the initial loading phase.
  if (!triggeringPrincipal) {
    triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
  }

  // Passing nullptr as aSourceDocShell gives the same behaviour as before
  // aSourceDocShell was introduced. According to spec we should be passing
  // the source browsing context that was used when the history entry was
  // first created. bug 947716 has been created to address this issue.
  rv = InternalLoad(uri,
                    originalURI,
                    loadReplace,
                    referrerURI,
                    referrerPolicy,
                    triggeringPrincipal,
                    principalToInherit,
                    flags,
                    EmptyString(),      // No window target
                    contentType.get(),  // Type hint
                    NullString(),       // No forced file download
                    postData,           // Post data stream
                    nullptr,            // No headers stream
                    aLoadType,          // Load type
                    aEntry,             // SHEntry
                    true,
                    srcdoc,
                    nullptr,            // Source docshell, see comment above
                    baseURI,
                    nullptr,            // No nsIDocShell
                    nullptr);           // No nsIRequest
  return rv;
}

NS_IMETHODIMP
nsDocShell::GetShouldSaveLayoutState(bool* aShould)
{
  *aShould = false;
  if (mOSHE) {
    // Don't capture historystate and save it in history
    // if the page asked not to do so.
    mOSHE->GetSaveLayoutStateFlag(aShould);
  }

  return NS_OK;
}

nsresult
nsDocShell::PersistLayoutHistoryState()
{
  nsresult rv = NS_OK;

  if (mOSHE) {
    bool scrollRestorationIsManual = false;
    mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);

    nsCOMPtr<nsIPresShell> shell = GetPresShell();
    nsCOMPtr<nsILayoutHistoryState> layoutState;
    if (shell) {
      rv = shell->CaptureHistoryState(getter_AddRefs(layoutState));
    } else if (scrollRestorationIsManual) {
      // Even if we don't have layout anymore, we may want to reset the current
      // scroll state in layout history.
      GetLayoutHistoryState(getter_AddRefs(layoutState));
    }

    if (scrollRestorationIsManual && layoutState) {
      layoutState->ResetScrollState();
    }
  }

  return rv;
}

/* static */ nsresult
nsDocShell::WalkHistoryEntries(nsISHEntry* aRootEntry,
                               nsDocShell* aRootShell,
                               WalkHistoryEntriesFunc aCallback,
                               void* aData)
{
  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);

  nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
  if (!container) {
    return NS_ERROR_FAILURE;
  }

  int32_t childCount;
  container->GetChildCount(&childCount);
  for (int32_t i = 0; i < childCount; i++) {
    nsCOMPtr<nsISHEntry> childEntry;
    container->GetChildAt(i, getter_AddRefs(childEntry));
    if (!childEntry) {
      // childEntry can be null for valid reasons, for example if the
      // docshell at index i never loaded anything useful.
      // Remember to clone also nulls in the child array (bug 464064).
      aCallback(nullptr, nullptr, i, aData);
      continue;
    }

    nsDocShell* childShell = nullptr;
    if (aRootShell) {
      // Walk the children of aRootShell and see if one of them
      // has srcChild as a SHEntry.
      nsTObserverArray<nsDocLoader*>::ForwardIterator iter(
        aRootShell->mChildList);
      while (iter.HasMore()) {
        nsDocShell* child = static_cast<nsDocShell*>(iter.GetNext());

        if (child->HasHistoryEntry(childEntry)) {
          childShell = child;
          break;
        }
      }
    }
    nsresult rv = aCallback(childEntry, childShell, i, aData);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

// callback data for WalkHistoryEntries
struct MOZ_STACK_CLASS CloneAndReplaceData
{
  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
    : cloneID(aCloneID)
    , cloneChildren(aCloneChildren)
    , replaceEntry(aReplaceEntry)
    , destTreeParent(aDestTreeParent)
  {
  }

  uint32_t cloneID;
  bool cloneChildren;
  nsISHEntry* replaceEntry;
  nsISHEntry* destTreeParent;
  nsCOMPtr<nsISHEntry> resultEntry;
};

/* static */ nsresult
nsDocShell::CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
                                 int32_t aEntryIndex, void* aData)
{
  nsCOMPtr<nsISHEntry> dest;

  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
  uint32_t cloneID = data->cloneID;
  nsISHEntry* replaceEntry = data->replaceEntry;

  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
  if (!aEntry) {
    if (container) {
      container->AddChild(nullptr, aEntryIndex);
    }
    return NS_OK;
  }

  uint32_t srcID;
  aEntry->GetID(&srcID);

  nsresult rv = NS_OK;
  if (srcID == cloneID) {
    // Replace the entry
    dest = replaceEntry;
  } else {
    // Clone the SHEntry...
    rv = aEntry->Clone(getter_AddRefs(dest));
    NS_ENSURE_SUCCESS(rv, rv);
  }
  dest->SetIsSubFrame(true);

  if (srcID != cloneID || data->cloneChildren) {
    // Walk the children
    CloneAndReplaceData childData(cloneID, replaceEntry,
                                  data->cloneChildren, dest);
    rv = WalkHistoryEntries(aEntry, aShell,
                            CloneAndReplaceChild, &childData);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (srcID != cloneID && aShell) {
    aShell->SwapHistoryEntries(aEntry, dest);
  }

  if (container) {
    container->AddChild(dest, aEntryIndex);
  }

  data->resultEntry = dest;
  return rv;
}

/* static */ nsresult
nsDocShell::CloneAndReplace(nsISHEntry* aSrcEntry,
                            nsDocShell* aSrcShell,
                            uint32_t aCloneID,
                            nsISHEntry* aReplaceEntry,
                            bool aCloneChildren,
                            nsISHEntry** aResultEntry)
{
  NS_ENSURE_ARG_POINTER(aResultEntry);
  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);

  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
  nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);

  data.resultEntry.swap(*aResultEntry);
  return rv;
}

void
nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry)
{
  if (aOldEntry == mOSHE) {
    mOSHE = aNewEntry;
  }

  if (aOldEntry == mLSHE) {
    mLSHE = aNewEntry;
  }
}

struct SwapEntriesData
{
  nsDocShell* ignoreShell;     // constant; the shell to ignore
  nsISHEntry* destTreeRoot;    // constant; the root of the dest tree
  nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
                               // whose children will correspond to aEntry
};

nsresult
nsDocShell::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
                                 int32_t aEntryIndex, void* aData)
{
  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
  nsDocShell* ignoreShell = data->ignoreShell;

  if (!aShell || aShell == ignoreShell) {
    return NS_OK;
  }

  nsISHEntry* destTreeRoot = data->destTreeRoot;

  nsCOMPtr<nsISHEntry> destEntry;
  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);

  if (container) {
    // aEntry is a clone of some child of destTreeParent, but since the
    // trees aren't necessarily in sync, we'll have to locate it.
    // Note that we could set aShell's entry to null if we don't find a
    // corresponding entry under destTreeParent.

    uint32_t targetID, id;
    aEntry->GetID(&targetID);

    // First look at the given index, since this is the common case.
    nsCOMPtr<nsISHEntry> entry;
    container->GetChildAt(aEntryIndex, getter_AddRefs(entry));
    if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) {
      destEntry.swap(entry);
    } else {
      int32_t childCount;
      container->GetChildCount(&childCount);
      for (int32_t i = 0; i < childCount; ++i) {
        container->GetChildAt(i, getter_AddRefs(entry));
        if (!entry) {
          continue;
        }

        entry->GetID(&id);
        if (id == targetID) {
          destEntry.swap(entry);
          break;
        }
      }
    }
  } else {
    destEntry = destTreeRoot;
  }

  aShell->SwapHistoryEntries(aEntry, destEntry);

  // Now handle the children of aEntry.
  SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
  return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData);
}

static nsISHEntry*
GetRootSHEntry(nsISHEntry* aEntry)
{
  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
  nsISHEntry* result = nullptr;
  while (rootEntry) {
    result = rootEntry;
    result->GetParent(getter_AddRefs(rootEntry));
  }

  return result;
}

void
nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry)
{
  // We need to sync up the docshell and session history trees for
  // subframe navigation.  If the load was in a subframe, we forward up to
  // the root docshell, which will then recursively sync up all docshells
  // to their corresponding entries in the new session history tree.
  // If we don't do this, then we can cache a content viewer on the wrong
  // cloned entry, and subsequently restore it at the wrong time.

  nsISHEntry* newRootEntry = GetRootSHEntry(aEntry);
  if (newRootEntry) {
    // newRootEntry is now the new root entry.
    // Find the old root entry as well.

    // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
    // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
    nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr);
    if (oldRootEntry) {
      nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
      GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
      nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem);
      if (rootShell) { // if we're the root just set it, nothing to swap
        SwapEntriesData data = { this, newRootEntry };
        nsIDocShell* rootIDocShell = static_cast<nsIDocShell*>(rootShell);
        nsDocShell* rootDocShell = static_cast<nsDocShell*>(rootIDocShell);

#ifdef DEBUG
        nsresult rv =
#endif
        SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data);
        NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed");
      }
    }
  }

  *aPtr = aEntry;
}

nsresult
nsDocShell::GetRootSessionHistory(nsISHistory** aReturn)
{
  nsresult rv;

  nsCOMPtr<nsIDocShellTreeItem> root;
  // Get the root docshell
  rv = GetSameTypeRootTreeItem(getter_AddRefs(root));
  // QI to nsIWebNavigation
  nsCOMPtr<nsIWebNavigation> rootAsWebnav(do_QueryInterface(root));
  if (rootAsWebnav) {
    // Get the handle to SH from the root docshell
    rv = rootAsWebnav->GetSessionHistory(aReturn);
  }
  return rv;
}

nsresult
nsDocShell::GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn)
{
  NS_ENSURE_ARG_POINTER(aReturn);
  if (!aChannel) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel));
  if (multiPartChannel) {
    nsCOMPtr<nsIChannel> baseChannel;
    multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel));
    *aReturn = httpChannel;
    NS_IF_ADDREF(*aReturn);
  }
  return NS_OK;
}

bool
nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel)
{
  // By default layout State will be saved.
  if (!aChannel) {
    return false;
  }

  // figure out if SH should be saving layout state
  bool noStore = false;
  aChannel->IsNoStoreResponse(&noStore);
  return noStore;
}

NS_IMETHODIMP
nsDocShell::GetEditor(nsIEditor** aEditor)
{
  NS_ENSURE_ARG_POINTER(aEditor);

  if (!mEditorData) {
    *aEditor = nullptr;
    return NS_OK;
  }

  return mEditorData->GetEditor(aEditor);
}

NS_IMETHODIMP
nsDocShell::SetEditor(nsIEditor* aEditor)
{
  nsresult rv = EnsureEditorData();
  if (NS_FAILED(rv)) {
    return rv;
  }

  return mEditorData->SetEditor(aEditor);
}

NS_IMETHODIMP
nsDocShell::GetEditable(bool* aEditable)
{
  NS_ENSURE_ARG_POINTER(aEditable);
  *aEditable = mEditorData && mEditorData->GetEditable();
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetHasEditingSession(bool* aHasEditingSession)
{
  NS_ENSURE_ARG_POINTER(aHasEditingSession);

  if (mEditorData) {
    nsCOMPtr<nsIEditingSession> editingSession;
    mEditorData->GetEditingSession(getter_AddRefs(editingSession));
    *aHasEditingSession = (editingSession.get() != nullptr);
  } else {
    *aHasEditingSession = false;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::MakeEditable(bool aInWaitForUriLoad)
{
  nsresult rv = EnsureEditorData();
  if (NS_FAILED(rv)) {
    return rv;
  }

  return mEditorData->MakeEditable(aInWaitForUriLoad);
}

bool
nsDocShell::ChannelIsPost(nsIChannel* aChannel)
{
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
  if (!httpChannel) {
    return false;
  }

  nsAutoCString method;
  httpChannel->GetRequestMethod(method);
  return method.EqualsLiteral("POST");
}

void
nsDocShell::ExtractLastVisit(nsIChannel* aChannel,
                             nsIURI** aURI,
                             uint32_t* aChannelRedirectFlags)
{
  nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
  if (!props) {
    return;
  }

  nsresult rv = props->GetPropertyAsInterface(
    NS_LITERAL_STRING("docshell.previousURI"),
    NS_GET_IID(nsIURI),
    reinterpret_cast<void**>(aURI));

  if (NS_FAILED(rv)) {
    // There is no last visit for this channel, so this must be the first
    // link.  Link the visit to the referrer of this request, if any.
    // Treat referrer as null if there is an error getting it.
    (void)NS_GetReferrerFromChannel(aChannel, aURI);
  } else {
    rv = props->GetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"),
                                    aChannelRedirectFlags);

    NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "Could not fetch previous flags, URI will be treated like referrer");
  }
}

void
nsDocShell::SaveLastVisit(nsIChannel* aChannel,
                          nsIURI* aURI,
                          uint32_t aChannelRedirectFlags)
{
  nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
  if (!props || !aURI) {
    return;
  }

  props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"),
                                aURI);
  props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"),
                             aChannelRedirectFlags);
}

void
nsDocShell::AddURIVisit(nsIURI* aURI,
                        nsIURI* aReferrerURI,
                        nsIURI* aPreviousURI,
                        uint32_t aChannelRedirectFlags,
                        uint32_t aResponseStatus)
{
  MOZ_ASSERT(aURI, "Visited URI is null!");
  MOZ_ASSERT(mLoadType != LOAD_ERROR_PAGE &&
             mLoadType != LOAD_BYPASS_HISTORY,
             "Do not add error or bypass pages to global history");

  // Only content-type docshells save URI visits.  Also don't do
  // anything here if we're not supposed to use global history.
  if (mItemType != typeContent || !mUseGlobalHistory || UsePrivateBrowsing()) {
    return;
  }

  nsCOMPtr<IHistory> history = services::GetHistoryService();

  if (history) {
    uint32_t visitURIFlags = 0;

    if (!IsFrame()) {
      visitURIFlags |= IHistory::TOP_LEVEL;
    }

    if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
      visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
    } else if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) {
      visitURIFlags |= IHistory::REDIRECT_PERMANENT;
    }

    if (aResponseStatus >= 300 && aResponseStatus < 400) {
      visitURIFlags |= IHistory::REDIRECT_SOURCE;
    }
    // Errors 400-501 and 505 are considered unrecoverable, in the sense a
    // simple retry attempt by the user is unlikely to solve them.
    // 408 is special cased, since may actually indicate a temporary
    // connection problem.
    else if (aResponseStatus != 408 &&
             ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
              aResponseStatus == 505)) {
      visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
    }

    (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags);
  } else if (mGlobalHistory) {
    // Falls back to sync global history interface.
    (void)mGlobalHistory->AddURI(aURI,
                                 !!aChannelRedirectFlags,
                                 !IsFrame(),
                                 aReferrerURI);
  }
}

//*****************************************************************************
// nsDocShell: Helper Routines
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::SetLoadType(uint32_t aLoadType)
{
  mLoadType = aLoadType;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetLoadType(uint32_t* aLoadType)
{
  *aLoadType = mLoadType;
  return NS_OK;
}

nsresult
nsDocShell::ConfirmRepost(bool* aRepost)
{
  nsCOMPtr<nsIPrompt> prompter;
  CallGetInterface(this, static_cast<nsIPrompt**>(getter_AddRefs(prompter)));
  if (!prompter) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCOMPtr<nsIStringBundleService> stringBundleService =
    mozilla::services::GetStringBundleService();
  if (!stringBundleService) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIStringBundle> appBundle;
  nsresult rv = stringBundleService->CreateBundle(kAppstringsBundleURL,
                                                  getter_AddRefs(appBundle));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIStringBundle> brandBundle;
  rv = stringBundleService->CreateBundle(kBrandBundleURL,
                                         getter_AddRefs(brandBundle));
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ASSERTION(prompter && brandBundle && appBundle,
               "Unable to set up repost prompter.");

  nsXPIDLString brandName;
  rv = brandBundle->GetStringFromName(u"brandShortName",
                                      getter_Copies(brandName));

  nsXPIDLString msgString, button0Title;
  if (NS_FAILED(rv)) { // No brand, use the generic version.
    rv = appBundle->GetStringFromName(u"confirmRepostPrompt",
                                      getter_Copies(msgString));
  } else {
    // Brand available - if the app has an override file with formatting, the
    // app name will be included. Without an override, the prompt will look
    // like the generic version.
    const char16_t* formatStrings[] = { brandName.get() };
    rv = appBundle->FormatStringFromName(u"confirmRepostPrompt",
                                         formatStrings,
                                         ArrayLength(formatStrings),
                                         getter_Copies(msgString));
  }
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = appBundle->GetStringFromName(u"resendButton.label",
                                    getter_Copies(button0Title));
  if (NS_FAILED(rv)) {
    return rv;
  }

  int32_t buttonPressed;
  // The actual value here is irrelevant, but we can't pass an invalid
  // bool through XPConnect.
  bool checkState = false;
  rv = prompter->ConfirmEx(
    nullptr, msgString.get(),
    (nsIPrompt::BUTTON_POS_0 * nsIPrompt::BUTTON_TITLE_IS_STRING) +
      (nsIPrompt::BUTTON_POS_1 * nsIPrompt::BUTTON_TITLE_CANCEL),
    button0Title.get(), nullptr, nullptr, nullptr, &checkState, &buttonPressed);
  if (NS_FAILED(rv)) {
    return rv;
  }

  *aRepost = (buttonPressed == 0);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt,
                                     nsIStringBundle** aStringBundle)
{
  NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt),
                    NS_ERROR_FAILURE);

  nsCOMPtr<nsIStringBundleService> stringBundleService =
    mozilla::services::GetStringBundleService();
  NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE);

  NS_ENSURE_SUCCESS(
    stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle),
    NS_ERROR_FAILURE);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent,
                           int32_t* aOffset)
{
  NS_ENSURE_ARG_POINTER(aChild || aParent);

  nsCOMPtr<nsIDOMNodeList> childNodes;
  NS_ENSURE_SUCCESS(aParent->GetChildNodes(getter_AddRefs(childNodes)),
                    NS_ERROR_FAILURE);
  NS_ENSURE_TRUE(childNodes, NS_ERROR_FAILURE);

  int32_t i = 0;

  for (; true; i++) {
    nsCOMPtr<nsIDOMNode> childNode;
    NS_ENSURE_SUCCESS(childNodes->Item(i, getter_AddRefs(childNode)),
                      NS_ERROR_FAILURE);
    NS_ENSURE_TRUE(childNode, NS_ERROR_FAILURE);

    if (childNode.get() == aChild) {
      *aOffset = i;
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
}

nsIScrollableFrame*
nsDocShell::GetRootScrollFrame()
{
  nsCOMPtr<nsIPresShell> shell = GetPresShell();
  NS_ENSURE_TRUE(shell, nullptr);

  return shell->GetRootScrollFrameAsScrollableExternal();
}

NS_IMETHODIMP
nsDocShell::EnsureScriptEnvironment()
{
  if (mScriptGlobal) {
    return NS_OK;
  }

  if (mIsBeingDestroyed) {
    return NS_ERROR_NOT_AVAILABLE;
  }

#ifdef DEBUG
  NS_ASSERTION(!mInEnsureScriptEnv,
               "Infinite loop! Calling EnsureScriptEnvironment() from "
               "within EnsureScriptEnvironment()!");

  // Yeah, this isn't re-entrant safe, but that's ok since if we
  // re-enter this method, we'll infinitely loop...
  AutoRestore<bool> boolSetter(mInEnsureScriptEnv);
  mInEnsureScriptEnv = true;
#endif

  nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
  NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE);

  uint32_t chromeFlags;
  browserChrome->GetChromeFlags(&chromeFlags);

  bool isModalContentWindow =
    (mItemType == typeContent) &&
    (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL_CONTENT_WINDOW);
  // There can be various other content docshells associated with the
  // top-level window, like sidebars. Make sure that we only create an
  // nsGlobalModalWindow for the primary content shell.
  if (isModalContentWindow) {
    nsCOMPtr<nsIDocShellTreeItem> primaryItem;
    nsresult rv =
      mTreeOwner->GetPrimaryContentShell(getter_AddRefs(primaryItem));
    NS_ENSURE_SUCCESS(rv, rv);
    isModalContentWindow = (primaryItem == this);
  }

  // If our window is modal and we're not opened as chrome, make
  // this window a modal content window.
  mScriptGlobal =
    NS_NewScriptGlobalObject(mItemType == typeChrome, isModalContentWindow);
  MOZ_ASSERT(mScriptGlobal);

  mScriptGlobal->SetDocShell(this);

  // Ensure the script object is set up to run script.
  return mScriptGlobal->EnsureScriptEnvironment();
}

NS_IMETHODIMP
nsDocShell::EnsureEditorData()
{
  bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor();
  if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) {
    // We shouldn't recreate the editor data if it already exists, or
    // we're shutting down, or we already have a detached editor data
    // stored in the session history. We should only have one editordata
    // per docshell.
    mEditorData = new nsDocShellEditorData(this);
  }

  return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}

nsresult
nsDocShell::EnsureTransferableHookData()
{
  if (!mTransferableHookData) {
    mTransferableHookData = new nsTransferableHookData();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::EnsureFind()
{
  nsresult rv;
  if (!mFind) {
    mFind = do_CreateInstance("@mozilla.org/embedcomp/find;1", &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  // we promise that the nsIWebBrowserFind that we return has been set
  // up to point to the focused, or content window, so we have to
  // set that up each time.

  nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject();
  NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED);

  // default to our window
  nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO);
  nsCOMPtr<nsPIDOMWindowOuter> windowToSearch;
  nsFocusManager::GetFocusedDescendant(ourWindow, true,
                                       getter_AddRefs(windowToSearch));

  nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind);
  if (!findInFrames) {
    return NS_ERROR_NO_INTERFACE;
  }

  rv = findInFrames->SetRootSearchFrame(ourWindow);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = findInFrames->SetCurrentSearchFrame(windowToSearch);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}

bool
nsDocShell::IsFrame()
{
  nsCOMPtr<nsIDocShellTreeItem> parent;
  GetSameTypeParent(getter_AddRefs(parent));
  return !!parent;
}

NS_IMETHODIMP
nsDocShell::IsBeingDestroyed(bool* aDoomed)
{
  NS_ENSURE_ARG(aDoomed);
  *aDoomed = mIsBeingDestroyed;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult)
{
  NS_ENSURE_ARG(aResult);
  *aResult = mIsExecutingOnLoadHandler;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState)
{
  if (mOSHE) {
    mOSHE->GetLayoutHistoryState(aLayoutHistoryState);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState)
{
  if (mOSHE) {
    mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
  }
  return NS_OK;
}

nsRefreshTimer::nsRefreshTimer()
  : mDelay(0), mRepeat(false), mMetaRefresh(false)
{
}

nsRefreshTimer::~nsRefreshTimer()
{
}

NS_IMPL_ADDREF(nsRefreshTimer)
NS_IMPL_RELEASE(nsRefreshTimer)

NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_END_THREADSAFE

NS_IMETHODIMP
nsRefreshTimer::Notify(nsITimer* aTimer)
{
  NS_ASSERTION(mDocShell, "DocShell is somehow null");

  if (mDocShell && aTimer) {
    // Get the delay count to determine load type
    uint32_t delay = 0;
    aTimer->GetDelay(&delay);
    mDocShell->ForceRefreshURIFromTimer(mURI, delay, mMetaRefresh, aTimer, mPrincipal);
  }
  return NS_OK;
}

nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
    nsIInterfaceRequestor* aRequestor)
{
  if (aRequestor) {
    mWeakPtr = do_GetWeakReference(aRequestor);
  }
}

nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy()
{
  mWeakPtr = nullptr;
}

NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor)

NS_IMETHODIMP
nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID,
                                                  void** aSink)
{
  NS_ENSURE_ARG_POINTER(aSink);
  nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr);
  if (ifReq) {
    return ifReq->GetInterface(aIID, aSink);
  }
  *aSink = nullptr;
  return NS_NOINTERFACE;
}

nsresult
nsDocShell::SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer)
{
  if (!aContentViewer) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIURI> baseURI;
  nsresult rv = NS_ERROR_NOT_AVAILABLE;

  if (sURIFixup) {
    rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(baseURI));
  }

  // Get the current document and set the base uri
  if (baseURI) {
    nsIDocument* document = aContentViewer->GetDocument();
    if (document) {
      document->SetBaseURI(baseURI);
    }
  }
  return rv;
}

//*****************************************************************************
// nsDocShell::nsIAuthPromptProvider
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
                          void** aResult)
{
  // a priority prompt request will override a false mAllowAuth setting
  bool priorityPrompt = (aPromptReason == PROMPT_PROXY);

  if (!mAllowAuth && !priorityPrompt) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  // we're either allowing auth, or it's a proxy request
  nsresult rv;
  nsCOMPtr<nsIPromptFactory> wwatch =
    do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = EnsureScriptEnvironment();
  NS_ENSURE_SUCCESS(rv, rv);

  // Get the an auth prompter for our window so that the parenting
  // of the dialogs works as it should when using tabs.

  return wwatch->GetPrompt(mScriptGlobal->AsOuter(), aIID,
                           reinterpret_cast<void**>(aResult));
}

//*****************************************************************************
// nsDocShell::nsILoadContext
//*****************************************************************************

NS_IMETHODIMP
nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow)
{
  CallGetInterface(this, aWindow);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow)
{
  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
  if (win) {
    win = win->GetTop();
  }
  win.forget(aWindow);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetTopFrameElement(nsIDOMElement** aElement)
{
  *aElement = nullptr;
  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
  if (!win) {
    return NS_OK;
  }

  nsCOMPtr<nsPIDOMWindowOuter> top = win->GetScriptableTop();
  NS_ENSURE_TRUE(top, NS_ERROR_FAILURE);

  // GetFrameElementInternal, /not/ GetScriptableFrameElement -- if |top| is
  // inside <iframe mozbrowser>, we want to return the iframe, not null.
  // And we want to cross the content/chrome boundary.
  nsCOMPtr<nsIDOMElement> elt =
    do_QueryInterface(top->GetFrameElementInternal());
  elt.forget(aElement);
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetNestedFrameId(uint64_t* aId)
{
  *aId = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn)
{
  if (Preferences::GetBool("privacy.trackingprotection.enabled", false)) {
    *aIsTrackingProtectionOn = true;
  } else if (UsePrivateBrowsing() &&
             Preferences::GetBool("privacy.trackingprotection.pbmode.enabled", false)) {
    *aIsTrackingProtectionOn = true;
  } else {
    *aIsTrackingProtectionOn = false;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetIsContent(bool* aIsContent)
{
  *aIsContent = (mItemType == typeContent);
  return NS_OK;
}

bool
nsDocShell::IsOKToLoadURI(nsIURI* aURI)
{
  NS_PRECONDITION(aURI, "Must have a URI!");

  if (!mFiredUnloadEvent) {
    return true;
  }

  if (!mLoadingURI) {
    return false;
  }

  nsCOMPtr<nsIScriptSecurityManager> secMan =
    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
  return secMan &&
         NS_SUCCEEDED(secMan->CheckSameOriginURI(aURI, mLoadingURI, false));
}

//
// Routines for selection and clipboard
//
nsresult
nsDocShell::GetControllerForCommand(const char* aCommand,
                                    nsIController** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = nullptr;

  NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE);

  nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot();
  NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);

  return root->GetControllerForCommand(aCommand, aResult);
}

NS_IMETHODIMP
nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = false;

  nsresult rv = NS_ERROR_FAILURE;

  nsCOMPtr<nsIController> controller;
  rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
  if (controller) {
    rv = controller->IsCommandEnabled(aCommand, aResult);
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::DoCommand(const char* aCommand)
{
  nsresult rv = NS_ERROR_FAILURE;

  nsCOMPtr<nsIController> controller;
  rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
  if (controller) {
    rv = controller->DoCommand(aCommand);
  }

  return rv;
}

NS_IMETHODIMP
nsDocShell::DoCommandWithParams(const char* aCommand, nsICommandParams* aParams)
{
  nsCOMPtr<nsIController> controller;
  nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsICommandController> commandController =
    do_QueryInterface(controller, &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return commandController->DoCommandWithParams(aCommand, aParams);
}

nsresult
nsDocShell::EnsureCommandHandler()
{
  if (!mCommandManager) {
    nsCOMPtr<nsPICommandUpdater> commandUpdater =
      do_CreateInstance("@mozilla.org/embedcomp/command-manager;1");
    if (!commandUpdater) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow();
    nsresult rv = commandUpdater->Init(domWindow);
    if (NS_SUCCEEDED(rv)) {
      mCommandManager = do_QueryInterface(commandUpdater);
    }
  }

  return mCommandManager ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::CanCutSelection(bool* aResult)
{
  return IsCommandEnabled("cmd_cut", aResult);
}

NS_IMETHODIMP
nsDocShell::CanCopySelection(bool* aResult)
{
  return IsCommandEnabled("cmd_copy", aResult);
}

NS_IMETHODIMP
nsDocShell::CanCopyLinkLocation(bool* aResult)
{
  return IsCommandEnabled("cmd_copyLink", aResult);
}

NS_IMETHODIMP
nsDocShell::CanCopyImageLocation(bool* aResult)
{
  return IsCommandEnabled("cmd_copyImageLocation", aResult);
}

NS_IMETHODIMP
nsDocShell::CanCopyImageContents(bool* aResult)
{
  return IsCommandEnabled("cmd_copyImageContents", aResult);
}

NS_IMETHODIMP
nsDocShell::CanPaste(bool* aResult)
{
  return IsCommandEnabled("cmd_paste", aResult);
}

NS_IMETHODIMP
nsDocShell::CutSelection(void)
{
  return DoCommand("cmd_cut");
}

NS_IMETHODIMP
nsDocShell::CopySelection(void)
{
  return DoCommand("cmd_copy");
}

NS_IMETHODIMP
nsDocShell::CopyLinkLocation(void)
{
  return DoCommand("cmd_copyLink");
}

NS_IMETHODIMP
nsDocShell::CopyImageLocation(void)
{
  return DoCommand("cmd_copyImageLocation");
}

NS_IMETHODIMP
nsDocShell::CopyImageContents(void)
{
  return DoCommand("cmd_copyImageContents");
}

NS_IMETHODIMP
nsDocShell::Paste(void)
{
  return DoCommand("cmd_paste");
}

NS_IMETHODIMP
nsDocShell::SelectAll(void)
{
  return DoCommand("cmd_selectAll");
}

//
// SelectNone
//
// Collapses the current selection, insertion point ends up at beginning
// of previous selection.
//
NS_IMETHODIMP
nsDocShell::SelectNone(void)
{
  return DoCommand("cmd_selectNone");
}

// link handling

class OnLinkClickEvent : public Runnable
{
public:
  OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
                   nsIURI* aURI,
                   const char16_t* aTargetSpec,
                   const nsAString& aFileName,
                   nsIInputStream* aPostDataStream,
                   nsIInputStream* aHeadersDataStream,
                   bool aIsTrusted);

  NS_IMETHOD Run() override
  {
    nsAutoPopupStatePusher popupStatePusher(mPopupState);

    // We need to set up an AutoJSAPI here for the following reason: When we do
    // OnLinkClickSync we'll eventually end up in nsGlobalWindow::OpenInternal
    // which only does popup blocking if !LegacyIsCallerChromeOrNativeCode().
    // So we need to fake things so that we don't look like native code as far
    // as LegacyIsCallerNativeCode() is concerned.
    AutoJSAPI jsapi;
    if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) {
      mHandler->OnLinkClickSync(mContent, mURI,
                                mTargetSpec.get(), mFileName,
                                mPostDataStream, mHeadersDataStream,
                                nullptr, nullptr);
    }
    return NS_OK;
  }

private:
  RefPtr<nsDocShell> mHandler;
  nsCOMPtr<nsIURI> mURI;
  nsString mTargetSpec;
  nsString mFileName;
  nsCOMPtr<nsIInputStream> mPostDataStream;
  nsCOMPtr<nsIInputStream> mHeadersDataStream;
  nsCOMPtr<nsIContent> mContent;
  PopupControlState mPopupState;
  bool mIsTrusted;
};

OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler,
                                   nsIContent* aContent,
                                   nsIURI* aURI,
                                   const char16_t* aTargetSpec,
                                   const nsAString& aFileName,
                                   nsIInputStream* aPostDataStream,
                                   nsIInputStream* aHeadersDataStream,
                                   bool aIsTrusted)
  : mHandler(aHandler)
  , mURI(aURI)
  , mTargetSpec(aTargetSpec)
  , mFileName(aFileName)
  , mPostDataStream(aPostDataStream)
  , mHeadersDataStream(aHeadersDataStream)
  , mContent(aContent)
  , mPopupState(mHandler->mScriptGlobal->GetPopupControlState())
  , mIsTrusted(aIsTrusted)
{
}

NS_IMETHODIMP
nsDocShell::OnLinkClick(nsIContent* aContent,
                        nsIURI* aURI,
                        const char16_t* aTargetSpec,
                        const nsAString& aFileName,
                        nsIInputStream* aPostDataStream,
                        nsIInputStream* aHeadersDataStream,
                        bool aIsTrusted)
{
  NS_ASSERTION(NS_IsMainThread(), "wrong thread");

  if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
    return NS_OK;
  }

  // On history navigation through Back/Forward buttons, don't execute
  // automatic JavaScript redirection such as |anchorElement.click()| or
  // |formElement.submit()|.
  //
  // XXX |formElement.submit()| bypasses this checkpoint because it calls
  //     nsDocShell::OnLinkClickSync(...) instead.
  if (ShouldBlockLoadingForBackButton()) {
    return NS_OK;
  }

  if (aContent->IsEditable()) {
    return NS_OK;
  }

  nsresult rv = NS_ERROR_FAILURE;
  nsAutoString target;

  nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
  if (browserChrome3) {
    nsCOMPtr<nsIDOMNode> linkNode = do_QueryInterface(aContent);
    nsAutoString oldTarget(aTargetSpec);
    rv = browserChrome3->OnBeforeLinkTraversal(oldTarget, aURI,
                                               linkNode, mIsAppTab, target);
  }

  if (NS_FAILED(rv)) {
    target = aTargetSpec;
  }

  nsCOMPtr<nsIRunnable> ev =
    new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName,
                         aPostDataStream, aHeadersDataStream, aIsTrusted);
  return NS_DispatchToCurrentThread(ev);
}

NS_IMETHODIMP
nsDocShell::OnLinkClickSync(nsIContent* aContent,
                            nsIURI* aURI,
                            const char16_t* aTargetSpec,
                            const nsAString& aFileName,
                            nsIInputStream* aPostDataStream,
                            nsIInputStream* aHeadersDataStream,
                            nsIDocShell** aDocShell,
                            nsIRequest** aRequest)
{
  // Initialize the DocShell / Request
  if (aDocShell) {
    *aDocShell = nullptr;
  }
  if (aRequest) {
    *aRequest = nullptr;
  }

  if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
    return NS_OK;
  }

  // XXX When the linking node was HTMLFormElement, it is synchronous event.
  //     That is, the caller of this method is not |OnLinkClickEvent::Run()|
  //     but |HTMLFormElement::SubmitSubmission(...)|.
  if (aContent->IsHTMLElement(nsGkAtoms::form) &&
      ShouldBlockLoadingForBackButton()) {
    return NS_OK;
  }

  if (aContent->IsEditable()) {
    return NS_OK;
  }

  {
    // defer to an external protocol handler if necessary...
    nsCOMPtr<nsIExternalProtocolService> extProtService =
      do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
    if (extProtService) {
      nsAutoCString scheme;
      aURI->GetScheme(scheme);
      if (!scheme.IsEmpty()) {
        // if the URL scheme does not correspond to an exposed protocol, then we
        // need to hand this link click over to the external protocol handler.
        bool isExposed;
        nsresult rv =
          extProtService->IsExposedProtocol(scheme.get(), &isExposed);
        if (NS_SUCCEEDED(rv) && !isExposed) {
          return extProtService->LoadURI(aURI, this);
        }
      }
    }
  }

  uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
  if (IsElementAnchor(aContent)) {
    MOZ_ASSERT(aContent->IsHTMLElement());
    nsAutoString referrer;
    aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer);
    nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer);
    while (tok.hasMoreTokens()) {
      const nsAString& token = tok.nextToken();
      if (token.LowerCaseEqualsLiteral("noreferrer")) {
        flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
                 INTERNAL_LOAD_FLAGS_NO_OPENER;
        // We now have all the flags we could possibly have, so just stop.
        break;
      }
      if (token.LowerCaseEqualsLiteral("noopener")) {
        flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
      }
    }
  }

  // Get the owner document of the link that was clicked, this will be
  // the document that the link is in, or the last document that the
  // link was in. From that document, we'll get the URI to use as the
  // referer, since the current URI in this docshell may be a
  // new document that we're in the process of loading.
  nsCOMPtr<nsIDocument> refererDoc = aContent->OwnerDoc();
  NS_ENSURE_TRUE(refererDoc, NS_ERROR_UNEXPECTED);

  // Now check that the refererDoc's inner window is the current inner
  // window for mScriptGlobal.  If it's not, then we don't want to
  // follow this link.
  nsPIDOMWindowInner* refererInner = refererDoc->GetInnerWindow();
  NS_ENSURE_TRUE(refererInner, NS_ERROR_UNEXPECTED);
  if (!mScriptGlobal ||
      mScriptGlobal->AsOuter()->GetCurrentInnerWindow() != refererInner) {
    // We're no longer the current inner window
    return NS_OK;
  }

  nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI();
  uint32_t refererPolicy = refererDoc->GetReferrerPolicy();

  // get referrer attribute from clicked link and parse it
  // if per element referrer is enabled, the element referrer overrules
  // the document wide referrer
  if (IsElementAnchor(aContent)) {
    net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicyAsEnum();
    if (refPolEnum != net::RP_Unset) {
      refererPolicy = refPolEnum;
    }
  }

  // referer could be null here in some odd cases, but that's ok,
  // we'll just load the link w/o sending a referer in those cases.

  nsAutoString target(aTargetSpec);

  // If this is an anchor element, grab its type property to use as a hint
  nsAutoString typeHint;
  nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(aContent));
  if (anchor) {
    anchor->GetType(typeHint);
    NS_ConvertUTF16toUTF8 utf8Hint(typeHint);
    nsAutoCString type, dummy;
    NS_ParseRequestContentType(utf8Hint, type, dummy);
    CopyUTF8toUTF16(type, typeHint);
  }

  // Clone the URI now, in case a content policy or something messes
  // with it under InternalLoad; we do _not_ want to change the URI
  // our caller passed in.
  nsCOMPtr<nsIURI> clonedURI;
  aURI->Clone(getter_AddRefs(clonedURI));
  if (!clonedURI) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult rv = InternalLoad(clonedURI,                 // New URI
                             nullptr,                   // Original URI
                             false,                     // LoadReplace
                             referer,                   // Referer URI
                             refererPolicy,             // Referer policy
                             aContent->NodePrincipal(), // Triggering is our node's
                                                        // principal
                             aContent->NodePrincipal(),
                             flags,
                             target,                    // Window target
                             NS_LossyConvertUTF16toASCII(typeHint).get(),
                             aFileName,                 // Download as file
                             aPostDataStream,           // Post data stream
                             aHeadersDataStream,        // Headers stream
                             LOAD_LINK,                 // Load type
                             nullptr,                   // No SHEntry
                             true,                      // first party site
                             NullString(),              // No srcdoc
                             this,                      // We are the source
                             nullptr,                   // baseURI not needed
                             aDocShell,                 // DocShell out-param
                             aRequest);                 // Request out-param
  if (NS_SUCCEEDED(rv)) {
    DispatchPings(this, aContent, aURI, referer, refererPolicy);
  }
  return rv;
}

NS_IMETHODIMP
nsDocShell::OnOverLink(nsIContent* aContent,
                       nsIURI* aURI,
                       const char16_t* aTargetSpec)
{
  if (aContent->IsEditable()) {
    return NS_OK;
  }

  nsCOMPtr<nsIWebBrowserChrome2> browserChrome2 = do_GetInterface(mTreeOwner);
  nsresult rv = NS_ERROR_FAILURE;

  nsCOMPtr<nsIWebBrowserChrome> browserChrome;
  if (!browserChrome2) {
    browserChrome = do_GetInterface(mTreeOwner);
    if (!browserChrome) {
      return rv;
    }
  }

  nsCOMPtr<nsITextToSubURI> textToSubURI =
    do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // use url origin charset to unescape the URL
  nsAutoCString charset;
  rv = aURI->GetOriginCharset(charset);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString spec;
  rv = aURI->GetSpec(spec);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString uStr;
  rv = textToSubURI->UnEscapeURIForUI(charset, spec, uStr);
  NS_ENSURE_SUCCESS(rv, rv);

  mozilla::net::PredictorPredict(aURI, mCurrentURI,
                                 nsINetworkPredictor::PREDICT_LINK,
                                 this, nullptr);

  if (browserChrome2) {
    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
    rv = browserChrome2->SetStatusWithContext(nsIWebBrowserChrome::STATUS_LINK,
                                              uStr, element);
  } else {
    rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, uStr.get());
  }
  return rv;
}

NS_IMETHODIMP
nsDocShell::OnLeaveLink()
{
  nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
  nsresult rv = NS_ERROR_FAILURE;

  if (browserChrome) {
    rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK,
                                  EmptyString().get());
  }
  return rv;
}

bool
nsDocShell::ShouldBlockLoadingForBackButton()
{
  if (!(mLoadType & LOAD_CMD_HISTORY) ||
      EventStateManager::IsHandlingUserInput() ||
      !Preferences::GetBool("accessibility.blockjsredirection")) {
    return false;
  }

  bool canGoForward = false;
  GetCanGoForward(&canGoForward);
  return canGoForward;
}

bool
nsDocShell::PluginsAllowedInCurrentDoc()
{
  bool pluginsAllowed = false;

  if (!mContentViewer) {
    return false;
  }

  nsIDocument* doc = mContentViewer->GetDocument();
  if (!doc) {
    return false;
  }

  doc->GetAllowPlugins(&pluginsAllowed);
  return pluginsAllowed;
}

//----------------------------------------------------------------------
// Web Shell Services API

// This functions is only called when a new charset is detected in loading a
// document. Its name should be changed to "CharsetReloadDocument"
NS_IMETHODIMP
nsDocShell::ReloadDocument(const char* aCharset, int32_t aSource)
{
  // XXX hack. keep the aCharset and aSource wait to pick it up
  nsCOMPtr<nsIContentViewer> cv;
  NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE);
  if (cv) {
    int32_t hint;
    cv->GetHintCharacterSetSource(&hint);
    if (aSource > hint) {
      nsCString charset(aCharset);
      cv->SetHintCharacterSet(charset);
      cv->SetHintCharacterSetSource(aSource);
      if (eCharsetReloadRequested != mCharsetReloadState) {
        mCharsetReloadState = eCharsetReloadRequested;
        return Reload(LOAD_FLAGS_CHARSET_CHANGE);
      }
    }
  }
  // return failure if this request is not accepted due to mCharsetReloadState
  return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
}

NS_IMETHODIMP
nsDocShell::StopDocumentLoad(void)
{
  if (eCharsetReloadRequested != mCharsetReloadState) {
    Stop(nsIWebNavigation::STOP_ALL);
    return NS_OK;
  }
  // return failer if this request is not accepted due to mCharsetReloadState
  return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
}

NS_IMETHODIMP
nsDocShell::GetPrintPreview(nsIWebBrowserPrint** aPrintPreview)
{
  *aPrintPreview = nullptr;
#if NS_PRINT_PREVIEW
  nsCOMPtr<nsIDocumentViewerPrint> print = do_QueryInterface(mContentViewer);
  if (!print || !print->IsInitializedForPrintPreview()) {
    Stop(nsIWebNavigation::STOP_ALL);
    nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::CreateWithInheritedAttributes(this);
    nsresult rv = CreateAboutBlankContentViewer(principal, nullptr);
    NS_ENSURE_SUCCESS(rv, rv);
    print = do_QueryInterface(mContentViewer);
    NS_ENSURE_STATE(print);
    print->InitializeForPrintPreview();
  }
  nsCOMPtr<nsIWebBrowserPrint> result = do_QueryInterface(print);
  result.forget(aPrintPreview);
  return NS_OK;
#else
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

#ifdef DEBUG
unsigned long nsDocShell::gNumberOfDocShells = 0;
#endif

NS_IMETHODIMP
nsDocShell::GetCanExecuteScripts(bool* aResult)
{
  *aResult = mCanExecuteScripts;
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::SetFrameType(uint32_t aFrameType)
{
  mFrameType = aFrameType;
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetFrameType(uint32_t* aFrameType)
{
  *aFrameType = mFrameType;
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsApp(bool* aIsApp)
{
  *aIsApp = (mFrameType == FRAME_TYPE_APP);
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsMozBrowserOrApp(bool* aIsMozBrowserOrApp)
{
  *aIsMozBrowserOrApp = (mFrameType != FRAME_TYPE_REGULAR);
  return NS_OK;
}

uint32_t
nsDocShell::GetInheritedFrameType()
{
  if (mFrameType != FRAME_TYPE_REGULAR) {
    return mFrameType;
  }

  nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
  GetSameTypeParent(getter_AddRefs(parentAsItem));

  nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
  if (!parent) {
    return FRAME_TYPE_REGULAR;
  }

  return static_cast<nsDocShell*>(parent.get())->GetInheritedFrameType();
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsIsolatedMozBrowserElement(bool* aIsIsolatedMozBrowserElement)
{
  bool result = mFrameType == FRAME_TYPE_BROWSER &&
                mOriginAttributes.mInIsolatedMozBrowser;
  *aIsIsolatedMozBrowserElement = result;
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement)
{
  MOZ_ASSERT(!mOriginAttributes.mInIsolatedMozBrowser ||
             (GetInheritedFrameType() == FRAME_TYPE_BROWSER),
             "Isolated mozbrowser should only be true inside browser frames");
  bool result = (GetInheritedFrameType() == FRAME_TYPE_BROWSER) &&
                mOriginAttributes.mInIsolatedMozBrowser;
  *aIsInIsolatedMozBrowserElement = result;
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsInMozBrowserOrApp(bool* aIsInMozBrowserOrApp)
{
  *aIsInMozBrowserOrApp = (GetInheritedFrameType() != FRAME_TYPE_REGULAR);
  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetIsTopLevelContentDocShell(bool* aIsTopLevelContentDocShell)
{
  *aIsTopLevelContentDocShell = false;

  if (mItemType == typeContent) {
    nsCOMPtr<nsIDocShellTreeItem> root;
    GetSameTypeRootTreeItem(getter_AddRefs(root));
    *aIsTopLevelContentDocShell = root.get() == static_cast<nsIDocShellTreeItem*>(this);
  }

  return NS_OK;
}

/* [infallible] */ NS_IMETHODIMP
nsDocShell::GetAppId(uint32_t* aAppId)
{
  if (mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
    *aAppId = mOriginAttributes.mAppId;
    return NS_OK;
  }

  nsCOMPtr<nsIDocShell> parent;
  GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));

  if (!parent) {
    *aAppId = nsIScriptSecurityManager::NO_APP_ID;
    return NS_OK;
  }

  return parent->GetAppId(aAppId);
}

// Implements nsILoadContext.originAttributes
NS_IMETHODIMP
nsDocShell::GetOriginAttributes(JS::MutableHandle<JS::Value> aVal)
{
  JSContext* cx = nsContentUtils::GetCurrentJSContext();
  MOZ_ASSERT(cx);

  return GetOriginAttributes(cx, aVal);
}

// Implements nsIDocShell.GetOriginAttributes()
NS_IMETHODIMP
nsDocShell::GetOriginAttributes(JSContext* aCx,
                                JS::MutableHandle<JS::Value> aVal)
{
  bool ok = ToJSValue(aCx, mOriginAttributes, aVal);
  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
  return NS_OK;
}

bool
nsDocShell::CanSetOriginAttributes()
{
  MOZ_ASSERT(mChildList.IsEmpty());
  if (!mChildList.IsEmpty()) {
    return false;
  }

  // TODO: Bug 1273058 - mContentViewer should be null when setting origin
  // attributes.
  if (mContentViewer) {
    nsIDocument* doc = mContentViewer->GetDocument();
    if (doc) {
      nsIURI* uri = doc->GetDocumentURI();
      if (!uri) {
        return false;
      }
      nsCString uriSpec = uri->GetSpecOrDefault();
      MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank"));
      if (!uriSpec.EqualsLiteral("about:blank")) {
        return false;
      }
    }
  }

  return true;
}

nsresult
nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs)
{
  if (!CanSetOriginAttributes()) {
    return NS_ERROR_FAILURE;
  }

  AssertOriginAttributesMatchPrivateBrowsing();
  mOriginAttributes = aAttrs;

  bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
  // Chrome docshell can not contain OriginAttributes.mPrivateBrowsingId
  if (mItemType == typeChrome && isPrivate) {
    mOriginAttributes.mPrivateBrowsingId = 0;
  }

  SetPrivateBrowsing(isPrivate);

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes)
{
  if (!aOriginAttributes.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }

  AutoJSAPI jsapi;
  if (!jsapi.Init(&aOriginAttributes.toObject())) {
    return NS_ERROR_UNEXPECTED;
  }

  JSContext* cx = jsapi.cx();
  if (NS_WARN_IF(!cx)) {
    return NS_ERROR_FAILURE;
  }

  DocShellOriginAttributes attrs;
  if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) {
    return NS_ERROR_INVALID_ARG;
  }

  return SetOriginAttributes(attrs);
}

NS_IMETHODIMP
nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes,
                                JSContext* aCx)
{
  DocShellOriginAttributes attrs;
  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
    return NS_ERROR_INVALID_ARG;
  }

  return SetOriginAttributes(attrs);
}

NS_IMETHODIMP
nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL)
{
  uint32_t appId = nsIDocShell::GetAppId();
  if (appId != nsIScriptSecurityManager::NO_APP_ID &&
      appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
    nsCOMPtr<nsIAppsService> appsService =
      do_GetService(APPS_SERVICE_CONTRACTID);
    NS_ASSERTION(appsService, "No AppsService available");
    appsService->GetManifestURLByLocalId(appId, aAppManifestURL);
  } else {
    aAppManifestURL.SetLength(0);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetAsyncPanZoomEnabled(bool* aOut)
{
  if (nsIPresShell* presShell = GetPresShell()) {
    *aOut = presShell->AsyncPanZoomEnabled();
    return NS_OK;
  }

  // If we don't have a presShell, fall back to the default platform value of
  // whether or not APZ is enabled.
  *aOut = gfxPlatform::AsyncPanZoomEnabled();
  return NS_OK;
}

bool
nsDocShell::HasUnloadedParent()
{
  RefPtr<nsDocShell> parent = GetParentDocshell();
  while (parent) {
    bool inUnload = false;
    parent->GetIsInUnload(&inUnload);
    if (inUnload) {
      return true;
    }
    parent = parent->GetParentDocshell();
  }
  return false;
}

bool
nsDocShell::IsInvisible()
{
  return mInvisible;
}

void
nsDocShell::SetInvisible(bool aInvisible)
{
  mInvisible = aInvisible;
}

void
nsDocShell::SetOpener(nsITabParent* aOpener)
{
  mOpener = do_GetWeakReference(aOpener);
}

nsITabParent*
nsDocShell::GetOpener()
{
  nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener));
  return opener;
}

// The caller owns |aAsyncCause| here.
void
nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
                                         const char16_t* aFunctionName,
                                         const char16_t* aFilename,
                                         const uint32_t aLineNumber,
                                         JS::Handle<JS::Value> aAsyncStack,
                                         const char* aAsyncCause)
{
  // If first start, mark interval start.
  if (mJSRunToCompletionDepth == 0) {
    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
    if (timelines && timelines->HasConsumer(this)) {
      timelines->AddMarkerForDocShell(this, Move(
        mozilla::MakeUnique<JavascriptTimelineMarker>(
          aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START,
          aAsyncStack, aAsyncCause)));
    }
  }

  mJSRunToCompletionDepth++;
}

void
nsDocShell::NotifyJSRunToCompletionStop()
{
  mJSRunToCompletionDepth--;

  // If last stop, mark interval end.
  if (mJSRunToCompletionDepth == 0) {
    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
    if (timelines && timelines->HasConsumer(this)) {
      timelines->AddMarkerForDocShell(this, "Javascript", MarkerTracingType::END);
    }
  }
}

void
nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
                                            const nsString& aKeyword)
{
  if (aProvider.IsEmpty()) {
    return;
  }

  if (XRE_IsContentProcess()) {
    dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
    if (contentChild) {
      contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword);
    }
    return;
  }

#ifdef MOZ_TOOLKIT_SEARCH
  nsCOMPtr<nsIBrowserSearchService> searchSvc =
    do_GetService("@mozilla.org/browser/search-service;1");
  if (searchSvc) {
    nsCOMPtr<nsISearchEngine> searchEngine;
    searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
    if (searchEngine) {
      nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
      if (obsSvc) {
        // Note that "keyword-search" refers to a search via the url
        // bar, not a bookmarks keyword search.
        obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
      }
    }
  }
#endif
}

NS_IMETHODIMP
nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNonSubresourceRequest,
                                      bool* aShouldIntercept)
{
  *aShouldIntercept = false;
  // No in private browsing
  if (UsePrivateBrowsing()) {
    return NS_OK;
  }

  if (mSandboxFlags) {
    // If we're sandboxed, don't intercept.
    return NS_OK;
  }

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    return NS_OK;
  }

  nsresult result;
  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
    do_GetService(THIRDPARTYUTIL_CONTRACTID, &result);
  NS_ENSURE_SUCCESS(result, result);

  if (mCurrentURI &&
      nsContentUtils::CookiesBehavior() == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
    nsAutoCString uriSpec;
    if (!(mCurrentURI->GetSpecOrDefault().EqualsLiteral("about:blank"))) {
      // Reject the interception of third-party iframes if the cookie behaviour
      // is set to reject all third-party cookies (1). In case that this pref
      // is not set or can't be read, we default to allow all cookies (0) as
      // this is the default value in all.js.
      bool isThirdPartyURI = true;
      result = thirdPartyUtil->IsThirdPartyURI(mCurrentURI, aURI,
                                               &isThirdPartyURI);
      if (NS_FAILED(result)) {
          return result;
      }

      if (isThirdPartyURI) {
        return NS_OK;
      }
    }
  }

  if (aIsNonSubresourceRequest) {
    PrincipalOriginAttributes attrs;
    attrs.InheritFromDocShellToDoc(mOriginAttributes, aURI);
    nsCOMPtr<nsIPrincipal> principal =
      BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
    *aShouldIntercept = swm->IsAvailable(principal, aURI);
    return NS_OK;
  }

  nsCOMPtr<nsIDocument> doc = GetDocument();
  if (!doc) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  ErrorResult rv;
  *aShouldIntercept = swm->IsControlled(doc, rv);
  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel)
{
  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    aChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
    return NS_OK;
  }

  nsCOMPtr<nsIChannel> channel;
  nsresult rv = aChannel->GetChannel(getter_AddRefs(channel));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDocument> doc;

  bool isSubresourceLoad = !nsContentUtils::IsNonSubresourceRequest(channel);
  if (isSubresourceLoad) {
    doc = GetDocument();
    if (!doc) {
      return NS_ERROR_NOT_AVAILABLE;
    }
  } else {
    // For top-level navigations, save a document ID which will be passed to
    // the FetchEvent as the clientId later on.
    rv = nsIDocument::GenerateDocumentId(mInterceptedDocumentId);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  bool isReload = mLoadType & LOAD_CMD_RELOAD;

  nsCOMPtr<nsIURI> uri;
  rv = channel->GetURI(getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, rv);

  PrincipalOriginAttributes attrs;
  attrs.InheritFromDocShellToDoc(mOriginAttributes, uri);

  ErrorResult error;
  swm->DispatchFetchEvent(attrs, doc, mInterceptedDocumentId, aChannel,
                          isReload, isSubresourceLoad, error);
  if (NS_WARN_IF(error.Failed())) {
    return error.StealNSResult();
  }

  return NS_OK;
}

bool
nsDocShell::InFrameSwap()
{
  RefPtr<nsDocShell> shell = this;
  do {
    if (shell->mInFrameSwap) {
      return true;
    }
    shell = shell->GetParentDocshell();
  } while (shell);
  return false;
}

NS_IMETHODIMP
nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError)
{
  if (mContentViewer) {
    nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
    if (doc) {
      doc->WarnOnceAbout(nsIDocument::DeprecatedOperations(aWarning), aAsError);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDocShell::GetEditingSession(nsIEditingSession** aEditSession)
{
  if (!NS_SUCCEEDED(EnsureEditorData())) {
    return NS_ERROR_FAILURE;
  }

  mEditorData->GetEditingSession(aEditSession);
  return *aEditSession ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsDocShell::GetScriptableTabChild(nsITabChild** aTabChild)
{
  *aTabChild = GetTabChild().take();
  return *aTabChild ? NS_OK : NS_ERROR_FAILURE;
}

already_AddRefed<nsITabChild>
nsDocShell::GetTabChild()
{
  nsCOMPtr<nsIDocShellTreeOwner> owner(mTreeOwner);
  nsCOMPtr<nsITabChild> tc = do_GetInterface(owner);
  return tc.forget();
}

nsICommandManager*
nsDocShell::GetCommandManager()
{
  NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
  return mCommandManager;
}

NS_IMETHODIMP
nsDocShell::GetProcessLockReason(uint32_t* aReason)
{
  MOZ_ASSERT(aReason);

  nsPIDOMWindowOuter* outer = GetWindow();
  MOZ_ASSERT(outer);

  // Check if we are a toplevel window
  if (outer->GetScriptableParentOrNull()) {
    *aReason = PROCESS_LOCK_IFRAME;
    return NS_OK;
  }

  // If we have any other toplevel windows in our tab group, then we cannot
  // perform the navigation.
  nsTArray<nsPIDOMWindowOuter*> toplevelWindows =
    outer->TabGroup()->GetTopLevelWindows();
  if (toplevelWindows.Length() > 1) {
    *aReason = PROCESS_LOCK_RELATED_CONTEXTS;
    return NS_OK;
  }
  MOZ_ASSERT(toplevelWindows.Length() == 1);
  MOZ_ASSERT(toplevelWindows[0] == outer);

  // If we aren't in a content process, we cannot perform a cross-process load.
  if (!XRE_IsContentProcess()) {
    *aReason = PROCESS_LOCK_NON_CONTENT;
    return NS_OK;
  }

  *aReason = PROCESS_LOCK_NONE;
  return NS_OK;
}